Самый быстрый файл, читающий в C

Прямо сейчас я использую освобожденный () для чтения файла, но на другом освобожденном языке () неэффективный i'v, сказал. Действительно ли это - то же в C? Если так, как более быстрое чтение файла было бы сделано?

18
задан Jay 8 June 2010 в 23:35
поделиться

7 ответов

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

Для Posix посмотрите mmap, а для Windows - OpenFileMapping

15
ответ дан 30 November 2019 в 06:09
поделиться

Что вас тормозит?

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

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

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

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

8
ответ дан 30 November 2019 в 06:09
поделиться

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

Имейте в виду, что fread - это оболочка для чтения.

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

1
ответ дан 30 November 2019 в 06:09
поделиться

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

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

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

0
ответ дан 30 November 2019 в 06:09
поделиться

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

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

0
ответ дан 30 November 2019 в 06:09
поделиться

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

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

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

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

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

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

Итак, давайте посмотрим, может ли это выявить какие-либо различия:

#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.

... или нет заметной разницы. (fread иногда выигрывает, иногда читается)

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

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


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

# 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.

... и один очень скучающий программист позже, мы прочитали компакт-диск ISO с диска . 12 раз. Перед каждым тестом кеш диска очищался, и во время каждого теста было достаточно (примерно столько же) свободной оперативной памяти, чтобы дважды удерживать CD ISO в ОЗУ.

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

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

32
ответ дан 30 November 2019 в 06:09
поделиться

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

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

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

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

0
ответ дан 30 November 2019 в 06:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: