Я пытаюсь оптимизировать приложение C ++ Win32, связанное с вводом / выводом. То, что он на самом деле делает, это что-то очень похожее на рекурсию папки и вычисление криптографического хэша каждого найденного файла.
Это однопоточное приложение, использующее файлы с отображением в памяти, поэтому легко представить, что оно, похоже, не использует много ресурсов ЦП, поскольку большую часть времени основной поток переводится в режим ожидания, ожидая завершения ввода-вывода.
Я думаю о нескольких решениях, но я не уверен в этом, поэтому я хотел бы узнать ваше мнение.
Любая другая идея / предложение / и т. Д. Будет очень признателен 🙂
Благодарю.
Прежде чем что-либо распараллеливать, всегда сначала спрашивайте себя: оправдывает ли дополнительная сложность полученную производительность? Чтобы ответить на этот вопрос с минимальными усилиями, просто проверьте, какой процент максимальной пропускной способности чтения у вас уже есть. То есть проверьте текущую пропускную способность чтения, а затем проверьте максимальную пропускную способность. Не используйте теоретический максимум для этого вычисления. Затем подумайте о том, сколько сложности и сколько возможных проблем внесено даже в самый простой подход, чтобы получить последние несколько%.
Как уже упоминалось в комментариях, наибольший прирост производительности здесь, вероятно, достигается конвейерной обработкой (то есть перекрывающимися вычислениями и вводом / выводом). И самый простой способ реализовать это с асинхронным чтением. В этом потоке перечислены несколько способов реализации асинхронного файлового ввода-вывода в C ++..
Если вам не нужна портативность, просто используйте Windows ПЕРЕКРЫТЫЙ API. Boost ASIO не делает File I / O очень простым (пока). Я не смог найти хороших примеров.
Обратите внимание, что, в зависимости от конфигурации вашей системы, вам нужно запустить несколько потоков, чтобы полностью насытить пропускную способность ввода / вывода (особенно, если файлы этой папки фактически находятся на нескольких дисках, что возможно). Даже если вы читаете только с одного устройства, вы могли бы справиться (несколько) лучше с несколькими потоками, чтобы уменьшить накладные расходы ОС.
Мой опыт показывает, что отображение памяти не особенно быстрое, так что, вероятно, я бы отказался от этого в первую очередь.
Потоки (явно или через IOCP), вероятно, тоже не принесут пользы, если в целевой системе не много дисков, и они могут разделиться так, чтобы разные потоки работали на разных физических дисках.
Как только вы отказались от отображения памяти и сделали явный ввод-вывод, вы, вероятно, захотите использовать FILE_FLAG_NO_BUFFERING и читать относительно большие блоки (скажем, несколько мегабайт за раз). Проверьте требования к выравниванию на вашем блоке памяти — они немного хитры (или, может быть, «утомительным» было бы более подходящее слово для их описания). Также обратите внимание, что это работает только для операций чтения, кратных размеру сектора диска, поэтому в типичном случае вам нужно открыть файл дважды: один раз с FILE_FLAG_NO_BUFFERING, чтобы прочитать большую часть данных, а затем снова без этого флага, чтобы прочитать » хвост «файла.
Хотя он только копирует файл (а не обрабатывает его содержимое), и, вероятно, это чистый C, а не C ++, возможно, некоторый демонстрационный код поможет хотя бы немного:
int do_copy(char const *in, char const *out) {
HANDLE infile;
HANDLE outfile;
char *buffer;
DWORD read, written;
DWORD junk=0;
unsigned long little_tail;
unsigned long big_tail;
unsigned __int64 total_copied = 0;
unsigned __int64 total_size = 0;
BY_HANDLE_FILE_INFORMATION file_info;
#define size (1024 * 8192)
buffer = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
if ( NULL == buffer)
return 0;
infile = CreateFile(in,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_FLAG_NO_BUFFERING,
NULL);
GetFileInformationByHandle(infile, &file_info);
total_size = (unsigned __int64)file_info.nFileSizeHigh << 32 | (unsigned __int64)file_info.nFileSizeLow / 100;
outfile = CreateFile(out,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_FLAG_NO_BUFFERING,
NULL);
if ((infile == HNULL) || (outfile == HNULL))
return 0;
while (ReadFile(infile, buffer, size, &read, NULL) && read == size) {
WriteFile(outfile, buffer, read, &written, NULL);
total_copied += written;
fprintf(stderr, "\rcopied: %lu %%", (unsigned long)(total_copied / total_size));
}
little_tail = read % 4096;
big_tail = read - little_tail;
WriteFile(outfile, buffer, big_tail, &written, NULL);
CloseHandle(infile);
CloseHandle(outfile);
outfile = CreateFile(out,
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
fprintf(stderr, "\rcopied: 100 %%\n");
SetFilePointer(outfile, 0, &junk, FILE_END);
WriteFile(outfile, buffer+big_tail, little_tail, &written, NULL);
CloseHandle(outfile);
VirtualFree(buffer, size, MEM_RELEASE);
return 1;
}