LINUX.ORG.RU

[C] Изменение кода в рантайме

 


0

0

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

#include <string.h>
#include <stdio.h>

typedef int (*Func)(void);

#ifdef PATCH
char buf[1024];
#define func   ((Func)buf)
#else
Func func;
#endif

int f1(void) { return 1; }
int f2(void) { return 2; }
int f_(void) { return 0; }

int main(void)
{
	int i, sum = 0;
#ifdef PATCH
	memcpy(buf, f2, (char *)f_ - (char *)f2);
#else
	func = f2;
#endif
	for(i = 0; i != 0xFFFFFFF; ++i)
		sum += func();
	printf("sum = %d\n", sum);
	return 0;
}
Действительно, разница значительна:
$ cc -O2 main.c && time ./a.out
sum = 536870910
 
real    0m1.353s
user    0m1.350s
sys     0m0.010s
$ cc -O2 -DPATCH main.c && time ./a.out
sum = 536870910
 
real    0m0.968s
user    0m0.960s
sys     0m0.000s
Сам вижу проблемы:

1. размер функции не определить в общем случае надежно;

2. buf может оказаться неисполняемым.

Можно как-нибудь обойти эти проблемы? Еще лучше, есть стандартные решения для подобных хаков?

★★★★

Последнее исправление: unsigned (всего исправлений: 1)

Ответ на: комментарий от Deleted

Выделить буфер через mmap религия запрещает?

Не, поставленная задача.

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

в рантайме компайлер пытается предсказать результат проверки условия

Наверное, в рантайме - все же процессор.

и когда угадывает то на условие потерь нет.

Есть, конечно, но незначительные.

ф-и инлайновая

Функция не инлайновая и не может таковой быть.

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

Я бы вызывал разные куски кода с циклом for

Да, сам к этому склонялся. Только многовато кода продублируется.

эффективность использования кеша процессора (для кода) может дать обратный результат

Тут, афаик, потери не столько на обращении к памяти, сколько на сбросе конвейера. Хотя читал где-то, что интелы недавно научились и здесь предсказывать переход. Но меня интересует не только интел.

А вообще, ты уже написал программу, отпрофилировал ее и точно определил какой фрагмент нужно оптимизировать?

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

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

Ну, с ТАКИМИ оптимизациями я, конечно, загнул. Интерес больше теоретический.

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

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

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

Только эта разница меня и волновала (как показано в ОП, порядка 40% на инструкцию - а это кое-что).

Это 40% только потому, что твой цикл фактически пустой. Он содержит сложение результата, увеличение счетчика и проверку условия в цикле.
Вот как он выглядит на x86:

loop:
call dword ptr[func]
add ebx, eax
inc esi
cmp esi, FFFFFFFFh
jne loop

Разумеется, на фоне операций с регистрами обращение к памяти выглядит достаточно дорого. Засунь в этот цикл реальную работу, и все твои проценты растворяться, как утренний туман.

Нельзя убрать вызов - функций несколько на выбор.

Можно. Сделав выбор, надо сразу переходить на нужную функцию.

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

И вот мы снова возвращаемся к man inline/#define )))

Кроме того, в приличных компиляторах есть встроенный ассемблер.

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

ты свой buf всеравно через указатель на функцию вызваешь

Нет.

Да. )))

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

Функция не инлайновая и не может таковой быть.

Пуркуа? Инлайновым щас м.б. все что угодно вроде как? А если ф-я толстая, то оптимизировать затраты на ее вызов как то странно...

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

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

Только профайлер может сказать истину для конкретного случая.

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

Только профайлер может сказать истину для конкретного случая.

+100500. Нет, даже +200500;-)

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

Почему сразу facepalm? Задача то решаема.

Пишем генератор сишного кода, продергиваем gcc и заставляет его сгенерить so, подгружаем so и дергаем сгенеренный код.

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

ты свой buf всеравно через указатель на функцию вызваешь

Нет.

Листинг утверждает обратное:

gcc -O2 -S -DPATCH main.c
main:
        pushl   %ebp
        movl    $f_, %eax
        movl    %esp, %ebp
        andl    $-16, %esp
        pushl   %edi
        subl    $f2, %eax
        pushl   %esi
        movl    $buf, %edi
        pushl   %ebx
        xorl    %esi, %esi
        subl    $20, %esp
        xorl    %ebx, %ebx
        movl    $1024, 12(%esp)
        movl    %eax, 8(%esp)
        movl    $f2, 4(%esp)
        movl    $buf, (%esp)
        call    __memcpy_chk
        .p2align 4,,7
        .p2align 3
.L8:
        call    *%edi
        addl    $1, %ebx
        addl    %eax, %esi
        cmpl    $268435455, %ebx
        jne     .L8
        movl    %esi, 8(%esp)
        movl    $.LC0, 4(%esp)
        movl    $1, (%esp)
        call    __printf_chk
        addl    $20, %esp
        xorl    %eax, %eax
        popl    %ebx
        popl    %esi
        popl    %edi
        movl    %ebp, %esp
        popl    %ebp
        ret

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

Кроссплатформенный

so

Хотя бы поэтому и facepalm :) И вообще большинство хаков на уровне сишного кода плохо переносимы.

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

УМВР:
call buf

Это - оптимизация, выполненная компилятором.

LamerOk ★★★★★
()

Интерпретатор luajit-а стоит посмотреть.

anonymous
()

Полагаю, выделить буфер с помощью mmap() c PROT_EXEC.

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

У меня указатель не может быть const

Я, вроде бы, сказал также, что Func func=f2 компилится в call f2 не?

no-such-file ★★★★★
()
Ответ на: комментарий от AIv

в рантайме компайлер пытается предсказать результат проверки условия

Вы о чём? Какой ещё компайлер в рантайме? Тут вам не лисп.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Не комапайлер а проц, описка. Ну кто хотел тот понял.

AIv ★★★★★
()

Можно как-нибудь обойти эти проблемы?

Обрадую, есть другие проблемы: настройка адресов переходов и переменных в загруженном модуле.

anonymous
()

Оу, haters gonna hate. Но мне, как не-задроту-прогеру, а математику доставил твой пример.

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