Встраивание (вид) функций во время выполнения в C

Я думал о типичной проблеме, которая очень JIT-способна, но трудно решить с помощью необработанного C. Сценарий устанавливает ряд указателей на функции, которые будут «составлены» (как в математической композиции функций) один раз во время выполнения. а потом звонил много-много раз.

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

На таком языке, как Lisp, я, вероятно, мог бы обработать код и заменить «виртуальный» вызов фактическим содержимым функций, а затем вызвать compile, чтобы получить оптимизированную версию, но это кажется очень хакерским и подверженным ошибкам в C, и использование C является требованием для этой проблемы ;-)

Итак, знаете ли вы, есть ли стандартный, портативный и безопасный способ добиться этого на C?

Ваше здоровье


person fortran    schedule 05.05.2010    source источник


Ответы (3)


Возможно, вы захотите взглянуть на LLVM. У них есть библиотека, которая позволяет JIT-код (и многое другое) из C, она поддерживает многие платформы и является проектом с открытым исходным кодом: http://llvm.org/

person wump    schedule 05.05.2010
comment
Да, это вариант, который я рассматривал, но я думал о чем-то более простом, я не думаю, что мне нужен весь JIT, просто склеивание некоторых функций вместе, как если бы они были просто большой функцией. Спасибо, в любом случае :-) - person fortran; 06.05.2010

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

void my_algorithm(int (*func)(...), ...)
{
    /* ... */
}

Также предположим, что вы заранее знаете все возможные значения, которые может принимать указатель на функцию. Например:

my_algorithm(func_1, ...);
//...
my_algorithm(func_2, ...);

Сначала преобразуйте исходный my_algorithm() в макрос:

#define MY_ALGORITHM(func, ...)       \
{                                     \
    /* ... */                         \
}

Затем перепишите my_algorithm() как:

extern int func_1(...);
extern int func_2(...);

void my_algorithm(int (*func)(...), ...)
{
    if (func == func_1)
        MY_ALGORITHM(func_1, ...)
    else if (func == func_2)
        MY_ALGORITHM(func_2, ...)
    else
        assert(0 && "Unexpected function arg to my_algorithm()");
}

Это, конечно, удвоит размер скомпилированного объектного файла. И, на первый взгляд, он устраняет только один уровень косвенности. Но если func_1 и/или func_2 встроены, вы можете получить значительное ускорение.

И вы даже можете «передавать» макросы, например:

#define HYPOT_Y(x) hypot(x, y)
MY_ALGORITHM(HYPOT_Y, ...);     //assumes y is known

Второе предложение представляет собой вариант этого с использованием макросов X ( http://en.wikipedia.org/wiki/C_preprocessor#X-Macros). Вместо #define поместите тело исходного my_algorithm() в отдельный файл my_algorithm.h. Затем перепишите my_algorithm() как:

void my_algorithm(int (*func)(...), ...)
{
    if (func == func_1)
        #define func func_1
        #include "my_algorithm.h"
        #undef func
    else if (func == func_2)
        #define func func_2
        #include "my_algorithm.h"
        #undef func
    else
        assert(0 && "Unexpected function arg to my_algorithm()");
}

Я бы, наверное, использовал X-макросы, если код больше пары десятков строк. Его преимущества включают (без каламбура):

  • Нет уродливых обратных косых черт
  • Упрощенная отладка (например, обратная трассировка и пошаговое выполнение).

Это все стандартный C. Но скорее олдскульный.

person Joseph Quinsey    schedule 06.05.2010
comment
Если вы сделаете это, пожалуйста, пожалуйста, используйте оператор switch и передайте целое число/перечисление вместо использования набора if/else if/и т. д. Переключатели НАСТОЛЬКО быстрее. - person SoapBox; 06.05.2010
comment
Вы можете включить константу вместо указателя функции. Вообще не берите адрес функции, если он вам не нужен, это заставляет компилятор предоставить не встроенную копию функции только для того, чтобы дать ваш указатель. Кроме того, встраивание (с большинством компиляторов C) не будет работать с extern, так как функции должны быть включены в одну и ту же единицу компиляции. - person wump; 06.05.2010
comment
к сожалению, это не так, функции, которые будут использоваться, известны только во время выполнения, и они могут быть составлены разными способами... и в любом случае есть ответвления ^_^ - person fortran; 06.05.2010
comment
Если используемые функции заранее неизвестны, вы никогда не сможете встроить их на этапе компиляции. Вам нужен JIT. - person wump; 06.05.2010
comment
Да, согласен. Мое предложение будет работать, только если вы заранее знаете все возможные значения, которые может принимать указатель на функцию. А что касается начального ветвления, если алгоритм похож на числовое интегрирование, стоимость этого незначительна. - person Joseph Quinsey; 06.05.2010

Если вы согласны с поддержкой только x86, вы можете попробовать внедрить TCC:

http://bellard.org/tcc/

person SK-logic    schedule 06.05.2010
comment
к сожалению, мне нужно, чтобы код был переносимым на разные платформы :-( спасибо за ссылку, может быть интересно посмотреть! - person fortran; 06.05.2010
comment
Тогда LLVM — это ваша платформа. В конце концов, это не так уж плохо, и его легко использовать через простой C API. Другой альтернативой является реализация интерпретатора Forth с многопоточным кодом на языке C (используя вычисляемое расширение goto GCC) и создание для него кода. Должен быть в 2-6 раз медленнее нисходящего JIT, но тривиален и абсолютно переносим. Этого достаточно, чтобы просто склеить функции. - person SK-logic; 06.05.2010