Як насправді працює фріад?


75

Декларація freadнаступна:

Питання полягає в тому: чи є різниця у продуктивності читання двох таких викликів fread:

  1. fread(a, 1, 1000, stdin);
  2. fread(a, 1000, 1, stdin);

Чи буде він читати 1000байти відразу щоразу?

Відповіді:


106

Різниця в продуктивності може бути, а може і не бути. Існує різниця в семантиці.

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

намагається прочитати 1 елемент даних довжиною 1000 байт.

Вони різні, оскільки fread()повертає кількість елементів даних, які вона змогла прочитати, а не кількість байтів. Якщо він досягає кінця файлу (або умови помилки) перед зчитуванням повних 1000 байт, перша версія повинна вказати, скільки саме байтів вона прочитала; другий просто не вдається і повертає 0.

На практиці, ймовірно, це просто виклик функції нижчого рівня, яка намагається прочитати 1000 байт і вказує, скільки байт вона насправді прочитала. Для більших читань він може здійснювати кілька дзвінків нижчого рівня. Обчислення значення, яке потрібно повернути, fread()відрізняється, але витрати на обчислення є тривіальними.

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

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


другий може не потрудитися щось прочитати. В обох випадках показник позиції файлу висувається на кількість успішно прочитаних символів, тобто 900, чи не повинно бути, що у другій версії індикатор положення файлу не просувається, оскільки нічого не читається? Іншими словами, чи не слід fread(a, 1000, N, stdin);завжди перевищувати показник fp на множину 1000?
Шахбаз,

1
Неважливо, знайшов. C11 в 7.21.8.1.2 та 7.21.8.2.2 говорить: Якщо виникає помилка, результуюче значення показника положення файлу для потоку є невизначеним.
Шахбаз,

так що немає можливості відновити положення індикатора? Або щоб уникнути читання того останнього шматка, який невпорядкований покажчиком положення?
Девід 天宇 Вонг

1
@David 天宇 Wong: Якщо вам потрібно відновити позицію, телефонуйте ftellдо дзвінка fread, а потім fseekпісля.
Кіт Томпсон,

2
Специфікація POSIX набагато суворіша ... вона вимагає, щоб fread робив розмір fgetc для кожного об'єкта, тому в обох випадках буде зроблено точно таку ж кількість fgetc (але значення повернення будуть різними).
Jim Balter

17

Відповідно до специфікації , ці два варіанти можуть по-різному трактуватися реалізацією.

Якщо у вашому файлі менше 1000 байтів, fread(a, 1, 1000, stdin)(прочитайте 1000 елементів по 1 байту кожен) буде копіювати всі байти до EOF. З іншого боку, результат fread(a, 1000, 1, stdin)(зчитування 1 1000-байтового елемента), що зберігається у, aне визначений, оскільки недостатньо даних для завершення зчитування «першого» (і єдиного) елемента в 1000 байтів.

Звичайно, деякі реалізації все ще можуть копіювати елемент "частково" на стільки байтів, скільки потрібно.


15

Це було б деталлю реалізації. У glibc вони однакові за продуктивністю, оскільки це реалізовано в основному як (Посилання http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/iofread.c ):

Зверніть увагу, що C та POSIXstandard не гарантує, що повний об'єкт розміру sizeпотрібно читати щоразу. Якщо повний об'єкт неможливо прочитати (наприклад, stdinмає лише 999 байт, але ви просили size == 1000), файл залишатиметься у визначеному стані (C99 §7.19.8.1 / 2).

Редагувати: Дивіться інші відповіді про POSIX.


Ви згадуєте стандарт POSIX, але він вимагає реалізації fread з точки зору fgetc, який є набагато детермінованішим, ніж вимога C.
Jim Balter

1
Awesome anwer .. !! саме те, що потрібно всім, хто десантується тут .. !!! Я здивований, що у нього стільки голосів.
mk ..

Це те саме і для fwrite?
mk ..

Важливий момент: Ви можете зламати файл, читаючи записи розміром> 1.
ArekBulski

5

freadдзвінки getcвнутрішньо. за Minixкількістю разів getcвикликається просто size*nmembтак, скільки разів getcбуде викликано, залежить від добутку цих двох. Тож обидва fread(a, 1, 1000, stdin)і fread(a, 1000, 1, stdin)буде запускати getc 1000=(1000*1)Times. Ось проста реалізація freadMinix


1
справжня відповідь на мій погляд
Сатвік

3

