2017-09-13 93 views
3

我正在處理性能至關重要的項目。該應用程序正在處理大量的數據。代碼是用C++編寫的,我需要做一些修改。優化C++模板執行

有給出下面的代碼(這不是我的代碼,我就簡化到最小):

void process<int PARAM1, int PARAM2>() { 
    // processing the data 
} 

void processTheData (int param1, int param2) { // wrapper 

    if (param1 == 1 && param2 == 1) { // Ugly looking block of if's 
     process<1, 1>(); 
    else if(param1 == 1 && param2 == 2) { 
     process<1, 2>(); 
    else if(param1 == 1 && param2 == 3) { 
     process<1, 3>(); 
    else if(param1 == 1 && param2 == 4) { 
     process<1, 4>(); 
    else if(param1 == 2 && param2 == 1) { 
     process<2, 1>(); 
    else if(param1 == 2 && param2 == 2) { 
     process<2, 2>(); 
    else if(param1 == 2 && param2 == 3) { 
     process<2, 3>(); 
    else if(param1 == 2 && param2 == 4) { 
     process<2, 4>(); 
    } // and so on.... 

} 

和主要功能:

int main(int argc, char *argv[]) { 

    factor1 = atoi(argv[1]); 
    factor2 = atoi(argv[2]); 

    // choose some optimal param1 and param2 
    param1 = choseTheOptimal(factor1, factor2); 
    param2 = choseTheOptimal(factor1, factor2); 

    processTheData(param1, param2); //start processing 

    return 0; 
} 

希望的代碼看起來清晰。

的功能:

  • 過程是處理數據的核心功能,
  • processTheData是處理功能的包裝。

還有就是PARAMS(參數1 參數2)取值的數量有限(比方說,約10×10)。

param1param2在執行前不知道。

所以它使用該函數的參數,而不是模板常數如果我簡單地重寫過程功能(裝置過程(INT PARAM1,INT PARAM2))則處理大約慢10倍。

由於所述PARAM1PARAM2以上的必須過程函數的恆定。

有沒有什麼聰明的方法擺脫這個醜陋的塊如果位於processTheData函數?

+0

簡而言之:沒有沒有。當然,你可以重構它,以便以其他方式醜陋。也許它可以用一些宏清理。但這是C++的基礎。有些地方需要生成所有這些代碼,以便優化,並有條件地執行它。結束。 –

+0

出於好奇,如果你使參數爲'const',它有幫助嗎? – NathanOliver

+0

不幸的是const不起作用。我在想metaprogamming解決方案,但我沒有經驗。 – nosbor

回答

8

像這樣。

#include <array> 
#include <utility> 

template<int PARAM1, int PARAM2> 
void process() { 
    // processing the data 
} 

// make a jump table to call process<X, Y> where X is known and Y varies  
template<std::size_t P1, std::size_t...P2s> 
constexpr auto make_table_over_p2(std::index_sequence<P2s...>) 
{ 
    return std::array<void (*)(), sizeof...(P2s)> 
    { 
     &process<int(P1), int(P2s)>... 
    }; 
} 

// make a table of jump tables to call process<X, Y> where X and Y both vary  
template<std::size_t...P1s, std::size_t...P2s> 
constexpr auto make_table_over_p1_p2(std::index_sequence<P1s...>, std::index_sequence<P2s...> p2s) 
{ 
    using element_type = decltype(make_table_over_p2<0>(p2s)); 
    return std::array<element_type, sizeof...(P1s)> 
    { 
     make_table_over_p2<P1s>(p2s)... 
    }; 
} 


void processTheData (int param1, int param2) { // wrapper 

    // make a 10x10 jump table 
    static const auto table = make_table_over_p1_p2(
     std::make_index_sequence<10>(), 
     std::make_index_sequence<10>() 
    ) ; 

    // todo - put some limit checks here 

    // dispatch 
    table[param1][param2](); 
} 
4

這就是我所說的matic開關。它需要一個運行時間值(在指定範圍內),並將其轉換爲編譯時間值。

namespace details 
{ 
    template<std::size_t I> 
    using index_t = std::integral_constant<std::size_t, I>; 

    template<class F> 
    using f_result = std::result_of_t< F&&(index_t<0>) >; 
    template<class F> 
    using f_ptr = f_result<F>(*)(F&& f); 
    template<class F, std::size_t I> 
    f_ptr<F> get_ptr() { 
    return [](F&& f)->f_result<F> { 
     return std::forward<F>(f)(index_t<I>{}); 
    }; 
    } 
    template<class F, std::size_t...Is> 
    auto dispatch(F&& f, std::size_t X, std::index_sequence<Is...>) { 
    static const f_ptr<F> table[]={ 
     get_ptr<F, Is>()... 
    }; 
    return table[X](std::forward<F>(f)); 
    } 
} 
template<std::size_t max, class F> 
details::f_result<F> 
dispatch(F&& f, std::size_t I) { 
    return details::dispatch(std::forward<F>(f), I, std::make_index_sequence<max>{}); 
} 

這是做的是建立一個跳轉表,將運行時數據轉換爲編譯時間常量。我使用lambda,因爲它使它更好,更通用,並將它傳遞給一個常量。一個整型常量是一個運行時無狀態對象,其類型攜帶常量。

一個例子使用:

template<std::size_t a, std::size_t b> 
void process() { 
    static_assert(sizeof(int[a+1]) + sizeof(int[b+1]) >= 0); 
} 

constexpr int max_factor_1 = 10; 
constexpr int max_factor_2 = 10; 

int main() { 
    int factor1 = 1; 
    int factor2 = 5; 

    dispatch<max_factor_1>(
     [factor2](auto factor1) { 
     dispatch<max_factor_2>(
      [factor1](auto factor2) { 
      process< decltype(factor1)::value, decltype(factor2)::value >(); 
      }, 
      factor2 
     ); 
     }, 
     factor1 
    ); 
} 

其中max_factor_1max_factor_2constexpr值或表達式。

這使用C++ 14自動lambda表達式和constexpr隱式轉換從整型常量。

Live example

+1

ow!我的眼睛受傷了:) –

+0

呃 - 我甚至不知道我是否相信OP最初斷言的瓶頸在哪裏,並且這段代碼......將它隱藏在標題的3層深處,所以我再也不需要再看它了:) –

+0

@ MichaelDorgan另一種我認爲我更喜歡的方式是'switch_upto (factor1)'返回一個lambda,而lambda反過來做魔術開關。然後,我可以得到'switch_upto (factor1,factor2)',並將該lambda傳遞給2個參數... – Yakk

0

這就是我想出來的。它使用較少的花式功能(只有enable_if,沒有可變模板或函數指針),但它也不那麼通用。將代碼粘貼到godbolt表示編譯器能夠完全將此優化爲可能在實際代碼中具有性能優勢的示例代碼。

#include <type_traits> 

template <int param1, int param2> 
void process() { 
    static_assert(sizeof(int[param1 + 1]) + sizeof(int[param2 + 1]) > 0); 
} 

template <int limit2, int param1, int param2> 
std::enable_if_t<(param2 > limit2)> pick_param2(int) { 
    static_assert("Invalid value for parameter 2"); 
} 

template <int limit2, int param1, int param2> 
std::enable_if_t<param2 <= limit2> pick_param2(int p) { 
    if (p > 0) { 
     pick_param2<limit2, param1, param2 + 1>(p - 1); 
    } else { 
     process<param1, param2>(); 
    } 
} 

template <int limit1, int limit2, int param> 
std::enable_if_t<(param > limit1)> pick_param1(int, int) { 
    static_assert("Invalid value for parameter 1"); 
} 

template <int limit1, int limit2, int param> 
std::enable_if_t<param <= limit1> pick_param1(int p1, int p2) { 
    if (p1 > 0) { 
     pick_param1<limit1, limit2, param + 1>(p1 - 1, p2); 
    } else { 
     pick_param2<limit2, param, 0>(p2); 
    } 
} 

template <int limit_param1, int limit_param2> 
void pick_params(int param1, int param2) { 
    pick_param1<limit_param1, limit_param2, 0>(param1, param2); 
} 

int main() { 
    int p1 = 3; 
    int p2 = 5; 
    pick_params<10, 10>(p1, p2); 
} 

我對分析結果感興趣。