LINUX.ORG.RU

C: с указателями на функции код тормозит


0

0

Собственно, subj. Функции View* возвращают указатель на static char *, по сути все три делают double2string преобразование, просто по-разному (некоторые округляют, некоторые добавляют k/M в конце числа). Опции сборки: -O4 -march=i486 -fomit-frame-pointer -std=c99 -lm

char *(* MYview)(double ivalue);

int main(void) {
int i, x = 3;

if (x == 1) MYView = View1;
else if (x == 2) MYView = View2;
else if (x == 3) MYView = View3;

for (i = 0; i < 1000000; i++) fprintf(stdout, "%s\n", MyView(47653423.0));

return 0;
}

Код выше тормознее, чем приведенный ниже на 20% (согласно поля real команды time: 3.300 для кода выше > 2.700 для кода ниже):

int main(void) {
int i, x = 3;

for (i = 0; i < 1000000; i++) {
if (x == 1) fprintf(stdout, "%s\n", View1(47653423.0));
else if (x == 2) fprintf(stdout, "%s\n", View2(47653423.0));
else if (x == 3) fprintf(stdout, "%s\n", View3(47653423.0));

return 0;
}

И если это нормально, тогда может кто то пояснит в каких случаях указатели на функции эффективны?

★★★★★

Разница в размере бинарника показывает, что код, приведенный ниже занимает больше места (на 32 байта), чем код выше, но это нормально, ведь ниже и кода больше.

Код ниже еще и каждую итерацию цикла делает 3 сравнения, неужели загрузка адреса функции тяжелее, чем 3 сравнения int для x86?

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

При тестировании stdout перенаправлялся в файл, сейчас сравню без fprintf, но уверен, на итоговой разнице это не скажется (придется правда увеличить кол-во итераций, чтобы заметить разницу).

P.S. В родной среде - программе, эти указатели нужны именно в fprintf.

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

Переделал:
1. Без fprintf.
2. Возвращаемое значение помещается в char *r.
3. Опции сборки слегка изменил: -march=i686, вместо i486 (i686 в subj коде давала некоторый выигрыш).
4. Кол-во итераций увеличил до 5млн.

Результаты (поле real команды time для 3-х испытаний):
- без указателей на функции с if внутри цикла: 9.976 9.764 9.850
- с указателями на фунцию: 12.580 11.798 12.646

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

ну а что ты удивляешься -- косвенный вызов всегда был дороже, а теперь тем более -- потому что если вызов напрямую то конвейер может сразу заполняться, а если через указатель то конвейер скорее всего сбросится

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

На HP-UX B.11.23 U 9000/785 разница меньше, но аналогична по сути:

Результаты (поле real команды time для 3-х испытаний):
- без указателей на функции с if внутри цикла: 15.801 16.171 15.864
- с указателями на фунцию: 16.832 16.508 17.611

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

> если вызов напрямую то конвейер может сразу заполняться, а если через указатель то конвейер скорее всего сбросится

Да, наверное так, при конвеерах я уже не учил asm/C :)

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

 .file   "test.c"
        .section        .rodata.str1.1,"aMS",@progbits,1
.LC1:
        .string "%s\n"
        .text
        .p2align 4,,15
.globl main
        .type   main, @function
main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        movl    $1099348241, %edx
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $20, %esp
        movl    %edx, 4(%esp)
        movl    $2013265920, (%esp)
        call    View3
        movl    %eax, 8(%esp)
        movl    $.LC1, %eax
        movl    %eax, 4(%esp)
        movl    stdout, %eax
        movl    %eax, (%esp)
        call    fprintf
        addl    $20, %esp
        xorl    %eax, %eax
        popl    %ecx
        popl    %ebp
        leal    -4(%ecx), %esp
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.1.2 20060901 (prerelease) (Debian 4.1.1-13)"
        .section        .note.GNU-stack,"",@progbits

anonymous
()

по сообщению из топика:
приведённый код (что выше, что ниже разделителя)
является бредом, который gcc при опции -O4 пытается 
оптимизировать..судя по всему безуспешно, ибо бред крепок :)

1. кусок кода выше разделителя говорит об элементарном незнании С,

2. кусок кода ниже разделителя так-же бредов ибо включает условное выполнение по константе внутри цикла.

