LINUX.ORG.RU

[C] Чтение файла с помощью fgetc

 


0

1

Навеяно тредом про Exim. Решил я проверить, действительно ли всё так хорошо и поток байт из файла можно читать с помощью fgetc(), а буферы винта и ФС сами разрулят ситуацию.

Для проверки использовался следующий код:

#include <stdio.h>
#include <time.h>

int main() {
    const int n = 16384;
    char buf[n];

    time_t t1 = time(0);
    FILE* f = fopen("./1.avi","rb");
    while(!feof(f)) fgetc(f);//fread(buf,1,n,f);
    fclose(f);
    time_t t2 = time(0);

    printf("%i%,t2-t2);
    return 0;

Каждый раз брался новый файл, чтобы оно не кэшировалось слишком.

Использовалось две ОС: 1) Mandriva Linux 2011.0, ext3, размер блока 4096 2) MS Windows XP, NTFS, размер блока 4096

Таблицы экспериментов:

Mandriva, Ext3                              Windows, NTFS
метод | буфер | размер | время | мб/сек|    метод | буфер | размер | время | мб/сек|
---------------------------------------|    ---------------------------------------|
fgetc |     - |    264 |    12 |  22.0 |    fgetc |     - |    426 |    71 |   6.0 |
---------------------------------------|    ---------------------------------------|
fread |     1 |    441 |    39 |  11.3 |    fread |     1 |    426 |    71 |   6.0 |
      |     2 |    122 |     6 |  20.3 |          |     2 |    426 |    36 |  11.8 |
      |     4 |    183 |     6 |  30.5 |          |     4 |    497 |    29 |  17.1 |
      |     8 |    291 |     9 |  32.3 |          |     8 |    703 |    17 |  41.4 |
      |    16 |    273 |     8 |  34.1 |          |    16 |   1399 |    37 |  37.8 |
      |    32 |    834 |    18 |  46.3 |          |    32 |   1429 |    14 | 102.1 |
      |    64 |    740 |    17 |  43.5 |          |    64 |   1400 |    13 | 107.7 |
      |   128 |    889 |    20 |  44.5 |          |   128 |   1399 |    16 |  87.4 |
      |   256 |    622 |    14 |  44.4 |          |   256 |   1402 |    17 |  82.5 |
      |   512 |    734 |    13 |  56.5 |          |   512 |   1489 |    18 |  82.7 |
      |  1024 |    733 |    11 |  66.6 |          |  1024 |   4852 |    73 |  66.5 |
      |  2048 |    734 |    13 |  56.5 |          |  2048 |   4852 |    56 |  86.6 |
      |  4096 |    783 |    15 |  52.2 |          |  4096 |   4852 |    56 |  86.6 |
      |  8192 |    783 |    15 |  52.2 |          |  8192 |   4852 |    56 |  88.2 |
      | 16384 |    834 |    19 |  43.9 |          | 16384 |    834 |    56 |  86.6 |
---------------------------------------|    ---------------------------------------|

Выводы делайте сами.

★★★

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

post-factum ★★★★★
()
Ответ на: комментарий от Sadler

А зачем файл читать посимвольно? Лучше же read'ом читать большими блоками, кратными размеру блока на диске.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

> А зачем файл читать посимвольно? Лучше же read'ом читать большими блоками, кратными размеру блока на диске.

В этом и заключается моя позиция.

Sadler ★★★
() автор топика
Ответ на: комментарий от Eddy_Em

> кратными размеру блока на диске.

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

Sadler ★★★
() автор топика
Ответ на: комментарий от Eddy_Em

А что, кто-то рекомендует посимвольно? Покажите мне этих людей!

В том самом треде, на который я дал ссылку в посте, есть два примера: yoghurt, Eshkin_kot .

Sadler ★★★
() автор топика
Ответ на: комментарий от Sadler

Ну, например, при обработке CGI запросов (multipart/form-data) приходится искать ограничитель. И тут уж приходится придумывать, как скорость увеличить. Некоторые считывают входной поток посимвольно, параллельно со считыванием проверяя наличие разделителя. Мне проще скинуть все на жесткий диск (или даже сделать mmap), а потом читать построчно и вызывать strstr. Вот надо будет по скорости прикинуть, насколько этот способ медленнее посимвольного считывания.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Sadler

Болезный, где ты в том треде увидел явный вызов fgetc?

while ((ch = (receive_getc)()) != EOF)

receive_getc тут может указывать хоть на чёрта лысого.

Да и вообще в том топике речь шла о сети, не?

yoghurt ★★★★★
()
Ответ на: комментарий от Eddy_Em

Я бы читал блоками в софтовый буфер, а из него брал посимвольно. Должно быть весьма шустро.

Sadler ★★★
() автор топика
Ответ на: комментарий от yoghurt

