У меня есть необходимость в тестировании производительности для двух решений: одна, которая использует полиморфизм для запуска типа коммутатора и один, который использует случай переключения, чтобы выбрать, какую из функций выполнять. Я действительно должен оптимизировать этот код. Я написал следующий тестовый пример (вы можете просто скопировать вставку кода, скомпилировать его с помощью g++ -std=c++14 -O3
и запустить его с помощью echo 1 | ./a.out
!). Код действительно прост, если вы его прочитали!
#include <iostream>
#include <chrono>
#include <functional>
#include <array>
#include <cassert>
#include <vector>
#include <memory>
using namespace std;
struct profiler
{
std::string name;
std::chrono::high_resolution_clock::time_point p;
profiler(std::string const &n) :
name(n), p(std::chrono::high_resolution_clock::now()) { }
~profiler()
{
using dura = std::chrono::duration<double>;
auto d = std::chrono::high_resolution_clock::now() - p;
std::cout << name << ": "
<< std::chrono::duration_cast<dura>(d).count()
<< std::endl;
}
};
#define PROFILE_BLOCK(pbn) profiler _pfinstance(pbn)
class Base {
public:
virtual int increment(int in) {
return in + 2;
}
};
class Derived : public Base {
public:
int increment(int in) override {
return ++in;
}
};
int increment_one(int in) {
return in + 2;
}
int increment_two(int in) {
return ++in;
}
int increment_three(int in) {
return in + 4;
}
int increment_four(int in) {
return in + 2;
}
static constexpr unsigned long long NUMBER_LOOP{5000000000};
int main() {
int which_function;
cin >> which_function;
{
PROFILE_BLOCK("nothing");
}
{
PROFILE_BLOCK("switch case");
auto counter = 0;
for (unsigned long long i = 0; i < NUMBER_LOOP; ++i) {
switch(which_function) {
case 0:
counter = increment_one(counter);
break;
case 1:
counter = increment_two(counter);
break;
case 2:
counter = increment_three(counter);
break;
case 3:
counter = increment_four(counter);
break;
default:
assert(false);
break;
}
}
cout << counter << endl;
}
{
PROFILE_BLOCK("polymorphism");
auto counter = 0;
std::unique_ptr<Base> ptr_base{new Derived()};
for (unsigned long long i = 0; i < NUMBER_LOOP; ++i) {
counter = ptr_base->increment(counter);
}
}
return 0;
}
Результат, который я получаю, когда я создаю с помощью g++ -std=c++14 -O3
и запускаю с echo 1 | ./a.out
,
nothing: 1.167e-06
705032704
switch case: 4.089e-06
polymorphism: 9.299
Я не понимаю, что именно приводит к тому, что коммутационный регистр будет почти таким же быстрым, как и случай nothing
. Это из-за inlining? Это потому, что компилятор предварительно вычисляет значения для каждого сценария ввода и помещает их в таблицу поиска? Что заставляет коммутатор так быстро работать?
И как я могу приступить к написанию более справедливого теста производительности для этого сценария? В общем, я никогда не понимаю, быстрый ли код из-за прямого неоптимизированного перевода между кодом С++ и сборкой или же его компилятор предварительно вычисляет значение и полностью пропускает компиляцию и создает код "no-op style".
Примечание Структура profiler
была скопирована непосредственно с другого ответа SO и не имеет отношения к вопросу, кроме того, что он измеряет время
Примечание Как указано в комментариях ниже, @dau_sama работает с тем же тестом в ящике linux с gcc вместо результатов clang в случае коммутатора, занимающего гораздо больше времени (3,34 в этом случае), но все же намного меньше, чем случай полиморфизма.