昨年、ある__INLINE_CODE_0__を設計しました。これはイベントの処理を担当し、クラスインターフェースを提供します。サービス初期化時に、呼び出し元が対応するクラスを実装し、オブジェクトポインタをモジュールに渡します。
接触した__,好奇心害死猫,就想着这些接口都用C11
関数オブジェクトコールバックによる実装は、純粋仮想関数のインターフェース定義と比較して、より柔軟である。
疑問が生じた。二つの異なる文法、どちらがパフォーマンス面で速いのか? 私はコンパイラ理論を理解していないので、コードを書いて試してみよう。
まえがき
オンラインURLで、異なるコンパイラやコンパイルパラメータを選択し、__INLINE_CODE_0__プラットフォーム上でコードを実行したり、対応するアセンブリコードを確認できます。
- 技術検証を時々行う際、ウェブページで短いコードを実行するのは便利です。
- 色分けすることで、異なるアセンブリに対応するコードを区別でき、ローカルのデバッガーよりも見やすくなります。
正文
標準委員会が文法の規則を策定し、コンパイルレベルでの実装方法は各コンパイラの自由です。ここで言及せざるを得ないのは、Microsoftのコンパイラは非常に優れているということです。シンタックスシュガーは万能ではなく、コールバックインターフェースも多くありません。__INLINE_CODE_0__の使用の方が便利で、空のコールバック関数インターフェースを定義する必要もありません。コールバックインターフェースの種類が多様な場合は、従来の仮想関数の方がビジネスインターフェースの統一に有利です。
- プラットフォーム、両者とも性能はほぼ同等で、大きな違いはありません。
- 比較して、一度あたり1.35ns増加。
通常の業務システム開発においては、このレベルのパフォーマンス損失は無視できる。INLINE_CODE_0,在设计的上,能带来更多的便捷。在设计多信号处理时,尤为明显,底层有事件触发,如果需要落地日志,出入日志对象的的处理函数。当需要更多的业务处理接口时,底层用__INLINE_CODE_1__保存__INLINE_CODE_2__BOLD_6QT
における信号とスロット、ログ、監視、業務1、業務2は、互いに完全に分離されている。
コード
Counter: 1000000
Time: 3966us
Counter: 1000000
Time: 5316us
#include <iostream>
#include <chrono>
#include <memory>
#include <functional>
#include <atomic>
#include <string>
std::atomic_int64_t counter = 0;
// 定义回调接口
class UserInterface
{
public:
virtual void name() = 0;
virtual void full_name() = 0;
};
class User : public UserInterface
{
public:
void name() {}
void full_name() { counter++; }
};
void to_string(UserInterface* user)
{
user->name();
user->full_name();
}
using name_handler = std::function<void()>;
using full_name_handler = std::function<void()>;
class Test
{
name_handler name_;
full_name_handler full_name_;
public:
void set_name_handler(name_handler name)
{
name_ = name;
}
void set_full_name_handler(full_name_handler full_name)
{
full_name_ = full_name;
}
void to_string()
{
name_();
full_name_();
}
};
int main()
{
User user;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; i++)
{
to_string(&user);
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Counter: " << counter << std::endl;
std::cout << "Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << "us" << std::endl;
counter = 0;
auto name = []() {};
auto full_name = []() { counter++; };
Test test;
test.set_name_handler(name);
test.set_full_name_handler(full_name);
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; i++)
{
test.to_string();
}
end = std::chrono::high_resolution_clock::now();
std::cout << "Counter: " << counter << std::endl;
std::cout << "Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << "us" << std::endl;
return 0;
}
追記
資料を探している時に、類似のコードスニペットにたどり着きました。
#include <iostream>
#include <chrono>
#include <memory>
#include <functional>
using namespace std;
using namespace std::chrono;
class Base
{
public:
Base(){}
virtual ~Base(){}
virtual int func(int i) = 0;
};
class Derived : public Base
{
public:
Derived(int base = 10) : base{base}
{
}
~Derived(){}
virtual int func(int i)
{
return i*base;
}
private:
int base;
};
struct Func
{
int base;
int operator()(int i)
{
return i*base;
}
Func(int base) : base {base}
{
}
};
const int base = 10;
int calculate(int i)
{
return base*i;
}
int main()
{
const int num = 10000;
Base *p = new Derived{10};
int total = 0;
auto start = high_resolution_clock::now();
for (int i = 0; i < num; ++i)
{
total += p->func(i);
}
auto end = high_resolution_clock::now();
std::cout<<"result: "<<total<<"\nvirtual call elapsed: \t"<<duration_cast<nanoseconds>(end-start).count()<<" nanoseconds.\n"<<std::endl;
total = 0;
start = high_resolution_clock::now();
for (int i = 0; i < num; ++i)
{
total += calculate(i);
}
end = high_resolution_clock::now();
std::cout<<"result: "<<total<<"\ndirect function call elapsed: \t"<<duration_cast<nanoseconds>(end-start).count()<<" nanoseconds.\n"<<std::endl;
Func functor{10};
total = 0;
start = high_resolution_clock::now();
for (int i = 0; i < num; ++i)
{
total += functor(i);
}
end = high_resolution_clock::now();
std::cout<<"result: "<<total<<"\nfunctor call elapsed: \t"<<duration_cast<nanoseconds>(end-start).count()<<" nanoseconds.\n"<<std::endl;
int base = 10;
function<int(int)> lambda = [base](int i)
{
return i*base;
};
total = 0;
start = high_resolution_clock::now();
for (int i = 0; i < num; ++i)
{
total += lambda(i);
}
end = high_resolution_clock::now();
std::cout<<"result: "<<total<<"\nlambda call elapsed: \t"<<duration_cast<nanoseconds>(end-start).count()<<" nanoseconds.\n"<<std::endl;
return 0;
}
/*
test on mac mini i7 2.7GHz
clang++ -std=c++11 chronotest.cpp -O0
output:
result: 499950000
virtual call elapsed: 43171 nanoseconds.
result: 499950000
direct function call elapsed: 31379 nanoseconds.
result: 499950000
functor call elapsed: 41497 nanoseconds.
result: 499950000
lambda call elapsed: 207416 nanoseconds.
===================================================
clang++ -std=c++11 chronotest.cpp -O1
output:
result: 499950000
virtual call elapsed: 26144 nanoseconds.
result: 499950000
direct function call elapsed: 22384 nanoseconds.
result: 499950000
functor call elapsed: 33477 nanoseconds.
result: 499950000
lambda call elapsed: 55799 nanoseconds.
===================================================
clang++ -std=c++11 chronotest.cpp -O2
result: 499950000
virtual call elapsed: 22284 nanoseconds.
result: 499950000
direct function call elapsed: 36 nanoseconds.
result: 499950000
functor call elapsed: 30 nanoseconds.
result: 499950000
lambda call elapsed: 28292 nanoseconds.
===================================================
clang++ -std=c++11 chronotest.cpp -O3
result: 499950000
virtual call elapsed: 18975 nanoseconds.
result: 499950000
direct function call elapsed: 29 nanoseconds.
result: 499950000
functor call elapsed: 30 nanoseconds.
result: 499950000
lambda call elapsed: 22542 nanoseconds.
===================================================
clang++ -std=c++11 chronotest.cpp -O4
result: 499950000
virtual call elapsed: 22141 nanoseconds.
result: 499950000
direct function call elapsed: 30 nanoseconds.
result: 499950000
functor call elapsed: 30 nanoseconds.
result: 499950000
lambda call elapsed: 22584 nanoseconds.
*/
ここでは、通常の関数とラムダ関数の2つのモードが追加され、インターフェースコールバック方式と直接呼び出しの比較を提供します。性能損失は桁違いであり、ラムダ関数の性能は関数に近く、場合によってはラムダ関数の性能の方が優れています。コンパイラ原理に関しては知識の空白ですが、アクセスする変数のアドレスと関数が隣接しているため、__INLINE____処理に有利であると推測しています。
添付 INLINE_CODE_0 の実行結果
result: 499950000
virtual call elapsed: 6143 nanoseconds.
result: 499950000
direct function call elapsed: 30 nanoseconds.
result: 499950000
functor call elapsed: 31 nanoseconds.
result: 499950000
lambda call elapsed: 15134 nanoseconds.