在C ++ 11中删除了重载函数float pow(float base, int iexp )
,现在pow
返回double
。在我的程序中,我正在计算大量这些(单精度),我对如何做到最有效的方式感兴趣。
是否有一些具有上述签名的特殊功能(在标准库或任何其他中)?
如果没有,在任何其他操作(将其他所有操作都转换为pow
之前)将float
的结果显式地转换为double
是否更好(在单精度方面的性能方面) )或将iexp
投射到float
并使用重载函数float pow(float base, float exp)
?
编辑:为什么我需要float
而不使用double
?
主要原因是RAM - 我需要数十或数百GB,因此这种减少是巨大的优势。所以我需要从float
获取float
。现在我需要最有效的方法来实现这一目标(减少演员阵容,使用已经优化的算法等)。
答案 0 :(得分:2)
您可以使用exponentiation by squaring轻松编写自己的fpow
。
float my_fpow(float base, unsigned exp)
{
float result = 1.f;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
<小时/>
此算法提供最佳准确度,可在 | base |时使用float
类型归档&GT; 1
我们想要计算pow(a, n)
a
为基数且n
为指数。
让我们定义 b 1 = a 1 , b 2 = a 2 , b 3 = a 4 , b 4 = a 8 ,等等。
然后 a n 是所有此类 b i 的产品,其中i th bit在 n 中设置。
所以我们订购了 B = {b k1 ,b k1 ,...,b kn } 和任何 j 在 n 中设置位k j 。
以下明显的算法 A 可用于舍入误差最小化:
现在,让我们证明 B 中的元素可以从左到右乘以而不会失去准确性。它来自以下事实:
b j &gt; B'的子> 1 子> * B <子> 2 子> * ... * B <子> J-1 子> 的
因为 b j = b j-1 * b j-1 = b j-1 子> * b <子> J-2 子> * b <子> J-2 子> = ... = b <子> J-1 子> * b <子> J-2 * ... * b 1 * b 1
因为, b 1 = a 1 = a 且其模数不止一个:
b j &gt; B'的子> 1 子> * B <子> 2 子> * ... * B <子> J-1 子> 的
因此我们可以得出结论,在从左到右的乘法过程中,累加器变量小于 B 中的任何元素。
然后,表达式result *= base;
(除了第一次迭代之外,确实)会将 B 中的两个最小数字相乘,因此舍入误差最小。因此,代码使用算法 A 。
答案 1 :(得分:2)
另一个问题只能用'#34;错误的问题&#34;来诚实地回答。或者至少:&#34;你真的愿意去那里吗?&#34;。 float
理论上需要ca.模具空间减少80%(对于相同的循环次数),因此批量加工可以便宜得多。因此,GPU喜欢float
。
但是,让我们看看x86(诚然,你没有说出你所依赖的架构,所以我选择了最常见的架构)。模具空间的价格已经支付。通过使用float
进行计算,您几乎没有任何收获。实际上,您甚至可能输掉吞吐量,因为需要从float
到double
的额外扩展,以及额外的四舍五入到float
精度。换句话说,您需要支付额外费用以获得不太准确的结果。这通常是需要避免的,除非您需要与其他程序最大程度地兼容。
见Jens&#39;评论也是如此。这些选项使编译器可以忽略某些语言规则以获得更高的性能。毋庸置疑,这有时会适得其反。
在x86上有两种情况float
可能更高效:
double
,如果它们支持,它通常要慢得多。然而,你只会注意到做这么多的计算。你知道你是否做了GPGPU。使用编译器内在函数进行显式向量化也是一种选择 - 当然,您可以做出选择,但这需要进行成本效益分析。可能你的编译器能够自动矢量化一些循环,但这通常仅限于&#34;显而易见的&#34;应用程序,例如将vector<float>
中的每个数字乘以另一个float
的地方,这种情况并不是那么明显的IMO。即使您pow
这样的向量中的每个数字int
,编译器也可能不够智能,无法有效地对其进行向量化,尤其是当pow
驻留在另一个翻译单元中时,有效的链接时间代码生成。
如果您还没有准备考虑更改程序的整体结构以允许有效使用SIMD(包括GPGPU),并且您不在float
确实便宜得多的架构上,我建议你坚持使用double
,并考虑float
最好一种可能有助于节省RAM或改善缓存局部性的存储格式(当你很多他们)。即便如此,测量也是一个很好的主意。
那就是说,你可以试试ivaigult的算法(只有double
用于中间和结果),这与一个名为Egyptian multiplication的经典算法有关(和各种各样的其他名称),只是操作数相乘而不是添加。我不知道pow(double, double)
如何正常工作,但可以想象这种算法在某些情况下会更快。同样,你应该成为关于基准测试的强迫症。
答案 2 :(得分:2)
答案 3 :(得分:1)
是否有一些具有上述签名的特殊功能(在标准库或任何其他中)?
不幸的是,不是我所知道的。
但是,正如许多人已经提到基准测试是必要的,以了解是否存在任何问题。
我已经组装了一个快速基准 online 。基准代码:
#include <iostream>
#include <boost/timer/timer.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <cmath>
int main ()
{
boost::random::mt19937 gen;
boost::random::uniform_real_distribution<> dist(0, 10000000);
const size_t size = 10000000;
std::vector<float> bases(size);
std::vector<float> fexp(size);
std::vector<int> iexp(size);
std::vector<float> res(size);
for(size_t i=0; i<size; i++)
{
bases[i] = dist(gen);
iexp[i] = std::floor(dist(gen));
fexp[i] = iexp[i];
}
std::cout << "float pow(float, int):" << std::endl;
{
boost::timer::auto_cpu_timer timer;
for(size_t i=0; i<size; i++)
res[i] = std::pow(bases[i], iexp[i]);
}
std::cout << "float pow(float, float):" << std::endl;
{
boost::timer::auto_cpu_timer timer;
for(size_t i=0; i<size; i++)
res[i] = std::pow(bases[i], fexp[i]);
}
return 0;
}
基准测试结果(快速结论):
int
- c ++ 03的版本似乎要快一点。我不确定它是否在误差范围内,因为我只在线运行基准测试。pow
调用int
而使用{{1}},c + 11似乎也更有效。如果其他人能够验证其配置是否也适用,那将会很棒。
答案 4 :(得分:-1)
尝试使用powf()代替。这是C99函数,也应该在C ++ 11中可用。