2017-09-13 76 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); 
} 

我对分析结果感兴趣。