Ответ 1
Если вы хотите выйти за пределы спецификации C в конкретный код ОС, сопоставление памяти обычно считается наиболее эффективным способом.
Для Posix проверьте mmap
и для Windows проверьте OpenFileMapping
Сейчас я использую fread() для чтения файла, но на другом языке fread() неэффективен. Это то же самое в C? Если да, то как бы ускорить чтение файлов?
Если вы хотите выйти за пределы спецификации C в конкретный код ОС, сопоставление памяти обычно считается наиболее эффективным способом.
Для Posix проверьте mmap
и для Windows проверьте OpenFileMapping
Это действительно не имеет значения.
Если вы читаете с реального жесткого диска, он будет медленным. Жесткий диск - это горло вашей бутылки, и это оно.
Теперь, если вы глупы в своем обращении к read/fread/whatever и говорите, что fread() - байт за раз, то да, это будет медленным, поскольку накладные расходы на fread ( ) превысит накладные расходы при чтении с диска.
Если вы вызываете read/fread/whatever и запрашиваете приличную часть данных. Это будет зависеть от того, что вы делаете: иногда все хотят/нуждаются в 4 байтах (чтобы получить uint32), но иногда вы можете читать в больших кусках (4 KiB, 64 KiB и т.д. RAM дешево, пойти на что-то значительное.)
Если вы делаете небольшие чтения, некоторые вызовы более высокого уровня, такие как fread(), помогут вам, буферизуя данные за вашей спиной. Если вы делаете большие чтения, это может быть не очень полезно, но переход с fread на чтение, вероятно, не принесет такого большого улучшения, поскольку вы узко простыли на скорости диска.
Вкратце: если вы можете, попросите либеральную сумму при чтении и попытайтесь свести к минимуму то, что вы пишете. Для больших сумм, степени 2, как правило, более дружелюбны, чем что-либо еще, но, конечно же, это ОС, аппаратные средства и зависящие от погоды.
Итак, посмотрим, могут ли это выявить какие-либо отличия:
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#define BUFFER_SIZE (1 * 1024 * 1024)
#define ITERATIONS (10 * 1024)
double now()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + tv.tv_usec / 1000000.;
}
int main()
{
unsigned char buffer[BUFFER_SIZE]; // 1 MiB buffer
double end_time;
double total_time;
int i, x, y;
double start_time = now();
#ifdef USE_FREAD
FILE *fp;
fp = fopen("/dev/zero", "rb");
for(i = 0; i < ITERATIONS; ++i)
{
fread(buffer, BUFFER_SIZE, 1, fp);
for(x = 0; x < BUFFER_SIZE; x += 1024)
{
y += buffer[x];
}
}
fclose(fp);
#elif USE_MMAP
unsigned char *mmdata;
int fd = open("/dev/zero", O_RDONLY);
for(i = 0; i < ITERATIONS; ++i)
{
mmdata = mmap(NULL, BUFFER_SIZE, PROT_READ, MAP_PRIVATE, fd, i * BUFFER_SIZE);
// But if we don't touch it, it won't be read...
// I happen to know I have 4 KiB pages, YMMV
for(x = 0; x < BUFFER_SIZE; x += 1024)
{
y += mmdata[x];
}
munmap(mmdata, BUFFER_SIZE);
}
close(fd);
#else
int fd;
fd = open("/dev/zero", O_RDONLY);
for(i = 0; i < ITERATIONS; ++i)
{
read(fd, buffer, BUFFER_SIZE);
for(x = 0; x < BUFFER_SIZE; x += 1024)
{
y += buffer[x];
}
}
close(fd);
#endif
end_time = now();
total_time = end_time - start_time;
printf("It took %f seconds to read 10 GiB. That %f MiB/s.\n", total_time, ITERATIONS / total_time);
return 0;
}
... дает:
$ gcc -o reading reading.c
$ ./reading ; ./reading ; ./reading
It took 1.141995 seconds to read 10 GiB. That 8966.764671 MiB/s.
It took 1.131412 seconds to read 10 GiB. That 9050.637376 MiB/s.
It took 1.132440 seconds to read 10 GiB. That 9042.420953 MiB/s.
$ gcc -o reading reading.c -DUSE_FREAD
$ ./reading ; ./reading ; ./reading
It took 1.134837 seconds to read 10 GiB. That 9023.322991 MiB/s.
It took 1.128971 seconds to read 10 GiB. That 9070.207522 MiB/s.
It took 1.136845 seconds to read 10 GiB. That 9007.383586 MiB/s.
$ gcc -o reading reading.c -DUSE_MMAP
$ ./reading ; ./reading ; ./reading
It took 2.037207 seconds to read 10 GiB. That 5026.489386 MiB/s.
It took 2.037060 seconds to read 10 GiB. That 5026.852369 MiB/s.
It took 2.031698 seconds to read 10 GiB. That 5040.119180 MiB/s.
... или нет заметной разницы. (иногда он иногда выигрывает, иногда читается)
Примечание: медленный mmap
является неожиданным. Возможно, это связано с тем, что я просил его выделить для меня буфер. (Я не был уверен в требованиях о поставке указателя...)
В самом деле кратковременно: преждевременно не оптимизируйте. Сделайте его запущенным, сделайте все правильно, сделайте это быстро, этот порядок.
По популярному запросу я проверил тест на реальном файле. (Первые 675 MiB 32-битного CD-диска для установки Ubuntu 10.04 ISO) Это были результаты:
# Using fread()
It took 31.363983 seconds to read 675 MiB. That 21.521501 MiB/s.
It took 31.486195 seconds to read 675 MiB. That 21.437967 MiB/s.
It took 31.509051 seconds to read 675 MiB. That 21.422416 MiB/s.
It took 31.853389 seconds to read 675 MiB. That 21.190838 MiB/s.
# Using read()
It took 33.052984 seconds to read 675 MiB. That 20.421757 MiB/s.
It took 31.319416 seconds to read 675 MiB. That 21.552126 MiB/s.
It took 39.453453 seconds to read 675 MiB. That 17.108769 MiB/s.
It took 32.619912 seconds to read 675 MiB. That 20.692882 MiB/s.
# Using mmap()
It took 31.897643 seconds to read 675 MiB. That 21.161438 MiB/s.
It took 36.753138 seconds to read 675 MiB. That 18.365779 MiB/s.
It took 36.175385 seconds to read 675 MiB. That 18.659097 MiB/s.
It took 31.841998 seconds to read 675 MiB. That 21.198419 MiB/s.
... и один очень скучный программист, мы прочитали CD CD с диска. 12 раз. Перед каждым тестом кеш диска был очищен, и во время каждого теста было достаточно и примерно столько же, что и RAM, чтобы содержать CD ISO дважды в ОЗУ.
Одна заметка: изначально я использовал большой malloc() для заполнения памяти и, таким образом, минимизировал влияние кэширования диска. Возможно, стоит отметить, что mmap
здесь ужасно. Остальные два решения просто побежали, mmap
побежали и, по причинам, которые я не могу объяснить, начали подталкивать память к обмену, что и привело к ее снижению. (Программа не протекала, насколько мне известно (исходный код выше) - фактическая "используемая память" оставалась постоянной на протяжении всех испытаний.)
read() отправил самое быстрое время в целом, fread() отправил действительно последовательное время. Однако это может быть связано с небольшим икотой во время тестирования. Все сказали, что три метода были примерно равны. (Особенно fread
и read
...)
Что замедляет вас?
Если вам требуется максимально быстрое чтение файла (при этом он отлично работает с операционной системой), перейдите прямо к вызовам вашей ОС и убедитесь, что вы изучаете, как их использовать наиболее эффективно.
Если время поиска является проблемой, переустановите свои данные на диск (если можете) и сохраните его в больших, предварительно обработанных файлах, вместо того, чтобы загружать небольшие куски отсюда и там.
Если время передачи данных является проблемой, возможно, рассмотрим сжатие данных.
Я думаю о системном вызове read.
Имейте в виду, что fread - это оболочка для чтения.
С другой стороны, у fread есть внутренний буфер, поэтому "чтение" может быть быстрее, но я думаю, что "fread" будет более эффективным.
Если fread
работает медленно, это связано с добавлением дополнительных слоев, которые он добавляет к основному механизму операционной системы, для чтения из файла, который мешает использованию вашей конкретной программы fread
. Другими словами, он медленный, потому что вы не используете его так, как он был оптимизирован для.
Сказав это, более быстрое чтение файлов будет достигнуто путем понимания того, как работают функции ввода-вывода операционной системы, и предоставления вашей собственной абстракции, которая лучше управляет вашей программой. Большую часть времени вы можете сделать это с помощью памяти, сопоставляющей файл.
Однако, если вы нажимаете пределы машины, на которой вы работаете, отображение памяти, вероятно, будет недостаточным. В этот момент вам действительно нужно выяснить, как оптимизировать код ввода/вывода.
Проблема, которую некоторые люди отметили здесь, заключается в том, что в зависимости от вашего источника, размера вашего целевого буфера и т.д. вы можете создать настраиваемый обработчик для этого конкретного случая, но есть и другие случаи, такие как блочные/символьные устройства, т.е. /dev/ *, где стандартные правила, подобные этому, выполняются или не применяются, и ваш источник поддержки может быть чем-то, что всплывает персонаж без серийной работы без какой-либо буферизации, такой как I2C-шина, стандартный RS-232 и т.д. И есть некоторые другие источники, символьные устройства - это память, которая может отображать большие разделы памяти, такие как nvidia, с их символьным устройством видеоввода (/dev/nvidiactl).
Еще одна реализация дизайна, которую многие люди выбрали в высокопроизводительных приложениях, является асинхронным, а не синхронным вводом-выводом для обработки данных. Посмотрите на libaio и портированные версии libaio, которые предоставляют готовые решения для асинхронного ввода-вывода, а также изучают использование чтения с разделяемой памятью между рабочим и потребительским потоком (но имейте в виду, что это увеличит сложность программирования, если вы пойдете этот маршрут). Асинхронный ввод-вывод - это то, что вы не можете получить из коробки с помощью stdio, который вы можете получить со стандартными системными вызовами ОС. Просто будьте осторожны, поскольку есть бит чтения, которые являются "портативными" в соответствии со спецификацией, но не все операционные системы (например, FreeBSD) поддерживают POSIX STREAM (по выбору).
Еще одна вещь, которую вы можете сделать (в зависимости от того, насколько переносимы ваши данные), - это сжатие и/или преобразование в двоичный формат, такой как форматы базы данных, например BDB, SQL и т.д. Некоторые форматы базы данных переносимы на разных машинах с использованием endianness функции преобразования.
В целом было бы лучше взять набор алгоритмов и методов, запустить тесты производительности с использованием разных методов и оценить лучший алгоритм, который будет служить для средней задачи, которую будет выполнять ваше приложение. Это поможет вам определить, что лучший алгоритм.
Возможно, проверьте, как это делается perl. Подпрограммы Perl I/O оптимизированы и, как я понимаю, причина, по которой обработка текста с помощью фильтра perl может быть в два раза быстрее, чем одно и то же преобразование с помощью sed
.
Очевидно, что perl довольно сложный, а I/O - лишь малая часть того, что он делает. Я никогда не смотрел на его источник, поэтому я не мог дать вам более удобные указания, чем указать вам здесь.