У меня есть приложение, которое последовательно считывает данные из файла. Некоторые считываются непосредственно из указателя на mmap
файл эды и другие части memcpy
ред из файла в другой буфер. Я заметил низкую производительность при выполнении большой memcpy
всей необходимой мне памяти (блоки 1 МБ) и лучшей производительности при выполнении множества меньших вызовов memcpy
(в моих тестах я использовал 4 КБ, размер страницы, который занимал 1/3 время для запуска.) Я считаю, что проблема заключается в очень большом количестве серьезных ошибок страницы при использовании большой memcpy
.
Я пробовал различные параметры настройки (MAP_POPUATE
, MADV_WILLNEED
, MADV_SEQUENTIAL
) без какого-либо заметного улучшения.
Я не уверен, почему многие маленькие вызовы memcpy
должны быть быстрее; это кажется противоречивым. Есть ли способ улучшить это?
Ниже приведены результаты и тестовый код.
Запуск на CentOS 7 (linux 3.10.0), компилятор по умолчанию (gcc 4.8.5), чтение 29-гигабайтного файла из массива RAID обычных дисков.
Работа с /usr/bin/time -v
:
4 КБ memcpy
:
User time (seconds): 5.43
System time (seconds): 10.18
Percent of CPU this job got: 75%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:20.59
Major (requiring I/O) page faults: 4607
Minor (reclaiming a frame) page faults: 7603470
Voluntary context switches: 61840
Involuntary context switches: 59
1 memcpy
:
User time (seconds): 6.75
System time (seconds): 8.39
Percent of CPU this job got: 23%
Elapsed (wall clock) time (h:mm:ss or m:ss): 1:03.71
Major (requiring I/O) page faults: 302965
Minor (reclaiming a frame) page faults: 7305366
Voluntary context switches: 302975
Involuntary context switches: 96
MADV_WILLNEED
, похоже, не оказало большого влияния на результат копирования 1 MADV_WILLNEED
.
MADV_SEQUENTIAL
значительно замедлил результат копирования 1 MADV_SEQUENTIAL
, я не дождался его завершения (не менее 7 минут).
MAP_POPULATE
замедлил результат копирования 1 Мб примерно на 15 секунд.
Упрощенный код, используемый для теста:
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
try {
char *filename = argv[1];
int fd = open(filename, O_RDONLY);
if (fd == -1) {
throw std::runtime_error("Failed open()");
}
off_t file_length = lseek(fd, 0, SEEK_END);
if (file_length == (off_t)-1) {
throw std::runtime_error("Failed lseek()");
}
int mmap_flags = MAP_PRIVATE;
#ifdef WITH_MAP_POPULATE
mmap_flags |= MAP_POPULATE; // Small performance degredation if enabled
#endif
void *map = mmap(NULL, file_length, PROT_READ, mmap_flags, fd, 0);
if (map == MAP_FAILED) {
throw std::runtime_error("Failed mmap()");
}
#ifdef WITH_MADV_WILLNEED
madvise(map, file_length, MADV_WILLNEED); // No difference in performance if enabled
#endif
#ifdef WITH_MADV_SEQUENTIAL
madvise(map, file_length, MADV_SEQUENTIAL); // Massive performance degredation if enabled
#endif
const uint8_t *file_map_i = static_cast<const uint8_t *>(map);
const uint8_t *file_map_end = file_map_i + file_length;
size_t memcpy_size = MEMCPY_SIZE;
uint8_t *buffer = new uint8_t[memcpy_size];
while (file_map_i != file_map_end) {
size_t this_memcpy_size = std::min(memcpy_size, static_cast<std::size_t>(file_map_end - file_map_i));
memcpy(buffer, file_map_i, this_memcpy_size);
file_map_i += this_memcpy_size;
}
}
catch (const std::exception &e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}