@@ -316,6 +316,10 @@ class CompressedExternalIdTableBase {
316316 CompressedExternalIdTableWriter writer_;
317317 std::future<void > compressAndWriteFuture_;
318318
319+ // Store whether this table has previously already been iterated over (in
320+ // which case this member becomes `false`).
321+ std::atomic<bool > isFirstIteration_ = true ;
322+
319323 [[no_unique_address]] BlockTransformation blockTransformation_{};
320324
321325 public:
@@ -364,6 +368,7 @@ class CompressedExternalIdTableBase {
364368 }
365369 writer_.clear ();
366370 numBlocksPushed_ = 0 ;
371+ isFirstIteration_ = true ;
367372 }
368373
369374 protected:
@@ -401,6 +406,9 @@ class CompressedExternalIdTableBase {
401406 // until the pushing is actually finished, and return `true`. Using this
402407 // function allows for an efficient usage of this class for very small inputs.
403408 bool transformAndPushLastBlock () {
409+ if (!isFirstIteration_) {
410+ return numBlocksPushed_ != 0 ;
411+ }
404412 // If we have pushed at least one (complete) block, then the last future
405413 // from pushing a block is still in flight. If we have never pushed a block,
406414 // then also the future cannot be valid.
@@ -549,6 +557,9 @@ class CompressedExternalIdTableSorter
549557 // output phase.
550558 int numBufferedOutputBlocks_ = 4 ;
551559
560+ // See the `moveResultOnMerge()` getter function for documentation.
561+ bool moveResultOnMerge_ = true ;
562+
552563 public:
553564 // Constructor.
554565 CompressedExternalIdTableSorter (
@@ -579,6 +590,18 @@ class CompressedExternalIdTableSorter
579590 // within this class.
580591 using Base::push;
581592
593+ // If set to `false` then the sorted result can be extracted multiple times.
594+ // If set to `true` then the result is moved out and unusable after the first
595+ // merge. In that case an exception will be thrown at the start of the second
596+ // merge.
597+ // Note: This mechanism gives a performance advantage for very small inputs
598+ // that can be completely sorted in RAM. In that case we can avoid a copy of
599+ // the sorted result.
600+ bool & moveResultOnMerge () {
601+ AD_CONTRACT_CHECK (this ->isFirstIteration_ );
602+ return moveResultOnMerge_;
603+ }
604+
582605 // Transition from the input phase, where `push()` can be called, to the
583606 // output phase and return a generator that yields the sorted elements one by
584607 // one. Either this function or the following function must be called exactly
@@ -594,6 +617,8 @@ class CompressedExternalIdTableSorter
594617 requires (N == NumStaticCols || N == 0 )
595618 cppcoro::generator<IdTableStatic<N>> getSortedBlocks (
596619 std::optional<size_t > blocksize = std::nullopt ) {
620+ // If we move the result out, there must only be a single merge phase.
621+ AD_CONTRACT_CHECK (this ->isFirstIteration_ || !this ->moveResultOnMerge_ );
597622 mergeIsActive_.store (true );
598623 // Explanation for the second argument: One block is buffered by this
599624 // generator, one block is buffered inside the `sortedBlocks` generator, so
@@ -604,6 +629,7 @@ class CompressedExternalIdTableSorter
604629 std::max (1 , numBufferedOutputBlocks_ - 2 ))) {
605630 co_yield block;
606631 }
632+ this ->isFirstIteration_ = false ;
607633 mergeIsActive_.store (false );
608634 }
609635
@@ -637,8 +663,15 @@ class CompressedExternalIdTableSorter
637663 auto & block = this ->currentBlock_ ;
638664 const auto blocksizeOutput = blocksize.value_or (block.numRows ());
639665 if (block.numRows () <= blocksizeOutput) {
640- co_yield std::move (this ->currentBlock_ ).template toStatic <N>();
666+ if (this ->moveResultOnMerge_ ) {
667+ co_yield std::move (this ->currentBlock_ ).template toStatic <N>();
668+ } else {
669+ auto blockAsStatic = IdTableStatic<N>(
670+ this ->currentBlock_ .clone ().template toStatic <N>());
671+ co_yield blockAsStatic;
672+ }
641673 } else {
674+ // TODO<C++23> Use `std::views::chunk`.
642675 for (size_t i = 0 ; i < block.numRows (); i += blocksizeOutput) {
643676 size_t upper = std::min (i + blocksizeOutput, block.numRows ());
644677 auto curBlock = IdTableStatic<NumStaticCols>(
0 commit comments