Я пишу код, анализирующий файлы журнала, с предостережением, что эти файлы сжаты и должны быть несжаты на лету. Этот код представляет собой несколько высокочувствительный фрагмент кода, поэтому я пытаюсь использовать различные методы, чтобы найти правильный. У меня по существу столько оперативной памяти, сколько потребуется программе, независимо от того, сколько потоков я использую.mmap vs. malloc: странная производительность
Я нашел метод, который, кажется, работает достаточно хорошо, и я пытаюсь понять, почему он предлагает лучшую производительность.
Оба метода имеют поток считывателя, который считывает из процесса gzip с каналами и записывает в большой буфер. Этот буфер затем анализируется при анализе следующей строки журнала, возвращая то, что по существу является структурой указателей на то, где в буфере находятся разные поля.
Код находится в D, но он очень похож на C или C++.
Общая переменная:
shared(bool) _stream_empty = false;;
shared(ulong) upper_bound = 0;
shared(ulong) curr_index = 0;
Синтаксический код:
//Lazily parse the buffer
void construct_next_elem() {
while(1) {
// Spin to stop us from getting ahead of the reader thread
buffer_empty = curr_index >= upper_bound -1 &&
_stream_empty;
if(curr_index >= upper_bound && !_stream_empty) {
continue;
}
// Parsing logic .....
}
}
Метод 1: Malloc буфер достаточно большой, чтобы вместить распакованный файл вперед.
char[] buffer; // Same as vector<char> in C++
buffer.length = buffer_length; // Same as vector reserve in C++ or malloc
Метод 2: Использование анонимной карты памяти в качестве буфера
MmFile buffer;
buffer = new MmFile(null,
MmFile.Mode.readWrite, // PROT_READ || PROT_WRITE
buffer_length,
null); // MAP_ANON || MAP_PRIVATE
Считыватель резьба:
ulong buffer_length = get_gzip_length(file_path);
pipe = pipeProcess(["gunzip", "-c", file_path],
Redirect.stdout);
stream = pipe.stdout();
static void stream_data() {
while(!l.stream.eof()) {
// Splice is a reference inside the buffer
char[] splice = buffer[upper_bound..upper_bound + READ_SIZE];
ulong read = stream.rawRead(splice).length;
upper_bound += read;
}
// Clean up
}
void start_stream() {
auto t = task!stream_data();
t.executeInNewThread();
construct_next_elem();
}
Я получаю значительно более высокую производительность из метода 1, даже на по порядку величины
User time (seconds): 112.22
System time (seconds): 38.56
Percent of CPU this job got: 151%
Elapsed (wall clock) time (h:mm:ss or m:ss): 1:39.40
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 3784992
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 5463
Voluntary context switches: 90707
Involuntary context switches: 2838
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
против
User time (seconds): 275.92
System time (seconds): 73.92
Percent of CPU this job got: 117%
Elapsed (wall clock) time (h:mm:ss or m:ss): 4:58.73
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 3777336
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 944779
Voluntary context switches: 89305
Involuntary context switches: 9836
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
Как способом больше ошибок страниц с помощью метода 2.
Может кто-то помочь мне пролить свет на то, почему существует такое суровое снижение производительности с помощью ММАПА?
Если кто-нибудь знает о каких-либо лучших способах решения этой проблемы, я бы с радостью его услышал.
EDIT -----
Изменен метод 2, чтобы сделать:
char * buffer = cast(char*)mmap(cast(void*)null,
buffer_length,
PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE,
-1,
0);
теперь получает увеличение 3x производительности по сравнению с использованием простой MmFile. Я пытаюсь понять, что может привести к такой резкой разнице в производительности, что это, по сути, только обертка вокруг mmap.
Perf номера для только с использованием прямым символом * ММАПА против Mmfile, способом гораздо меньше разломов страницы:
User time (seconds): 109.99
System time (seconds): 36.11
Percent of CPU this job got: 151%
Elapsed (wall clock) time (h:mm:ss or m:ss): 1:36.20
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 3777896
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 2771
Voluntary context switches: 90827
Involuntary context switches: 2999
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
Ну, ничего неожиданного. 'mmap()' заставляет файл «сбой» в chunk by schunk. Объем ввода-вывода в обоих случаях одинаковый, но он выполняется в менее желательные моменты в случае mmap(). – wildplasser
и mmap использует opIndex (перегруженный метод), вместо этого вы можете вывести void [] для прямого доступа, выполнив 'buffer = mmap [];' –
wildplasser. Я напрямую пишу в mmap из трубы, прежде чем обращаться к нему. Итак, я жду на страницах, чтобы загрузить, прежде чем писать им, а также иметь их впереди с malloc? Я напрямую пишу в mmap из трубы, прежде чем я прочитаю их, поэтому я бы предположил, что эти страницы будут кэшироваться некоторое время. – sprw121