对我的c++代码进行分析后,发现pow
函数被大量使用。
我的一些pow
函数有一个整数指数和另一个非整数指数。我只对整数指数感兴趣。
为了提高性能,我正在寻找这样定义宏的方法:
#define pow(x,n) ({
double product;
if (typeid(n).name() == "i") {
for(int i = 0; i < n-1; i++)
product *= x;}
else
product = pow(x,n);
product;
})
但是我没有得到预期的关于运行时的增益。我认为这是由于else
部分在我的宏,我调用经典的pow
函数。
如何在"编写"宏之前提前确定指数的类型?在预处理过程中?
理想情况下,我希望这个宏只应用于指数是整数的情况,但似乎我的尝试是不相关的。
根据你的建议,我尝试了三个选项:
第一个选项:只添加重载内联函数与基础是integer
或double
:
// First option
inline int pow(int x, int n){
// using simple mechanism for repeated multiplication
int product = 1;
for(int i = 0; i < n; i++){
product *= x;
}
return product;
}
inline int pow(double x, int n){
// using simple mechanism for repeated multiplication
double product = 1;
for(int i = 0; i < n; i++){
product *= x;
}
return product;
}
结果:运行时间= 1分08秒
第二个选项:定义一个宏,如果指数n
不是整数,则通过内联my_pow
函数调用:
// Second option
inline double my_pow(double x, double n){
return pow(x,n);
}
#define pow(x,n) ({
double product = 1;
if (typeid(n) == typeid(int)) {
for(int i = 0; i < n; i++)
product *= x;}
else product = my_pow(x,n);
product;
})
Result: runtime = 51.86 sec
第三个选项:以template<typename type_t>
作为答案的建议
template<typename type_t>
inline double pow(const double x, type_t n)
{
// This is compile time checking of types.
// Don't use the RTTI thing you are trying to do
//if constexpr (is_floating_point_v<type_t>)
if (typeid(n) != typeid(int))
{
return pow(x, n);
}
else
{
double value = 1;
for (type_t i = 0; i < n; ++i) value *= x;
return value;
}
}
Result: runtime = 52.84 sec
因此,最后,从这些第一个测试中,最好的选择是第二个,我使用宏与调用pow
函数的一般情况的函数相结合(整数和浮点指数)。
是否有更有效的解决方案,或者第二个选择是最好的?
如果你只需要在浮点类型之间切换,你可以使用模板来代替宏。
#include <cassert>
#include <cmath>
#include <type_traits>
namespace my_math
{
template<typename type_t>
inline double pow(const double x, type_t n)
{
// this is compile time checking of types
// don't use the rtti thing you are trying to do
if constexpr (std::is_floating_point_v<type_t>)
{
return std::pow(x, n);
}
else
{
double value = 1;
for (type_t i = 0; i < n; ++i) value *= x;
return value;
}
};
}
int main()
{
assert(my_math::pow(2, 0) == 1);
assert(my_math::pow(2.0, 1) == 2.0);
assert(my_math::pow(3.0, 2.0) == 9.0);
assert(my_math::pow(4.0f, 3.0f) == 64.0f);
return 0;
}
一旦你确定你的pow()
被使用,这里有一个建议,使你的pow()
功能更好。
这个想法可能很难实现,但如果你经常使用高功率,这可能是值得的,让我给你看一个例子:
你想计算pow(a,17)
.
使用你的系统,你需要16次乘法。
现在让我们把17变成二进制,你得到10001,这意味着,经过一些计算,你可以把pow(a,17)
写成:
square(square(square(square(a))))*a, where square(a) = a*a
这样只剩下5次乘法,可能会提高性能。事实上,它是从O(n)
到O(log n)
(其中n
是指数)。
编辑
让我告诉你,你可以做到这一点:想象你需要计算一个数字n
的25次方。然后,首先,你需要知道你需要正方形的次数,使用简单的公式:
a = round_down(log(25) / log(2)) = 4
因此,您需要从0到4的所有正方形,并创建以下数组(sqr(n)
代表n
的正方形):
[1, n, sqr(n), sqr(sqr(n)), sqr(sqr(sqr(n))), sqr(sqr(sqr(sqr(n))))] with meanings:
0, 1, 2, 4, 8, 16
你需要最后一部分(16次幂),你剩下9,它比8大。
你需要8,你只剩下1,它比4小。你不需要4。
你不需要2。
你需要1,你只剩下0,在这里循环停止。
So: n^25 = n * sqr(sqr(sqr(n))) * sqr(sqr(sqr(sqr(n))))
(我承认,这个解释不是百分之百的,但是你明白我的意思)
与其手工调整解决方案,不如尝试使用编译器优化:-O3 -ffast-math
或-O2 -ffast-math
看看这个答案。
哪个更有效率?用幂的平方还是用自身乘以它?
和
如果您使用双精度(double), std::pow(x, n)将比手工制作的等效对象慢,除非您使用- fast-math,在这种情况下,绝对没有开销。
c++ 11性能提示:何时使用std::pow?
使用c++标准https://www.cplusplus.com/reference/ctgmath/作为宏版本。或者为pow函数使用constexpr语句,而不是宏。