1+2 в обоих случаях gcc бедняга оптимизировал все до одного вызова функции внутри цикла, НО в первом случае вызов косвенный, во втором прямой..разницу Вы указали сами.(

по поводу случая когда косвенный вызов может быть быстрее (но не надёжнее):
char *(*MYview)(double)[4];
MYview[0]=errorFunc;MYview[1]=View[1];MYview[2]=View[2];/*etc*/
..
и вызов внутри цикла MYview[x](y);



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

Если выше-ниже мой код бредов, может быть покажете пример кода в более пристойном с вашей точки зрения виде? Про массив указателей - не догадался, да. Только почему не надежнее, если элементы будут правильно заполнены и число вариантов функций лимитировано при сборке программы?

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

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

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

2saper

> элементы будут правильно заполнены и число вариантов функций лимитировано..

вот как раз это зависит от человека и в подавляющем числе случаев несоблюдается :)

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

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

> View[1],View[2] заменить соответственно но View1, View2

Да и "char *(*MYview)(double)[4]" не мешало бы заменить на "char *(*MYview[4])(double)".

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

Хм. Если в исходном коде 'int x = 3;' заменить на 'int x; scanf("%d", &x);', то скорость будет быстрее, чем с массивами, выбираю этот вариант (он более удобен, т.к. в самой программе неудобно где то еще хранить этот x).

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

Я бы заменил if-else конструкцию на switch-case если x по любому предполагается как int

PS: вызов функции по ссылке в оптимизированном коде по любому медленнее чем статически вкомпилённой. Если нужна чисто скорость а не гибкость (например подгружать новые функции через плагины) я бы так делать не стал бы.

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

> кстати,там вызов sort и mailx по абсолютным, константным путям не есть хорошо :)

Зато безопасно, мало ли у кого точки в PATH прописаны :-) Да и потом, я всё проверил: и sort и mailx на Solaris, OpenBSD, FreeBSD, Linux Slackware, HP-UX, AIX находится в /usr/bin.

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

Да, про CentOS мне сообщали уже. Но это проблема RH-подобных дистрибутивов и сборщиков под них, увы безопасностью тут жертвовать нельзя (можно Makefile раздуть конечно же, добавив туда и CMDSORT= и CMDMAILX=, но зачем это делать). А отсутствие sort и mailx в /usr/bin (хотя бы симв. ссылок на них) считаю самым настоящим бредом.

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

"Самый настоящий бред" - это закладываться на такие настройки без возможности их поменять. /usr/bin может не быть вообще.

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

> без возможности их поменять

Они определены в заголовочном файле global.h, кому нужно - поменяет или сделает симв. ссылку.

> /usr/bin может не быть вообще

Это в системах с C:\Program Files что ли? ;-)

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

> А через ./configure разве сложно сделать определение пути?

Этот ./configure еще нужно написать.

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

посмотрел код free-sa,
ради шутки сделал мелкий патч:
в main() в самом начале 
FILE *fake;
fake=popen("sort","w");
и в самом конце 
pclose(fake);

смешно, но на небольшом (6M) access.log получился выигрыш :)
то есть 'патченный' free-sa всегда чуть быстрее оригинального :))

uname -a 

Linux localhost 2.6.17-5mdv #1 SMP Wed Sep 13 14:32:31 EDT 2006 i686 Intel(R) Pentium(R) 4 CPU 2.40GHz GNU/Linux

ls -al /var/log/squid/access.log

-rw-r----- 1 squid squid 6370547 Май  6 02:20 var/log/squid/access.log


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

Да, безусловно передача/прием данных во внешний sort через pipe мне тоже нравится, да и код у меня такой еще из дипломного проекта (~1997) есть, руки пока не дошли, пытаюсь удовлетворить имеющиеся Feature Request/Bugs на sourceforge.net. Я рад, что большинство ошибок возникли только из-за того, что я изначально рассчитывал на KOI8-R локаль, а не по причине плохой работы с указателями или еще чем то посерьезнее. С pipe-ами нужно многое понять, у некоторых access.log ежедневный ~22Гб, как оно в трубу полезет, откуда возьмет память? К тому же код нужно будет сделать кроссплатформенным, а это несколько сложно.

