• popcnt使用硬件指令和查表法


    popcnt是“population count”的缩写,该操作一般翻译为“位1计数”,即统计有多少个“为1的位”。例如,十六进制数“FF”,它有8个为1的位,即“popcnt(0xFF) = 8”。popcnt主要应用在密码学与通信安全,例如计算汉明重量(Hamming weight)。

    x86体系最初是没有硬件popcnt指令的,只能靠软件计算。2008年底,Intel发布了Nehalem架构的处理器,增加了SSE4.2指令集,其中就有硬件popcnt指令。虽然它名义上是属于SSE4.2指令集,但它并不使用XMM寄存器(SSE的128位寄存器),而是使用GPR寄存器(General-Purpose Registers,通用寄存器)。甚至它的CPUID标志位也不是SSE4.2(CPUID.01H:ECX.SSE4_2[bit 20]),而是专门的POPCNT标志位(CPUID.01H:ECX.POPCNT[bit 23])。

    参考[C++] 测试硬件popcnt(位1计数)指令与各种软件算法,利用模板实现静态多态优化性能popcount 算法分析这两篇文章

    对比 popcount 的各种算法,高效在于能利用并行计算,去掉循环,使用减法和模运算。
    通过减1的循环算法(parse/dense)在知道数只有三五位为1(或0)的话,其实效率也不赖。
    查表法的效率挺不错的,如果没有硬件指令的支持,选用这个是可以的。
    Hacker's Delight 中的算法,在开源项目中广为引用。

    总的来说,硬件指令最快,查表其次,然后是Hacker's Delight里的hacker_popcnt实现

    gcc的5.50.6 X86 Built-in Functions中提到

    The following built-in functions are changed to generate new SSE4.2 instructions when -msse4.2 is used.

    int __builtin_popcount (unsigned int)
    Generates the popcntl machine instruction.

    int __builtin_popcountl (unsigned long)
    Generates the popcntl or popcntq machine instruction, depending on the size of unsigned long.

    int __builtin_popcountll (unsigned long long)
    Generates the popcntq machine instruction.

    但事实上在编译时加入-mpopcnt(同时会定义 __POPCNT__宏)即可让这些函数生成对应的硬件指令。另外也可以在c文件中添加一行#pragma GCC target ("popcnt")来使其生成硬件指令。当没有生成硬件指令的时候,其会调用gcc用软件实现的popcnt函数(该函数是采用了Hacker's Delight这本书里的hacker_popcnt算法)

    也就是说gcc的内置popcnt函数对应着硬件指令实现和hacker_popcnt实现。而我想优先使用硬件指令实现,其次是查表法实现。所以就有了如下代码

    static inline uint32_t popcnt32(uint32_t v)
    {
    #ifdef __POPCNT__
        return __builtin_popcount(v);
    #else
        static const uint32_t countTable[256] = {
            0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
        return (countTable[(uint8_t)v] + countTable[(uint8_t)(v >> 8)]) + (countTable[(uint8_t)(v >> 16)] + countTable[(uint8_t)(v >> 24)]);
    #endif
    }
    
    static inline uint32_t popcnt64(uint64_t v)
    {
    #ifdef __POPCNT__
        return __builtin_popcountll(v);
    #else
        return popcnt32((uint32_t)v) + popcnt32((uint32_t)(v >> 32));
    #endif
    }
    
    static inline uint32_t popcnt_array(uint32_t *x, uint32_t len)
    {
        uint32_t cnt0 = 0, cnt1 = 0, seg = len >> 2, res = len & 0x3;
        uint64_t *X = (uint64_t *)x;
    
        seg <<= 1;
        for (uint32_t i = 0; i < seg; i += 2)
        {
            cnt0 += popcnt64(X[i]);
            cnt1 += popcnt64(X[i + 1]);
        }
    
        for (uint32_t i = 1; i <= res; i++)
        {
            cnt0 += popcnt32(x[len - i]);
        }
    
        return cnt0 + cnt1;
    }
    
  • 相关阅读:
    Php开发学习---言简意赅,内含视频教程
    决策树剪枝的三种方法
    梯度弥散与梯度爆炸
    算法岗面试题积累一
    《转》从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度
    算法复习-全排列的非递归和递归实现(含重复元素)
    xgboost使用细节
    pandas Series和dataframe
    python 字符词串和字符串的转换
    (转载)自然语言处理中的Attention Model:是什么及为什么
  • 原文地址:https://www.cnblogs.com/Tifa-Best/p/13819042.html
Copyright © 2020-2023  润新知