From 36604f8b926801e5d16dc38c8a7110aa9861ed6f Mon Sep 17 00:00:00 2001 From: kevin Heifner Date: Wed, 13 May 2026 13:54:34 -0500 Subject: [PATCH] kv: drop double-scan in kv_idx_lower_bound on miss When the secondary-index seek misses inside (code, table_id) -- either the index ended or landed past our (code, table_id) pair -- the old code issued a second lower_bound((code, table_id)) just to decide whether the table is empty. All rows of (code, table_id), if any, must lie strictly before the first-miss iterator, so the table is non-empty iff prev(itr) is in (code, table_id), with itr == idx.begin() short-circuiting to "no rows before." Return value and slot allocation are bit-identical for every input; this saves a B-tree hop per end-of-range secondary scan. Pure host-side optimization, not a consensus change -- contracts cannot observe a difference beyond CPU time, which is subjective in Sysio. Covered by existing kv_api_tests (idx_lower_bound + testidxempty for the empty-table -1 path) and the kv_intrinsic_probe_tests suite via PR #308 (lbpast + lbempty probes). --- libraries/chain/apply_context.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 804168c470..c50422c97d 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -1070,8 +1070,15 @@ int32_t apply_context::kv_idx_lower_bound(name code, uint16_t table_id, auto itr = idx.lower_bound(boost::make_tuple(code, table_id, sv_sec)); if (itr == idx.end() || itr->code != code || itr->table_id != table_id) { - auto first = idx.lower_bound(boost::make_tuple(code, table_id)); - if (first != idx.end() && first->code == code && first->table_id == table_id) { + // Seek missed inside (code, table_id). All rows of the table, if any, + // lie strictly before itr in the index, so the table is non-empty iff + // prev(itr) is in (code, table_id). itr == begin() means no rows below. + bool table_has_rows = false; + if (itr != idx.begin()) { + auto prev = std::prev(itr); + table_has_rows = (prev->code == code && prev->table_id == table_id); + } + if (table_has_rows) { const uint32_t slot_index = kv_secondary_iterators.allocate(code, table_id); auto& slot = kv_secondary_iterators.get(slot_index); slot.status = kv_it_stat::iterator_end;