Формат параметров абстрагирующей функции и его влияние на производительность?


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

План состоит в том, чтобы использовать его аналогично thiscall - все параметры правильно выровнены в месте расположения указателя и параметры извлекаются через косвенное воздействие. Не должно быть медленнее, чем чтение их из стека, по крайней мере ИМО.

Так что вместо:

int foo(int a, int b) { return a+b; }

У меня может быть что-то вроде:

void foo2(void *p) {
   *(int*)p = *(int*)(p + 4) + *(int*)(p + 8);
}
Итак, мой вопрос заключается в том, что потенциально может пойти не так, используя этот подход? Что я могу сказать сразу, так это то, что он работает "в темноте", поэтому было бы очень важно правильно рассчитать смещения. Это также немного неудобно, так как все временные данные должны быть предоставлены пользователем. Предполагая, что мой компилятор виртуальной машины будет иметь дело с этими двумя проблемами я в основном обеспокоен производительностью - я не хочу создавать нормальную функцию и для каждой нормальной функции оболочку void * - я хотел бы напрямую использовать это Соглашение для всех функций, поэтому я не могу не задаться вопросом, насколько хорошо компилятор будет выполнять работу по вставке функций при использовании в скомпилированном коде? Будут ли какие-либо другие возможные последствия для производительности, которые я упускаю из виду (исключая __fastcall , которые будут использовать на один регистр больше и на один меньше косвенность)?
2 3

2 ответа:

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

typedef void (__cdecl * function_with_any_parameters)();

Вам нужно будет обязательно определить все функции, которые вы хотите вызвать, как:

void __cdecl f(type1 arg1, type2 arg2, type3 arg3); // any amount of arguments

И просто вызывайте их с нужным количеством аргументов:

f(arg1, arg2, arg3, arg4);

Если вы хотите пройти через один указатель, то у вас есть дополнительные накладные расходы: один указатель. То проще всего было бы определить все функции как принимающие указатель на анонимную структуру:

void f(struct {type1 a; type2 b;} * args);

Затем вы можете вызвать функцию с указателем на соответствующую структуру, чтобы избежать любых перекосов.

struct {type1 a; type2 b;} args = {arg1, arg2};
f(&args);

Вы эффективно реализуете cdecl самостоятельно.

После выполнения нескольких тестов я бы сказал, что компилятор делает довольно хорошую работу по оптимизации подобных функций указателя. Функция void * так же быстра, как функция add и регулярный оператор +.

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