ToB企服应用市场:ToB评测及商务社交产业平台

标题: Redis - 二进制位数组 [打印本页]

作者: 大号在练葵花宝典    时间: 2023-5-22 21:45
标题: Redis - 二进制位数组
简介

Redis 使用字符串对象来表示位数组,因为字符串对象使用的 SDS 数据结构是二进制安全的,所以程序可以直接使用 SDS 结构来保存位数组,并使用 SDS 结构的操作函数来处理位数组。
在 SDS 结构当中,buf 字节数组除了字符串结尾的 \0 空字符,其余的位置都存储着一个字节长的位数组,一个字节可以存储 8 位的二进制。
这里需要注意的是,在 buf 数组中存储的二进制位数组的顺序与实际书写的顺序相反,比如 01010101 存储在 buf 数组中的结构是 10101010 这样的倒序,使用逆序来保存位数组可以简化 SETBIT 的实现。
命令使用

Redis 提供了 GETBIT、SETBIT、BITCOUNT、BITOP、BITPOS、BITFIELD、BITFIELD_RO 等命令用于处理二进制位数组。
GETBIT

GETBIT   命令用于返回位数组 bitarray 在 offset 偏移量上的二进制位的值。其详细执行过程如下:
SETBIT

SETBIT    可以看作是 GETBIT 的反向操作,只是需要注意设置二进制位时有可能需要扩展 buf 数组的长度。
具体的执行过程如下:
由于 buf 数组使用逆序保存位数组,当 Redis 对 buf 数组进行扩展之后,写入操作都可以直接在新扩展的二进制位中完成,而不必改动位数组原来已有的二进制位。
BITCOUNT

BITCOUNT key [start] [end] 命令用于统计给定位数组中,值为 1 的二进制位的数量。
BITOP

BITOP operation destkey key [key ...] 支持对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
因为 BITOP AND、BITOP OR、BITOP XOR 三个命令可以接受多个位数组作为输入,程序需要遍历输入的每个位数组的每个字节来进行计算,所以这些命令的复杂度为 \(O(n^2)\);与此相反,因为 BITOP NOT 命令只接受一个位数组输入,所以它的复杂度为 \(O(n)\)。
BITPOS

BITPOS key bit [start [end [BYTE | BIT]]] 返回字符串中设置为 1 或 0 的第一个位的位置。
默认情况下,整个字符串都会被检索一遍。命令的
使用 start 和 end 参数默认可以指定一个字节的范围,在 7.0.0 版本之后,提供了 BYTE 和 BIT 指定以字节为范围还是位为范围。
二进制位统计算法

BITCOUNT 命令要做的工作初看上去并不复杂,但实际上要高效地实现这个命令并不容易,需要用到一些精巧的算法。
遍历算法

实现 BITCOUNT 命令最简单直接的方法,就是遍历位数组中的每个二进制位,并在遇到值为 1 的二进制位时,将计数器的值增一。
遍历算法虽然实现起来简单,但效率非常低,因为这个算法在每次循环中只能检查一个二进制位的值是否为 1,所以检查操作执行的次数将与位数组包含的二进制位的数量成正比。
查表算法

查表算法就是创建一个表,表的键为某种排列的位数组,而表的值则是相应位数组中值为 1 的二进制位的数量。
创建了这种表之后,就可以根据输入的位数组进行查表,在无须对位数组的每个位进行检查的情况下,直接知道这个位数组包含了多少个值为 1 的二进制位。
初看起来,只要创建一个足够大的表,那么统计工作就可以轻易地完成,但这个问题实际上并没有那么简单,因为查表法的实际效果会受到内存和缓存两方面因素的限制:
variable-precision SWAR 算法

BITCOUNT 命令要解决统计一个位数组中非 0 二进制位的数量的问题,在数学上被称为“计算汉明重量(Hamming Weight)”。目前已知效率最好的通用算法为 variable-precision SWAR 算法,该算法通过一系列位移和位运算操作,可以在常数时间内计算多个字节的汉明重量,并且不需要使用任何额外的内存。
以下是一个处理 32 位长度位数组的 variable-precision SWAR 算法的实现:
  1. uint32_t swar(uint32_t i){
  2.     i = (i & 0x55555555) + ((i>>1) & 0x55555555);  // 步骤 1
  3.     i = (i & 0x33333333) + ((i>>2) & 0x33333333);  // 步骤 2
  4.     i = (i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F);  // 步骤 3
  5.     i = (i - 0x01010101) >> 24;                    // 步骤 4
  6.     return i;
  7. }
复制代码
variable-precision SWAR 算法实质上是通过分而治之的思想,将计算拆解成多个小问题去解决:

因为 variable-precision SWAR 算法是一个常数复杂度的操作,所以可以按照自己的需要,在一次循环中多次执行 variable-precision SWAR 算法,从而按倍数提升计算汉明重量的效率。
当然,在一个循环里执行多个 variable-precision SWAR 算法调用这种优化方式是有极限的:一旦循环中处理的位数组的大小超过了缓存的大小,这种优化的效果就会降低并最终消失。
Redis 的实现

BITCOUNT 命令的实现用到了查表和 variable-precision SWAR 两种算法:
实际上 BITCOUNT 命令实现的算法复杂度为 \(O(n)\),其中 n 为输入二进制位的数量。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4