У меня есть функция класса, которую я хотел бы итеративно вызывать внутри цикла, и, хотя цикл исправлен, я хочу иметь возможность предоставлять различные функции (из данного объекта). Чтобы приблизиться к этому, я создал шаблонную структуру MyWrapper
для объекта, функцию которого я хочу вызвать, саму функцию и данные для оценки функции. (В этом смысле функция-член всегда будет иметь одну и ту же сигнатуру)
Однако я обнаружил, что использование указателя на функцию-член влечет за собой огромные затраты производительности, хотя во время компиляции я знаю функцию, которую хочу вызвать. Поэтому я бездельничал, чтобы попытаться исправить это, и (хотя мне все еще неясно, почему происходит первая ситуация), я испытал другое интересное поведение.
В следующей ситуации каждый вызов функции-оболочки MyWrapper::eval
фактически попытается скопировать весь мой объект Grid
в параметр для данной функции, которую он должен обернуть, f
, хотя вызов MyEquation::eval
будет знать, что не нужно копировать его каждый раз (из-за оптимизации).
template<typename T>
double neighbour_average(T *v, int n)
{
return v[-n] + v[n] - 2 * v[0];
}
template<typename T>
struct MyEquation
{
T constant;
int n;
T eval(Grid<T, 2> v, int i)
{
return rand() / RAND_MAX + neighbour_average(v.values + i, n) + constant;
}
};
template<typename T, typename R, typename A>
struct MyWrapper
{
MyWrapper(T &t, R(T::*f)(A, int), A a) : t{ t }, f{ f }, a{ a } {}
auto eval(int i)
{
return (t.*f)(a, i);
}
protected:
A a;
T &t;
R(T::*f)(A, int);
};
int main(int argc, char *argv[])
{
srand((unsigned int)time(NULL));
for (iter_type i = 0; i < config().len_; ++i)
{
op.values[i] = rand() / RAND_MAX;
}
srand((unsigned int)time(NULL));
double constant = rand() / RAND_MAX;
int n = 2;
int test_len = 100'000,
int test_run = 100'000'000;
Grid<double, 2> arr(100, 1000);
MyEquation<double> eq{ constant, n };
MyWrapper weq(eq, &MyEquation<double>::eval, arr); // I'm wrapping what I want to do
{
// Time t0("wrapper thing");
for (int i = 0; i < test_run; ++i)
{
arr.values[n + i % (test_len - n)] += weq.eval(n + i % (test_len - n)); // a call to the wrapping class to evaluate
}
}
{
// Time t0("regular thing");
for (int i = 0; i < test_run; ++i)
{
arr.values[n + i % (test_len - n)] += rand() / RAND_MAX + neighbour_average(arr.values + n + i % (test_len - n), n) + constant; // a usage of the neighbour function without the wrapping call
}
}
{
// Time t0("function thing");
for (int i = 0; i < test_run; ++i)
{
arr.values[n + i % (test_len - n)] += eq.eval(arr, n + i % (test_len - n)); // raw evaluation of my equation
}
}
}
Некоторый контекст:
Grid
- это просто прославленный динамический массив Grid::values
с несколькими вспомогательными функциями.
Я сохранил некоторые (казалось бы, ненужные) шаблоны для моей функции и объекта, потому что они близко соответствуют тому, как на самом деле настроен мой код.
Класс Time
даст мне продолжительность жизни объекта, поэтому это быстрый и грязный способ измерения определенных блоков кода.
Так или иначе ...
Если следующий код будет изменен, и сигнатура функции, принятой MyWrapper
, будет R(T::*f)(A&, int)
, тогда время выполнения MyWrapper::eval
будет практически идентично другим вызовам (что я и так хочу в любом случае).
Почему компилятор (msvc 2017) не знает, что он должен обрабатывать вызов weq.eval(n)
(и, следовательно, (t.*f)(a, n)
) с теми же соображениями оптимизации, что и при прямой оценке, если сигнатура и функция задаются при компиляции время