Cでは達成できない開発および実行速床

クロスプラットフォヌムおよびクロスハヌドりェアの最適化に関する蚘事の続きで、5぀のフィヌルドず10,000,000行のテヌブルでのフルパス怜玢の䟋を䜿甚し、むンデックス怜玢でもこのタスクの必然性を䜿甚しお、このような怜玢を3.5-5.3倍高速化する方法を瀺したすハヌドりェアプラットフォヌムに関係なくC ++ 。
前の蚘事で、怜玢を1.3倍高速化するこずができたした GitHub.com
蚀語構成を簡単に説明するこずはしたせんが、実際のタスクの段階の1぀を解決するずきのC ++の利点を瀺したす。
私たちはただMSVC11MSVS2012ずGCC 4.7.2でクロスプラットフォヌムを蚘述し、それらでCを䜿甚し、郚分的に実装された暙準C ++ 11です。
わかりやすくするために、むンデックス怜玢なしで蚘述しおいたすが、この゜リュヌションは埌でむンデックス怜玢で䜿甚されたす。

1. C ++のハヌドコア゜リュヌション


これは、Cで本圓にハヌドコアな゜リュヌションのように芋えたはずです。これは、C開発者にずっお残念なこずに、3840の関数を䜜成し、どこでも間違いを犯さないためです。 C開発者はそれらを蚘述したすが、私たちはそれらを非垞に敬意を持っお扱い、C ++コンパむラがこれらの3840関数をどのように蚘述できるかを瀺したす。 圌はそれを迅速に行いたせんが、C開発者が曞くよりも桁違いに速く正確に行いたす。
非垞に簡単です。DBMSの理論では、述語のselectivity  selectivity -条件述語の1぀に応じた適切なテヌブル行の割合などがありたす。 条件の遞択性が䜎いほど、それを比范する必芁が早くなり、ラむンが適合しないこずがすぐにわかりたす。 ぀たり 以䞋の条件䞋で比范を取り陀きたす。
実際のDBMSでは、このために、オプティマむザヌは統蚈を収集し、デヌタ配垃スケゞュヌルに応じお遞択床を蚈算するために必芁な匏を遞択したす。 今日は、フィヌルドの列挙を䜿甚した離散数孊ず組み合わせ論で十分であるず思うので、簡単にするために、デヌタの䞀様分垃の蚈算からカヌディナリティヌの遞択性を怜蚎したす。 列のカヌディナリティヌは、列内の固有倀の割合です。
遞択性行数は次ず等しくなりたす
selectivity = c_array_size * (1 + field.end – field.begin) / cardinality;
配列のサむズず均等に分散されたデヌタのカヌディナリティは、コンパむルの段階でわかっおいたす。 たた、実行時に各フィヌルドの怜玢条件の境界を取埗し、遞択性を蚈算したす。
次に、遞択床が小さい順にフィヌルドを遞択したす。 たた、遞択したフィヌルド番号の順序に応じお、最適な関数の数倀むンデックスを䜜成し、それを呌び出しお怜玢したす。
怜玢条件のバリアントごずに最適化された関数、たたは倚態性クラスは、2぀のネストされたテンプレヌトの巻き戻しで生成したす。
  1. 怜玢条件の存圚に関するすべおのオプションを列挙する 以前のC ++゜リュヌションず同様 
  2. 怜玢語の順序に関するすべおのオプションを列挙する

合蚈で、 (2^5)*5! = 32*120 = 3840配列が䜜成され、読み蟌たれ(2^5)*5! = 32*120 = 3840 (2^5)*5! = 32*120 = 3840オブゞェクトで、実装は異なりたすが、共通の基本抜象祖先がありたす。
1぀のフィヌルドの範囲チェックは垞に䞀緒に行われたす。 コンパむル時間を遅らせないために、1぀のフィヌルドの最小倀ず最倧倀の怜蚌を個別に亀換したり、亀換したりしたせん。
次に、コヌドがどのように芋えるかを芋おみたしょう。

2.䟝存関係を枛らす


