Как действительно работает fread?

Объявление fread выглядит следующим образом:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

Вопрос: есть ли разница в производительности чтения двух таких вызовов на fread:

char a[1000];
  • fread(a, 1, 1000, stdin);
  • fread(a, 1000, 1, stdin);

Будет ли каждый раз читать 1000 байты?

Ответ 1

В производительности может быть и не быть. Существует различие в семантике.

fread(a, 1, 1000, stdin);

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

fread(a, 1000, 1, stdin);

пытается прочитать 1 элемент данных длиной 1000 байт.

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

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

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

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

Ответ 2

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

Если ваш файл меньше 1000 байт, fread(a, 1, 1000, stdin) (чтение 1000 элементов по 1 байт каждый) все равно будет копировать все байты до EOF. С другой стороны, результат fread(a, 1000, 1, stdin) (считанный 1 1000-байтовый элемент), хранящийся в a, не указан, потому что недостаточно данных для завершения чтения "первого" (и только) 1000-байтового элемента.

Конечно, некоторые реализации могут все же скопировать элемент "partial" на столько байтов, сколько необходимо.

Ответ 3

Это будет детализация реализации. В glibc два идентичны по производительности, поскольку они реализованы в основном как (Ref http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/iofread.c):

size_t fread (void* buf, size_t size, size_t count, FILE* f)
{
    size_t bytes_requested = size * count;
    size_t bytes_read = read(f->fd, buf, bytes_requested);
    return bytes_read / size;
}

Обратите внимание, что стандарт C и POSIX не гарантирует, что полный объект размером size должен читаться каждый раз. Если полный объект не может быть прочитан (например, stdin имеет только 999 байт, но вы запросили size == 1000), файл будет оставлен в заданном состоянии (C99 §7.19.8.1/2).

Изменить: см. другие ответы о POSIX.

Ответ 4

fread вызывает getc внутренне. в Minix количество раз getc вызывается просто size*nmemb, поэтому количество вызовов getc будет зависеть от продукта этих двух. Итак, оба fread(a, 1, 1000, stdin) и fread(a, 1000, 1, stdin) будут запускать getc 1000=(1000*1) Times. Вот реализация siimple fread из Minix

size_t fread(void *ptr, size_t size, size_t nmemb, register FILE *stream){
register char *cp = ptr;
register int c;
size_t ndone = 0;
register size_t s;

if (size)
    while ( ndone < nmemb ) {
    s = size;
    do {
        if ((c = getc(stream)) != EOF)
            *cp++ = c;
        else
            return ndone;
    } while (--s);
    ndone++;
}

return ndone;
}

Ответ 5

Не может быть разницы в производительности, но эти вызовы не совпадают.

  • fread возвращает количество прочитанных элементов, поэтому эти вызовы возвращают разные значения.
  • Если элемент не может быть полностью прочитан, его значение неопределенно:

Если возникает ошибка, результирующее значение индикатора позиции файла для потока неопределенный. Если частичный элемент считывается, его значение является неопределенным. (ISO/IEC 9899: TC2 7.19.8.1)

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

Ответ 6

Еще одна форма предложения http://pubs.opengroup.org/onlinepubs/000095399/functions/fread.html примечательна

Функция fread() должна считывать в массив, на который указывает ptr до элементов nitems, размер которых задается размером в байтах, из потока, на который указывает поток. Для каждого объекта вызовы размера должны выполняться функции fgetc() и результаты, хранящиеся, в порядке чтения в массиве без знака char, который точно накладывает объект.

В обоих случаях данные будут доступны через fgetc()...!

Ответ 7

Я хотел пояснить ответы здесь. fread выполняет буферизацию ввода-вывода. Фактическое использование fread используемых блоков блоков чтения определяется используемой реализацией C.

Все современные библиотеки C будут иметь одинаковую производительность с двумя вызовами:

fread(a, 1, 1000, file);
fread(a, 1000, 1, file);

Даже что-то вроде:

for (int i=0; i<1000; i++)
  a[i] = fgetc(file)

Должны приводить к тем же шаблонам доступа к диску, хотя fgetc будет медленнее из-за большего количества вызовов в стандартные библиотеки c, а в некоторых случаях необходимо, чтобы диск выполнил дополнительные запросы, которые в противном случае были бы оптимизированы.

Возвращаясь к разнице между двумя формами fread. Первое возвращает фактическое количество прочитанных байтов. Последний возвращает 0, если размер файла меньше 1000, в противном случае он возвращает 1. В обоих случаях буфер заполняется теми же данными, то есть содержимым файла до 1000 байт.

В общем, вы, вероятно, хотите сохранить 2-й параметр (размер) равным 1, чтобы вы получили количество прочитанных байтов.