> Болезный, где ты в том треде увидел явный вызов fgetc?

Цитирую: «Функция receive_getc = fgetc(stdin)»

Sadler ★★★
() автор топика
Ответ на: комментарий от yoghurt

> stdin

Ну и? Чем этот поток лучше потока из файла? Давай я сделаю cat file | тестилка , результат особо не поменяется.

Sadler ★★★
() автор топика
Ответ на: комментарий от yoghurt

> Тем, что в своём примере ты юзаешь таки винт ещё, а на stdin данные могут поступать откуда угодно.

В предыдущем треде негласно считали, что это винт, ибо практически в каждом сообщении говорили про кэш винта.

Sadler ★★★
() автор топика
Ответ на: комментарий от yoghurt

> Помимо кеша винта есть ещё сокбуфы, да и чего только нет.

Я сейчас проверю cat /dev/zero | head -cX | тестилка . Мне уже стало интересно, как оно себя поведёт без винта.

Sadler ★★★
() автор топика
Ответ на: комментарий от Sadler

> ибо практически в каждом сообщении говорили про кэш винта.

Я про кеш винта ничего не говорил :)

Eshkin_kot ★★
()
Ответ на: комментарий от yoghurt

Помимо кеша винта есть ещё сокбуфы, да и чего только нет.

Вот, пожалуйста, без использования винта, только память:

Mandriva, stdin (/dev/zero | head)                          
метод | буфер | размер | время | мб/сек|
---------------------------------------|
fgetc |     - |    500 |    43 |  11.6 |
---------------------------------------|
fread |     1 |    500 |    90 |   5.5 |
      |     2 |    500 |    45 |  11.1 |
      |     4 |    500 |    26 |  19.2 |
      |     8 |    500 |    13 |  38.5 |
      |    16 |   1000 |    16 |  62.5 |
      |    32 |   1000 |    10 | 100.0 |
      |    64 |   2000 |    13 | 153.9 |
      |   128 |   2000 |    14 | 142.9 |
      |   256 |   2000 |    15 | 133.3 |
      |   512 |   2000 |    14 | 142.9 |
      |  1024 |   2000 |    15 | 133.3 |
      |  2048 |   2000 |    13 | 153.9 |
      |  4096 |   2000 |    13 | 153.9 |
      |  8192 |   2000 |    15 | 133.3 |
      | 16384 |   2000 |    14 | 142.9 |
---------------------------------------|

Памяти более 2 ГБ свободно не было, а в своп лезть очень не хотелось, потому максимальный размер входного потока 2 ГБ. Тем не менее, fgetc всё равно показал свою неэффективность.

Sadler ★★★
() автор топика
Ответ на: комментарий от Eddy_Em

> А если там гигабайтный файл? :)

Дак блоками же читаем. Памяти больше, чем размер блока, не съест.

Sadler ★★★
() автор топика

Выводы делайте сами.

вывод №1 TC не умеет писать тесты на производительность

вывод №2 TC не слишком разбирается с тем, что хочет померять

итог : некорректное сравнение по скорости кислого и мягкого

MKuznetsov ★★★★★
()
Ответ на: комментарий от Sadler

Так будет сложнее обрабатывать: что, если у вас начало строки с разделителем попадет на конец блока? Как вы его будете выделять, не считывая следующий блок?

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от MKuznetsov

> итог : некорректное сравнение по скорости кислого и мягкого

Аргументировать сможем?

Sadler ★★★
() автор топика
Ответ на: комментарий от Eddy_Em

> Так будет сложнее обрабатывать: что, если у вас начало строки с разделителем попадет на конец блока? Как вы его будете выделять, не считывая следующий блок?

Это лишь вопрос реализации. Зато сам по себе такой метод работает быстрее.

Sadler ★★★
() автор топика
Ответ на: комментарий от Sadler

такой метод работает быстрее

А теперь добавь сюда логику, которая была в exim.

unsigned ★★★★
()
Ответ на: комментарий от Sadler

Аргументировать сможем?

1) вы наверное думаете, что реализовали блочное чтение..

2) что fread и fgetc имеют исключительное отношение в ФС

3) что они алгоритмически применяются в одинаковых случаях и надо выбрать кто из них быстрее.

4) и что погрешности в секунды роли не играют

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

> и что погрешности в секунды роли не играют

Играют, но мне лень.

что они алгоритмически применяются в одинаковых случаях и надо выбрать кто из них быстрее.

Они могут применяться в одинаковых случаях.

что fread и fgetc имеют исключительное отношение в ФС

Это Вы себе придумали.

вы наверное думаете, что реализовали блочное чтение

А вот с этим спорить не буду, думаю.

Sadler ★★★
() автор топика
Ответ на: комментарий от Sadler

>Тем не менее, fgetc всё равно показал свою неэффективность.

