Skip to content

Conversation

@travisdowns
Copy link
Member

@travisdowns travisdowns commented Jan 29, 2026

Summary

  • Fix bug in iobuf::operator<=>(string_view) where inverted stop_iteration logic caused incorrect results for multi-fragment iobufs
  • Simplify iobuf::operator==(string_view) to delegate to the fixed operator<=>
  • Add benchmarks for iobuf vs string_view comparisons

Benchmark results

operator<=>:

Test Instructions Runtime
sv_cmp_0000 49 -> 36 (-27%) 2.28ns -> 2.16ns (-5%)
sv_cmp_0001 87 -> 64 (-26%) 3.61ns -> 3.17ns (-12%)
sv_cmp_1024 (different) 121 -> 60 (-50%) 4.98ns -> 3.44ns (-31%)
sv_cmp_1024 (same) 256 -> 214 (-16%) 10.37ns -> 9.41ns (-9%)

operator==:

Test Instructions Runtime
sv_eq_0000 38 -> 30 (-21%) 2.30ns -> 1.95ns (-15%)
sv_eq_0001 83 -> 58 (-30%) 3.61ns -> 2.96ns (-18%)
sv_eq_1024 (different) 80 -> 57 (-29%) 3.70ns -> 3.25ns (-12%)
sv_eq_1024 (same) 247 -> 209 (-15%) 10.50ns -> 9.46ns (-10%)

Test plan

  • Unit tests added for multi-fragment iobuf <=> string_view comparisons
  • Benchmarks added and show consistent improvements

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v25.3.x
  • v25.2.x
  • v25.1.x

Release Notes

  • none

The previous implementation had inverted stop_iteration logic - it
stopped when the comparison was equal and continued when not equal,
causing incorrect results when the first fragment comparison determined
the ordering.

Replaced iterator_consumer pattern with a simpler for-each loop over
fragments. Added comprehensive tests covering multi-fragment comparisons.

Benchmark results (instructions, runtime):
- sv_cmp_0000: 49 -> 36 inst (-27%), 2.28ns -> 2.16ns (-5%)
- sv_cmp_0001: 87 -> 64 inst (-26%), 3.61ns -> 3.17ns (-12%)
- sv_cmp_1024 (different): 121 -> 60 inst (-50%), 4.98ns -> 3.44ns (-31%)
- sv_cmp_1024 (same): 256 -> 214 inst (-16%), 10.37ns -> 9.41ns (-9%)

Runtime improvements are consistent with instruction count reductions.
Simplify operator== by delegating to the fixed operator<=> implementation.
This reduces code duplication and ensures consistent comparison behavior.

Benchmark results (instructions, runtime):
- sv_eq_0000: 38 -> 30 inst (-21%), 2.30ns -> 1.95ns (-15%)
- sv_eq_0001: 83 -> 58 inst (-30%), 3.61ns -> 2.96ns (-18%)
- sv_eq_1024 (different): 80 -> 57 inst (-29%), 3.70ns -> 3.25ns (-12%)
- sv_eq_1024 (same): 247 -> 209 inst (-15%), 10.50ns -> 9.46ns (-10%)

Runtime improvements are consistent with instruction count reductions.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a bug in iobuf::operator<=>(string_view) where inverted stop iteration logic caused incorrect comparison results for multi-fragment iobufs. The fix simplifies the implementation to use a clearer loop-based approach and delegates operator== to the corrected spaceship operator for consistency.

Changes:

  • Fixed operator<=> logic by replacing the iterator_consumer pattern with a simpler fragment-by-fragment loop
  • Simplified operator== to delegate to the fixed operator<=>
  • Added comprehensive unit tests for multi-fragment iobuf comparisons with string_view
  • Added benchmarks to measure performance of iobuf vs string_view comparisons

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/v/bytes/iobuf.cc Fixed operator<=> implementation and simplified operator== to use the corrected spaceship operator
src/v/bytes/tests/iobuf_tests.cc Added unit tests for multi-fragment iobuf spaceship operator comparisons with string_view
src/v/bytes/tests/iobuf_bench.cc Added benchmarks for iobuf vs string_view equality and comparison operations

