C++ 提高计算介于1和2之间的数字的log2的性能
我正在尝试使用整数算术运算来计算C++ 提高计算介于1和2之间的数字的log2的性能,c++,c,logarithm,fixed-point,C++,C,Logarithm,Fixed Point,我正在尝试使用整数算术运算来计算log2(x) 输入x是一个介于1和2之间的值 因为这只会产生0,所以所有内容都预先按16缩放 换言之: 该函数采用整数值x*2^16,而不是x 函数返回log2(x)*2^16的整数值,而不是log2(x) 这是我的密码: uint64_t Log2(uint64_t x) { static uint64_t TWO = (uint64_t)2 << 16; uint64_t res = 0; for (int i=0
log2(x)
输入x
是一个介于1和2之间的值
因为这只会产生0,所以所有内容都预先按16缩放
换言之:
- 该函数采用整数值
,而不是x*2^16
x
- 函数返回
log2(x)*2^16的整数值,而不是
log2(x)
uint64_t Log2(uint64_t x)
{
static uint64_t TWO = (uint64_t)2 << 16;
uint64_t res = 0;
for (int i=0; i<16; i++)
{
x = (x * x) >> 16;
if (x >= TWO)
{
x >>= 1;
res += 1 << (15 - i);
}
}
return res;
}
uint64\u t日志2(uint64\u t x)
{
静态uint64_t TWO=(uint64_t)2 16;
如果(x>=2)
{
x>>=1;
res+=1由于您的代码已经非常快,我会尝试展开循环。编写循环体16次会使代码无法读取,但可以节省循环开销和表达式1请相信您的编译器:)。只需使用足够高的优化级别,编译器就会对此类微观优化进行排序
示例:
gcc ARM-
gcc-x86-64
gcc-AVR
因此几乎没有分支,没有缓存刷新和未命中(或者至少是最小数量)-简单的流水线优化执行时间虽然您在评论中说不需要基于查找表的解决方案,但我仍然在这里提供了一个解决方案。原因很简单:这个查找表是516字节。如果我用-O3
编译日志2
,我会得到一个约740字节的函数,所以它是在同一个范围内的
我没有创建与您的解决方案完全匹配的解决方案。原因很简单:您的版本没有尽可能精确。我使用了rint(log(in/65536.0f)/log(2)*65536)
作为参考。您的版本产生的最差差值为2,平均差值为1.0。此建议版本的最差差值为1,平均差值为0.2。因此此版本更准确
关于性能:我检查了两个微基准:
- 使用一个简单的LCG随机生成器进行输入。我的版本快29倍
- 线性使用数字0x10000->0x20000。我的版本快17倍
解决方案非常简单(使用initTable()
初始化查找表),它在表元素之间线性插值:
unsigned short table[0x102];
void initTable() {
for (int i=0; i<0x102; i++) {
int v = rint(log(i*0x100/65536.0f+1)/log(2)*65536);
if (v>0xffff) v = 0xffff;
table[i] = v;
}
}
int log2(int val) {
int idx = (val-0x10000)>>8;
int l0 = table[idx];
int l1 = table[idx+1];
return l0+(((l1-l0)*(val&0xff)+128)>>8);
}
无符号短表[0x102];
void initTable(){
对于(inti=0;i0xffff)v=0xffff;
表[i]=v;
}
}
int log2(int val){
int idx=(val-0x10000)>>8;
int l0=表[idx];
int l1=表[idx+1];
返回l0+((l1-l0)*(val&0xff)+128)>>8);
}
我刚刚玩过桌子,下面是进一步的结果:
- 您可以减小表大小,使其包含0x82个元素(260字节),但最大错误仍然为1,平均错误为0.32(在这种情况下,您需要将
0.5+
放入rint()
中)
- 您可以将表大小减小为包含0x42元素(132字节),最坏的错误为2,平均错误为0.53(在这种情况下,您需要将
0.75+
放入rint()
)
- 进一步减小表大小会显著增加最大错误
让循环向后运行?那么就不需要31-i
。您的帖子会更适合。因此,一个具有快速、完全流水线乘法的深度流水线、无序体系结构。我建议用无分支代码替换if
。我还将仔细研究如何缩短依赖项的长度链,但这可能很难使用此算法-每次迭代都取决于上一次迭代中的x
,这大大限制了OoOE引擎利用指令级并行性。@Olaf:你真的认为,当涉及到优化时,AVR、x86和GPU是完全一样的吗?功能代码i回顾关于堆栈溢出,这是一个离题的问题。这类问题属于on。除了ARM版本中与“正常”循环相比较的分支之外,还有很多分支?你认为这是很多吗?与“没有分支”相比,它们中的许多将不会被执行-那里有十几个分支,所以这是一个奇怪的说法。但可以肯定的是,即使与循环相比:它删除了可预测的分支,而不可预测的分支仍然存在。确定更改为几乎