2015-12-28 3 views
0

Я спросил this вопрос о том, как читать текстовый файл, начиная со смещения pos, на смещение end до mmap(). В частности, текстовый файл считывается нескольких потоков со следующим кодом:concurrent mmap() на части текста

void getNextKeyValue() { 
    key = pos;//value is the actual file offset 
    char *mmappedData = (char*) mmap(NULL, end-pos+1, PROT_READ, MAP_PRIVATE , fd, pos); 
    assert(mmappedData != NULL); 
    value.assign(mmappedData); 
    assert(munmap(mmappedData, end-pos+1)==0); 
    morePairs = false; 
} 

нерегистрируемой переменными объявляются и инициализируются где-то в другом месте. Что, кстати, следующий код читал весь текстовый файл, а не со смещения pos до end.

В зависимости от программы программа прерывается (без ошибок) с несколькими потоками, в то время как она завершается корректно только одним потоком, который читает весь файл.

UPDATE:

После this примера (вы можете попробовать мою версию, используя cout InstEd из write, HERE с ./main main.cpp 10 20) я узнал, что то, что я делаю неправильно, что я напечатал считанные данные через cout<<mmappedData<<endl , Если вы используете , то печатается правая часть текста.

То, что я до сих пор не понимаю почему весь текст хранится внутри mmappedData (или addr после связанного примера): mmap использования четко говорится, что количество прочитанных байт являются вторым аргументом, начиная с 4-го арга ,

+0

Почему карта и размонтирование? Невозможно ли просто сопоставить один раз и позволить нескольким потокам читать один и тот же отображаемый указатель? – jxh

+0

Поскольку это для проекта paralll, разные потоки могут читать параллельно различные части файла. Так что (теоретически) это более эффективно. – justHelloWorld

+0

Эм, извините, но я не знаю, что такое NUMI, поэтому я не понял ваш комментарий. – justHelloWorld

ответ

1

Обновление новой информации:

Вашей проблема заключается в том, что вы понимаете, как mmap работы. mmap отображает страницы памяти, а не байты; даже если вы попросите отобразить только 24 байта, на самом деле он отобразит несколько килобайт (в большинстве систем 4 КБ), не гарантируя, что данные NUL завершены (и фактически, если отображение не достигнет конца файла, для текста входной файл, вероятно, не NUL завершен). И метод std::string::assign, который принимает только char * в качестве аргумента, использует строковую логику C-стиля; он просто продолжает читать символы, пока не достигнет NUL. Если вам «повезло», страница после mmap читается и содержит NUL, и она просто копирует все до NUL в строку (или, что то же самое, когда делает cout << mmappedData, записывает ее), если нет, вы пытаетесь выполнить segfault доступ к адресам без памяти после вашего сопоставления.

Если цель состоит в том, чтобы назначить определенное количество байтов в std::string из char*, вам нужно использовать два-Arg форму assign, чтобы начало и длина явно так же, как вы делаете с write, то это будет только использовать данные, вам необходимо:

value.assign(mmappedData+pos-pa_offset, end-pos); 

Оригинал спекулятивный ответ:

код дано не достаточно ясно, чтобы исключить о Ther вопросы, но если вы постоянно перечитывать значения, и NDEBUG устанавливается (отключение assert с), то у вас есть две пересекающиеся проблемы:

  1. Вы не проверять возвращаемое значение для mmap (и даже с включенным assert s, он не проверяется правильно; погрешность возврата для mmap: MAP_FAILED, не NULL)
  2. Вы никогда не были munmap -ing; вызов munmap находится внутри assert, а с assert s отключен, код, который вы утверждаете, удаляется препроцессором.

