一、简介
多个位图可以按照bit位逐一进行逻辑操作,AND,OR,NOT,XOR操作。
当key不存在时,当做0填充的位图进行计算,而对于多个长度不一的位图进行操作时,将按照最长的作为标准,其他比它短的都将填充0补齐。将计算结果存入目标位图中。

二、BITOP
BITOP op_name target_key src_key1 src_key2 src_key3... src_keyN
根据不同的操作(op_name)对src_key1,src_key2,…,src_keyN进行计算,然后将计算结果存入target_key中,并且返回最长的字节数给客户端。
- BITOP
ANDdestkey srckey1 srckey2 srckey3 … srckeyN - BITOP
ORdestkey srckey1 srckey2 srckey3 … srckeyN - BITOP
XORdestkey srckey1 srckey2 srckey3 … srckeyN - BITOP
NOTdestkey srckey
2.1 识别操作符
void bitopCommand(client *c) {
char *opname = c->argv[1]->ptr;
...
/* Parse the operation name. */
if ((opname[0] == 'a' || opname[0] == 'A') && !strcasecmp(opname,"and"))
op = BITOP_AND;
else if((opname[0] == 'o' || opname[0] == 'O') && !strcasecmp(opname,"or"))
op = BITOP_OR;
else if((opname[0] == 'x' || opname[0] == 'X') && !strcasecmp(opname,"xor"))
op = BITOP_XOR;
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
op = BITOP_NOT;
else {
addReplyErrorObject(c,shared.syntaxerr);
return;
}
2.2 检验参数个数
此步骤只是针对非(NOT)操作,NOT的操作数只能是一个。
...
/* Sanity check: NOT accepts only a single key argument. */
if (op == BITOP_NOT && c->argc != 4) {
addReplyError(c,"BITOP NOT must be called with a single source key.");
return;
}
...
2.3 获取所有key以及对应的位图
动态分配三个数组,src数组存位图字符串地址,len存放位图字符串长度,objects存放key对应的对象;
对于key不存在的objects元素为NULL,src为NULL, len为0;
在计算获取位图对象的时候,同时也做了类型检验,只能是String类型;
并且计算出了字符串位图的最大长度和最小长度。
/* Lookup keys, and store pointers to the string objects into an array. */
numkeys = c->argc - 3;
src = zmalloc(sizeof(unsigned char*) * numkeys);
len = zmalloc(sizeof(long) * numkeys);
objects = zmalloc(sizeof(robj*) * numkeys);
for (j = 0; j < numkeys; j++) {
o = lookupKeyRead(c->db,c->argv[j+3]);
/* Handle non-existing keys as empty strings. */
if (o == NULL) {
objects[j] = NULL;
src[j] = NULL;
len[j] = 0;
minlen = 0;
continue;
}
/* Return an error if one of the keys is not a string. */
if (checkType(c,o,OBJ_STRING)) {
unsigned long i;
for (i = 0; i < j; i++) {
if (objects[i])
decrRefCount(objects[i]);
}
zfree(src);
zfree(len);
zfree(objects);
return;
}
objects[j] = getDecodedObject(o);
src[j] = objects[j]->ptr;
len[j] = sdslen(objects[j]->ptr);
if (len[j] > maxlen) maxlen = len[j];
if (j == 0 || len[j] < minlen) minlen = len[j];
}
2.4 进行具体的逻辑操作
只有maxlen大于0才进行操作,因为maxlen=0, 则说明所有的key都不存在,无法计算。
/* Compute the bit operation, if at least one string is not empty. */
if (maxlen){
...
}
2.4.1 动态分配结果存储空间
unsigned char *res = NULL; /* Resulting string. */
...
res = (unsigned char*) sdsnewlen(NULL,maxlen);
2.4.2 非ARM平台的加速计算
#if defined(__sparc) && !defined(__sparc__)
#define __sparc__
#endif
#if defined(__sparc__) || defined(__arm__)
#define USE_ALIGNED_ACCESS
#endif
在非ARM架构下,使用多字节进行快速的计算,比单个单个字节的计算效率高很多
针对公共最小长度大于等于sizeof(unsigned long)*4, 并且操作的key的个数小于等于16,才进入此逻辑进行加速
/* Fast path: as far as we have data for all the input bitmaps we
* can take a fast path that performs much better than the
* vanilla algorithm. On ARM we skip the fast path since it will
* result in GCC compiling the code using multiple-words load/store
* operations that are not supported even in ARM >= v6. */
j = 0;
#ifndef USE_ALIGNED_ACCESS
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
unsigned long *lp[16];
unsigned long *lres = (unsigned long*) res;
/* Note: sds pointer is always aligned to 8 byte boundary. */
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
memcpy(res,src[0],minlen);
/* Different branches per different operations for speed (sorry). */
if (op == BITOP_AND) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] &= lp[i][0];
lres[1] &= lp[i][1];
lres[2] &= lp[i][2];
lres[3] &= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_OR) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] |= lp[i][0];
lres[1] |= lp[i][1];
lres[2] |= lp[i][2];
lres[3] |= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_XOR) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] ^= lp[i][0];
lres[1] ^= lp[i][1];
lres[2] ^= lp[i][2];
lres[3] ^= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_NOT) {
while(minlen >= sizeof(unsigned long)*4) {
lres[0] = ~lres[0];
lres[1] = ~lres[1];
lres[2] = ~lres[2];
lres[3] = ~lres[3];
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
}
}
#endif
2.4.3 逐一计算
根据不同的操作进行计算,(或加速剩余的数据)
在内层循环也做了优化,比如对于AND操作,只要有一个0,则不用继续当前的的后续循环了,直接进行下一个字节的新一轮开始;对于OR操作,当值为0xff也是一样。这个就是短路AND和OR。
byte = (len[i] <= j) ? 0 : src[i][j];则按照最大长度补齐0的操作
/* j is set to the next byte to process by the previous loop. */
for (; j < maxlen; j++) {
output = (len[0] <= j) ? 0 : src[0][j];
if (op == BITOP_NOT) output = ~output;
for (i = 1; i < numkeys; i++) {
int skip = 0;
byte = (len[i] <= j) ? 0 : src[i][j];
switch(op) {
case BITOP_AND:
output &= byte;
skip = (output == 0);
break;
case BITOP_OR:
output |= byte;
skip = (output == 0xff);
break;
case BITOP_XOR: output ^= byte; break;
}
if (skip) {
break;
}
}
res[j] = output;
}
2.4.4 释放中间临时数组
这里对于object进行了引用计算减一,因为在获取的时候引用计数加一了,为了对象不会在操作的时候被意外删除了。
for (j = 0; j < numkeys; j++) {
if (objects[j])
decrRefCount(objects[j]);
}
zfree(src);
zfree(len);
zfree(objects);
2.4.5 将结果写入目标key
根据代码逻辑,可以看出当所有的key都不存在是,将会将目标对象删除(如果存在)。
/* Store the computed value into the target key */
if (maxlen) {
o = createObject(OBJ_STRING,res);
setKey(c,c->db,targetkey,o);
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
decrRefCount(o);
server.dirty++;
} else if (dbDelete(c->db,targetkey)) {
signalModifiedKey(c,c->db,targetkey);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
server.dirty++;
}
2.4.6 将结果响应给client
将结果的最长字节数返回给了客户端。
addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */

被折叠的 条评论
为什么被折叠?



