8000 improve subquery handling (#11779) · thedemodev/arangodb@c379db2 · GitHub
[go: up one dir, main page]

10000
Skip to content

Commit c379db2

Browse files
authored
improve subquery handling (arangodb#11779)
1 parent 7dc90cc commit c379db2

File tree

7 files changed

+95
-59
lines changed

7 files changed

+95
-59
lines changed

arangod/Aql/AqlItemBlock.cpp

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,10 @@ void AqlItemBlock::rescale(size_t nrItems, RegisterCount nrRegs) {
420420
/// @brief clears out some columns (registers), this deletes the values if
421421
/// necessary, using the reference count.
422422
void AqlItemBlock::clearRegisters(RegIdFlatSet const& toClear) {
423+
bool const checkShadowRows = hasShadowRows();
424+
423425
for (size_t i = 0; i < _nrItems; i++) {
424-
if (isShadowRow(i)) {
426+
if (checkShadowRows && isShadowRow(i)) {
425427
// Do not clear shadow rows:
426428
// 1) our toClear set is only valid for data rows
427429
// 2) there will never be anything to clear for shadow rows
@@ -454,14 +456,15 @@ void AqlItemBlock::clearRegisters(RegIdFlatSet const& toClear) {
454456

455457
SharedAqlItemBlockPtr AqlItemBlock::cloneDataAndMoveShadow() {
456458
auto const numRows = size();
459+
auto const numRegs = getNrRegs();
457460

458461
std::unordered_set<AqlValue> cache;
459462
cache.reserve(_valueCount.size());
460-
SharedAqlItemBlockPtr res{aqlItemBlockManager().requestBlock(numRows, getNrRegs())};
463+
SharedAqlItemBlockPtr res{aqlItemBlockManager().requestBlock(numRows, numRegs)};
461464

462465
for (size_t row = 0; row < numRows; row++) {
463-
for (RegisterId col = 0; col < getNrRegs(); col++) {
464-
if (isShadowRow(row)) {
466+
if (isShadowRow(row)) {
467+
for (RegisterId col = 0; col < numRegs; col++) {
465468
AqlValue a = stealAndEraseValue(row, col);
466469
AqlValueGuard guard{a, true};
467470
auto [it, inserted] = cache.emplace(a);
@@ -471,7 +474,9 @@ SharedAqlItemBlockPtr AqlItemBlock::cloneDataAndMoveShadow() {
471474
// otherwise, destroy this; we used a cached value.
472475
guard.steal();
473476
}
474-
} else {
477+
}
478+
} else {
479+
for (RegisterId col = 0; col < numRegs; col++) {
475480
AqlValue a = getValue(row, col);
476481
::CopyValueOver(cache, a, row, col, res);
477482
}
@@ -883,8 +888,7 @@ void AqlItemBlock::setValue(size_t index, RegisterId varNr, AqlValue const& valu
883888
// First update the reference count, if this fails, the value is empty
884889
if (value.requiresDestruction()) {
885890
if (++_valueCount[value] == 1) {
886-
size_t mem = value.memoryUsage();
887-
increaseMemoryUsage(mem);
891+
increaseMemoryUsage(value.memoryUsage());
888892
}
889893
}
890894

@@ -931,18 +935,21 @@ void AqlItemBlock::eraseValue(size_t index, RegisterId varNr) {
931935
}
932936

933937
void AqlItemBlock::eraseAll() {
934-
for (size_t i = 0; i < numEntries(); i++) {
938+
size_t const n = numEntries();
939+
for (size_t i = 0; i < n; i++) {
935940
auto& it = _data[i];
936941
if (!it.isEmpty()) {
937942
it.erase();
938943
}
939944
}
940945

946+
size_t totalUsed = 0;
941947
for (auto const& it : _valueCount) {
942-
if (it.second > 0) {
943-
decreaseMemoryUsage(it.first.memoryUsage());
948+
if (ADB_LIKELY(it.second > 0)) {
949+
totalUsed += it.first.memoryUsage();
944950
}
945951
}
952+
decreaseMemoryUsage(totalUsed);
946953
_valueCount.clear();
947954
}
948955

@@ -954,10 +961,11 @@ void AqlItemBlock::referenceValuesFromRow(size_t currentRow,
954961
TRI_ASSERT(reg < getNrRegs());
955962
if (getValueReference(currentRow, reg).isEmpty()) {
956963
// First update the reference count, if this fails, the value is empty
957-
if (getValueReference(fromRow, reg).requiresDestruction()) {
958-
++_valueCount[getValueReference(fromRow, reg)];
964+
AqlValue const& a = getValueReference(fromRow, reg);
965+
if (a.requiresDestruction()) {
966+
++_valueCount[a];
959967
}
960-
_data[getAddress(currentRow, reg)] = getValueReference(fromRow, reg);
968+
_data[getAddress(currentRow, reg)] = a;
961969
}
962970
}
963971
// Copy over subqueryDepth
@@ -1010,7 +1018,10 @@ size_t AqlItemBlock::capacity() const noexcept { return _data.capacity(); }
10101018
bool AqlItemBlock::isShadowRow(size_t row) const {
10111019
/// This value is only filled for shadowRows.
10121020
/// And it is guaranteed to be only filled by numbers this way.
1013-
return _data[getSubqueryDepthAddress(row)].isNumber();
1021+
1022+
/// in case the format of shadowRows is ever adjusted, make sure to also
1023+
/// adjust the method AqlValue::isShadowRowDepthValue()
1024+
return _data[getSubqueryDepthAddress(row)].isShadowRowDepthValue();
10141025
}
10151026

10161027
AqlValue const& AqlItemBlock::getShadowRowDepth(size_t row) const {
@@ -1021,6 +1032,9 @@ AqlValue const& AqlItemBlock::getShadowRowDepth(size_t row) const {
10211032

10221033
void AqlItemBlock::setShadowRowDepth(size_t row, AqlValue const& other) {
10231034
TRI_ASSERT(other.isNumber());
1035+
TRI_ASSERT(other.isShadowRowDepthValue());
1036+
/// in case the format of shadowRows is ever adjusted, make sure to also
1037+
/// adjust the method AqlValue::isShadowRowDepthValue()
10241038
_data[getSubqueryDepthAddress(row)] = other;
10251039
TRI_ASSERT(isShadowRow(row));
10261040
// Might be shadowRow before, but we do not care, set is unique
@@ -1029,14 +1043,14 @@ void AqlItemBlock::setShadowRowDepth(size_t row, AqlValue const& other) {
10291043

10301044
void AqlItemBlock::makeShadowRow(size_t row) {
10311045
TRI_ASSERT(!isShadowRow(row));
1032-
_data[getSubqueryDepthAddress(row)] = AqlValue{VPackSlice::zeroSlice()};
1046+
_data[getSubqueryDepthAddress(row)] = AqlValue{AqlValueHintZero()};
10331047
TRI_ASSERT(isShadowRow(row));
10341048
_shadowRowIndexes.emplace(row);
10351049
}
10361050

10371051
void AqlItemBlock::makeDataRow(size_t row) {
10381052
TRI_ASSERT(isShadowRow(row));
1039-
_data[getSubqueryDepthAddress(row)] = AqlValue{VPackSlice::noneSlice()};
1053+
_data[getSubqueryDepthAddress(row)] = AqlValue{AqlValueHintNone()};
10401054
TRI_ASSERT(!isShadowRow(row));
10411055
_shadowRowIndexes.erase(row);
10421056
}
@@ -1067,6 +1081,7 @@ size_t AqlItemBlock::decrRefCount() const noexcept {
10671081
RegisterCount AqlItemBlock::internalNrRegs() const noexcept {
10681082
return _nrRegs + 1;
10691083
}
1084+
10701085
size_t AqlItemBlock::getAddress(size_t index, RegisterId varNr) const noexcept {
10711086
TRI_ASSERT(index < _nrItems);
10721087
TRI_ASSERT(varNr < _nrRegs);

arangod/Aql/AqlItemBlockInputRange.cpp

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,23 @@ std::pair<ExecutorState, InputAqlItemRow> AqlItemBlockInputRange::peekDataRow()
6868
}
6969

7070
std::pair<ExecutorState, InputAqlItemRow> AqlItemBlockInputRange::nextDataRow() {
71-
auto res = peekDataRow();
72-
if (res.second) {
73-
++_rowIndex;
71+
// this is an optimized version that intentionally does not call peekDataRow()
72+
// in order to save a few if conditions
73+
if (hasDataRow()) {
74+
TRI_ASSERT(_block != nullptr);
75+
auto state = nextState<LookAhead::NEXT, RowType::DATA>();
76+
return std::make_pair(state, InputAqlItemRow{_block, _rowIndex++});
7477
}
75-
return res;
78+
return std::make_pair(nextState<LookAhead::NOW, RowType::DATA>(),
79+
InputAqlItemRow{CreateInvalidInputRowHint{}});
7680
}
7781

82+
/// @brief: this is a performance-optimized version of nextDataRow() that must only
83+
/// be used if it is sure that there is another data row
7884
std::pair<ExecutorState, InputAqlItemRow> AqlItemBlockInputRange::nextDataRow(HasDataRow /*tag unused*/) {
7985
TRI_ASSERT(_block != nullptr);
8086
TRI_ASSERT(hasDataRow());
81-
// must calculate nextState() before the increase _rowIndex here.
87+
// must calculate nextState() before the increase of _rowIndex here.
8288
auto state = nextState<LookAhead::NEXT, RowType::DATA>();
8389
return std::make_pair(state, InputAqlItemRow{_block, _rowIndex++});
8490
}
@@ -116,19 +122,15 @@ std::pair<ExecutorState, ShadowAqlItemRow> AqlItemBlockInputRange::nextShadowRow
116122
ShadowAqlItemRow row{_block, _rowIndex};
117123
// Advance the current row.
118124
_rowIndex++;
119-
return std::make_pair(nextState<LookAhead::NOW, RowType::SHADOW>(), row);
125+
return std::make_pair(nextState<LookAhead::NOW, RowType::SHADOW>(), std::move(row));
120126
}
121127
return std::make_pair(nextState<LookAhead::NOW, RowType::SHADOW>(),
122128
ShadowAqlItemRow{CreateInvalidShadowRowHint{}});
123129
}
124130

125131
size_t AqlItemBlockInputRange::skipAllRemainingDataRows() {
126-
ExecutorState state;
127-
InputAqlItemRow row{CreateInvalidInputRowHint{}};
128-
129132
while (hasDataRow()) {
130-
std::tie(state, row) = nextDataRow();
131-
TRI_ASSERT(row.isInitialized());
133+
++_rowIndex;
132134
}
133135
return 0;
134136
}
@@ -144,11 +146,9 @@ ExecutorState AqlItemBlockInputRange::nextState() const noexcept {
144146
return _finalState;
145147
}
146148

147-
bool isShadowRow = isShadowRowAtIndex(testRowIndex);
148-
149149
if constexpr (RowType::DATA == type) {
150150
// We Return HASMORE, if the next row is a data row
151-
if (!isShadowRow) {
151+
if (!isShadowRowAtIndex(testRowIndex)) {
152152
return ExecutorState::HASMORE;
153153
}
154154
return ExecutorState::DONE;

arangod/Aql/AqlItemMatrix.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,11 @@ size_t AqlItemMatrix::numberOfBlocks() const noexcept { return _blocks.size(); }
3939

4040
std::pair<SharedAqlItemBlockPtr, size_t> AqlItemMatrix::getBlock(size_t index) const noexcept {
4141
TRI_ASSERT(index < numberOfBlocks());
42-
if (index == 0) {
43-
// The first block could contain a shadowRow
44-
// and the first unused data row, could be after the
45-
// shadowRow.
46-
return {_blocks[index], _startIndexInFirstBlock};
47-
}
42+
// The first block could contain a shadowRow
43+
// and the first unused data row, could be after the
44+
// shadowRow.
4845
// All other blocks start with the first row as data row
49-
return {_blocks[index], 0};
46+
return {_blocks[index], index == 0 ? _startIndexInFirstBlock : 0};
5047
}
5148

5249
InputAqlItemRow AqlItemMatrix::getRow(AqlItemMatrix::RowIndex index) const noexcept {

arangod/Aql/AqlValue.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ uint64_t AqlValue::hash(uint64_t seed) const {
120120
return 0;
121121
}
122122

123+
/// @brief whether or not the value is a shadow row depth entry
124+
bool AqlValue::isShadowRowDepthValue() const noexcept {
125+
/// this is a performance-optimized version of the check
126+
/// isUInt() || isSmallInt()
127+
/// VelocyPack UInts are in the range 0x28 - 0x2f, and
128+
/// VelocyPack SmallInts are in the range 0x30 - 0x39
129+
return _data.internal[0] >= 0x28 && _data.internal[0] <= 0x39 && ADB_LIKELY(type() == VPACK_INLINE);
130+
}
131+
123132
/// @brief whether or not the value contains a none value
124133
bool AqlValue::isNone() const noexcept {
125134
switch (type()) {
@@ -1398,6 +1407,11 @@ AqlValue::AqlValue(uint8_t const* pointer) {
13981407
TRI_ASSERT(!VPackSlice(_data.pointer).isExternal());
13991408
}
14001409

1410+
AqlValue::AqlValue(AqlValueHintNone const&) noexcept {
1411+
_data.internal[0] = 0x00; // none in VPack
1412+
setType(AqlValueType::VPACK_INLINE);
1413+
}
1414+
14011415
AqlValue::AqlValue(AqlValueHintNull const&) noexcept {
14021416
_data.internal[0] = 0x18; // null in VPack
14031417
setType(AqlValueType::VPACK_INLINE);

arangod/Aql/AqlValue.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ struct AqlValueHintDocumentNoCopy {
7878
uint8_t const* ptr;
7979
};
8080

81+
struct AqlValueHintNone {
82+
constexpr AqlValueHintNone() noexcept = default;
83+
};
84+
8185
struct AqlValueHintNull {
8286
constexpr AqlValueHintNull() noexcept = default;
8387
};
@@ -172,6 +176,8 @@ struct AqlValue final {
172176

173177
// construct from docvec, taking over its ownership
174178
explicit AqlValue(std::vector<arangodb::aql::SharedAqlItemBlockPtr>* docvec) noexcept;
179+
180+
explicit AqlValue(AqlValueHintNone const&) noexcept;
175181

176182
explicit AqlValue(AqlValueHintNull const&) noexcept;
177183

@@ -246,9 +252,11 @@ struct AqlValue final {
246252
bool isDocvec() const noexcept;
247253

248254
/// @brief hashes the value
249-
// uint64_t hash(transaction::Methods*, uint64_t seed = 0xdeadbeef) const;
250255
uint64_t hash(uint64_t seed = 0xdeadbeef) const;
251256

257+
/// @brief whether or not the value is a shadow row depth entry
258+
bool isShadowRowDepthValue() const noexcept;
259+
252260
/// @brief whether or not the value contains a none value
253261
bool isNone() const noexcept;
254262

arangod/Aql/ExecutionBlockImpl.cpp

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -173,18 +173,23 @@ ExecutionBlockImpl<Executor>::~ExecutionBlockImpl() = default;
173173
174174
template <class Executor>
175175
std::unique_ptr<OutputAqlItemRow> ExecutionBlockImpl<Executor>::createOutputRow(
176-
SharedAqlItemBlockPtr& newBlock, AqlCall&& call) {
176+
SharedAqlItemBlockPtr&& newBlock, AqlCall&& call) {
177177
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
178178
if (newBlock != nullptr) {
179179
// Assert that the block has enough registers. This must be guaranteed by
180180
// the register planning.
181181
TRI_ASSERT(newBlock->getNrRegs() == _registerInfos.numberOfOutputRegisters());
182182
// Check that all output registers are empty.
183-
for (auto const& reg : _registerInfos.getOutputRegisters()) {
184-
for (size_t row = 0; row < newBlock->size(); row++) {
185-
if (!newBlock->isShadowRow(row)) {
186-
AqlValue const& val = newBlock->getValueReference(row, reg);
187-
TRI_ASSERT(val.isEmpty());
183+
size_t const n = newBlock->size();
184+
auto const& regs = _registerInfos.getOutputRegisters();
185+
if (!regs.empty()) {
186+
bool const hasShadowRows = newBlock->hasShadowRows();
187+
for (size_t row = 0; row < n; row++) {
188+
if (!hasShadowRows || !newBlock->isShadowRow(row)) {
189+
for (auto const& reg : regs) {
190+
AqlValue const& val = newBlock->getValueReference(row, reg);
191+
TRI_ASSERT(val.isEmpty());
192+
}
188193
}
189194
}
190195
}
@@ -199,7 +204,7 @@ std::unique_ptr<OutputAqlItemRow> ExecutionBlockImpl<Executor>::createOutputRow(
199204
}
200205
}();
201206
202-
return std::make_unique<OutputAqlItemRow>(newBlock, registerInfos().getOutputRegisters(),
207+
return std::make_unique<OutputAqlItemRow>(std::move(newBlock), registerInfos().getOutputRegisters(),
203208
registerInfos().registersToKeep(),
204209
registerInfos().registersToClear(),
205210
call, copyRowBehaviour);
@@ -346,7 +351,7 @@ auto ExecutionBlockImpl<IdExecutor<ConstFetcher>>::injectConstantBlock<IdExecuto
346351
_hasUsedDataRangeBlock = false;
347352
_upstreamState = ExecutionState::HASMORE;
348353
349-
_rowFetcher.injectBlock(block);
354+
_rowFetcher.injectBlock(std::move(block));
350355
351356
resetExecutor();
352357
}
@@ -362,8 +367,8 @@ std::pair<ExecutionState, Result> ExecutionBlockImpl<IdExecutor<ConstFetcher>>::
362367
TRI_ASSERT(_skipped.nothingSkipped());
363368
_skipped.reset();
364369
// We inject an empty copy of our skipped here,
365-
// This is resettet, but will maintain the size
366-
injectConstantBlock(block, _skipped);
370+
// This is resetted, but will maintain the size
371+
injectConstantBlock(std::move(block), _skipped);
367372
368373
// end of default initializeCursor
369374
return ExecutionBlock::initializeCursor(input);
@@ -466,34 +471,32 @@ template <class Executor>
466471
auto ExecutionBlockImpl<Executor>::allocateOutputBlock(AqlCall&& call, DataRange const& inputRange)
467472
-> std::unique_ptr<OutputAqlItemRow> {
468473
if constexpr (Executor::Properties::allowsBlockPassthrough == BlockPassthrough::Enable) {
469-
SharedAqlItemBlockPtr newBlock{nullptr};
470474
// Passthrough variant, re-use the block stored in InputRange
471475
if (!_hasUsedDataRangeBlock) {
472476
// In the pass through variant we have the contract that we work on a
473477
// block all or nothing, so if we have used the block once, we cannot use it again
474478
// however we cannot remove the _lastRange as it may contain additional information.
475-
newBlock = _lastRange.getBlock();
476479
_hasUsedDataRangeBlock = true;
480+
SharedAqlItemBlockPtr newBlock = _lastRange.getBlock();
481+
return createOutputRow(std::move(newBlock), std::move(call));
477482
}
478483
479-
return createOutputRow(newBlock, std::move(call));
484+
return createOutputRow(SharedAqlItemBlockPtr{nullptr}, std::move(call));
480485
} else {
481486
if constexpr (isMultiDepExecutor<Executor>) {
482487
// MultiDepExecutor would require dependency handling.
483488
// We do not have it here.
484489
if (!inputRange.hasShadowRow() && !inputRange.hasDataRow()) {
485490
// On empty input do not yet create output.
486491
// We are going to ask again later
487-
SharedAqlItemBlockPtr newBlock{nullptr};
488-
return createOutputRow(newBlock, std::move(call));
492+
return createOutputRow(SharedAqlItemBlockPtr{nullptr}, std::move(call));
489493
}
490494
} else {
491495
if (!inputRange.hasShadowRow() && !inputRange.hasDataRow() &&
492496
inputRange.upstreamState() == ExecutorState::HASMORE) {
493497
// On empty input do not yet create output.
494498
// We are going to ask again later
495-
SharedAqlItemBlockPtr newBlock{nullptr};
496-
return createOutputRow(newBlock, std::move(call));
499+
return createOutputRow(SharedAqlItemBlockPtr{nullptr}, std::move(call));
497500
}
498501
}
499502
@@ -532,12 +535,11 @@ auto ExecutionBlockImpl<Executor>::allocateOutputBlock(AqlCall&& call, DataRange
532535
533536
if (blockSize == 0) {
534537
// There is no data to be produced
535-
SharedAqlItemBlockPtr newBlock{nullptr};
536-
return createOutputRow(newBlock, std::move(call));
538+
return createOutputRow(SharedAqlItemBlockPtr{nullptr}, std::move(call));
537539
}
538540
SharedAqlItemBlockPtr newBlock =
539541
_engine->itemBlockManager().requestBlock(blockSize, _registerInfos.numberOfOutputRegisters());
540-
return createOutputRow(newBlock, std::move(call));
542+
return createOutputRow(std::move(newBlock), std::move(call));
541543
}
542544
}
543545

0 commit comments

Comments
 (0)
0