azecevic000 opened a new pull request, #12141:
URL: https://github.com/apache/arrow-rs/pull/12141

   # Which issue does this PR close?
   
   N/A
   
   # Rationale for this change
   
   Buffers without an allocation have a dangling pointer in the unmapped first 
page. On x86-64 CPUs with AVX512 and glibc >=2.35 , the `memcmp` and `bcmp` of 
length 0 that Rust uses to compare empty slices take up to about 100 ns at such 
an address, instead of 2 ns. So each compare of values of an all-empty string 
or binary array is slow. The CPU walks the page tables on every call, because 
the TLB does not cache unmapped pages. Then it cancels the page fault, because 
the mask covers it.
   
   # What changes are included in this PR?
   
   All buffers without an allocation now point at `ZERO_SIZE_AREA`, a static 
aligned to 256 bytes (at least `ALIGNMENT` on all targets), as Arrow C++ does 
with `zero_size_area`:
   
   - `dangling_ptr()` returns its address. The `cfg(miri)` variant is no longer 
necessary, because the address now comes from a real static.
   - `From<Vec<T>> for MutableBuffer` (so also `Buffer::from_vec`) uses it for 
a `Vec` without an allocation.
   - `MutableBuffer` uses it after it shrinks to size 0. Before, it kept the 
pointer to the freed memory.
   
   Empty buffers already shared addresses before (`0x80`, or `0x1` for 
`Vec<u8>`), so code cannot rely on a unique address for them.
   
   # Are these changes tested?
   
   `test_buffers_without_allocation_point_to_zero_size_area` checks the three 
places. The existing tests pass.
   
   Intel Xeon Platinum 8375C (AVX512VL and AVX512BW), glibc 2.35 or later, one 
core, 1,048,576 empty strings.
   
   `arrow_writer` benchmark, `string_empty` from 
https://github.com/apache/arrow-rs/pull/12140, criterion median per iteration:
   
   | benchmark | `main` | this PR | change |
   |---|---:|---:|---:|
   | `string_empty/default` | 205.6 ms | 15.0 ms | -92.7% |
   | `string_empty/bloom_filter` | 205.9 ms | 15.4 ms | -92.5% |
   | `string_empty/parquet_2` | 205.9 ms | 15.1 ms | -92.7% |
   | `string_empty/zstd` | 205.7 ms | 15.1 ms | -92.6% |
   | `string_empty/zstd_parquet_2` | 205.4 ms | 15.2 ms | -92.6% |
   | `string_empty/cdc` | 208.3 ms | 17.4 ms | -91.6% |
   | `string_empty/number_distinct_values` | 226.4 ms | 36.2 ms | -84.0% |
   
   With this PR, `string_empty/default` is as fast as the same values in an 
allocated buffer (15.04 ms and 15.05 ms).
   
   No regressions: the existing `string`, `string_non_null` and 
`string_dictionary` cases (21 benchmarks) show no change (-3.8% to +3.5%, all 
within criterion's noise). In `buffer_create`, the changes are -1.7% to +2.8%; 
a control run of the unchanged code moved the same three bool cases by +1.2% to 
+1.8%.
   
   # Are there any significant user-facing changes?
   
   No. Only the address of empty buffers changes.
   
   # AI use
   
   An AI coding agent wrote the code. I have reviewed it and verified the 
benchmark results.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to