Skip to content

Commit dd8ecb0

Browse files
parker-felixEvergreen Agent
authored andcommitted
SERVER-82569 Add MonoBlock fast path to block based logical and/or
1 parent 464ce20 commit dd8ecb0

File tree

3 files changed

+138
-2
lines changed

3 files changed

+138
-2
lines changed

src/mongo/db/exec/sbe/expressions/sbe_block_expr_test.cpp

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ class SBEBlockExpressionTest : public EExpressionTestFixture {
7474
extracted.tags[i], extracted.vals[i], expected[i].first, expected[i].second);
7575
ASSERT_EQ(t, value::TypeTags::NumberInt32) << extracted;
7676
ASSERT_EQ(value::bitcastTo<int32_t>(v), 0)
77-
<< "Got " << extracted[i] << " expected " << expected[i] << " full extracted output"
78-
<< extracted;
77+
<< "Got " << extracted[i] << " expected " << expected[i]
78+
<< " full extracted output " << extracted;
7979
}
8080
}
8181

@@ -86,6 +86,28 @@ class SBEBlockExpressionTest : public EExpressionTestFixture {
8686
void testCmpScalar(const std::vector<std::pair<value::TypeTags, value::Value>>& testValues,
8787
EPrimBinary::Op,
8888
StringData cmpFunctionName);
89+
90+
std::pair<std::vector<bool>, std::vector<bool>> naiveLogicalAndOr(
91+
std::unique_ptr<value::ValueBlock> leftBlock,
92+
std::unique_ptr<value::ValueBlock> rightBlock) {
93+
auto left = leftBlock->extract();
94+
auto right = rightBlock->extract();
95+
ASSERT_EQ(left.count, right.count);
96+
97+
std::vector<bool> andRes;
98+
std::vector<bool> orRes;
99+
100+
for (size_t i = 0; i < left.count; ++i) {
101+
ASSERT_EQ(left.tags[i], value::TypeTags::Boolean);
102+
ASSERT_EQ(right.tags[i], value::TypeTags::Boolean);
103+
auto leftBool = value::bitcastTo<bool>(left.vals[i]);
104+
auto rightBool = value::bitcastTo<bool>(right.vals[i]);
105+
andRes.push_back(leftBool && rightBool);
106+
orRes.push_back(leftBool || rightBool);
107+
}
108+
109+
return std::pair{andRes, orRes};
110+
}
89111
};
90112

