2020#include " bytes_utils.h"
2121#include " exceptions.h"
2222#include < algorithm>
23+ #include < cmath>
2324#include < cstring>
2425#include < limits>
2526
@@ -81,15 +82,15 @@ void ByteBuffer::InitializeFromSpan() {
8182 // Variable-size layout stores [u32 size][element value] back-to-back.
8283 // Single pass validates shape and captures per-element prefix offsets.
8384 offsets_.clear ();
84- // TODO: consider a heuristic reserve estimate to reduce offsets_ reallocations.
85+ offsets_. reserve ( EstimateOffsetsReserveCountFromSample (elements_span_));
8586 size_t cursor = 0 ;
8687 while (cursor < elements_span_.size ()) {
87- if (elements_span_.size () - cursor < 4 ) {
88+ if (elements_span_.size () - cursor < kSizePrefixBytes ) {
8889 throw InvalidInputException (" Malformed variable-size buffer: truncated length prefix" );
8990 }
9091 offsets_.push_back (cursor);
9192 const size_t current_element_size = ReadSizeAt (elements_span_, cursor);
92- cursor += 4 ;
93+ cursor += kSizePrefixBytes ;
9394 if (elements_span_.size () - cursor < current_element_size) {
9495 throw InvalidInputException (" Malformed variable-size buffer: truncated element payload" );
9596 }
@@ -100,6 +101,46 @@ void ByteBuffer::InitializeFromSpan() {
100101 num_elements_ = offsets_.size ();
101102}
102103
104+ size_t ByteBuffer::EstimateOffsetsReserveCountFromSample (tcb::span<const uint8_t > bytes) {
105+ if (bytes.empty ())
106+ return 0 ;
107+
108+ // Sample the first 10 elements to estimate the total element count.
109+ size_t cursor = 0 ;
110+ size_t sampled_elements = 0 ;
111+ while (cursor < bytes.size () && sampled_elements < 10 ) {
112+ if (bytes.size () - cursor < kSizePrefixBytes ) {
113+ throw InvalidInputException (" Malformed variable-size buffer: truncated length prefix" );
114+ }
115+ const size_t current_element_size = ReadSizeAt (bytes, cursor);
116+ cursor += kSizePrefixBytes ;
117+ if (bytes.size () - cursor < current_element_size) {
118+ throw InvalidInputException (" Malformed variable-size buffer: truncated element payload" );
119+ }
120+ cursor += current_element_size;
121+ ++sampled_elements;
122+ }
123+
124+ if (sampled_elements == 0 || cursor == 0 )
125+ return 0 ;
126+
127+ // If sampling consumed the full buffer (<= sample window), we already know the exact count.
128+ if (cursor == bytes.size ())
129+ return sampled_elements;
130+
131+ // Estimate total element count from sample density:
132+ // (sampled_elements / sampled_bytes) * total_bytes.
133+ // - sampled_elements / sampled_bytes gives "elements per byte" in the sampled prefix,
134+ // - then multiplying by total_bytes extrapolates a full-buffer estimate.
135+ const long double estimated =
136+ (static_cast <long double >(bytes.size ()) * static_cast <long double >(sampled_elements)) /
137+ static_cast <long double >(cursor);
138+ const size_t estimated_count = static_cast <size_t >(std::ceil (estimated));
139+ const size_t estimated_with_headroom =
140+ static_cast <size_t >(std::ceil (static_cast <long double >(estimated_count) * 1 .1L ));
141+ return std::max (estimated_with_headroom, sampled_elements);
142+ }
143+
103144// -----------------------------------------------------------------------------
104145// Span reader methods
105146// -----------------------------------------------------------------------------
@@ -130,7 +171,7 @@ tcb::span<const uint8_t> ByteBuffer::GetElement(size_t position) const {
130171 throw InvalidInputException (" Element position has not been written yet" );
131172 }
132173 const size_t element_size = ReadSizeAt (elements_span_, offset);
133- return elements_span_.subspan (offset + 4 , element_size);
174+ return elements_span_.subspan (offset + kSizePrefixBytes , element_size);
134175}
135176
136177// -----------------------------------------------------------------------------
@@ -186,7 +227,7 @@ void ByteBuffer::InitializeForWriteBuffer(size_t variable_size_reserved_bytes_hi
186227 // Reserve write_buffer to at least the size of the prefix [u32 size] bytes for all elements,
187228 // and use a larger reserved-bytes hint if given as a best guess to reduce reallocations.
188229 // write_buffer is not initialized to anything since we will be appending to it during SetElement, just reserving capacity.
189- const size_t min_required_prefix_bytes = num_elements_ * static_cast < size_t >( sizeof ( uint32_t )) ;
230+ const size_t min_required_prefix_bytes = num_elements_ * kSizePrefixBytes ;
190231 const size_t variable_size_reserved_bytes = std::max (variable_size_reserved_bytes_hint, min_required_prefix_bytes);
191232 write_buffer_.clear ();
192233 write_buffer_.reserve (variable_size_reserved_bytes);
@@ -195,6 +236,9 @@ void ByteBuffer::InitializeForWriteBuffer(size_t variable_size_reserved_bytes_hi
195236 offsets_.clear ();
196237 offsets_.resize (num_elements_, kUnsetVariableElementOffset );
197238
239+ // next_expected_sequential_position_ is initialized to 0 for sequential write checking.
240+ next_expected_sequential_position_ = 0 ;
241+
198242 // elements_span_ is re-bound to the write buffer.
199243 RebindSpanToWriteBuffer ();
200244}
@@ -239,7 +283,16 @@ void ByteBuffer::SetElement(size_t position, tcb::span<const uint8_t> element) {
239283 const size_t offset = write_buffer_.size ();
240284 offsets_[position] = offset;
241285 append_u32_le (write_buffer_, static_cast <uint32_t >(element.size ()));
242- write_buffer_.insert (write_buffer_.end (), element.begin (), element.end ());
286+ write_buffer_.insert (write_buffer_.end (), element.begin (), element.end ()); // Appends at the end of the buffer.
287+
288+ // Update next_expected_sequential_position_ for sequential write checking.
289+ if (next_expected_sequential_position_ != kUnsetVariableElementOffset ) {
290+ if (position == next_expected_sequential_position_) {
291+ next_expected_sequential_position_ += 1 ;
292+ } else {
293+ next_expected_sequential_position_ = kUnsetVariableElementOffset ;
294+ }
295+ }
243296
244297 RebindSpanToWriteBuffer ();
245298}
@@ -259,49 +312,51 @@ std::vector<uint8_t> ByteBuffer::FinalizeAndTakeBuffer() {
259312 return std::move (write_buffer_);
260313 }
261314
262- // Variable-size: validate all offsets and records first, and detect whether
263- // the buffer is already sequential/unfragmented.
264- bool is_sequential = true ;
265- size_t current_offset = 0 ;
315+ // For variable-size when all elements were written exactly once and in sequential order,
316+ // we can skip out-of-order or fragmentation checks. This is the fast path.
317+ // This is the most common behavior when writing elements in single threaded mode.
318+ if (next_expected_sequential_position_ == num_elements_) {
319+ if (num_elements_ > 0 ) {
320+ const size_t last_element_offset = offsets_[num_elements_ - 1 ];
321+ const size_t last_element_size = ReadSizeAt (elements_span_, last_element_offset);
322+ const size_t logical_size = last_element_offset + kSizePrefixBytes + last_element_size;
323+ if (logical_size != write_buffer_.size ()) {
324+ throw InvalidInputException (" FinalizeAndTakeBuffer: trailing bytes detected beyond last element" );
325+ }
326+ }
327+ write_buffer_finalized_ = true ;
328+ return std::move (write_buffer_);
329+ }
330+
331+ // For variable-size, when elements are written out of order, assume the buffer is fragmented and potentially with orphaned bytes
332+ // The buffer is validated and rebuilt into a compact buffer in one pass.
333+ std::vector<uint8_t > result;
334+ result.reserve (write_buffer_.size ());
266335 for (size_t i = 0 ; i < num_elements_; ++i) {
267336 const size_t element_offset = offsets_[i];
268337 if (element_offset == kUnsetVariableElementOffset ) {
269338 throw InvalidInputException (" Cannot finalize variable-size buffer: not all elements were written" );
270339 }
271- if (element_offset > write_buffer_.size () || (write_buffer_.size () - element_offset) < 4 ) {
340+ if (element_offset > write_buffer_.size () || (write_buffer_.size () - element_offset) < kSizePrefixBytes ) {
272341 throw InvalidInputException (" Cannot finalize variable-size buffer: invalid element offset" );
273342 }
274343
275344 const size_t element_size = ReadSizeAt (elements_span_, element_offset);
276- if (element_size > (write_buffer_.size () - element_offset - 4 )) {
345+ if (element_size > (write_buffer_.size () - element_offset - kSizePrefixBytes )) {
277346 throw InvalidInputException (" Cannot finalize variable-size buffer: malformed element payload" );
278347 }
279348
280- if (element_offset != current_offset) {
281- is_sequential = false ;
282- }
283- current_offset += 4 + element_size;
284- }
285- // Detect trailing/orphan bytes in write_buffer_ and force rewrite if present.
286- if (current_offset != write_buffer_.size ()) {
287- is_sequential = false ;
349+ const size_t record_size = kSizePrefixBytes + element_size;
350+ result.insert (
351+ result.end (),
352+ write_buffer_.data () + element_offset,
353+ write_buffer_.data () + element_offset + record_size);
288354 }
289355
290- // If sequential, transfer ownership of write_buffer_ directly.
291- if (is_sequential) {
292- write_buffer_finalized_ = true ;
293- return std::move (write_buffer_);
294- }
356+ // Defrag path returns a new buffer; release the original fragmented write buffer.
357+ write_buffer_.clear ();
358+ write_buffer_.shrink_to_fit ();
295359
296- // Fragmented: rebuild a compact buffer in element order.
297- std::vector<uint8_t > result;
298- result.reserve (current_offset);
299- for (size_t i = 0 ; i < num_elements_; ++i) {
300- const size_t element_offset = offsets_[i];
301- const size_t elem_size = ReadSizeAt (elements_span_, element_offset);
302- const uint8_t * start = write_buffer_.data () + element_offset;
303- result.insert (result.end (), start, start + 4 + elem_size);
304- }
305360 write_buffer_finalized_ = true ;
306361 return result;
307362}
0 commit comments