сравнение скорости между fgetc / fputc и fread / fwrite в C - PullRequest
8 голосов
/ 24 ноября 2011

Итак (просто для удовольствия), я просто пытался написать C-код для копирования файла. Я читаю вокруг, и кажется, что все функции для чтения из потока вызывают fgetc() (надеюсь, это правда?), Поэтому я использовал эту функцию:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define FILEr "img1.png"
#define FILEw "img2.png"
main()
{
    clock_t start,diff;
    int msec;
    FILE *fr,*fw;
    fr=fopen(FILEr,"r");
    fw=fopen(FILEw,"w");
    start=clock();
    while((!feof(fr)))
        fputc(fgetc(fr),fw);
    diff=clock()-start;
    msec=diff*1000/CLOCKS_PER_SEC;
    printf("Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);
    fclose(fr);
    fclose(fw);
}

Это дало время работы 140 мс для этого файла на 2.10 ГГц процессоре Core2Duo T6500 Dell inspiron. Однако, когда я пытаюсь использовать fread / fwrite, у меня уменьшается время выполнения, так как я продолжаю увеличивать количество байтов (т. Е. Переменная st в следующем коде), передаваемых для каждого вызова, до тех пор, пока оно не достигнет пика около 10 мс! Вот код:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define FILEr "img1.png"
#define FILEw "img2.png"
main()
{
    clock_t start,diff;
    // number of bytes copied at each step
    size_t st=10000;
    int msec;
    FILE *fr,*fw;
    // placeholder for value that is read
    char *x;
    x=malloc(st);
    fr=fopen(FILEr,"r");
    fw=fopen(FILEw,"w");
    start=clock();
    while(!feof(fr))
     {
        fread(x,1,st,fr);
        fwrite(x,1,st,fw);
     }
    diff=clock()-start;
    msec=diff*1000/CLOCKS_PER_SEC;
    printf("Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);
    fclose(fr);
    fclose(fw);
    free(x);
}

Почему это происходит? Т.е. если fread на самом деле является множественными вызовами fgetc, то почему разница в скорости? РЕДАКТИРОВАТЬ: указано, что «увеличение количества байтов» относится к переменной st во втором коде

Ответы [ 4 ]

15 голосов
/ 24 ноября 2011

fread() не вызывает fgetc() для чтения каждого байта.

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

6 голосов
/ 24 ноября 2011

Вы забываете о буферизации файлов ( inode, dentry и кеши страниц ).

Очистите их перед запуском:

echo 3 > /proc/sys/vm/drop_caches

Справка:

Бенчмаркинг - это искусство. Обратитесь к bonnie++, iozone и phoronix для правильного сравнения производительности файловой системы. Характерно, что bonnie++ не позволит использовать эталонный тест с записанным объемом менее чем в 2 раза доступной системной памяти.

Почему?

(ответ: буферные эффекты!)

3 голосов
/ 28 февраля 2012

Функции stdio будут заполнять буфер чтения размером «BUFSIZ», как определено в stdio.h, и будут делать только один системный вызов read (2) каждый раз, когда буфер очищается.Они не будут делать индивидуальный системный вызов read (2) для каждого потребляемого байта - они читают большие куски.BUFSIZ, как правило, что-то вроде 1024 или 4096.

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

С другой стороны, как вы заметили, вы можете сделать системный вызов read (2) произвольного размера, установив этот размер в вызове, хотя вы получаете убывающую отдачус этим в какой-то момент.

Кстати, вы могли бы также использовать open (2), а не fopen (3), если вы делаете это таким образом.Нет смысла открывать файл, который вы собираетесь использовать только для его файлового дескриптора.

2 голосов
/ 24 ноября 2011

Как Сехе говорит, что это отчасти потому, что буферизация, но это еще не все, и я объясню, почему это так и в то же время, почему fgetc() даст больше задержки.

fgetc() вызывается для каждого байта, считываемого из файла.

fread() вызывается для каждых n байтов локального буфера для данных файла.

Итак, для файла 10 МБ:

fgetc() называется: 10 485 760 раз

При fread с буфером 1 КБ функция вызывается 10 240 раз.

Для простоты скажем, что каждый вызов функции занимает 1 мс: fgetc займет 10 485 760 мс = 10485,76 секунд ~ 2 9127 часов fread займет 10 240 мс = 10,24 с

Кроме того, ОС обычно выполняет чтение и запись на одном и том же устройстве, я полагаю, что ваш пример делает это на одном жестком диске. ОС при чтении исходного файла перемещает головки жесткого диска по дискам вращающегося диска в поисках файла, затем читает 1 байт, помещает его в память, затем снова перемещает головку чтения / записи по вращающимся дискам жесткого диска, глядя на место что ОС и контроллер жесткого диска согласились определить местонахождение файла назначения и затем записывают 1 байт из памяти. В приведенном выше примере это происходит более 10 миллионов раз для каждого файла: в общей сложности более 20 миллионов раз, при использовании буферизованной версии это всего лишь более 20 000 раз.

Помимо того, что ОС при чтении с диска помещает в память еще несколько КБ данных на жестком диске для повышения производительности, это может ускорить работу программы даже при использовании менее эффективного fgetc, поскольку программа считывает данные из памяти ОС. вместо того, чтобы читать прямо с жесткого диска. Это то, к чему относится ответ Сихе.

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

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

Резюме: Каждый вызов функции имеет определенные издержки, чтение с жесткого диска имеет другие издержки, а кеширование / буферы помогают ускорить процесс.

Другая информация

http://en.wikipedia.org/wiki/Disk_read-and-write_head

http://en.wikipedia.org/wiki/Hard_disk#Components

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