Здравствуйте, FR, Вы писали:
FR>Так ведь и обратный посыл который тут активно пиарится, "не оптимизируй пока не припрет" тоже неверен. Часто когда припрет оказывается уже поздно.
тоже верно
но таких случаев все-таки меньше. К тому же, это все равно не отменяет правила "сначала проверь, потом оптимизируй"
Здравствуйте, eao197, Вы писали:
E>А вообще-то я имел ввиду, что getch из стандартной библиотеки не обязательно будет каждый раз обращаться к OS. Более вероятно (чем лучше библиотека), что getch берет данные из внутреннего буфера, а запрашивает OS только при его опустошении.
Да надо было просто посмотреть исходники CRT. Они не секрет
int __cdecl fgetc ( REG1 FILE *stream)
{
int retval;
_ASSERTE(stream != NULL);
_lock_str(stream);
retval = _getc_lk(stream);
_unlock_str(stream);
return(retval);
}
>Причем стандартная библиотека может знать об особенностях OS и ее файловой системы, поэтому подчитываться могут блоки оптимального размера (скажем, сразу класстер или сектор). А если мы сами начнем читать данные блоками и не угадаем с размером блока, то ручной буфферизированный ввод будет проигрывать getch.
Здравствуйте, FR, Вы писали:
FR>Простенький тест
FR>у меня на 7mb файле выдает такие результаты: FR>
FR>size = 1, time = 2383, sum = 332666720.000000
FR>size = 1, time = 2333, sum = 332666720.000000
FR>size = 1, time = 2334, sum = 332666720.000000
FR>size = 1024, time = 120, sum = 332666720.000000
FR>size = 1024, time = 110, sum = 332666720.000000
FR>size = 1024, time = 130, sum = 332666720.000000
FR>разница в почти 20 раз фигня?
Напомню, что fread(1) и fgetc -- это чуть разные понятия. Что и доказывается простой модификацией примера:
size = 128, time = 93, sum = 1096917.000000
size = 128, time = 93, sum = 1096917.000000
size = 128, time = 94, sum = 1096917.000000
size = 1024, time = 94, sum = 1096917.000000
size = 1024, time = 78, sum = 1096917.000000
size = 1024, time = 78, sum = 1096917.000000
size = 1, time = 219, sum = 1096917.000000
size = 1, time = 250, sum = 1096917.000000
size = 1, time = 219, sum = 1096917.000000
Разница осталась, но уже далеко не на порядок.
Хотя результаты сильно зависят от компилятора. Например, Borland C++ 5.6 и Digital Mars C++ 8.42n:
size = 128, time = 218, sum = 1096917.000000
size = 128, time = 250, sum = 1096917.000000
size = 128, time = 188, sum = 1096917.000000
size = 1024, time = 219, sum = 1096917.000000
size = 1024, time = 156, sum = 1096917.000000
size = 1024, time = 219, sum = 1096917.000000
size = 1, time = 2515, sum = 1096917.000000
size = 1, time = 2500, sum = 1096917.000000
size = 1, time = 2516, sum = 1096917.000000
И от операционки. Например GNU C++ 3.2.2 на Slackware 10.0:
size = 128, time = 150000, sum = 1096917.000000
size = 128, time = 150000, sum = 1096917.000000
size = 128, time = 160000, sum = 1096917.000000
size = 1024, time = 130000, sum = 1096917.000000
size = 1024, time = 130000, sum = 1096917.000000
size = 1024, time = 120000, sum = 1096917.000000
size = 1, time = 750000, sum = 1096917.000000
size = 1, time = 750000, sum = 1096917.000000
size = 1, time = 750000, sum = 1096917.000000
(значение time в 1000 раз больше, т.к. под Linux-ом CLOCK_PER_SEC так же больше).
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
А дай-ка и я свой вклад внесу. За основу взял твой, но переделал по сути
#include <stdio.h>
#include <time.h>
//------------------------------------------------------------------------------
void Test( int nRepeat, int nPasses, int nSize)
{
char buf[1024];
double sum = 0.0;
FILE *file = fopen("test.txt", "rb");
clock_t t1 = clock();
for ( int i = 0; i < nRepeat; i++)
{
fseek(file,0,SEEK_SET);
for ( int j = 0; j < nPasses; j++)
size_t count = fread(buf+j, 1, nSize, file);
for(j = 0; i < 1024; ++i) sum += buf[i];
}
fclose(file);
clock_t t2 = clock();
printf("nSize = %d, time = %d, sum = %f\n", nSize, t2 - t1, sum);
}
int main(int argc, char *argv[])
{
Test(100000, 1024,1 );
Test(100000, 1, 1024);
return 0;
}
Автоматический буфер — это специально для тех, кто меня упрекает в том. что я могу иметь memory overrun. У них прекрасная возможность появляется еще раз меня упрекнуть, грешно им этой возможности не дать . Проверку, открылся ли файл, убрал по той же причине.
Итак, читается все время один и тот же первый Кбайт файла. nRepeat — цикл для замера времени. nPasses (число проходов цикла) — как нетрудно догадаться, либо 1024(побайтно), либо 1 (Кбайт) Сейчас, конечно, мне скажут — а что будет если иные значения передать ? Не передавайте, очень Вас прошу . Ну а nSize — размер элемента — 1 или 1024.
Еще раз обращаю внимание — весь Кбайт давно в кэше. Обращений к диску здесь вообще не происходит.
Результат (Release, VC6)
nSize = 1, time = 5546, sum = 70936.000000
nSize = 1024, time = 454, sum = 70936.000000
20 раз не получилось, только 12, ну да ладно, хватит и этого
Пойдем дальше. Перепишем это на WinAPI напрямую
void Test( int nRepeat, int nPasses, int nSize)
{
char buf[1024];
double sum = 0.0;
HANDLE hFile = CreateFile("test.txt", GENERIC_READ,0,NULL,OPEN_EXISTING,0,0);
clock_t t1 = clock();
DWORD dwBytes;
for ( int i = 0; i < nRepeat; i++)
{
SetFilePointer(hFile, 0, 0, FILE_BEGIN);
for ( int j = 0; j < nPasses; j++)
ReadFile(hFile,buf+j, nSize, &dwBytes,NULL);
for(j = 0; i < 1024; ++i) sum += buf[i];
}
CloseHandle(hFile);
clock_t t2 = clock();
printf("nSize = %d, time = %d, sum = %f\n", nSize, t2 - t1, sum);
}
Эффект чудовищный. Я уж думал, что у меня просто зависло. Проверил — нет. Пошел покурить, вернулся — готово.
nSize = 1, time = 230562, sum = 70936.000000
nSize = 1024, time = 375, sum = 70936.000000
Объяснение.
Для чтения 1024 байт за 1 раз ReadFile несколько эффективнее. fread — штука сложная, ее текст несколько десятков строк занимает. Ей проверить надо, не вышел ли я за границы файла, не пересек ли границу ее внутреннего буфера. А ReadFile — это просто вызов драйвера ФС, тот обнаруживает, что запрос идет с нулевого смещения, размер хороший, ну и копирует мне из кеша 1 Кбайт. Но, в общем, разница не столь уж большая — 375 против 454.
А вот при чтении по 1 байту ReadFile ужасен. На каждый вызов делается переключение в защищенный режим по int 2E, и делается это у меня 100000 * 1024 раз. Неудивительно, что результаты просто устрашающие.
Итог. При буферизованном вводе fread демпфирует эффект чтения по одному байту. В результате получаем в 12 раз хуже, всего лишь . Ну а при небуферизованном вводе (обращения к ОС) имеем проигрыш в 615(!!!!) раз.
Разумеется, надо учесть и то, что при чтении по 1 байту у меня проход по циклу 1024 раза, а при чтении по 1024 байта — всего 1 раз. Но ведь если хотите читать по одному байту , а прочитать 1024 — платите циклом .
В действительности это не очень существенно. Заменим строчку
ReadFile(hFile,buf+j, nSize, &dwBytes,NULL);
на
f(j);
где
void f(int j)
{
j++;
}
(сделать это приходится, иначе оптимизатор вообще выкинет цикл)
nSize = 1, time = 687, sum = 17621.000000
nSize = 1024, time = 125, sum = 17789.000000
Сумма, здесь, конечно, бессмысленная — данные не читались, мусор суммировался. Так что все время уходит именно на ReadFile.
P.S. Честно говоря, знал, что эффект будет до своих опытов, но чтобы такой — не ожидал. Господа, не откажите в просьбе — проверьте код. Может, я ошибку какую-то сделал и не вижу ее ?
"McSeem2" <12737@users.rsdn.ru> сообщил/сообщила в новостях следующее: news:1459073@news.rsdn.ru... > Вообще-то я удивлен такой большой разницей.
Ну это-то и не вызывает удивления, т.к. при чтении по одному байту функция fread() будет вызвана в 1024 раз больше чем при чтении по килобайту. А на вызов функции требуется определенное время -- затолкать параметры в стек, прочитать их, локальные переменные, и проч. и проч. Если есть задача читать по байту -- надо пользоваться getc(), который определен в RTL Visual C++ следующим образом:
P.S. И еще, у меня складывается ощущение, что большинству участников данного спора надо перечитать (или прочтитать) K&R. Узнаете очень много нового и интересного про настоящее программирование.
PD>P.S. Честно говоря, знал, что эффект будет до своих опытов, но чтобы такой — не ожидал. Господа, не откажите в просьбе — проверьте код. Может, я ошибку какую-то сделал и не вижу ее ?
Точно, сделал. В суммировании. Черт меня угораздил его оставить. Вместо
for(j = 0; i < 1024; ++i) sum += buf[i];
должно быть, конечно
for(j = 0; j < 1024; ++i) sum += buf[j];
Ошибка, конечно, серьезная. Посыпаю голову пеплом . Но на результатах это не очень сказалось — просто цикл выполнился на 1024 раза меньше.
fread
nSize = 1, time = 6031, sum = 7093600000.000000
nSize = 1024, time = 797, sum = 7093600000.000000
ReadFile
nSize = 1, time = 231390, sum = 7093600000.000000
nSize = 1024, time = 750, sum = 7093600000.000000
Здравствуйте, sch, Вы писали:
sch>Вывод для 11-мегабайтового файла: size = 11250929, tm2 — tm1 = 31, ratio = 362933.193548 sch>Вывод для 1,5-гигабайтовго файла: size = 1469875238, tm2 — tm1 = 47812, ratio = 30742.810131
sch>Вывод: нефига выступать коли руки кривые.
Я конечно понимаю что у тебя руки такие прямые что уже не гнутся, и что копьютер у тебя очень шустрый, но твой тест у меня только в 1.63 раза быстрее чем fread(1)
Здравствуйте, FR, Вы писали:
FR>Здравствуйте, sch, Вы писали:
sch>>Вывод для 11-мегабайтового файла: size = 11250929, tm2 — tm1 = 31, ratio = 362933.193548 sch>>Вывод для 1,5-гигабайтовго файла: size = 1469875238, tm2 — tm1 = 47812, ratio = 30742.810131
sch>>Вывод: нефига выступать коли руки кривые.
FR>Я конечно понимаю что у тебя руки такие прямые что уже не гнутся, и что копьютер у тебя очень шустрый, но твой тест у меня только в 1.63 раза быстрее чем fread(1)
На некоторых платформах getc() определен не как макрос, а как функция.
Какой компилятор используешь, какая операционная система?
Здравствуйте, sch, Вы писали:
sch>Здравствуйте, FR, Вы писали:
FR>>Здравствуйте, sch, Вы писали:
sch>>>Вывод для 11-мегабайтового файла: size = 11250929, tm2 — tm1 = 31, ratio = 362933.193548 sch>>>Вывод для 1,5-гигабайтовго файла: size = 1469875238, tm2 — tm1 = 47812, ratio = 30742.810131
sch>>>Вывод: нефига выступать коли руки кривые.
FR>>Я конечно понимаю что у тебя руки такие прямые что уже не гнутся, и что копьютер у тебя очень шустрый, но твой тест у меня только в 1.63 раза быстрее чем fread(1)
sch>На некоторых платформах getc() определен не как макрос, а как функция. sch>Какой компилятор используешь, какая операционная система?
VC7.1 Win2k. Да и не такой и большой вклад дает вызов функции на фоне операций с диском. То что твой первый результат такой шустрый объясняется только тем что твой файл полностью засосало в кеш операционки, реально ты мерял скорость памяти, попробую удали и создай файл заново, скорость упадет на порядок. Да и пожалуйста не кричи больше.
Здравствуйте, FR, Вы писали:
FR>VC7.1 Win2k. Да и не такой и большой вклад дает вызов функции на фоне операций с диском. То что твой первый результат такой шустрый объясняется только тем что твой файл полностью засосало в кеш операционки, реально ты мерял скорость памяти, попробую удали и создай файл заново, скорость упадет на порядок.
У меня та же платформа.
Возможно оптимизатор заинлайнил fread(), так что надо посмотреть.
FR>Да и пожалуйста не кричи больше.
Прошу прощения если это выглядело так, я собственно и не кричал
Здравствуйте, sch, Вы писали:
sch>Здравствуйте, FR, Вы писали:
FR>>VC7.1 Win2k. Да и не такой и большой вклад дает вызов функции на фоне операций с диском. То что твой первый результат такой шустрый объясняется только тем что твой файл полностью засосало в кеш операционки, реально ты мерял скорость памяти, попробую удали и создай файл заново, скорость упадет на порядок. sch>У меня та же платформа. sch>Возможно оптимизатор заинлайнил fread(), так что надо посмотреть.
Здравствуйте, FR, Вы писали:
FR>Так ведь и обратный посыл который тут активно пиарится, "не оптимизируй пока не припрет" тоже неверен. Часто когда припрет оказывается уже поздно.
Все не совсем так. Лично я выступаю за очень простой набор правил:
Разработка
1. Пишем программу так чтобы было не стыдно показать ее код/дизайн другим, и чтобы этот код максимально просто поддавался бы развитию и модификации.
2. Проектируя код задумываемся над тем в каких условиях он должен работать и с какими проблемами можем столкнутся. Исходя из этого выбираем приемлемые алгоритмы и средства (библиотеки, рантаймы, компиляторы и т.п.) и другие проектные и локальные решения.
3. Когда встает выбор того или иного решения, которое может повлиять на производительность смотрим на то насколько это решение ухудшает дизайн приложения и что оно может дать с точки зрения производительности. Если дизайн не ухудшается и не усложняется реализация, то выбираем потенциально более быстрый вариант. При этом, если это не сложно, имеет смысл проверить свои предположения на практике (сделать тест). Проверяя предположение не забываем о том, что полученные результаты могут зависеть от локальных условий (например, процессора или драйвера видеокарты). Если же дизайн усложняется, то плюем на оптимизацию до того момента пока не сможем проверить влияние этого решения. Попутно записываем в список TODO требование проверить, это предположение.
4. Тестируем приложение на максимально широком списке девайсов и в максимально разнообразных условиях.
5. Если есть нарекания пользователей, начинаем заниматься оптимизациями которые могут ухудшить дизайн приложения.
Оптимизация
1. Собираем список нареканий пользователей.
2. Производим собственное тестирование с привлечением профайлеров и т.п.
3. Определяем приоритеты оптимизации и возможное ее влияние на дизайн (расширяемость и поддерживаемость) программы.
4. Эмулируем ситуацию в более простом окружении, чтобы изолировать ее от возможных побочных воздействий. Проверяем, что изолированный вариант повторяет ситуацию с тормозами. Если этот пункт не выполним (например, очень трудоемок), то стараемся создать тест, демонстрирующий проблему.
5. Выносим окончательное суждение о причине проблем.
6. Находим другое решение и думаем, как оно влияет на дизайн программы.
7. Проверяем решение на тесте.
8. Модифицируем программу пытаясь минимизировать вредное влияние оптимизации.
9. Снова тестируем чтобы убедиться что проблема устранена.
10. Если есть другие проблемы, переходим к пункту 1 этого списка и начинаем решать их.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.