Проблема производительности Win32 IO - PullRequest
1 голос
/ 22 декабря 2009

Недавно я столкнулся с «забавной» проблемой, связанной с реализацией CRTL в Microsoft. tmpfile помещает временные файлы в корневой каталог и полностью игнорирует каталог временных файлов. Это имеет проблемы с пользователями, которые не имеют прав доступа к корневому каталогу (скажем, в нашем кластере). Более того, использование _tempnam потребует от приложения не забыть удалить временные файлы, что невозможно без значительного количества переделок.

Поэтому я укусила пулю и написала Win32-версию всех подпрограмм ввода-вывода (create_temp, read, write, seek, flush), которые вызывают соответствующий метод. Одна вещь, которую я заметил, - это ужасная производительность библиотеки.

Результаты теста:

CRTL:    4:30.05 elapsed
Win32:  11:18.06 elapsed

Stats measured in my routines:
Writes:  3129934 (   44,642,745,008 bytes)
Reads:    935903 (    8,183,423,744 bytes)
Seeks:   2205757 (2,043,782,657,968 bytes traveled)
Flushes:   92442

Пример использования метода CRTL v. Win32:

int io_write(FILE_POINTER fp, size_t words, const void *buffer)
{
#if !defined(USE_WIN32_IO)
    {
        size_t words_written = 0;

        /* read the data */
        words_written = fwrite(buffer, sizeof(uint32_t), words, fp);
        if (words_written != words)
        {
            return errno;
        }
    }
#else /* !defined(USE_WIN32_IO) */
    {
        DWORD bytesWritten;

        if (!WriteFile(fp, buffer, words * sizeof(uint32_t), &bytesWritten, NULL)
            || (bytesWritten != words * sizeof(uint32_t)))
        {
            return GetLastError();
        }
    }
#endif /* USE_WIN32_IO */

    return E_SUCCESS;
}

Как видите, они практически идентичны, но производительность (в режиме выпуска) сильно отличается. Время, проведенное в WriteFile и SetFilePointer, превосходит время, потраченное в fwrite и fseeko, что кажется нелогичным.

Идеи

ОБНОВЛЕНИЕ: perfmon отмечает, что fflush примерно в 10 раз дешевле, чем FlushFileBuffers, а fwrite примерно в 1,1 раза медленнее, чем WriteFile. Конечным результатом является огромная потеря производительности, когда FlushFileBuffers используется так же, как и fflush. Также нет изменений от FILE_ATTRIBUTE_NORMAL до FILE_FLAG_RANDOM_ACCESS.

Ответы [ 4 ]

2 голосов
/ 22 декабря 2009

Возможно, я сумасшедший, но не проще ли написать замену для tmpfile, которая использует fopen(temporaryname, "wbTD+"), где вы создаете свой собственный temporaryname?

По крайней мере, тогда вам не нужно беспокоиться о повторной реализации <file.h>.

2 голосов
/ 22 декабря 2009

Я думаю, что это, вероятно, связано с этой проблемой, описанной на странице MSDN для FlushFileBuffers:

Из-за дискового кэширования в системе, Функция FlushFileBuffers может быть неэффективно при использовании после каждого записывать на дисковод, когда много записи выполняются отдельно. Если приложение выполняется несколько записей на диск, а также необходимо чтобы обеспечить запись критических данных постоянные медиа, приложение следует использовать небуферизованный ввод / вывод вместо часто вызывает FlushFileBuffers. Чтобы открыть файл для небуферизованного ввода-вывода, вызвать функцию CreateFile с FILE_FLAG_NO_BUFFERING и FILE_FLAG_WRITE_THROUGH флаги. это предотвращает содержание файла кэшируется и сбрасывает метаданные в диск с каждой записью. Для большего информация, см. CreateFile.

В общем случае FlushFileBuffers - это «дорогая» операция, , поскольку она сбрасывает все данные в кеше обратной записи :

FlushFileBuffers (): эта функция сбрасывает все в кеше обратной записи, так как не знает, какая часть кэша принадлежит вашему файлу. Это может занять много времени, в зависимости от размера кеша и скорости носителя. Насколько это необходимо? Есть поток, который проходит и пишет грязные страницы, так что, вероятно, не очень необходимо.

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

/* lowio commit to ensure data is written to disk */
if (str->_flag & _IOCOMMIT) {
        return (_commit(_fileno(str)) ? EOF : 0);
}

Из реализации _commit:

if ( !FlushFileBuffers((HANDLE)_get_osfhandle(filedes)) ) {
        retval = GetLastError();
}
2 голосов
/ 22 декабря 2009

Традиционно функции библиотеки времени выполнения C буферизуют данные и запускают только операцию записи (отсюда необходимость таких функций, как fflush). Я не думаю, что WriteFile буферизует операцию записи, поэтому каждый раз, когда вы вызываете WriteFile, запускается операция ввода-вывода, тогда как при fwrite ввод-вывод запускается, когда буфер достигает определенного размера.

Как видно из ваших измерений, буферизованный ввод / вывод имеет тенденцию быть более эффективным ...

1 голос
/ 22 декабря 2009

Мне все еще немного неясно, в чем вопрос. Вы начинаете с разговора об управлении временем жизни временного файла, а затем переходите к переносу интерфейса ввода-вывода всего файла. Вы спрашиваете о том, как управлять временным файлом без потери производительности при переносе всех файловых операций ввода-вывода? Или вас интересует, как функции CRT могут быть быстрее, чем функции WinAPI, над которыми они построены?

Несколько сравнений, выполняемых между функциями времени выполнения C и функциями WinAPi, относятся к разновидности яблок и апельсинов.

Функции времени выполнения C буферизуют ввод / вывод в памяти библиотеки. В ОС есть еще один уровень буферизации (и кеширования).

fflush сбрасывает данные из библиотечных буферов в ОС. Он может идти непосредственно на диск или в буферы ОС для последующей записи. FlushFileBuffers получает данные из буферов ОС на диск, что обычно занимает больше времени, чем перемещение данных из буферов библиотеки в буферы ОС.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...