redis (bitcount) 的汉明算法逻辑

redis 的 bitcount 可以用来统计一个二进制数组中 1 的总数量。

实现这个命令,最简单的做法是对数组中每个字节都进行位移操作,统计每个位上 1 的数量。这个操作对内内存的需求很小,不过要频繁的位操作,比方数组有 100 万个字节,要执行 800 万次位操作。

进一步的算法是拿空间换时间,一个字节有 255 个值,每个值的 1 的位数是固定的,所以可以通过枚举这些值获取结果;这时候需要使用一个额外的数组保存这些枚举值;为了加快速度,可以将单字节扩展为双字节,甚至更多;不过这是有代价的,因为字节越多,需要的额外空间越大,甚至内存扛不住;即使扛住了,太大的数组也不利于缓存的使用。

汉明算法是专门处理这种需求的一个算法,它的逻辑很简单,就是分组处理,一次性处理多个位而不是 1 位位的处理。这个算法核心的逻辑如下:

int swar(uint32_t val){
    val = (val & 0x55555555) + ((val >> 1 ) & 0x55555555);
    val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
    val = (val & 0x0f0f0f0f) + ((val >> 4) & 0x0f0f0f0f);
    val = (val * 0x01010101) >> 24;
    return (int)val;
}

整个过程可以分为四步。

第一步

将传入的数字每两位为一组进行分割,先统计每组中低位的 1 的数量;再统计高位中 1 的数量;最后将两者相加,结果保存这两位中,表示这两位 1 的总数;执行最后一步的加法时,每组都不会有进位问题,因为两位的 1 最多为 2,而两位最大能表示的为 3,所以不会有溢出。
在这里插入图片描述

第二步

第二步类似于第一步,只是这次分组是按照 4 位来的,这 4 位总的 1 的数量保存在这 4 位上;而且由于 4 位最多 4 个 1,更不会有溢出问题,可以明确知道每组中的最高位一定为 0,即每组的格式都是 0—;

第三步

同样类似于第一步,只是这次按照 8 位分组,由于 最多 8 个 1,因此每个字节的格式都变成了0000----;

第四步

第四步用来汇总最终结果,它的过程是把每个字节的值进行相加,并写入到最高字节上,然后右移 24 位就得到结果了。右移这一步比较好理解,主要的问题在于 i*0x01010101.
在这里插入图片描述

上面是这个计算过程, 其实就是小学时候学习的乘法计算;虽然是二进制计算,但依然遵循完全一样的对齐逻辑,所以从数学上说,四个字节会被对齐做一个加法,由于每个字节最高 4 位都是 0,因此不会发生溢出,而低字节的那部分就更不会发生进位了,所以计算的结果中,最高字节正好存储了 1 的总个数,再右移就正好可以读取出来。不得不说这个方法挺神奇的。

一个普通移位操作和 swar 操作的时间对比

#include<stdio.h>
#include<stdint.h>
#include<time.h>

int swar(uint32_t val){
    val = (val & 0x55555555) + ((val >> 1 ) & 0x55555555);
    val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
    val = (val & 0x0f0f0f0f) + ((val >> 4) & 0x0f0f0f0f);
    val = (val * 0x01010101) >> 24;
    return (int)val;
}

int getbicount(uint32_t val){
    int res=0;
    uint32_t mask = 1;
    uint32_t temp;
    for(int i=1; i < 32; i++){
        temp = val & mask;
        if(temp != 0){
            res++;
        }
        mask <<= 1;
    }
    return res;
}

int main(){
    clock_t start;
    start = clock();
    for(uint32_t i=0; i < 1000000000; i++){
        swar(i);
    }
    printf("swar time is %ld\n",clock() - start);
    
    start=clock();
    for(uint32_t i=0; i < 1000000000; i++){
        getbicount(i);
    }
    printf("get count time is %ld\n",clock() - start);
    return 0;
}

因为我对 swar 算法的性能表示怀疑,毕竟它里边还有乘法计算,所以我做了一个对比,代码如上,测试1000000000 个数字。结果如下,能看出来,swar 性能上要不普通的移位算法优秀得多,在这么大数据加成的情况下,性能差距达两个数量级。

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值