@travisdowns
Copy link
Member Author

@rockwotj - not sure about backport here, I think you added the initial (maybe only) use case?

@rockwotj
Copy link
Contributor

Needs a backport to 25.3.x

rockwotj
rockwotj previously approved these changes Jan 29, 2026
Copy link
Contributor

@rockwotj rockwotj left a comment

Choose a reason for hiding this comment

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

very embarrassing there are so many bugs here, need some more fuzzing here

@travisdowns
Copy link
Member Author

need some more fuzzing here

This bug was actually turned up by fuzzing, as I had added more coverage to the fuzzer. That's in another PR.

@vbotbuildovich
Copy link
Collaborator

Retry command for Build#79846

please wait until all jobs are finished before running the slash command

/ci-repeat 1
skip-redpanda-build
skip-units
skip-rebase
tests/rptest/tests/write_caching_fi_e2e_test.py::WriteCachingFailureInjectionE2ETest.test_crash_all@{"use_transactions":false}

@vbotbuildovich
Copy link
Collaborator

vbotbuildovich commented Jan 29, 2026

CI test results

test results on build#79846
test_class test_method test_arguments test_kind job_url test_status passed reason test_history
WriteCachingFailureInjectionE2ETest test_crash_all {"use_transactions": false} integration https://buildkite.com/redpanda/redpanda/builds/79846#019c0ae7-aad5-4f7c-8e7f-5960189b3c0e FLAKY 22/31 Test FAILS after retries.Significant increase in flaky rate(baseline=0.0841, p0=0.0027, reject_threshold=0.0100) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=WriteCachingFailureInjectionE2ETest&test_method=test_crash_all
TxAtomicProduceConsumeTest test_basic_tx_consumer_transform_produce {"with_failures": true} integration https://buildkite.com/redpanda/redpanda/builds/79846#019c0ae7-aad1-4c77-a4db-4727e3242904 FLAKY 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0050, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=TxAtomicProduceConsumeTest&test_method=test_basic_tx_consumer_transform_produce
src/v/storage/tests/storage_e2e_single_thread src/v/storage/tests/storage_e2e_single_thread unit https://buildkite.com/redpanda/redpanda/builds/79846#019c0a90-b168-4a99-8811-97a99b4ba3ef FAIL 0/1
test results on build#79865
test_class test_method test_arguments test_kind job_url test_status passed reason test_history
coco_fixture test_decommission unit https://buildkite.com/redpanda/redpanda/builds/79865#019c0b5c-d21b-494b-9e68-0b267ae121fa FAIL 0/1

@travisdowns
Copy link
Member Author

/ci-repeat 1
skip-redpanda-build
skip-units
skip-rebase
tests/rptest/tests/write_caching_fi_e2e_test.py::WriteCachingFailureInjectionE2ETest.test_crash_all@{"use_transactions":false}

buf2.append_fragments(iobuf::from("a"));
buf2.append_fragments(iobuf::from("b"));
buf2.append_fragments(iobuf::from("c"));
// buf2 = "abc"
Copy link
Member

Choose a reason for hiding this comment

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

remove or reword what it's trying to say? Should it be ==?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well I guess it's like "this is the equivalent assignment" rather than an assertion, but regardless removed.

return !are_equal ? ss::stop_iteration::yes : ss::stop_iteration::no;
});
return are_equal;
return _size == o.size() && (*this <=> o) == std::strong_ordering::equal;
Copy link
Member

Choose a reason for hiding this comment

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

Other method calls size_bytes(). Shoudl this too?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, better to use the method, fixed.

Add sv_eq_* and sv_cmp_* benchmark cases to measure performance of
iobuf::operator== and operator<=> against string_view. Uses a
templated cmp_bench function with rhs_type parameter to support
both iobuf-vs-iobuf and iobuf-vs-string_view comparisons.

Tests both single-fragment and fragmented iobufs at sizes 0, 1, 1024.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants