Самое быстрое чтение файлов в C - PullRequest
18 голосов
/ 09 июня 2010

Сейчас я использую fread () для чтения файла, но на другом языке fread () неэффективен, как мне сказали. Это то же самое в C? Если да, то как будет выполняться чтение файла быстрее?

Ответы [ 7 ]

31 голосов
/ 09 июня 2010

Это действительно не должно иметь значения.

Если вы читаете с реального жесткого диска, это будет медленно. Жесткий диск - твоя горлышко от бутылки, и все.

Теперь, если вы глупы из-за вызова read / fread / что угодно и, скажем, fread () - байт за раз, тогда да, это будет медленно, как издержки fread ( ) превысит затраты на чтение с диска.

Если вы вызываете read / fread / what и запрашиваете приличную часть данных. Это будет зависеть от того, что вы делаете: иногда все, что вам нужно / нужно, - это 4 байта (чтобы получить uint32), но иногда вы можете читать большими кусками (4 КиБ, 64 КиБ и т. Д.). .)

Если вы выполняете небольшое чтение, некоторые вызовы более высокого уровня, такие как 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's %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's 8966.764671 MiB/s.
It took 1.131412 seconds to read 10 GiB. That's 9050.637376 MiB/s.
It took 1.132440 seconds to read 10 GiB. That's 9042.420953 MiB/s.
$ gcc -o reading reading.c -DUSE_FREAD
$ ./reading ; ./reading ; ./reading 
It took 1.134837 seconds to read 10 GiB. That's 9023.322991 MiB/s.
It took 1.128971 seconds to read 10 GiB. That's 9070.207522 MiB/s.
It took 1.136845 seconds to read 10 GiB. That's 9007.383586 MiB/s.
$ gcc -o reading reading.c -DUSE_MMAP
$ ./reading ; ./reading ; ./reading 
It took 2.037207 seconds to read 10 GiB. That's 5026.489386 MiB/s.
It took 2.037060 seconds to read 10 GiB. That's 5026.852369 MiB/s.
It took 2.031698 seconds to read 10 GiB. That's 5040.119180 MiB/s.

... или нет заметной разницы. (фред иногда побеждает, иногда читается)

Примечание : медленный mmap удивителен. Это может быть связано с тем, что я попросил выделить для меня буфер. (Я не был уверен в требованиях к указателю ...)

Короче говоря: не оптимизируйте преждевременно. Заставь его работать, сделай это правильно, сделай это быстро, этот порядок.


Вернувшись по многочисленным просьбам, я запустил тест на реальном файле. (Первые 675 МБ установочного компакт-диска Ubuntu 10.04 с 32-разрядным рабочим столом для ISO) Это были результаты:

# Using fread()
It took 31.363983 seconds to read 675 MiB. That's 21.521501 MiB/s.
It took 31.486195 seconds to read 675 MiB. That's 21.437967 MiB/s.
It took 31.509051 seconds to read 675 MiB. That's 21.422416 MiB/s.
It took 31.853389 seconds to read 675 MiB. That's 21.190838 MiB/s.
# Using read()
It took 33.052984 seconds to read 675 MiB. That's 20.421757 MiB/s.
It took 31.319416 seconds to read 675 MiB. That's 21.552126 MiB/s.
It took 39.453453 seconds to read 675 MiB. That's 17.108769 MiB/s.
It took 32.619912 seconds to read 675 MiB. That's 20.692882 MiB/s.
# Using mmap()
It took 31.897643 seconds to read 675 MiB. That's 21.161438 MiB/s.
It took 36.753138 seconds to read 675 MiB. That's 18.365779 MiB/s.
It took 36.175385 seconds to read 675 MiB. That's 18.659097 MiB/s.
It took 31.841998 seconds to read 675 MiB. That's 21.198419 MiB/s.

