См. Тесты здесь:
- http://quick -bench.com / rMsSb0Fg4I0WNFX8QbKugCe3hkc
Для 1. Я настроил тестовый сценарийгде операции в atomicMessagePassingTypeX
действительно короткие (только барьер оптимизации). Я выбрал примерно 100
элементов для vertices
и 100
итераций внешнего while
. Эти условия будут отличаться для вашего фактического кода, поэтому примените ли результаты моего теста к вашему случаю, вы должны проверить, сравнив свой собственный код.
Четыре тестовых примера: два ваших варианта, один с указателем функции, упомянутым в других ответах, и другой, где указатель функции вызывается через диспетчерскую лямбду, например:
template<typename Uint, typename Real>
void
Graph<Uint, Real>::
messagePassingLambda(Uint nit, Uint type)
{
using ftype = decltype(&Graph::atomicMessagePassingType1);
auto lambda = [&](ftype what_to_call) {
Uint count = 0; // round counter
while (count < nit) {
++count;
// many operations
for (auto &v : vertices) {
// many other operations
(this->*what_to_call)(v);
}
}
};
if(type == 1) lambda(&Graph::atomicMessagePassingType1);
else if(type == 2) lambda(&Graph::atomicMessagePassingType2);
else lambda(&Graph::atomicMessagePassingType3);
}
Попробуйте все комбинации GCC 9.1 / Clang 8.0 и O2 / O3. Вы увидите, что в O3 оба компилятора дают примерно одинаковую производительность для вашего «медленного» варианта, в случае GCC он на самом деле лучший. Компилятор выводит операторы if
/ else
как минимум из внутренних циклов, а затем, по какой-то причине, которая мне не совсем понятна, GCC меняет порядок команд во внутреннем цикле иначе, чем для первоговариант, в результате чего он будет даже чуть-чуть быстрее.
Вариант указателя функции неизменно самый медленный.
Лямбда-вариант фактически равен вашему первому варианту по производительности. Полагаю, понятно, почему они по сути одинаковы, если лямбда-указатель встроен.
Если он не встроен, то может быть значительное снижение производительности из-за косвенного вызова what_to_call
. Этого можно избежать, принудительно назначая различный тип с соответствующим прямым вызовом на каждом сайте вызова lambda
:
С C ++ 14 или более поздней версии вы можете сделать общую лямбду:
auto lambda = [&](auto what_to_call) {
измените форму вызова (this->*what_to_call)(v);
на what_to_call();
и вызовите ее с помощью другой лямбды:
lambda([&](){ atomicMessagePassingType1(v); });
, которая заставит компилятор создавать одну функцию для каждой отправки и удалить все потенциальные косвенные вызовы.
С C ++ 11 вы не можете создать универсальный лямбда или шаблон переменной, поэтому вам нужно написать фактический шаблон функции, принимающий вторичную лямбду в качестве аргумента.