Так что если у вас тонна потоков, вызывающих эту функцию снова и снова (особенно если отображаемая область велика), вы в конечном итоге закончите пространство виртуальной памяти; если вы набираете 1 МБ каждого звонка, даже на 64-битной системе (на самом деле это всего лишь 47 бит виртуального адресного пространства пользовательского режима), после вызовов ~ 134М вы потеряете виртуальное адресное пространство. Если вы наберете 1 ГБ каждого вызова, вы закончите после звонков ~ 134K. Разумеется, на 32-битной системе вы достигли пределов намного быстрее.

Кроме того, у меня нет возможности узнать, есть ли другие проблемы; value.assign также может быть проблемой, если это общие данные.

+0

Посмотрите ** ОБНОВЛЕНИЕ ** раздел пожалуйста. – justHelloWorld

+0

@justHelloWorld: Обновлен с реальным ответом. – ShadowRanger

1

Чтение данных из файла таким образом будет эффективно однопоточным. Каждая пара mmap/munmap требует двух модификаций сопоставлений памяти адресного пространства процесса - и только один поток может вызвать изменения в сопоставлениях адресного пространства в любой момент времени. Хуже того, изменение сопоставлений адресного пространства процесса - дорогостоящая операция сама по себе.

Либо отобразить весь файл - и оставить он отображается - и копировать данные из отображения по мере необходимости, или использовать низкоуровневые IO вызовы, которые не зависят от какой-либо общей стоимости или состояния, как pread(). Поскольку у вас уже есть открытый дескриптор файла fd в файл, что-то вроде этого:

void getNextKeyValue() { 
    char buffer[end-pos+1]; 
    ssize_t result = pread(fd, buffer, end-pos+1L, pos); 
    assert(result == (end-pos+1L)); 
    value.assign(buffer); 
    morePairs = false; 
} 

Обратите внимание, что вы можете не использование lseek() тогда read() - это не атомарная операция - другой поток может изменить указатель файла после вызова потока до lseek(), но перед его вызовом read().

И не используйте new/delete или malloc()/free() для буфера на каждом вызове getNextKeyValue() - еще раз, что эффективно моноволокна чтения данных.

0

Есть несколько вещей, которые касаются меня вашим подходом. Во-первых, я не могу представить, что данный файл действительно является текстовым файлом, потому что он работает, если вы просто вызываете assign() с необработанным указателем, и каким-то образом размер определяется. Это означает, что либо у вас встроенные NUL, либо что вы всегда копируете все до следующего NUL (который, вероятно, находится где-то за пределами отображаемого диапазона). В любом случае код пахнет! Это также является причиной того, почему cout << ptr «не работает», а write(stdout, ptr, size) работает, у вас есть переполнение буфера в вызове strlen() в первом случае.

Во-вторых, почему вы используете mmap()? Думаю, вы надеетесь получить некоторые преимущества относительно размера или времени. Тем не менее, простые старые C++ IOStreams, вероятно, будут mmap() скользящим окном в файл, что довольно эффективно. Тем не менее, вы должны сделать его легким для IOStreams сделать так:

// turn off some syncing that you shouldn't need 
std::ios_base::sync_with_stdio(false); 
// disable any character conversions 
std::ifstream in; 
in.imbue(std::locale::classic()); 
in.open(filename, std::ios_base::binary); 

Классическим локали не содержит каких-либо нетривиальных аспектов преобразования символов. Это не обязательно, но это также не мешает сделать это явным. Бинарный флаг используется, чтобы сообщить streambuffer, что он не должен выполнять какие-либо преобразования CR/LF, что в отличие от MS Windows.

Последняя вещь, которая немного сложна с C++ IOStreams - это позиционирование. Помимо начального положения (построенный по умолчанию streampos), вы должны указывать только seekp() и seekg(), которые вы получили от tellp() или tellg(). Однако есть надежда: без каких-либо преобразований символов смещение байтов может просто работать на вас. Если нет, вы все равно можете структурировать код, который отслеживает позиции вокруг этого, хотя это может быть немного сложно. Я мог бы предположить, что вам придется полагаться на поведение, определяемое реализацией, даже здесь.

Смежные вопросы