redis之短小精干的位图(三)逻辑操作

一、简介

多个位图可以按照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 AND destkey srckey1 srckey2 srckey3 … srckeyN
  • BITOP OR destkey srckey1 srckey2 srckey3 … srckeyN
  • BITOP XOR destkey srckey1 srckey2 srckey3 … srckeyN
  • BITOP NOT destkey 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. */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值