Qtフレームワークを使用してC ++プロジェクトの開発に携わっています。 このプロジェクトでは、Qtコンテナが多くの場所で使用されており、要素を走査するためにforeachマクロがよく使用されます。 ある時点で、このマクロの使用がどの程度正当化されるのか疑問に思いました。 さらに、実際にc ++ 11を「感じ」たいと思いました。 そして、ここに私が現時点で見つけたものがあります...
ヘルパー関数の作成
STLとQtのコンテナには共通の基本クラスがないため、QtコンテナはSTLスタイルのイテレータをサポートしているため、各コンテナにテスト関数を記述しないようにテンプレート関数を省こうとしました。
現時点では、テストの主な機能は次のようになっています。
template<typename TEST_CLASS> void runTests(int testCycles) { auto container = makeTestClass<TEST_CLASS>(); using namespace TestProcs; static auto tests = TestProcs::getRegisteredTests<TEST_CLASS>(); std::cout << "Run test for type:" << typeid(TEST_CLASS()).name() << std::endl; for (auto test : tests) { auto warmResultsTest = makeTestResults(*container, test.second, testCycles, true); std::cout << "\"" << test.first << "\" results(ms):" << getResultsString(warmResultsTest) << std::endl; } }
testCyclesパラメーターは、1つのテストの反復回数を設定します。 makeTestClassテンプレート関数は、テスト用のクラスを構築します。 ベクトルの場合、次のようになります。
template<typename TEST_CLASS> std::shared_ptr<TEST_CLASS> makeTestClass() { return std::shared_ptr < TEST_CLASS > (new TEST_CLASS(VECTOR_SIZE, 0)); }
さて、QListの場合は次のようになります。
template<> std::shared_ptr<QList<TestType> > makeTestClass() { return std::shared_ptr < QList<TestType> > (new QList<TestType>( QList<TestType>::fromVector(QVector<TestType>(VECTOR_SIZE)))); }
TestProcs :: getRegisteredTestsは、テストの名前とコンテナーでテストを実行する手順からベクトルを返します。
makeTestResultsは、実際に指定された回数だけテストを起動し、各反復の測定された実行時間のベクトルを返します。 これらのデータによると、テストの反復実行時間の平均値、最小値、および最大値が考慮されます。
私が実施したテストのリスト
- std ::累積(doAccumulateTest)
- foreach Qtマクロ(doQtForeachTest)
- 範囲ベースの(doNewForTestt)
- (doSTLForTest)のSTLスタイル
- ループ外のコンテナーの終わりを計算するためのSTLスタイル(doSTLForTest2)
namespace TestProcs { template<typename CONTAINER> int doAccumulateTest(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); sum = std::accumulate(container.begin(), container.end(), sum); return sum; } template<typename CONTAINER> int doQtForeachTest(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); foreach(auto item, container) { sum += item; } return sum; } template<typename CONTAINER> int doNewForTest(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); for (auto item : container) { sum += item; } return sum; } template<typename CONTAINER> int doSTLForTest(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); auto it = container.begin(); for (; it != container.end(); ++it) { sum += *it; } return sum; } template<typename CONTAINER> int doSTLForTest2(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); auto it = container.begin(); auto end = container.end(); for (; it != end; ++it) { sum += *it; } return sum; } template<typename TEST_CLASS> const std::vector<typename Typedefs<TEST_CLASS>::TestProcRecord>& getRegisteredTests() { static const std::vector<typename Typedefs<TEST_CLASS>::TestProcRecord> tests { { "New for", &doNewForTest<TEST_CLASS> }, { "Accumulate", &doAccumulateTest<TEST_CLASS> }, { "Qt foreach", &doQtForeachTest<TEST_CLASS> }, { "STL for", &doSTLForTest<TEST_CLASS> }, { "STL for 2", &doSTLForTest2<TEST_CLASS> } }; return tests; } }
試験結果
テストされたコンテナのリスト:
- QVector <qint32>(runTests <QVector <TestType >>(TEST_CYCLES))
- std :: vector <qint32>(runTests <std :: vector <TestType >>(TEST_CYCLES))
- QList <qint32>(runTests <QList <TestType >>(TEST_CYCLES))
各テストの反復回数= 20。
コンテナのデータサイズは30メガバイトです。
差
rdtscを除算して時間を測定しました
100,000あたり。
実際、結果テーブル(値が小さいほど高速になります):
結果表のデータのグラフィカル表現:
まとめ
テストを実施することで自分自身で実現したこと:
- foreachマクロはほとんどの場合、「正しい」STLループ(ループが開始する前に最終的なイテレータ値が計算されるもの)よりも遅くなります。
- foreachマクロは、次のように、STLコンテナでの使用には厳密に禁忌です コンテナのディープコピーを作成します(QtコンテナではCOWが使用されるため、パフォーマンスの壊滅的な「沈下」の影響を受けません)
- std :: vectorの場合、イテレータの最終値がどこで計算されるかは関係ありません(ループの開始前または本体の「内部」)
私の意見では、Qtプロジェクトでforeachマクロの使用を拒否すべきだと思います。
ご清聴ありがとうございました。
PS:はい、スペースなしで ">>"を書くことができることを知っています。
ソースコード