我正在尝试使用整数运算来计算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;
}
我正在寻找的是一种提高循环性能的方法。
任何想法都将受到高度赞赏。
答案 0 :(得分:2)
虽然你在评论中说你不想要一个基于查找表的解决方案,但我仍然在这里提出一个。原因很简单:这个查找表是516个字节。如果我用Log2
编译你的-O3
,我会得到一个~740字节的函数,所以它在同一个球场。
我没有创建一个完全匹配你的解决方案。原因很简单:您的版本不尽可能精确。我使用rint(log(in/65536.0f)/log(2)*65536)
作为参考。您的版本产生2的最差差异,平均差异为1.0。该提议的版本具有1的最差差异,并且平均差异为0.2。所以这个版本更准确。
关于绩效:我检查了两个微基准测试:
解决方案非常简单(使用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);
}
我刚刚玩过这张桌子,结果还有以下结果:
0.5+
放在rint()
中0.75+
放入rint()
)答案 1 :(得分:0)
由于您的代码已经非常快,我会尝试展开循环。编写循环体16次会使代码无法读取,但会节省循环开销,表达式1 << (15 - i)
将变为常量。
答案 2 :(得分:0)
相信你的编译器:)。只需使用足够高的优化级别,编译器就会对这种微优化进行排序。
例子: gcc ARM - https://godbolt.org/g/4XdPCp
gcc - x86-64 https://godbolt.org/g/nBNmLR
gcc - AVR https://godbolt.org/g/Mq81Sg
所以几乎没有分支,没有缓存刷新和&amp;错过(或至少是最小数量) - 简单的流水线,最佳执行时间