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

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

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

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

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

Я могу иметь что-то вроде:

void foo2(void *p) {
*(int*)p = *(int*)(p + 4) + *(int*)(p + 8);
}

Итак, мой вопрос: что может пойти не так при использовании этого подхода? Я могу сразу сказать, что он работает «в темноте», поэтому было бы важно правильно рассчитать смещения. Это также немного неудобно, так как все временные данные должны быть предоставлены пользователем. Предполагая, что мой компилятор ВМ будет иметь дело с этими двумя проблемами, я больше всего обеспокоен производительностью — я не хочу создавать нормальную функцию и для каждой нормальной функции void * Оболочка — я хотел бы напрямую использовать это соглашение для всех функций, так что я не могу не задаться вопросом, насколько полезной будет работа компилятора по вставке функций при использовании в скомпилированном коде? Будут ли какие-либо другие возможные последствия производительности, которые я пропускаю (исключая __fastcall который будет использовать еще один регистр и одно косвенное)?

3

Решение

Производительность (и простота использования), вероятно, вам лучше всего 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 самостоятельно.

0

Другие решения

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

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

0

По вопросам рекламы [email protected]