Код с system+pipe у меня есть, только реализован был через fork и dup, shell мне не по душе, опять таки по соображениям безопасности. Я вообще думаю добавить опцию buf_limit и если все данные для сортировки помещаются в этот лимит, то делать сортировку в программе, через qsort. Я знаю, что операции с файлам - один из главных тормозов в Free-SA (ну еще есть r_datetime.c, не знаю, но упаковка матрицы double за несколько лет и индексы мне кажутся сомнительными).

P.S. BTW я не программист, я безопасник, поэтому некоторые приёмы я не знаю. Можно увидеть, что произошло с Free-SA с 1.0.0 по текущую версию - многие написанные мною обработчики заменены на аналогичные системные. Вот про strdup, например, я вообще забыл в первых версиях и делал strlen+malloc+strcpy :-) Но мне это полезно, и по основному виду деятельности тоже.

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

'патч' был не к тому, что pipe лучше, а к тому, что сейчас free-sa просто надстройка над sort`ом, который дёргается 'часто и со вкусом' :)

50% процентов кода и 90% времени занимают подготовка аргументов sort`у, его вызов и разбор полёта :)

раз уж всё-равно исползуются временные файлы через которые access.log переползает сортируясь, возможно лучше взять sqlite и использовать хотя-бы временную базу.

зы: ускорение получалось видимо за счёт сокращения времени вызова execv sort, весь смысл popen - открыть elf и зажучить его в памяти, чтобы копии грузились быстрее :)

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

> 50% процентов кода и 90% времени занимают подготовка аргументов sort`у, его вызов и разбор полёта :)

Не совсем так, то, что уходит sort вполне может уходить и qsort (оптимальнее так точно будет, потому что хотя бы преобразования double2string и string2double удастся избежать).

> раз уж всё-равно исползуются временные файлы через которые access.log переползает сортируясь, возможно лучше взять sqlite и использовать хотя-бы временную базу

Тут сразу теряем портируемость (я не хочу, как amarok тащить свою копию sqlite). Собственно изначально free-sa был частью коммерческого RBSec-SA, и когда в последнем решили отказаться от SARG, то первая попытка была написать быстрый конвертер журналов в sql, а потом всё крутить в sql-е. Но скорость оказалась сильно низкой (хоть с генерацией HTML-я самой СУБД, хоть без него), а некоторые клиенты компании требуют именно скорости :-(

> зы: ускорение получалось видимо за счёт сокращения времени вызова execv sort, весь смысл popen - открыть elf и зажучить его в памяти, чтобы копии грузились быстрее :)

Да, я тоже подумал, что одних тех строчек мало, и что возможно вы дописывали что то еще ;-) А тут значит этакое кэширование получилось, посмотрю, спасибо.

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

> зы: ускорение получалось видимо за счёт сокращения времени вызова execv sort, весь смысл popen - открыть elf и зажучить его в памяти, чтобы копии грузились быстрее :)

Попробовал, получилась некоторая мистика, с popen на 1-2 секунды медленнее. Опции сборки те, что в global.mk для gcc (возможно причина в -march=i486?). Проверял несколько раз на тестовом 50Мб access.log (на нем все сборки проверяются на производительность):

с popen: 26.472 25.774 27.283 28.279 28.769 28.819

без popen: 24.343 25.051 25.863 26.177 26.128 26.913

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

Код был немного другой, но кажется смысл от этого не должен был поменяться:
в main() в самом начале
FILE *SortCache;
SortCache = popen(CMDSORT, "w");
и в самом конце
pclose(SortCache);

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

> Они определены в заголовочном файле global.h, кому нужно - поменяет или сделает симв. ссылку.

Это уже лучше.

> Это в системах с C:\Program Files что ли? ;-)

Монтируемый по сети /usr

execve
()
Ответ на: комментарий от saper

2saper: усли хочешь, могу полелиться маленькой программкой (С~400строк),
которая бысто парсит лог squid`а и тащит из него статистику.
в ней есть моменты, которые тебе могут пригодится
max_kma<>mail*ru
icq6901408

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

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

Единственный недостаток - парсер не должен свалиться иначе вместе с ним увалится и сквида. Но на программе в две сотни строк стабильности добиться несложно.

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

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

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

Если реплика о русском языке и школе была в мою сторону, то да, таки проходил. Более того, имел по этому предмету отметку "отлично", поелику могу позволить себе писать так, как мне нравится, а не так как положено. Хоть на "олбанском". А с какой целью интересуетесь ?

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