Можливо, різниці в продуктивності не буде, але ці дзвінки не однакові.

  • fread повертає кількість прочитаних елементів, тому ці виклики повернуть різні значення.
  • Якщо елемент неможливо прочитати повністю, його значення невизначене:

Якщо виникає помилка, результуюче значення показника положення файлу для потоку є невизначеним. Якщо зчитується частковий елемент, його значення невизначене. (ISO / IEC 9899: TC2 7.19.8.1)

Немає великої різниці у реалізації glibc , який просто помножує розмір елемента на кількість елементів, щоб визначити, скільки байтів читати, і ділить кількість прочитаних на розмір елемента в підсумку. Але версія із зазначенням розміру елемента 1 завжди повідомляє вам правильну кількість прочитаних байтів. Однак, якщо ви дбаєте лише про повністю прочитані елементи певного розміру, використання іншої форми позбавляє вас від ділення.


1

Примітно ще одне речення у формі http://pubs.opengroup.org/onlinepubs/000095399/functions/fread.html

Функція fread () зчитує в масив, на який вказує ptr, до елементів нітемів, розмір яких визначається розміром у байтах, з потоку, на який вказує потік. Для кожного об'єкта потрібно зробити виклики розміру до функції fgetc (), а результати, збережені в порядку читання, у масиві беззнакового символу, який точно накладає об'єкт.

До коротких даних в обох випадках буде доступний fgetc () ...!


так, я теж так відчуваю, але на цій сторінці написано "Функціональність, описана на цій довідковій сторінці, узгоджується зі стандартом ISO C". здається сумнів?
Jeegar Patel

@ Mr.32: стандарт говорить те саме про дзвінки на fgetc, тому Posix справді відповідає C99. Але стандарт не надає відповідній програмі жодних засобів, щоб визначити, "чи fgetcсправді" це викликано, або freadщось інше, що еквівалентно. 5.1.2.3 пояснює, що стандарт описує лише поведінку "абстрактної машини", і перелічує, яким чином реальна програма повинна відповідати цій поведінці. Це називається правилом "як би" у C ++, але не C (моя помилка раніше). Поведінка, що не спостерігається, не обов'язково повинна бути однаковою.
Стів Джессоп,

Отже, навіть якщо конкретна реалізація дає вам деякий спосіб підрахувати, скільки разів fgetcвикликається (можливо, дозволяючи вам зв’язати вашу програму з власною версією цієї функції, наприклад, шляхом модифікації та перекомпіляції libc), це може зробити це із застереженням що функція, яку ви замінюєте, викликається не завжди і лише тоді, коли стандарт описує абстрактну машину як її викликає.
Steve Jessop

@SteveJessop "Поведінка, що не спостерігається, не обов'язково повинна бути однаковою." То чому це задокументовано в POSIX?
Роман Бишко

@Beginner: оскільки опис поведінки абстрактної машини - це зручний спосіб описати ефект fread(або будь-який інший біт коду C). Це зафіксовано таким чином у Posix просто тому, що це задокументовано у стандарті.
Steve Jessop

1

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

Усі сучасні бібліотеки C матимуть однакову продуктивність із двома викликами:

Навіть щось на зразок:

Це має призвести до тих самих шаблонів доступу до диска, хоча fgetc буде повільнішим через більшу кількість викликів у стандартні бібліотеки c, а в деяких випадках необхідність диску для додаткових пошуків, які в іншому випадку були б оптимізовані.

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

Загалом, ви, мабуть, хочете зберегти 2-й параметр (розмір) таким, щоб ви отримали кількість прочитаних байтів.


"Усі сучасні бібліотеки C матимуть однакову продуктивність з двома викликами" - так. "у деяких випадках необхідність диска для додаткових пошуків, які в іншому випадку були б оптимізовані" - ні. fgetc просто читає з буфера в пам'яті stdio. І навіть якщо для потоку було встановлено, що він не буферизований, базовий диск ОС буфери читає.
Jim Balter

@Jim: fgetc читає зі stdio не так, як fread. Очевидним результатом цього є те, що fgetc завжди максимізує кількість пошуків / системних викликів (погано), де як fread мінімізує кількість пошуків / системних дзвінків, оскільки ви надаєте libc більше інформації про те, що ви робите.
Clarus

1
Вибачте, але ви не уявляєте, про що ви говорите ... немає жодного способу, в якому Fread чи fgetc різняться, що впливає на кількість пошуків, і ви не надали жодної підтримки цій абсурдній заяві. Зверніть увагу, що визначення fread у стандартах C99 та POSIX подано в термінах fgetc, як це обговорювалося в інших місцях на цій сторінці.
Jim Balter
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.