Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/130.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 提高计算介于1和2之间的数字的log2的性能_C++_C_Logarithm_Fixed Point - Fatal编程技术网

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版本中与“正常”循环相比较的分支之外,还有很多分支?你认为这是很多吗?与“没有分支”相比,它们中的许多将不会被执行-那里有十几个分支,所以这是一个奇怪的说法。但可以肯定的是,即使与循环相比:它删除了可预测的分支,而不可预测的分支仍然存在。确定更改为几乎