... и одному очень программисту позже, мы прочли CD ISO с диска. 12 раз Перед каждым тестом дисковый кэш очищался, и во время каждого теста было достаточно и примерно одинакового объема свободной памяти, чтобы дважды держать CD ISO в оперативной памяти.

Одно интересное замечание: я изначально использовал большой malloc () для заполнения памяти и, таким образом, минимизировал эффекты кеширования диска. Возможно, стоит отметить, что mmap здесь ужасно выступил. Два других решения просто работали, mmap запускались и, по причинам, которые я не могу объяснить, начали подталкивать память к обмену, что снижало ее производительность. (Насколько я знаю, программа не протекала (исходный код приведен выше) - фактическая «использованная память» оставалась неизменной на протяжении всех испытаний.)

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

15 голосов
/ 09 июня 2010

Если вы хотите выйти за пределы спецификации C в специфический для ОС код, отображение памяти обычно считается наиболее эффективным способом.

Для Posix: mmap, для Windows - OpenFileMapping

.
7 голосов
/ 09 июня 2010

Что тебя тормозит?

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

  1. Как физически расположены ваши данные? Например, вращающиеся диски могут быстрее считывать данные, хранящиеся на краях, и вы хотите минимизировать или исключить время поиска.
  2. Ваши данные предварительно обработаны? Вам нужно что-то делать между загрузкой их с диска и использованием?
  3. Каков оптимальный размер куска для чтения? (Возможно, он даже несколько кратен размеру сектора. Проверьте документацию по вашей ОС.)

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

Если время передачи данных является проблемой, возможно, стоит рассмотреть сжатие данных.

1 голос
/ 09 июня 2010

Я думаю о системном вызове read .

Имейте в виду, что fread является оберткой для read.

С другой стороны, fread имеетвнутренний буфер, так что «чтение» может быть быстрее, но я думаю, что «fread» будет более эффективным.

0 голосов
/ 09 июня 2010

Может быть, посмотрите, как это делает Perl.Процедуры ввода-вывода в Perl оптимизированы и, как я понимаю, являются причиной того, что обработка текста с помощью фильтра perl может быть в два раза быстрее, чем выполнение того же преобразования с sed.

Очевидно, что perl довольно сложен,и I / O - только одна маленькая часть того, что он делает.Я никогда не смотрел на его источник, поэтому я не мог дать вам лучшие указания, чем указать вам здесь .

0 голосов
/ 09 июня 2010

Проблема, которую некоторые люди отметили здесь, заключается в том, что в зависимости от вашего источника, вашего целевого размера буфера и т. Д. Вы можете создать собственный обработчик для этого конкретного случая, но есть и другие случаи, такие как блочные / символьные устройства, т.е./ dev / *, где такие стандартные правила применяются или не применяются, и ваш источник поддержки может быть чем-то, что выводит символ последовательно без какой-либо буферизации, например, шина I2C, стандарт RS-232 и т. д. А есть и другие источники, гдеСимвольные устройства - это отображаемые в памяти большие разделы памяти, как это делает nvidia со своим видеодрайвером (/dev/nvidiactl).

Еще одна конструктивная реализация, которую многие люди выбрали в высокопроизводительных приложениях, - асинхронная, а не синхронная.Ввод / вывод для обработки того, как данные читаются.Изучите libaio и портированные версии libaio, которые предоставляют готовые решения для асинхронного ввода-вывода, а также изучите использование чтения с общей памятью между рабочим и потребительским потоком (но имейте в виду, что это увеличит сложность программирования, если выэтот маршрут).Асинхронный ввод-вывод - это то, что вы не можете получить из коробки с помощью stdio, которую вы можете получить с помощью стандартных системных вызовов ОС.Просто будьте осторожны, поскольку есть биты чтения, которые являются «переносимыми» в соответствии со спецификацией, но не все операционные системы (например, FreeBSD) поддерживают POSIX STREAM (по выбору).

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

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

0 голосов
/ 09 июня 2010

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

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

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

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