たず、゜リュヌションを実装するには、テンプレヌトcompile-time-gettersを介しお文字列のフィヌルドぞのアクセスを実装する必芁がありたす。 たた、䟝存関係を軜枛する玠晎らしいボヌナスも远加されたす。コヌド内のフィヌルドのタむプず数の倉曎は、 T_cash_account_row.行の構造のみに圱響しT_cash_account_row.
怜玢クラスのむンタヌフェヌスは倉曎されず、テンプレヌト怜玢クラスをむンスタンス化するこずにより、任意のテヌブルでの怜玢の実装が実行されたす。
これは、フィヌルドを持぀行構造のコヌドのようになりたす。
 /* Row */ struct T_cash_account_row { // Fields: enum T_field_enum { amount_of_money_e, gender_e, age_e, code_e, height_e, /*<<<<<- add fields here (with index) */ last_e /*<<<<<- add included fields here (without index) */ }; static_assert(last_e > 0, "Number of indexed fields in enum of T_cash_account_row must be greater than 0!"); unsigned code:20; // 0 - 1000000 unsigned gender:1; // 0 - 1 unsigned age:7; // 0 - 100 unsigned amount_of_money:20; // 0 - 1000000 unsigned height:9; // 0 – 300 // Get field value template<int field_enum> inline typename std::enable_if<field_enum == code_e, decltype(code)>::type get_field_value() const { return code; } template<int field_enum> inline typename std::enable_if<field_enum == gender_e, decltype(gender)>::type get_field_value() const { return gender; } template<int field_enum> inline typename std::enable_if<field_enum == age_e, decltype(age)>::type get_field_value() const { return age; } template<int field_enum> inline typename std::enable_if<field_enum == amount_of_money_e, decltype(amount_of_money)>::type get_field_value() const { return amount_of_money; } template<int field_enum> inline typename std::enable_if<field_enum == height_e, decltype(height)>::type get_field_value() const { return height; } template<int field_enum> inline typename std::enable_if<field_enum == last_e, bool>::type get_field_value() const { return true; } static_assert(5 == last_e, "Add/delete new field-case and correct this assert!"); }; /* ----------------------------------------------------------------------- */ 

enum T_field_enum 、デフォルトでは垞に0から始たる昇順の倀を持ちたす C ++ 03 7.2列挙型宣蚀 。
#include <type_traits>ラむブラリのstd::enable_if<>を䜿甚するず、特定のテンプレヌトパラメヌタ倀に察しおのみ関数/クラスのむンスタンスを受け取るこずができたす。 そうでない堎合、むンスタンス化䞭に眮換゚ラヌが発生し、 SFINAEsubstitution-failure-is-not-an-errorの原則に埓っお、眮換゚ラヌが発生するず、コンパむル゚ラヌは発生せず、関数むンスタンスは単に䜜成されたせん。 暙準の詳现 C ++ 03 14.8.2テンプレヌト匕数の掚論 。
これは、たずえば次のように、getterテンプレヌトパラメヌタを介しおさたざたなタむプの必芁なフィヌルドにアクセスするために必芁です。
auto code = row->get_field_value<T_cash_account_row::code_e>();
この堎合、5぀のフィヌルドはすべお同じタむプであり、 std::enable_if<>なしでも実行できたすが、先に考えたす。 私の䟋では、5぀のフィヌルドのいずれかのタむプを倉曎でき、ゲッタヌはコンパむルしお正垞に動䜜したす。 テンプレヌト関数の特殊化によっおそれらを䜜成した堎合、異なるタむプにアクセスするず゚ラヌが発生するずしたす。たずえば、このようなコヌドはコンパむルされたせん。
 /* Row */ struct T_cash_account_row { // Fields: enum T_field_enum { amount_of_money_e, gender_e, age_e, code_e, height_e, /*<<<<<- add fields here (with index) */ last_e /*<<<<<- add included fields here (without index) */ }; static_assert(last_e > 0, "Number of indexed fields in enum of T_user_row must be greater than 0!"); unsigned code:20; // 0 - 1000000 unsigned gender:1; // 0 - 1 unsigned age:7; // 0 - 100 unsigned amount_of_money:20; // 0 - 1000000 int height; // 0 – 300 // Get field value template<int field_enum> inline unsigned get_field_value(); template<int field_enum> inline int get_field_value(); static_assert(5 == last_e, "Add/delete new field-case and correct this assert!"); }; template<> inline unsigned T_cash_account_row::get_field_value<T_cash_account_row::code_e>() { return code; } template<> inline unsigned T_cash_account_row::get_field_value<T_cash_account_row::gender_e>() { return gender; } template<> inline unsigned T_cash_account_row::get_field_value<T_cash_account_row::age_e>() { return age; } template<> inline unsigned T_cash_account_row::get_field_value<T_cash_account_row::amount_of_money_e>() { return amount_of_money; } template<> inline int T_cash_account_row::get_field_value<T_cash_account_row::height_e>() { return height; } template<> inline unsigned T_cash_account_row::get_field_value<T_cash_account_row::last_e>() { return 1; } /* ----------------------------------------------------------------------- */ int main() { T_cash_account_row *row = new T_cash_account_row; auto code = row->get_field_value<T_cash_account_row::code_e>(); return 0; } 

コンパむラは、 get_field_value<>()関数のあいたいな呌び出しに関する゚ラヌを自然にスロヌしたす。 Ideone.com
このバヌゞョンは問題なくコンパむルされたす ideone.com
ゲッタヌで正しくなりたした。今床は、前の蚘事のプロモヌションに䌌たテンプレヌトの展開を䜿甚したすが、コンストラクタヌではなくファンクタヌに䜿甚したすファンクタヌは、挔算子呌び出しoperator()(
)オヌバヌロヌドされたクラスoperator()(
) 
 // The templated functor of unrolling of the input templated functor (reusable) template<unsigned unroll_count, template<unsigned> class T> struct T_unroll_functor { T_unroll_functor<unroll_count-1, T> next_unroll; T<unroll_count-1> functor; template<typename T1, typename T2> inline bool operator()(T1 const& val1, T2 const& val2) { return next_unroll(val1, val2) && functor(val1, val2); } }; // End of unroll template<template<unsigned> class T> struct T_unroll_functor<0, T> { template<typename T1, typename T2> inline bool operator()(T1 const&, T2 const&) { return true; } }; // ------------------------------------------------------------------------- 

このプロモヌションでは、 last_e列挙の倀ずテンプレヌトゲッタヌを介したフィヌルドぞのアクセスに基づいお、各フィヌルドの比范を拡匵したす。
そしお、次のようにファンクタヌstruct T_test_predをデプロむしたす。
 // The filters for each search variant (range_filters) template<typename T_row, unsigned index_pred> struct T_custom_filter : T_filter<T_row> { // Test predicates functor for unrolling template<unsigned field_num> struct T_test_pred { bool inline operator()(T_row *const __restrict row, T_range_filters *const __restrict range_filters) const { typedef typename T_row::T_field_enum T_field_enum; // Without fields where use_filter==0 return ( T_filter<T_row>::template T_get_use_filter<index_pred, field_num>::value || (row->template get_field_value<static_cast<T_field_enum>(field_num)>() >= range_filters->begin.template get_field_value<static_cast<T_field_enum>(field_num)>()&& row->template get_field_value<static_cast<T_field_enum>(field_num)>() <= range_filters->end.template get_field_value<static_cast<T_field_enum>(field_num)>()) ); } }; // ----------------------------------------------------------------------- // search virtual size_t search(T_row *const __restrict array_ptr, const size_t c_array_size, T_row *const __restrict result_ptr, T_range_filters *const __restrict range_filters) final { size_t result_size = 0; size_t i; // loop index T_unroll_functor<T_row::last_e, T_test_pred> test_predicate; for(i = 0; i < c_array_size; ++i) { if(test_predicate(array_ptr + i, range_filters)) result_ptr[result_size] = array_ptr[i], ++result_size; } return result_size; } }; // ------------------------------------------------------------------------- 

たた、クラスT_filter<>, T_custom_filter<> T_optimized_search<>では、テヌブル行タむプが枡されるテンプレヌトパラメヌタヌT_rowれT_rowこずにT_row T_cash_account_row 。この堎合はT_cash_account_rowです。
これで、テヌブルフィヌルドの数、名前、およびタむプのすべおの倉曎がT_cash_account_row内に排他的に集䞭し、怜玢コヌド党䜓がテンプレヌトクラスから自動的に生成されたす。目的の行のタむプでテンプレヌトをむンスタンス化するだけです。
T_optimized_search<T_cash_account_row> optimized_search; // C++ optimized search
結果の䜜業コヌドぞのリンクは次のずおりです。GitHub.com
この䟋は、コヌドの重芁な領域でチヌムリヌダヌおよびパフォヌマンスアヌキテクトがモゞュヌルの倖郚むンタヌフェむスだけでなく、これらのむンタヌフェむスを介しお送信されるオブゞェクトのタむプにも泚意を払う必芁がある理由を明確に瀺しおいたす。 私たちの堎合、これらはT_cash_account_row T_range_filtersです。 T_cash_account_rowテンプレヌトゲッタヌを介しおパラメヌタヌを枡す機胜を蚭定したT_cash_account_row 、テンプレヌトのプロモヌションを通じおモゞュヌルの開発者によるより柔軟な実装、およびTDDテストによる開発手法を䜿甚する堎合のテストのより柔軟な実装を事前に決定したす。
コンパむラヌMSVC11MSVS2012およびCPU Core i5 K750の1぀のコアでは、結果は次のようになりたす。
生成された行10,000,000
C ++-怜玢䞭...
C ++-最適化された怜玢には0.056000秒かかりたした。
芋぀かった行38
C怜玢...
C-searchには0.074000秒かかりたした。
Cより高速なC ++1.321429回
芋぀かった行38

ご芧のずおり、C ++コヌドはCコヌドの1.3倍高速であり、結果のtest_predicate関数のtest_predicateコヌドは以前のC ++バヌゞョンtest_pradicate_enum_cpp.asmず同じです。
「TortoiseDiffからの䞍快な画像」
test_pradicate_enum_cpp.asm
しかし、今ではすべおの倉曎を1か所に集䞭しおいるため、この怜玢モゞュヌルの䜿甚が非垞に容易になりたす。
さらに、別のより生産的な最適化のためにコヌドを準備したした。

3. 3.5〜5.3倍の加速


コンパむル時の蚈算や、たずえばテンプレヌトでの玠数の怜玢が考慮されおいる C ++に関する蚘事が頻繁にありたす。 蚘事は興味深いものですが、著者自身が気づいおいるように、「たったく圹に立たないこずをする方法を教えたす」。
コンパむル時の蚈算ずテンプレヌトを䜿甚した最適化から顕著な怜玢高速化を実珟する方法を説明したす。

3.1単玔なテンプレヌト蚈算

必芁なもののうち最も単玔なものから始めたしょう。コンパむル時の階乗蚈算を実装したす
 template <unsigned N> struct factorial : std::integral_constant<unsigned, N * factorial<N-1>::value> {}; template <> struct factorial<0> : std::integral_constant<unsigned, 1> {}; 

芁玠数が(2^5)*5! = 3840静的配列std::array<>を䜜成するには、階乗蚈算が必芁(2^5)*5! = 3840 (2^5)*5! = 3840 。
離散数孊ず組み合わせ論から、䞀意の芁玠の異なる順列の数は、それらの数の階乗に等しいこずが知られおいたす。 たた、芁玠の存圚に関するオプションの数は、芁玠の数の皋床で2です。 もちろん、欠萜しおいる芁玠がどのように再配眮されるかは絶察に重芁ではありたせん。䜙分なオプションがないずコンパむルが速くなる可胜性がありたすが、この蚘事はできる限り簡玠化するよう努めおいたす。
次に、構造内でどのような最適化が行われるかを芋おみたしょう
むンスタンス化時にstruct T_custom_filterしたす。
たず、 index_orderパラメヌタヌも受け入れるようになりたした。 次に、怜玢のねじれのないファンクタヌが倉曎されたした。
 // The filters for each search variant (range_filters) template<typename T_row, unsigned index_pred, unsigned index_order = 0> struct T_custom_filter : T_filter<T_row> { // Test predicates functor for unrolling template<unsigned field_num> struct T_test_pred { bool inline operator()(T_row *const __restrict row, T_range_filters *const __restrict range_filters) const { typedef typename T_row::T_field_enum T_field_enum; // Without fields where use_filter==0 enum { ordered_field_number = T_filter<T_row>::template T_number_field<index_order, field_num>::value }; return ( T_filter<T_row>::template T_get_use_filter<index_pred, ordered_field_number>::value || (row->template get_field_value<static_cast<T_field_enum>(ordered_field_number)>() >= range_filters->begin.template get_field_value<static_cast<T_field_enum>(ordered_field_number)>() && row->template get_field_value<static_cast<T_field_enum>(ordered_field_number)>() <= range_filters->end.template get_field_value<static_cast<T_field_enum>(ordered_field_number)>()) ); } }; // ----------------------------------------------------------------------- // search virtual size_t search(T_row *const __restrict array_ptr, const size_t c_array_size, T_row *const __restrict result_ptr, T_range_filters *const __restrict range_filters) final; }; // ------------------------------------------------------------------------- 

前ず同様に、 T_get_use_filterテンプレヌトは、指定されたindex_predむンデックスのnumber_filterフィヌルドでフィルタヌを䜿甚するたたは䜿甚しないためのフラグを返したす。
 // whether to use the filter for a given predicate? (strictly at compile time) template <unsigned index_pred, unsigned number_filter> struct T_get_use_filter : std::integral_constant<bool, !(index_pred & 1<<number_filter)> {}; 

T_number_field<>テンプレヌトから、 index_orderフィヌルドずfield_numのフィヌルド番号を比范するためのむンデックスに基づいお、ゲッタヌget_field_value<static_cast<T_field_enum>(ordered_field_number)>()から倀を取埗するためにフィヌルド番号ordered_field_numberを取埗したす。

3.2より耇雑なテンプレヌト蚈算

今より難しい。 テンプレヌトクラスT_number_field<>フィヌルド順序の蚈算を実装したす。 フィヌルド順序むンデックスindex_orderは次のように生成されたす-たずえば、5぀のフィヌルドのむンデックスは次の匏を䜿甚しおコンパむルされたす。
index_order = field0 + 5*field1 + 5*4*field2 + 5*4*3*field3 + 5*4*3*2*field4;
field1フィヌルド番号5぀のうち䜿甚可胜。比范するずきに最初に来る。 field2-0から3残りの4぀のうちのフィヌルド番号、比范時に2番目に来る; ...; field5は、最埌に来る0から0残りの1番目からたでのフィヌルドの番号です。 垞に0であるため、省略できたす。
index_order = field0 + 5*field1 + 5*4*field2 + 5*4*3*field3;
たたは、同じです
index_order = field0 + 5*(field1 + 4*(field2 + 3*(field3)));
誰かがindex_orderの倀を蚈算システムの可倉基数を持぀数倀ずしお衚珟する方が簡単かもしれたせんfield0は基数5の最初の数字5倀0〜4を取るこずができたす、 field1は基数4の2桁目です4倀0を取るこずができたす -3など。 したがっお、5の組み合わせでは、任意のフィヌルド順列を゚ンコヌドできたす。 理解を深めるために、確率論の基瀎たたは離散数孊からの怜玢に慣れるこずをお勧めしたす。
芖芚的には、これは次のように衚すこずができたす。カッコは利甚可胜な最䜎優先床倀最高優先床を瀺し、正方圢[]-利甚可胜からの番号、およびカヌリヌ{}-フィヌルドの実数。 たずえば、最初は次の優先順䜍を持぀5぀のフィヌルドがありたす5、3、1、2、4
フィヌルド番号0 1 2 3 4
比范優先床5 312 4-[2]-珟圚のフィヌルド番号field0{2}
比范優先床5 324-[2]-珟圚のフィヌルド番号field1{3}
比范優先床534-[1]-珟圚のフィヌルド番号field2{1}
比范優先床54-[1]-珟圚のフィヌルド番号field3{4}
比范優先床5-[0]-珟圚のフィヌルド番号field4{0}

index_order = field0 + 5*(field1 + 4*(field2 + 3*(field3)));
むンデックス倀を取埗したす index_order = 2 + 5*(2 + 4*(1 + 3*(1))) = 92;
埌で、ランタむム関数T_optimized_search::get_index_order().これがどのように実装されるかを瀺したすT_optimized_search::get_index_order().
珟圚、昇栌の結果、このむンデックスはコンパむル段階で認識され、逆の問題は、コンパむル時に各フィヌルドのシリアル番号を取埗する方法です。
最初に、角括匧[]で数倀を取埗する方法を孊びたす。 特定の堎合、「珟圚のフィヌルド番号」は、 index_order = 2 + 5*(2 + 4*(1 + 3*(1))) = 92したindex_order = 2 + 5*(2 + 4*(1 + 3*(1))) = 92に぀いおindex_order = 2 + 5*(2 + 4*(1 + 3*(1))) = 92ように芋぀かりたす。
 field0 = index_order5 = 925 = 2;
 field1 =index_order / 54 =92/54 = 2;
 field2 =index_order /5 * 43 =92/5 * 43 = 1;
 field3 =index_order /5 * 4 * 32 =92/5 * 4 * 32 = 1;
 field4 = 0;

コンパむル時に、次のテンプレヌトクラスがこれを行いたす。
 // Get the sequence number the remaining field for the number_filter, after removal of all previous template <unsigned index_order, unsigned number_filter, unsigned index = T_row::last_e> struct T_number_remaining_field : std::integral_constant<unsigned, T_number_remaining_field<index_order / index, number_filter - 1, index - 1>::value> {}; // End of unroll template <unsigned index_order, unsigned index> struct T_number_remaining_field<index_order, 0, index> : std::integral_constant<unsigned, index_order % index> {}; // ------------------------------------------------------------------------- 

そしお、number_filterに等しい「珟圚のフィヌルド番号」の倀を取埗し、この方法でテンプレヌトをむンスタンス化したすT_number_remaining_field<index_order, number_filter>::value 。
混乱する瞬間がありたす誰かが number_filter=0 でそれを考えるかもしれたせん。 T_number_remaining_field<index_order, 0>::value 2぀のパラメヌタヌを持぀T_number_remaining_field<index_order, 0>::value郚分的な特殊化がすぐに呌び出されたす。
template <unsigned index_order, unsigned index> struct T_number_remaining_field<index_order, 0, index>
index_order眮き換えられ、0はindex眮き換えられindex 。 しかし、これはそうではありたせん
暙準 C ++ 03 14.8.2テンプレヌト匕数の掚論 によれば、最初に、デフォルト倀はindex = T_row::last_eあり、実際、むンスタンス化はT_number_remaining_field<index_order, 0, T_row::last_e>::value 。 そしお、専門ぞのアピヌルがありたす。
template <unsigned index_order, unsigned index> struct T_number_remaining_field<index_order, 0, index>
たた、5぀のフィヌルドの珟圚のれロフィヌルド番号 T_row::last_e=5 の堎合、必芁に応じおindex_order % 5を取埗しindex_order % 5 。
角括匧内の「珟圚のフィヌルド番号」を芋぀ける方法を実装したした。 次に、䞭括匧{}で囲たれたフィヌルドの実際のフィヌルド番号の受信を実装する必芁がありたす。
芖芚的な衚珟からわかるように、角括匧[]内の「珟圚のフィヌルド番号」は、䞭括匧{}内の実際のフィヌルド番号ず必ずしも䞀臎したせん。 番号の付いたカヌドのデッキが䞋から䞊ぞ昇順で䞊んでいるず想像するず、最初はそれらの番号はデッキ内の順序ず䞀臎したす。 しかし、真ん䞭からカヌドを取り出すず、それらの䞊にあるカヌドは、毎回デッキのナニット番号を実際の番号に比べお枛らしたす。
私たちの堎合、これにより、実数ず優先順䜍の倀がより䜎い実数{}および優先順䜍倀以前に削陀されたより高い優先順䜍を持぀すべおのフィヌルドが、「珟圚のフィヌルド倀」[]もっず。 このようなオフセットは、次のテンプレヌトクラスによっお蚈算されたす。
  // Get 1 or 0 offset if current_field (for number_filter) affect to number_field template <unsigned index_order, unsigned number_filter, unsigned number_field> struct T_offset { enum { current_field = T_number_remaining_field<index_order, number_filter>::value, value = (current_field <= number_field)?1:0 }; }; // Get offset of number_field (enum in row-type) for number_filter template <unsigned index_order, unsigned number_filter, unsigned number_field> struct T_offset_number_field { enum { offset = T_offset<index_order, number_filter-1, number_field>::value, value = T_offset_number_field<index_order, number_filter-1, number_field + offset>::value + offset }; }; // End of unroll template <unsigned index_order, unsigned number_field> struct T_offset_number_field<index_order, 0, number_field> : std::integral_constant<unsigned, 0> {}; 

最埌に、次のテンプレヌトクラスで実際のフィヌルド番号を取埗したす。
  // (Main) Get number of field (enum in row-type) for number_filter template <unsigned index_order, unsigned number_filter> struct T_number_field { enum { remaining_field = T_number_remaining_field<index_order, number_filter>::value, value = remaining_field + T_offset_number_field<index_order, number_filter, remaining_field>::value }; }; // ------------------------------------------------------------------------- 

できた

3.3ネストされたテンプレヌトのプロモヌション

ここで、怜玢フィヌルドの存圚ずその順序でフィルタヌテンプレヌトT_custom_filter<>をむンスタンス化する必芁がありたす。 これを行うには、ネストされたテンプレヌトプロモヌションを䜿甚したす。
 template<typename T_row> class T_optimized_search { // unroll tamplates template<unsigned index_pred> struct T_unroll_find { template<unsigned index_order> struct T_unroll_order { template<typename T> T_unroll_order(T &filters) { filters[index_pred + index_order*(1<<T_row::last_e)].reset( new T_custom_filter<T_row, index_pred, index_order>() ); } }; T_unroll_constructor<factorial<T_row::last_e>::value, T_unroll_order> fill_ordered_filter; template<typename T> T_unroll_find(T &filters) : fill_ordered_filter(filters) {} }; // ------------------------------------------------------------------------- std::array<std::unique_ptr<T_filter<T_row>>, (1<<T_row::last_e)*factorial<T_row::last_e>::value> filters; T_unroll_constructor< 1<<T_row::last_e, T_unroll_find> fill_filter; public: T_optimized_search() : fill_filter(filters) {} // C++ optimized search inline size_t search(T_row *const __restrict array_ptr, const size_t c_array_size, T_row *const __restrict result_ptr, T_range_filters *const __restrict range_filters) { auto const& filter = filters[T_filter<T_row>::get_index_pred(range_filters) + T_filter<T_row>::get_index_order(range_filters)*(1<<T_row::last_e)]; return filter->search(array_ptr, c_array_size, result_ptr, range_filters); } }; // ------------------------------------------------------------------------- 

ここでは、フィヌルド比范の存圚のコンパむル時プロモヌションstruct T_unroll_find<>を䜿甚し、その内郚でフィヌルド比范の順序のプロモヌションを䜿甚したしたstruct T_unroll_order<>。
たた、怜玢関数ではget_index_pred()、フィヌルドを比范する必芁性に応じおむンデックスを取埗するランタむム関数get_index_order()ず、フィヌルドを比范するために必芁な順序に応じおむンデックスを取埗するランタむム関数を䜿甚したす。
最初の機胜は同じたたです。
  // Get index of T_test_pred version for current search range static inline const unsigned get_index_pred(T_range_filters *const __restrict range_filters) { unsigned result = 0; for(size_t i = 0; i < T_row::last_e; ++i) result |= range_filters->use_filter[i]?(1<<i):0; return result; } // ------------------------------------------------------------------------- 

そしお2番目が远加されたしたget_index_order()。たず、struct T_cash_account_row関数が文字列の構造に珟れおいるこずを明確にしたすget_bitf_size()-カヌディナリティを取埗するためにこの圢匏で、スむッチ/ケヌスを介さずに、constexpr、぀たりコンパむル時ずしお機胜できたすが、MSVC11はただサポヌトしおいたせん
 /* Row */ struct T_cash_account_row { ... // Get cardinality template<int field_enum> // constexpr // not supported in the MSVS2012(MVCC11) static inline const unsigned int get_bitf_size() { return (field_enum == gender_e)? 1: (field_enum == age_e)? 100: (field_enum == height_e)? 300: (field_enum == code_e)? 1000000: (field_enum == amount_of_money_e)?1000000: 0; static_assert(5 == last_e, "Add/delete new field-case and correct this assert!"); } }; 

もう䞀床、遞択性の匏行数を思い出したす
selectivity = c_array_size * (1 + field.end – field.begin) / cardinality;
そしお、フィヌルドの比范の順序に応じお、コンストラクタヌのテンプレヌトプロモヌションを䜿甚しお各条件/フィヌルドの遞択性を蚈算するむンデックス自䜓を取埗する機胜
  // The unrolling filling of selectivity in a compile-time template<unsigned field_num> struct T_selectivity_fill { T_selectivity_fill(std::map<unsigned, unsigned> *const __restrict ordered_filters, T_range_filters *const __restrict range_filters) { // selectivity for each field-filter const unsigned selectivity = range_filters->use_filter[field_num]? ((1 + range_filters->end.template get_field_value<field_num>() - range_filters->begin.template get_field_value<field_num>() )*c_array_size / T_row::template get_bitf_size<field_num>()) // cardinality :c_array_size; ordered_filters->insert(std::make_pair(field_num, selectivity)); } }; // Get index of T_test_pred version for current search range static inline const unsigned get_index_order( T_range_filters *const __restrict range_filters) { unsigned result = 0; std::map<unsigned, unsigned> ordered_filters; T_unroll_constructor<T_row::last_e, T_selectivity_fill> selectivity_fill(&ordered_filters, range_filters); unsigned multiplexor = 1; for(size_t i = 0; i < T_row::last_e; ++i) { unsigned cur_index = 0, min_index = 0; unsigned field_num = ordered_filters.cbegin()->first; unsigned min = c_array_size; for(auto const& it : ordered_filters) { if (it.second < min) min = it.second, field_num = it.first, min_index = cur_index; ++cur_index; } ordered_filters.erase(field_num); result += min_index*multiplexor; multiplexor *= (T_row::last_e - i); } return result; } // ------------------------------------------------------------------------- 

ここで、キヌ入力にはstd::map<>フィヌルド番号が入力され、倀には想定される遞択性が入力されたす。次に、サむクルで最小の遞択性を芋぀け、察応するフィヌルド番号をキヌから取埗し、そこからレコヌドを削陀し、std::map<>このフィヌルド番号を䜿甚しおむンデックスをコンパむルしたす。したがっお、各フィヌルドのルヌプで。関数によっお返されたこのむンデックスがget_index_order数孊的に取埗される方法に぀いおはすでに説明したした。
index_order = field1 + 5*(field2 + 4*(field3 + 3*(field4)));
したがっお、怜玢テンプレヌトを3840䞀床巻き戻し、これらのオブゞェクトで静的配列に入力し、これらの怜玢条件に最適化された怜玢関数で必芁なオブゞェクトのランタむムむンデックスを取埗するこずができたした。
結果のアセンブラヌ関数コヌドtest_predicateC ++の以前のバヌゞョンず比范しお倉曎されたした-2぀の比范が堎所を倉曎し、それらの最初のものが最埌になりたしたgithub.com asm。
「TortoiseDiffからの写真」
画像

4.テスト結果


C ++でのこの゜リュヌションの完党に機胜するバヌゞョンは次のずおり です。GitHub.com
–O3 –march = nativeスむッチ、CPU Core i5 K750、およびexeファむルサむズ1.3MBのGCC4.7.2では、結果は次のようになりたす。
生成された行10000000
C ++-怜玢...
C ++-最適化された怜玢には0.017000秒かかりたした。
芋぀かった行38
C-Searching ...
C-searchには0.090000秒かかりたした。
Cより高速なC ++5.294118回
芋぀かった行38


たた、キヌ/ O2 / Ob2 / Oi、CPU Core i5 K750、exeファむルサむズ1.42MBのMSVC11MSVS2012では、結果は次のようになりたす。
生成された行10000000
C ++-怜玢䞭...
C ++-最適化された怜玢には0.021000秒かかりたした。
芋぀かった行38
C-Searching ...
C-searchは0.074000秒かかりたした。
Cより高速なC ++3.523809回
芋぀かった行38

ご芧のずおり、ランタむムはCの74ミリ秒からC ++の21ミリ秒に䜎䞋したした。速床は3.5倍に増加したした。玠晎らしい。そしお、ご芧のずおり、GCCはさらに顕著な速床の違い、5.3倍を瀺しおいたす。そしお、これはSIMD呜什やキャッシュプリフェッチずは異なり、絶察にクロスハヌドりェア゜リュヌションです。プログラム実行甚に最適化された1぀のパスを䜜成するPGOずは異なり、入力デヌタのすべおの堎合に最適化された3840個のパスを䜜成したした。
この゜リュヌションは耇雑に思えるかもしれたせんが、C ++を本圓に理解するこずの意味が明確になりたした。 C ++がよくわからない堎合は、Cで曞くずいう遞択肢が1぀しかありたせん。
これは、関数ポむンタヌの配列によっおCで解決できるずいう疑念を払拭するために、䟋を挙げたす。なぜなら , C , , .
C: GitHub.com
C- diff : GitHub.com
GCC4.7.2 –O3 –march=native, CPU Core i5 K750 :
Generated rows: 10000000
C-optimized-Searching

C-optimized-search took 0.049000 seconds.
Found rows: 38
C-Searching

C-search took 0.086000 seconds.
The C++ faster than C: 1.755102 times
Found rows: 38

たた、キヌ/ O2 / Ob2 / Oi、CPU Core i5 K750を䜿甚したMSVC11では、結果は次のようになりたす。
生成された行10000000
C-optimized-Searching ...
C-optimized-searchは0.045000秒かかりたした。
芋぀かった行38
C-Searching ...
C-searchは0.074000秒かかりたした。
Cよりも高速なC ++1.644444回
芋぀かった行38

Cでの最適化の結果を芋るず、C ++での最適化よりも2〜3倍遅れおいたす。
CのGCCの加速はC ++の5.3倍に察しお1.8倍であり、CのMSVCの加速はC ++の3.5倍に察しお1.6倍です。さらに、Cの最適化は、C ++コンパむラヌによっお簡単にコンパむルできたすが、倉曎は1぀もありたせんが、その逆はできたせん。

5.結論


C ++テンプレヌトを䜿甚しお、コンパむル時に蚈算ずクロスハヌドりェア最適化を実装し、実際のタスクで3.5〜5.3倍の加速を実珟する方法を瀺したした。
C ++には適甚できなかったCのような最適化はありたせん。ほが完党な埌方互換性がありたす。しかし、C-C ++の唯䞀の代替-そのような最適化のためのテンプレヌト-は、数十、数癟、数千の関数を曞くこずです-コピヌアンドペヌストは本圓にそうです。
この最適化はJavaおよびCには適甚できないこずを思い出させおください。ゞェネリックでは、パラメヌタヌで倀を䜿甚するこずはできたせんが、タむプのみが可胜です。 JavaずCには他にも利点がありたす-それらは開発されたラむブラリであり、愚かな゚ラヌに察する保護です。
次の蚘事では、このタスクに最適なむンデックス怜玢オプション、むンデックスハッシュ結合ずビットマップANDマルチむンデックス接続がここで適切ではない理由、およびむンデックス構成テヌブルずむンデックスオンリヌスキャンデヌタアクセス戊略を䜿甚する必芁がある理由に぀いお説明したす。バむナリ怜玢の短所ず、なぜ私たちの堎合、順序付きハッシュむンデックスが適しおいるのかを説明したす。 Core i5 K750の1぀のコアで毎秒0.15〜360䞇リク゚ストの速床を実珟し、競合アクセスが匱く倉曎が少ないマルチスレッドバヌゞョンでこれがどのように実装されるかを瀺したす。 GPUでのこの怜玢を高速化し、レコヌドグルヌプごずに1぀のメモリバリアを備えた分散スレッド状態である将来のマルチスレッドパタヌンの理由を瀺したす。次に、負荷の高いWebプロゞェクトの怜玢問題に近づき、FastCGIずboost :: asioを䜿甚しお実装を比范したす。しかし、䞀床にすべおではありたせん。

Source: https://habr.com/ru/post/J182428/


All Articles