Это не неэффективность, это вполне объяснимая разница.

И fread, и fgetc лочат/разлочивают дескриптор на время работы. И та, и другая юзают один и тот же буфер при чтении. И та, и другая дочитывают в этот буфер, когда выходят за пределы. Просто fgetc вызывается на порядок больше раз.

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

yoghurt ★★★★★
()
Ответ на: комментарий от yoghurt

а не мудохаться с ручной буферизацией.

Можно просто написать оболочку. Что-нибудь вроде:

const int max_buf_size = 512;
int buf_size = -1;
char buf[max_buf_size];
int buf_n = 0;



bool myfeof(FILE* f)
{
	return (feof(f) && (buf_n>=buf_size));
}

char myfgetc(FILE* f)
{
	if (buf_n<buf_size) return buf[buf_n++];
	else
	if (!feof(f))
	{
		buf_size = fread(buf,1,max_buf_size,f);
		buf_n = 0;
		return buf[buf_n++];
	}

	return 0;

}

Это всё равно будет работать быстрее. По крайней мере с ФС.

Sadler ★★★
() автор топика
Ответ на: комментарий от yoghurt

> Это не неэффективность, это вполне объяснимая разница.

Нет уж, не передёргивайте, это неэффективность, вызванная вполне объяснимой разницей.

Sadler ★★★
() автор топика
Ответ на: комментарий от vasily_pupkin

Ну правильно. max_buf_size элементов, каждый по 1 байту. Да и вообще я не говорил «юзайте мою реализацию». Я говорил о самой идее.

Sadler ★★★
() автор топика
Ответ на: комментарий от tensai_cirno

> Судя по тестам windows победил.

Просто винты разные =)

Sadler ★★★
() автор топика
Ответ на: комментарий от vasily_pupkin

И? Первый size - это размер типа данных, в байтах.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от vasily_pupkin

> Посмотреть что происходит, когда вызывается fgetc

Да как бы пока что меня вполне устроили результаты тестирования и с blackbox. Но посмотрю.

Sadler ★★★
() автор топика
Ответ на: комментарий от Sadler

> это неэффективность

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

unsigned ★★★★
()
Ответ на: комментарий от Sadler

вы наверное думаете, что реализовали блочное чтение

А вот с этим спорить не буду, думаю.

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

MKuznetsov ★★★★★
()
Ответ на: комментарий от vasily_pupkin

> setvbuf(f, buf,_IOFBF, n); И померять еще раз

Не помогло. Скорость немного выросла, на пару МБ/сек.

Sadler ★★★
() автор топика
Ответ на: комментарий от unsigned

> И вместо fgetc использовать getc.

Что-то толку всё равно нет, скорость не меняется.

Sadler ★★★
() автор топика
Ответ на: комментарий от MKuznetsov

позволю себе немного продолжить, а то newbie`сы ещё решат что надо все щели пихать fread и избегать fgetc и кол-во быдло кода резко возрастёт.

то что измеряет уважаемый TC реально зовётся кривой двойной буферизацией и ни разу не блочным чтением. На примере - если в приведённом коде, до тестов считать например 157 байт из файла (вполне жизненная ситуация - например чтение некоего заголовка), то замеры тестов с fgetc остануться в пределах погрешности измерений, а замеры заглушки через fread испортятся.

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

Ёпрст...fread, fgetc прикладные библиотечные функции, скрывающие за собой детали реализации и взаимодействие с системой. Если надо получить байт из потока, то юзается fgetc, если заранее известное кол-во байт/структур/блоков то fread, если строка нужна - то fgets. Во всех них нормально сделана буферизация.

А если сцуко программист прикладник, которому показалось что 100500 вызовов fgetc стоит заменить тем же числом вызовов (зато своих)функций и 1-м fread, будет хреначить ворох кода, втащит системно-зависимые вещи, да ещё и дублируя функции стандартных библиотек, то он как минимум неграмотное @#$@.

p.s. если скорость чтения/записи становиться критичной то её реализуют по возможности через mmap, а иначе через read/write

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

Хорошо, сдвинем на 157 байт.

буфер | размер | время | мб/сек|
-------------------------------|
128   | 1465   | 18    | 81.4  |
256   | 1562   | 18    | 86.8  |
512   | 1565   | 23    | 68.0  |
1024  | 2205   | 21    | 105   |
2048  | 1463   | 19    | 77    |
4096  | 1561   | 19    | 82.1  |
8192  | 1471   | 19    | 77.4  |
-------------------------------|

О боже, оно не стало медленнее! Да это же чудо! Или кое-кому надо аккуратнее с потоками желчи.

Sadler ★★★
() автор топика
Ответ на: комментарий от MKuznetsov

> заменить тем же числом вызовов (зато своих)

Откройте для себя inline.

Sadler ★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.