thisisnic commented on PR #51288:
URL: https://github.com/apache/arrow/pull/51288#issuecomment-5621479618

   _This comment was written by Claude (an AI assistant) at @thisisnic's 
request, not by Nic. Nic ran the session; the benchmark design, numbers, and 
reading below are mine._
   
   I benchmarked the cache against the same code with `r/src/altrep.cpp` 
reverted to the parent commit, on a 1M-row `string()` Array converted with 
`$as_vector()` (ALTREP, unmaterialised). `bench::mark`, median of 5 iterations, 
memory as reported by `bench`.
   
   | case | what it measures | no fix (ms) | with fix (ms) | no fix (MB) | with 
fix (MB) |
   |---|---|---|---|---|---|
   | sparse_access | 1000 random single-element reads (`x[[i]]`) | 2.9 | 4.6 | 
0.0 | 5.0 |
   | repeat_access | the same 1000 reads, 10 times | 9.2 | 8.0 | 0.0 | 5.0 |
   | full_pass | `for (s in x)` over all 1M elements | 315.3 | 357.0 | 0.0 | 
7.7 |
   | materialize_fresh | materialise with nothing cached | 249.5 | 244.5 | 7.6 
| 7.6 |
   | pass_then_materialize | full element-wise pass, then materialise | 568.2 | 
364.8 | 7.6 | 15.3 |
   
   Reading it:
   
   - Materialising a vector that has not been accessed element-wise is 
unchanged. This is the common path, since most operations on a column end up 
calling `Dataptr` and materialising.
   - Element-wise access costs about 13% more on a full pass, and allocates 
what a materialised vector would (one pointer per element).
   - Random reads across a large vector touch many 1024-element blocks, and 
each touched block is allocated whole (8KB), so 1000 random reads over 1M rows 
allocate most of the cache. The cache is still bounded above by the size of a 
materialised vector.
   - Two cases get faster: re-reading elements already accessed (cache hits 
skip conversion), and materialising after element-wise access, which is 36% 
faster because nothing is converted twice. The 15.3MB there is the cache and 
the new vector coexisting briefly; the cache is released when `data1` is 
dropped.
   
   So the cost is memory rather than time, only for elements that have actually 
been accessed, and never more than not using ALTREP at all.
   
   <details>
   <summary>Benchmark script</summary>
   
   ```r
   suppressMessages(devtools::load_all(quiet = TRUE))
   options(arrow.use_altrep = TRUE)
   library(bench)
   
   n <- 1e6L
   strings <- sprintf("string number %07d", seq_len(n))
   arr <- Array$create(strings)
   
   fresh <- function() {
     x <- arr$as_vector()
     stopifnot(is_arrow_altrep(x), !test_arrow_altrep_is_materialized(x))
     x
   }
   
   set.seed(1)
   sparse_idx <- sample.int(n, 1000L)
   
   bench::mark(
     sparse_access = { x <- fresh(); for (i in sparse_idx) x[[i]] },
     repeat_access = { x <- fresh(); for (k in 1:10) for (i in sparse_idx) 
x[[i]] },
     full_pass = { x <- fresh(); for (s in x) NULL },
     materialize_fresh = { x <- fresh(); test_arrow_altrep_force_materialize(x) 
},
     pass_then_materialize = { x <- fresh(); for (s in x) NULL; 
test_arrow_altrep_force_materialize(x) },
     iterations = 5, check = FALSE, memory = TRUE
   )
   ```
   
   </details>
   


-- 
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