Почему файловый ввод / вывод в больших кусках МЕНЬШЕ, чем в маленьких? - PullRequest
8 голосов
/ 18 мая 2011

Если вы вызываете ReadFile один раз с размером, примерно равным 32 МБ, это занимает заметно больше времени, чем при считывании эквивалентного количества байтов с меньшим размером порции, например 32 КБ.

Почему?

(Нет, мой диск не занят.)


Редактировать 1:

Забыл упомянуть - я делаю этос FILE_FLAG_NO_BUFFERING!


Редактировать 2:

Странно ...

У меня больше нет доступа к моей старой машине(PATA), но когда я тестировал его там, это заняло в 2 раза больше времени, иногда больше.На моем новом компьютере (SATA) разница составляет всего ~ 25%.

Вот фрагмент кода для проверки:

#include <memory.h>
#include <windows.h>
#include <tchar.h>
#include <stdio.h>

int main()
{
    HANDLE hFile = CreateFile(_T("\\\\.\\C:"), GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
        OPEN_EXISTING, FILE_FLAG_NO_BUFFERING /*(redundant)*/, NULL);
    __try
    {
        const size_t chunkSize = 64 * 1024;
        const size_t bufferSize = 32 * 1024 * 1024;
        void *pBuffer = malloc(bufferSize);

        DWORD start = GetTickCount();
        ULONGLONG totalRead = 0;
        OVERLAPPED overlapped = { 0 };
        DWORD nr = 0;
        ReadFile(hFile, pBuffer, bufferSize, &nr, &overlapped);
        totalRead += nr;
        _tprintf(_T("Large read: %d for %d bytes\n"),
            GetTickCount() - start, totalRead);

        totalRead = 0;
        start = GetTickCount();
        overlapped.Offset = 0;
        for (size_t j = 0; j < bufferSize / chunkSize; j++)
        {
            DWORD nr = 0;
            ReadFile(hFile, pBuffer, chunkSize, &nr, &overlapped);
            totalRead += nr;
            overlapped.Offset += chunkSize;
        }
        _tprintf(_T("Small reads: %d for %d bytes\n"),
            GetTickCount() - start, totalRead);
        fflush(stdout);
    }
    __finally { CloseHandle(hFile); }

    return 0;
}

Результат:

Большое чтение: 1076 для 67108864 байта
Маленькое чтение: 842 для 67108864 байта

Есть идеи?

Ответы [ 6 ]

1 голос
/ 10 июня 2011

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

Эффект, вероятно, более тяжелый с сильной фрагментацией, так как вам нужно прочитать больше сопоставлений файлов на диск.

РЕДАКТИРОВАТЬ: Чтобы было ясно, я запустилэто изменение локально, и видел почти идентичные времена с большими и маленькими чтениями.Повторно используя один и тот же дескриптор файла, я увидел похожие моменты из исходного вопроса.

1 голос
/ 18 мая 2011

Когда вы выполняете чтение 1024 * 32 КБ, читаете ли вы снова и снова один и тот же блок памяти, или же вы выделяете в общей сложности 32 МБ, а также заполняете все 32 МБ?

Если выВы читаете меньшие операции чтения в том же блоке памяти 32 КБ, тогда разница во времени, вероятно, заключается просто в том, что Windows не нужно очищать дополнительную память.


Обновление на основе FILE_FLAG_NO_BUFFERINGдополнение к вопросу:

Я не уверен на 100%, но я полагаю, что при использовании FILE_FLAG_NO_BUFFERING Windows заблокирует буфер в физической памяти, что позволит драйверу устройства работать с физическими адресами(например, для прямого доступа к памяти непосредственно в буфер).Он мог (я полагаю) сделать это, разбив большой запрос на меньшие запросы, но я подозреваю, что у Microsoft может быть философия, что «если вы запрашиваете FILE_FLAG_NO_BUFFERING, тогда мы предполагаем, что вы знаете, что делаете, и мыне встанет у вас на пути ".

Конечно, блокировка 32 МБ одновременно, а не 32 КБ, потребует больше ресурсов.Так что это будет похоже на мое первоначальное предположение, но на уровне физической памяти, а не на уровне виртуальной памяти.

Однако, так как я не работаю на MS и не имею доступа к источнику Windows,Я иду по смутным воспоминаниям о тех временах, когда я работал ближе с ядром Windows и моделью драйвера устройства (так что это более или менее спекуляция).

1 голос
/ 18 мая 2011

Это не только для окон.Некоторое время назад я провел некоторые тесты с библиотекой C ++ iostream и обнаружил, что существует оптимальный размер буфера для чтения, выше которого производительность снижается.К сожалению, у меня больше нет тестов, и я не могу вспомнить, какой был размер :-).Что касается того, почему, хорошо, есть много проблем, таких как большой буфер, возможно, вызывающий подкачку в других приложениях, работающих одновременно (так как буфер не может быть выгружен).

0 голосов
/ 18 июня 2011

Что вы, вероятно, наблюдаете, так это то, что при использовании меньших блоков второй блок данных может быть прочитан во время обработки первого, затем третий считывается во время обработки второго и т. Д. медленнее физического времени чтения или времени обработки. Если для обработки одного блока требуется столько же времени, сколько для чтения следующего, скорость может быть в два раза выше, чем при разделении обработки и чтения. При использовании блоков большего размера объем данных, считываемых во время обработки первого блока, будет ограничен объемом, меньшим, чем размер блока. Когда код будет готов для следующего блока данных, его часть будет прочитана, а часть - нет; поэтому необходимо, чтобы код ожидал получения остальной части данных.

0 голосов
/ 10 июня 2011

Возможное объяснение, по моему мнению, было бы в очереди команд с FILE_FLAG_NO_BUFFERING, так как это делает прямые чтения DMA на низком уровне.

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

С другой стороны, если вы выбросите дюжину или две дюжины запросов в драйвер, он просто перенаправит их на диск и диск и воспользуется NCQ.

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

0 голосов
/ 18 мая 2011

когда вы сделали FILE_FLAG_NO_BUFFERING, это означает, что операционная система не будет буферизовать ввод / вывод. Таким образом, каждый раз, когда вы вызываете функцию чтения, он будет выполнять системный вызов, который будет каждый время данные с диска. Затем для чтения одного файла с фиксированным размером, если вы используете меньший размер буфера, тогда требуется больше системных вызовов, поэтому требуется больше пользовательского пространства для пространства ядра и при каждом запуске дискового ввода-вывода. Вместо этого, если вы используете больший размер блока, то для того же размера файла, который будет считан, будет требоваться меньше системных вызовов, так что переключение между пользователем и пространством ядра будет меньше, и число раз, инициированное дисковым вводом-выводом, будет также меньше. Вот почему, как правило, более крупный блок требует меньше времени для чтения.

Попробуйте прочитать файл только 1 байт за раз без буферизации, затем попробуйте с помощью блока 4096 байт и посмотрите разницу.

...