selectivity
( selectivity
) - the percentage of matching rows of a table under one of the conditions (predicate). The lower the selectivity of the condition, the earlier it should be compared, and the sooner we understand that the line is not suitable. Those. let's get rid of comparisons for the following conditions.selectivity = c_array_size * (1 + field.end – field.begin) / cardinality;
(2^5)*5! = 32*120 = 3840
(2^5)*5! = 32*120 = 3840
objects with different implementations, but with a common base abstract ancestor.T_cash_account_row.
line 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
always have default values ​​in ascending order starting with 0 ( C ++ 03 7.2 Enumeration declarations ).std::enable_if<>
from the #include <type_traits>
library allows you to get instances of functions / classes only for certain values ​​of the template parameter. Otherwise, a substitution error occurs during instantiation, and according to the SFINAE principle (substitution-failure-is-not-an-error) , if there is a substitution error, no compilation error occurs, and an instance of the function is simply not created. More in detail in the standard ( C ++ 03 14.8.2 Template argument deduction ).auto code = row->get_field_value<T_cash_account_row::code_e>();
std::enable_if<>
, but we think ahead. In my example, you can change the type of any of the 5 fields, and the getters will compile and work fine. Suppose if we made them through the specialization of the template function, we would get errors when accessing different types, for example, such code will not compile: /* 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<>()
function call. Ideone.comoperator()(…)
): // 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
such promotion, we expand the comparisons for each field, based on the value of the last_e
enumeration and access to the fields through our sample getters.struct T_test_pred
as follows: // 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
, the template parameter T_row
, into which the row type of the table is transferred - in our case T_cash_account_row
.T_cash_account_row
, and the entire search code is generated automatically from the template classes; you just need to instantiate the template with the type of the necessary string:T_optimized_search<T_cash_account_row> optimized_search; // C++ optimized search
T_cash_account_row T_range_filters
. Having set the opportunity in T_cash_account_row
pass parameters through template getters, we predetermine a more flexible implementation by the developer of the module through template promotion, as well as a more flexible implementation of tests when using development techniques through TDD testing, which I will show in the next article.Generated rows: 10,000,000
C ++ - Searching ...
C ++ - optimized search took 0.056000 seconds.
Found rows: 38
C-Searching ...
C-search took 0.074000 seconds.
The C ++ faster than C: 1.321429 times
Found rows: 38
test_predicate
function is still identical to the previous C ++ version, test_pradicate_enum_cpp.asm . template <unsigned N> struct factorial : std::integral_constant<unsigned, N * factorial<N-1>::value> {}; template <> struct factorial<0> : std::integral_constant<unsigned, 1> {};
std::array<>
with the number of elements equal to (2^5)*5! = 3840
(2^5)*5! = 3840
.struct T_custom_filter
when instantiating it.index_order
parameter. And secondly, the searchable functor has changed in it. // 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
template returns a flag of using (or not using) a filter by the number_filter
field for a given index_pred index. // 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<>
template, based on the index_order
- the index of the order of comparing fields and the sequence number of the field field_num
, we get the field number ordered_field_number
to get its value from the getter get_field_value<static_cast<T_field_enum>(ordered_field_number)>()
.T_number_field<>
. The index of the order of the fields index_order
is formed as follows - for example, the index for 5 fields is composed of the following formula:index_order = field0 + 5*field1 + 5*4*field2 + 5*4*3*field3 + 5*4*3*2*field4;
field1
is the number of the field from 0 to 4 (out of 5 available), going first when comparing; field2 - field number from 0 to 3 (out of 4 remaining ones), coming second when comparing; ...; and field5 is the field number from 0 to 0 (of the 1 remaining one) going last, since it is always 0, then it can be omitted:index_order = field0 + 5*field1 + 5*4*field2 + 5*4*3*field3;
index_order = field0 + 5*(field1 + 4*(field2 + 3*(field3)));
field0
is the first digit with a base 5 (can take 5 values ​​0-4), in field1
is the second digit with a base 4 (it can take 4 values ​​0 -3), and so on. Thus, in (5!) Combinations, we can encode any field permutations. For a better understanding, it is advisable to familiarize yourself with the basics of the theory of probability or a search from discrete mathematics.Field number: 0 1 2 3 4 Comparison priority: 5 3 (1) 2 4 - [2] - current field number (field0) {2} Comparison priority: 5 3 (2) 4 - [2] - current field number (field1) {3} Comparison priority: 5 (3) 4 - [1] - current field number (field2) {1} Comparison priority: 5 (4) - [1] - current field number (field3) {4} Comparison priority: (5) - [0] - current field number (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().
index_order = 2 + 5*(2 + 4*(1 + 3*(1))) = 92
we 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> {}; // -------------------------------------------------------------------------
T_number_remaining_field<index_order, number_filter>::value
.number_filter=0
), i.e. T_number_remaining_field<index_order, 0>::value
immediately causes partial specialization with two parameters:template <unsigned index_order, unsigned index> struct T_number_remaining_field<index_order, 0, index>
index_order
substituted in index_order
, and 0 is substituted in index
. But it is not so!index = T_row::last_e
and in fact our instantiation is converted to T_number_remaining_field<index_order, 0, T_row::last_e>::value
. And then there is an appeal to the specialization:template <unsigned index_order, unsigned index> struct T_number_remaining_field<index_order, 0, index>
T_row::last_e=5
), we get index_order % 5
, as required. // 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 }; }; // -------------------------------------------------------------------------
T_custom_filter<>
filter template by the presence of search fields and by their order. To do this, we apply the nested promotion templates: 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<>
, and inside it we used the promotion of the field comparison order struct T_unroll_order<>
.get_index_pred()
— retrieve an index depending on the need to compare fields, and function get_index_order()
— retrieve an index depending on the required order of comparison of fields. // 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; } // -------------------------------------------------------------------------
get_index_order()
. First, I’ll clarify that struct T_cash_account_row
a function appeared in the structure of the line get_bitf_size()
— getting cardinality (it is in this form, and not through switch / case, it can act as constexpr, that is, compile-time, but MSVC11 does not yet support this): /* 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<>
filled with the field numbers, and the value is filled with the expected selectivity. Then in the cycle we find the minimum selectivity, take the corresponding field number from the key, delete the record from it std::map<>
, and use this field number to compile the index. And so in the loop for each field. We have already described how this index returned by the function is obtained get_index_order
mathematically:index_order = field1 + 5*(field2 + 4*(field3 + 3*(field4)));
3840
once, populate a static array with these objects and get the index of the necessary object with a search function optimized for the given search conditions at run-time.test_predicate
changed compared to the previous version in C ++ - two comparisons are reversed, the first of them is now the last: github.com asm .Generated rows: 10,000,000
C ++ - Searching ...
C ++ - optimized search took 0.017000 seconds.
Found rows: 38
C-Searching ...
C-search took 0.090000 seconds.
The C ++ faster than C: 5.294118 times
Found rows: 38
Generated rows: 10,000,000
C ++ - Searching ...
C ++ - optimized search took 0.021000 seconds.
Found rows: 38
C-Searching ...
C-search took 0.074000 seconds.
The C ++ is faster than C: 3.523809 times
Found rows: 38
Generated rows: 10,000,000
C-optimized-Searching ...
C-optimized-search took 0.049000 seconds.
Found rows: 38
C-Searching ...
C-search took 0.086000 seconds.
The C ++ is faster than C: 1.755102 times
Found rows: 38
Generated rows: 10,000,000
C-optimized-Searching ...
C-optimized-search took 0.045000 seconds.
Found rows: 38
C-Searching ...
C-search took 0.074000 seconds.
The C ++ is faster than C: 1.644444 times
Found rows: 38
Source: https://habr.com/ru/post/182428/
All Articles