Рассмотрим следующую программу:
#define _FILE_OFFSET_BITS 64 // Allow large files.
#define REVISION "POSIX Revision #9"
#include <iostream>
#include <cstdio>
#include <ctime>
const int block_size = 1024 * 1024;
const char block[block_size] = {};
int main()
{
std::cout << REVISION << std::endl;
std::time_t t0 = time(NULL);
std::cout << "Open: 'BigFile.bin'" << std::endl;
FILE * file;
file = fopen("BigFile.bin", "wb");
if (file != NULL)
{
std::cout << "Opened. Writing..." << std::endl;
for (int n=0; n<4096; n++)
{
size_t written = fwrite(block, 1, block_size, file);
if (written != block_size)
{
std::cout << "Write error." << std::endl;
return 1;
}
}
fclose(file);
std::cout << "Success." << std::endl;
time_t t1 = time(NULL);
if (t0 == ((time_t)-1) || t1 == ((time_t)-1))
{
std::cout << "Clock error." << std::endl;
return 2;
}
double ticks = (double)(t1 - t0);
std::cout << "Seconds: " << ticks << std::endl;
file = fopen("BigFile.log", "w");
fprintf(file, REVISION);
fprintf(file, " Seconds: %f\n", ticks);
fclose(file);
return 0;
}
std::cout << "Something went wrong." << std::endl;
return 1;
}
Он просто записывает 4 ГБ нулей в файл на диске и время, сколько это заняло.
В Linux это занимает в среднем 148 секунд. Под Windows на том же ПК это занимает в среднем 247 секунд.
Что за ад я делаю не так ?!
Код скомпилирован под GCC для Linux и Visual Studio для Windows, но я не могу представить себе юниверс, в котором используемый компилятор должен иметь какое-то измеримое значение для чистого теста ввода-вывода. Во всех случаях используется файловая система NTFS.
Я просто не понимаю, почему такая огромный разница в производительности существует. Я не знаю, почему Windows работает так медленно. Как заставить Windows работать на полной скорости, на которую явно способен диск?
(Приведенные выше цифры относятся к 32-разрядной версии OpenSUSE 13.1 и 32-разрядной версии Windows XP на старом ноутбуке Dell. Но я наблюдал похожие различия в скорости на нескольких компьютерах в офисе, работающих под управлением различных версий Windows.)
Редактировать: Исполняемый файл и файл, который он записывает, находятся на внешнем жестком диске USB, который отформатирован как NTFS и почти полностью пуст. Фрагментация почти наверняка не является проблемой. Это мог Это может быть проблема с драйверами, но я видел одинаковую разницу в производительности на нескольких других системах под управлением разных версий Windows. Там не установлен антивирус.
Я просто попытался изменить его, чтобы использовать Win32 API напрямую. (Очевидно, это работает только для Windows.) Время становится немного более неустойчивым, но все же в пределах нескольких процентов от того, что было раньше. Если я не укажу FILE_FLAG_WRITE_THROUGH
; тогда идет существенно помедленнее. Несколько других флагов делают его медленнее, но я не могу найти тот, который заставляет его идти Быстрее…
Вам нужно синхронизировать содержимое файла на диск, иначе вы просто измеряете уровень кэширования, выполняемый операционной системой.
Вызов fsync
прежде чем закрыть файл.
Если вы этого не сделаете, большая часть времени выполнения, скорее всего, будет потрачена на ожидание сброса кэша, чтобы в нем могли храниться новые данные, но, безусловно, часть записываемых вами данных не будет записана на диск время, когда вы закрываете файл. Таким образом, разница во времени выполнения, вероятно, связана с тем, что linux кэширует больше записей, прежде чем закончится свободное место в кеше. Напротив, если вы звоните fsync
перед закрытием файла, все записанные данные должны быть сброшены на диск до измерения времени.
Я подозреваю, если вы добавите fsync
вызов, время выполнения в двух системах не будет сильно отличаться.
Ваш тест не очень хороший способ измерить производительность, поскольку есть места, где разные оптимизации в разных ОС и библиотеках могут иметь огромное значение (сам компилятор не должен иметь большого значения).
Сначала мы можем рассмотреть fwrite
(или все, что работает на FILE*
) — это уровень библиотеки над уровнем ОС. Могут быть разные стратегии буферизации, которые имеют значение. Например, один умный способ реализации fwrite
будет очищать буферы, а затем отправлять блок данных прямо в ОС, а не проходить через уровень буфера. Это может привести к огромному преимуществу на следующем этапе
Во-вторых, у нас есть ОС / ядро, которые могут обрабатывать запись по-разному. Одной из разумных оптимизаций было бы копировать страницы, просто создав им псевдонимы, а затем использовать копирование при записи, если они были изменены в одном из псевдонимов. Linux уже делает (почти) это при выделении памяти для процесса (включая раздел BSS, где находится массив) — он просто помечает страницу как нули и может сохранить одну такую страницу для всех этих страниц, а затем создавать новую страницу всякий раз, когда кто-то меняет на нулевой странице. Повторное выполнение этого трюка означает, что ядро может просто создать псевдоним такой страницы в буфере диска. Это означает, что ядру не будет хватать дискового кэша при записи таких нулевых блоков, поскольку оно будет занимать только 4 КБ фактической памяти (за исключением таблиц страниц). Эта стратегия также возможна, если в блоке данных есть фактические данные.
Это означает, что запись может завершиться очень быстро без каких-либо данных фактически необходимо перенести на диск (до fwrite
завершает), даже без данных даже приходится копировать из одного места в другое в памяти.
Таким образом, вы используете разные библиотеки и разные ОС, и неудивительно, что они выполняют разные задачи в разное время.
Есть специальные оптимизации для страниц, которые являются нулями. Вы должны заполнить страницу случайными данными, прежде чем писать их.