Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/commands/cmd_bit.cc
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ class CommandBitOp : public Commander {
op_flag_ = kBitOpXor;
else if (opname == "not")
op_flag_ = kBitOpNot;
else if (opname == "diff")
op_flag_ = kBitOpDiff;
else if (opname == "diff1")
op_flag_ = kBitOpDiff1;
else if (opname == "andor")
op_flag_ = kBitOpAndOr;
else if (opname == "one")
op_flag_ = kBitOpOne;
else
return {Status::RedisInvalidCmd, errInvalidSyntax};
if (op_flag_ == kBitOpNot && args.size() != 4) {
Expand Down
123 changes: 120 additions & 3 deletions src/types/redis_bitmap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,9 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
for (uint64_t i = 0; i < frag_numkeys; i++) {
lp[i] = reinterpret_cast<const uint64_t *>(fragments[i].data());
}
memcpy(frag_res.get(), fragments[0].data(), frag_minlen);
if (op_flag != kBitOpDiff && op_flag != kBitOpDiff1 && op_flag != kBitOpAndOr) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented this based on the Redis solution: redis/redis#13898
However, the results are different. I've been looking for the cause but haven't found it yet. I'm not sure if I've overlooked something.

memcpy(frag_res.get(), fragments[0].data(), frag_minlen);
}
auto apply_fast_path_op = [&](auto op) {
// Note: kBitOpNot cannot use this op, it only applying
// to kBitOpAnd, kBitOpOr, kBitOpXor.
Expand Down Expand Up @@ -589,14 +591,106 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
j += sizeof(uint64_t) * 4;
frag_minlen -= sizeof(uint64_t) * 4;
}
} else if (op_flag == kBitOpDiff || op_flag == kBitOpDiff1 || op_flag == kBitOpAndOr) {
size_t processed = 0;
size_t k = 0;

while (frag_minlen >= sizeof(uint64_t) * 4) {
for (uint64_t i = 1; i < frag_numkeys; i++) {
lres[0] |= lp[i][k + 0];
lres[1] |= lp[i][k + 1];
lres[2] |= lp[i][k + 2];
lres[3] |= lp[i][k + 3];
}
// For Diff1, we need the OR of ALL keys (including the first)
if (op_flag == kBitOpDiff1) {
lres[0] |= lp[0][k + 0];
lres[1] |= lp[0][k + 1];
lres[2] |= lp[0][k + 2];
lres[3] |= lp[0][k + 3];
}
k += 4;
lres += 4;
j += sizeof(uint64_t) * 4;
frag_minlen -= sizeof(uint64_t) * 4;
processed += sizeof(uint64_t) * 4;
}

lres = reinterpret_cast<uint64_t *>(frag_res.get());
auto *first_key = reinterpret_cast<const uint64_t *>(fragments[0].data());
switch (op_flag) {
case kBitOpDiff:
for (uint64_t i = 0; i < processed; i += sizeof(uint64_t) * 4) {
lres[0] = (first_key[0] & ~lres[0]);
lres[1] = (first_key[1] & ~lres[1]);
lres[2] = (first_key[2] & ~lres[2]);
lres[3] = (first_key[3] & ~lres[3]);
lres += 4;
first_key += 4;
}
break;
case kBitOpDiff1:
for (uint64_t i = 0; i < processed; i += sizeof(uint64_t) * 4) {
lres[0] = (~first_key[0] & lres[0]);
lres[1] = (~first_key[1] & lres[1]);
lres[2] = (~first_key[2] & lres[2]);
lres[3] = (~first_key[3] & lres[3]);
lres += 4;
first_key += 4;
}
break;
case kBitOpAndOr:
for (uint64_t i = 0; i < processed; i += sizeof(uint64_t) * 4) {
lres[0] = (first_key[0] & lres[0]);
lres[1] = (first_key[1] & lres[1]);
lres[2] = (first_key[2] & lres[2]);
lres[3] = (first_key[3] & lres[3]);
lres += 4;
first_key += 4;
}
break;
}
} else if (op_flag == kBitOpOne) {
uint64_t lcommon_bits[4];
size_t k = 0;

while (frag_minlen >= sizeof(uint64_t) * 4) {
memset(lcommon_bits, 0, sizeof(lcommon_bits));

for (size_t i = 1; i < frag_numkeys; i++) {
lcommon_bits[0] |= (lres[0] & lp[i][k + 0]);
lcommon_bits[1] |= (lres[1] & lp[i][k + 1]);
lcommon_bits[2] |= (lres[2] & lp[i][k + 2]);
lcommon_bits[3] |= (lres[3] & lp[i][k + 3]);

lres[0] ^= lp[i][k + 0];
lres[1] ^= lp[i][k + 1];
lres[2] ^= lp[i][k + 2];
lres[3] ^= lp[i][k + 3];
}

lres[0] &= ~lcommon_bits[0];
lres[1] &= ~lcommon_bits[1];
lres[2] &= ~lcommon_bits[2];
lres[3] &= ~lcommon_bits[3];

k += 4;
lres += 4;
j += sizeof(uint64_t) * 4;
frag_minlen -= sizeof(uint64_t) * 4;
}
}
}
#endif

uint8_t output = 0, byte = 0;
uint8_t output = 0, byte = 0, disjunction = 0, common_bits = 0;
for (; j < frag_maxlen; j++) {
output = (fragments[0].size() <= j) ? 0 : fragments[0][j];
if (op_flag == kBitOpNot) output = ~output;
// For Diff1, disjunction starts with the first key's value
if (op_flag == kBitOpDiff1) {
disjunction = output;
}
for (uint64_t i = 1; i < frag_numkeys; i++) {
byte = (fragments[i].size() <= j) ? 0 : fragments[i][j];
switch (op_flag) {
Expand All @@ -609,11 +703,34 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
case kBitOpXor:
output ^= byte;
break;
case kBitOpDiff:
case kBitOpDiff1:
case kBitOpAndOr:
disjunction |= byte;
break;
case kBitOpOne:
common_bits |= (output & byte);
output ^= byte;
output &= ~common_bits;
break;
default:
break;
}
}
frag_res[j] = output;
switch (op_flag) {
case kBitOpDiff:
frag_res[j] = (output & ~disjunction);
break;
case kBitOpDiff1:
frag_res[j] = (~output & disjunction);
break;
case kBitOpAndOr:
frag_res[j] = (output & disjunction);
break;
default:
frag_res[j] = output;
break;
}
}

if (op_flag == kBitOpNot) {
Expand Down
4 changes: 4 additions & 0 deletions src/types/redis_bitmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ enum BitOpFlags {
kBitOpOr,
kBitOpXor,
kBitOpNot,
kBitOpDiff,
kBitOpDiff1,
kBitOpAndOr,
kBitOpOne,
};

namespace redis {
Expand Down
Loading
Loading