Что происходит за занавесом во время дискового ввода-вывода?
Когда я пытаюсь найти какую-то позицию в файле и записать небольшой объем данных (20 байт), что происходит за кулисами?
Мое понимание
Насколько мне известно, наименьшая единица данных, которая может быть записана или прочитана с диска, - это один сектор (традиционно 512 байт, но этот стандарт сейчас меняется). Это означает, что для записи 20 байтов мне нужно прочитать целый сектор, изменить часть его в памяти и записать обратно на диск.
Это то, что я ожидаю от небуферизованного ввода-вывода. Я также ожидаю, что буферизованный ввод / вывод будет делать примерно то же самое, но будьте умны в отношении своего кеша. Таким образом, я бы подумал, что если я выбрасываю локальность из окна, выполняя произвольные операции поиска и записи, буферизованный и небуферизованный ввод-вывод должен иметь одинаковую производительность ... может быть, небуферизованный получится немного лучше.
Опять же, я знаю этобез ума от буферизованного ввода-вывода для буферизации только одного сектора, так что я мог бы также ожидать, что он будет работать ужасно.
Мое заявление
Я храню значения, собранные драйвером устройства SCADA, который получает удаленную телеметрию на сумму свыше ста тысяч точек. В файле есть дополнительные данные, так что каждая запись составляет 40 байт, но только 20 байт необходимо записать во время обновления.
Тест перед внедрением
Чтобы проверить, что я неМне не нужно придумывать какое-то блестяще изощренное решение, я провел тест с использованием нескольких миллионов случайных записей, записанных в файл, который может содержать в общей сложности 200 000 записей. Каждый тест запускает генератор случайных чисел с одинаковым значением, чтобы быть справедливым. Сначала я стираю файл и дополняю его до общей длины (около 7,6 мегабайт), затем зацикливаюсь несколько миллионов раз, передавая случайное смещение файла и некоторые данные одной из двух тестовых функций:
void WriteOldSchool( void *context, long offset, Data *data )
{
int fd = (int)context;
lseek( fd, offset, SEEK_SET );
write( fd, (void*)data, sizeof(Data) );
}
void WriteStandard( void *context, long offset, Data *data )
{
FILE *fp = (FILE*)context;
fseek( fp, offset, SEEK_SET );
fwrite( (void*)data, sizeof(Data), 1, fp );
fflush(fp);
}
Может быть, никаких сюрпризов?
OldSchool
Метод вышел на первое место - очень много. Это было более чем в 6 раз быстрее (1,48 миллиона против 232000 записей в секунду). Чтобы убедиться, что я неНе обращая внимания на аппаратное кэширование, я увеличил размер своей базы данных до 20 миллионов записей (размер файла 763 мегабайта) и получил те же результаты.
Прежде чем указать на очевидный призыв кfflush
Позвольте мне сказать, что удаление этого не имело никакого эффекта. Я предполагаю, что это потому, что кеш должен быть зафиксирован, когда я ищу достаточно далеко, что я и делаюя делаю большую часть времени.
И что'происходит?
Мне кажется, что буферизованный ввод-вывод должен читать (и, возможно, записывать все) большую часть файла всякий раз, когда я пытаюсь записать. Поскольку я почти никогда не пользуюсь его кешем, это крайне расточительно.
Кроме того (и я нене знаю деталей аппаратного кэширования на диске), если буферизованный ввод / вывод пытается записать группу секторов, когда я изменяю только один, это снизит эффективность аппаратного кэша.
Есть ли эксперты по диску, которые могут прокомментировать и объяснить это лучше, чем мои экспериментальные результаты? знак равно