91113
TEST_F(SBEBlockExpressionTest, BlockExistsTest) {
@@ -550,8 +572,12 @@ TEST_F(SBEBlockExpressionTest, BlockApplyMaskedLambdaTest) {
550572
TEST_F(SBEBlockExpressionTest, BlockLogicAndOrTest) {
551573
value::ViewOfValueAccessor blockAccessorLeft;
552574
value::ViewOfValueAccessor blockAccessorRight;
575+
value::ViewOfValueAccessor falseMonoBlockAccessor;
576+
value::ViewOfValueAccessor trueMonoBlockAccessor;
553577
auto blockLeftSlot = bindAccessor(&blockAccessorLeft);
554578
auto blockRightSlot = bindAccessor(&blockAccessorRight);
579+
auto falseMonoBlockSlot = bindAccessor(&falseMonoBlockAccessor);
580+
auto trueMonoBlockSlot = bindAccessor(&trueMonoBlockAccessor);
555581

556582
auto leftBlock = makeBoolBlock({true, false, true, false});
557583
blockAccessorLeft.reset(sbe::value::TypeTags::valueBlock,
@@ -561,6 +587,18 @@ TEST_F(SBEBlockExpressionTest, BlockLogicAndOrTest) {
561587
blockAccessorRight.reset(sbe::value::TypeTags::valueBlock,
562588
value::bitcastFrom<value::ValueBlock*>(rightBlock.get()));
563589

590+
auto [fTag, fVal] = makeBool(false);
591+
std::unique_ptr<value::ValueBlock> falseMonoBlock =
592+
std::make_unique<value::MonoBlock>(*leftBlock->tryCount(), fTag, fVal);
593+
falseMonoBlockAccessor.reset(sbe::value::TypeTags::valueBlock,
594+
value::bitcastFrom<value::ValueBlock*>(falseMonoBlock.get()));
595+
596+
auto [tTag, tVal] = makeBool(true);
597+
std::unique_ptr<value::ValueBlock> trueMonoBlock =
598+
std::make_unique<value::MonoBlock>(*leftBlock->tryCount(), tTag, tVal);
599+
trueMonoBlockAccessor.reset(sbe::value::TypeTags::valueBlock,
600+
value::bitcastFrom<value::ValueBlock*>(trueMonoBlock.get()));
601+
564602
{
565603
auto expr = makeE<sbe::EFunction>(
566604
"valueBlockLogicalAnd",
@@ -584,6 +622,40 @@ TEST_F(SBEBlockExpressionTest, BlockLogicAndOrTest) {
584622

585623
assertBlockOfBool(runTag, runVal, {true, true, true, false});
586624
}
625+
626+
{
627+
std::vector<value::SlotId> blockSlots{blockLeftSlot, falseMonoBlockSlot, trueMonoBlockSlot};
628+
std::vector<std::unique_ptr<value::ValueBlock>> kBlocks;
629+
kBlocks.push_back(leftBlock->clone());
630+
kBlocks.push_back(falseMonoBlock->clone());
631+
kBlocks.push_back(trueMonoBlock->clone());
632+
633+
for (size_t i = 0; i < blockSlots.size(); ++i) {
634+
for (size_t j = 0; j < blockSlots.size(); ++j) {
635+
auto andExpr = makeE<sbe::EFunction>(
636+
"valueBlockLogicalAnd",
637+
sbe::makeEs(makeE<EVariable>(blockSlots[i]), makeE<EVariable>(blockSlots[j])));
638+
auto compiledAndExpr = compileExpression(*andExpr);
639+
640+
auto [andTag, andVal] = runCompiledExpression(compiledAndExpr.get());
641+
value::ValueGuard andGuard(andTag, andVal);
642+
643+
auto orExpr = makeE<sbe::EFunction>(
644+
"valueBlockLogicalOr",
645+
sbe::makeEs(makeE<EVariable>(blockSlots[i]), makeE<EVariable>(blockSlots[j])));
646+
auto compiledOrExpr = compileExpression(*orExpr);
647+
648+
auto [orTag, orVal] = runCompiledExpression(compiledOrExpr.get());
649+
value::ValueGuard orGuard(orTag, orVal);
650+
651+
auto [andNaive, orNaive] =
652+
naiveLogicalAndOr(kBlocks[i]->clone(), kBlocks[j]->clone());
653+
654+
assertBlockOfBool(andTag, andVal, andNaive);
655+
assertBlockOfBool(orTag, orVal, orNaive);
656+
}
657+
}
658+
}
587659
}
588660

589661
void SBEBlockExpressionTest::testFoldF(std::vector<bool> vals,

src/mongo/db/exec/sbe/values/block_interface.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ struct DeblockedTagValStorage {
125125
bool owned{false};
126126
};
127127

128+
/**
129+
* Struct representing a run of a single value.
130+
*/
131+
struct SingleRun {
132+
TypeTags tag;
133+
Value val;
134+
size_t count;
135+
};
136+
128137
/**
129138
* Interface for accessing a sequence of SBE Values independent of their backing storage.
130139
*
@@ -195,6 +204,14 @@ struct ValueBlock {
195204
return boost::none;
196205
}
197206

207+
/**
208+
* Returns a SingleRun if the contents are just a single run of the same value without
209+
* Nothings. boost::none if this cannot be determined in O(1) time or if it is not.
210+
*/
211+
virtual boost::optional<SingleRun> tryIsSingleRun() const {
212+
return boost::none;
213+
}
214+
198215
virtual std::unique_ptr<ValueBlock> map(const ColumnOp& op);
199216

200217
virtual TokenizedBlock tokenize();
@@ -266,6 +283,10 @@ class MonoBlock final : public ValueBlock {
266283
return _tag != TypeTags::Nothing;
267284
}
268285

286+
boost::optional<SingleRun> tryIsSingleRun() const override {
287+
return SingleRun{_tag, _val, _count};
288+
}
289+
269290
std::unique_ptr<ValueBlock> map(const ColumnOp& op) override {
270291
auto [tag, val] = op.processSingle(_tag, _val);
271292
return std::make_unique<MonoBlock>(_count, tag, val);

src/mongo/db/exec/sbe/vm/vm_block.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,27 @@ FastTuple<bool, value::TypeTags, value::Value> ByteCode::builtinValueBlockLogica
640640
invariant(rightTag == value::TypeTags::valueBlock);
641641
auto* rightValueBlock = value::bitcastTo<value::ValueBlock*>(rightVal);
642642

643+
auto leftRun = leftValueBlock->tryIsSingleRun();
644+
auto rightRun = rightValueBlock->tryIsSingleRun();
645+
646+
if (leftRun || rightRun) {
647+
if (!leftRun) {
648+
std::swap(leftRun, rightRun);
649+
swapStack();
650+
}
651+
652+
// We always assume that the inputs are blocks of bools that can provide a count in O(1).
653+
tassert(8256900,
654+
"Mismatch on size",
655+
*leftValueBlock->tryCount() == *rightValueBlock->tryCount());
656+
if (value::bitcastTo<bool>(leftRun->val)) {
657+
// and True is a noop.
658+
return moveFromStack(0);
659+
}
660+
// and False returns a block of all falses.
661+
return moveFromStack(1);
662+
}
663+
643664
auto blockOut = applyBoolBinOp<std::logical_and<>>(leftValueBlock, rightValueBlock);
644665
return {true,
645666
value::TypeTags::valueBlock,
@@ -658,6 +679,28 @@ FastTuple<bool, value::TypeTags, value::Value> ByteCode::builtinValueBlockLogica
658679
invariant(rightTag == value::TypeTags::valueBlock);
659680
auto* rightValueBlock = value::bitcastTo<value::ValueBlock*>(rightVal);
660681

682+
auto leftRun = leftValueBlock->tryIsSingleRun();
683+
auto rightRun = rightValueBlock->tryIsSingleRun();
684+
685+
if (leftRun || rightRun) {
686+
if (!leftRun) {
687+
std::swap(leftRun, rightRun);
688+
swapStack();
689+
}
690+
691+
// We always assume that the inputs are blocks of bools that can provide a count in O(1).
692+
tassert(8256901,
693+
"Mismatch on size",
694+
*leftValueBlock->tryCount() == *rightValueBlock->tryCount());
695+
696+
if (value::bitcastTo<bool>(leftRun->val)) {
697+
// or True returns a block of all trues.
698+
return moveFromStack(1);
699+
}
700+
// or False is a noop.
701+
return moveFromStack(0);
702+
}
703+
661704
auto blockOut = applyBoolBinOp<std::logical_or<>>(leftValueBlock, rightValueBlock);
662705
return {true,
663706
value::TypeTags::valueBlock,

0 commit comments

Comments
 (0)