Оптимизация связанного с вводом / выводом приложения Win32

Я пытаюсь оптимизировать приложение C ++ Win32, связанное с вводом / выводом. То, что он на самом деле делает, это что-то очень похожее на рекурсию папки и вычисление криптографического хэша каждого найденного файла.
Это однопоточное приложение, использующее файлы с отображением в памяти, поэтому легко представить, что оно, похоже, не использует много ресурсов ЦП, поскольку большую часть времени основной поток переводится в режим ожидания, ожидая завершения ввода-вывода.
Я думаю о нескольких решениях, но я не уверен в этом, поэтому я хотел бы узнать ваше мнение.

  1. Я мог бы порождать много потоков (имея пул работников фиксированного размера, чтобы поддерживать использование памяти под определенным порогом), но, честно говоря, я не знаю, может ли это улучшить ситуацию, каждый поток будет усыплен так же, как мой основной поток в текущей реализации плюс планировщик будет «тратить» много вычислительных мощностей на переключение контекстов.
  2. Я думал о портах завершения ввода / вывода (однопотоковый? Multi?), Но это означало бы отказаться от отображенных в память файлов (я не прав?) И использовать стандартные файловые операции. Если это так, не могли бы вы предоставить мне пример кода о том, как использовать IOCP для чтения и разработки заданного списка файлов, не переводя поток чтения в спящий режим?

Любая другая идея / предложение / и т. Д. Будет очень признателен 🙂

Благодарю.

1

Решение

Прежде чем что-либо распараллеливать, всегда сначала спрашивайте себя: оправдывает ли дополнительная сложность полученную производительность? Чтобы ответить на этот вопрос с минимальными усилиями, просто проверьте, какой процент максимальной пропускной способности чтения у вас уже есть. То есть проверьте текущую пропускную способность чтения, а затем проверьте максимальную пропускную способность. Не используйте теоретический максимум для этого вычисления. Затем подумайте о том, сколько сложности и сколько возможных проблем внесено даже в самый простой подход, чтобы получить последние несколько%.

Как уже упоминалось в комментариях, наибольший прирост производительности здесь, вероятно, достигается конвейерной обработкой (то есть перекрывающимися вычислениями и вводом / выводом). И самый простой способ реализовать это с асинхронным чтением. В этом потоке перечислены несколько способов реализации асинхронного файлового ввода-вывода в C ++..

Если вам не нужна портативность, просто используйте Windows ПЕРЕКРЫТЫЙ API. Boost ASIO не делает File I / O очень простым (пока). Я не смог найти хороших примеров.

Обратите внимание, что, в зависимости от конфигурации вашей системы, вам нужно запустить несколько потоков, чтобы полностью насытить пропускную способность ввода / вывода (особенно, если файлы этой папки фактически находятся на нескольких дисках, что возможно). Даже если вы читаете только с одного устройства, вы могли бы справиться (несколько) лучше с несколькими потоками, чтобы уменьшить накладные расходы ОС.

1

Другие решения

Мой опыт показывает, что отображение памяти не особенно быстрое, так что, вероятно, я бы отказался от этого в первую очередь.

Потоки (явно или через 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;
}
1

По вопросам рекламы [email protected]