andygrove commented on PR #5042:
URL: 
https://github.com/apache/datafusion-comet/pull/5042#issuecomment-5441581243

   > **Note on this review:** this was generated by an LLM (Claude Code) at my 
request while I worked through a review backlog. I have not verified the 
individual findings myself. Please treat everything below as suggestions to 
evaluate rather than as authoritative review feedback, and push back on 
anything that is wrong or already handled.
   
   Thanks for working on this. The ASCII fast path is a good idea and the 
benchmark numbers look worthwhile. I think there is a correctness problem in 
the scratch-buffer reuse, though, and I would like to work through it before 
this goes further.
   
   **`Vec::resize` does not reset existing elements**
   
   `resize` only fills the *new* slots when growing, and simply truncates when 
shrinking. Existing elements keep whatever the previous call left in them. That 
matters in `levenshtein_distance_with_threshold`, where the old code allocated 
`vec![out_of_band; m + 1]` and therefore started from a fully initialized row, 
but the new code only writes some of the cells.
   
   Concretely, in the threshold path:
   
   ```rust
   prev.resize(m + 1, out_of_band);
   for (i, value) in prev.iter_mut().enumerate().take(m.min(threshold) + 1) {
       *value = i as i32;
   }
   ```
   
   If the previous row used a longer buffer, `resize` truncates and leaves 
`prev[0..=m]` holding stale distances. The loop then initializes only 
`prev[0..=min(m, threshold)]`. With `threshold < m`, the cells from `threshold 
+ 1` through `m` should be `out_of_band` but instead hold values from an 
earlier, unrelated pair of strings.
   
   The same applies to `curr`. Inside the `j` loop only `curr[0]`, `curr[start 
- 1]`, `curr[start..=end]`, and `curr[end + 1]` are written. Everything outside 
that band used to be `out_of_band` from the fresh allocation and is now stale.
   
   So `levenshtein(s, t, threshold)` can return a different answer depending on 
what the *previous* row on the same thread computed. That is the worst kind of 
bug to find in production: nondeterministic, data-dependent, and invisible in a 
test that evaluates one pair at a time.
   
   The fix is to explicitly fill rather than rely on `resize`:
   
   ```rust
   prev.clear();
   prev.resize(m + 1, out_of_band);
   ```
   
   or `prev.resize(m + 1, out_of_band); prev.fill(out_of_band);` before the 
partial initialization. Same for `curr`. The plain `levenshtein_distance` path 
happens to be safe because both rows are fully overwritten, but it would be 
worth making the initialization uniform in both so the invariant is not 
something a future reader has to rediscover.
   
   **"Existing tests" is not enough for this change**
   
   Because the bug only appears when one call leaves state behind for the next, 
no single-pair test can catch it. Could you add a test that evaluates a whole 
column in one `invoke`, mixing long and short strings and ASCII with non-ASCII, 
and compares against a straightforward reference implementation? Something 
like: compute a batch in one order, compute the same pairs in reverse order, 
and assert the results match. That property directly targets the class of bug 
above and would have caught it.
   
   It would also be worth adding cases with a small threshold and a long 
shorter-string, since that is the shape that leaves the most cells 
uninitialized.
   
   **Two smaller points**
   
   `s.is_ascii()` is a full scan of the string, so non-ASCII inputs now pay an 
extra pass before falling into the `chars()` path. For a mostly-non-ASCII 
column that is a small regression. Probably fine, but worth confirming the 
benchmark covers a non-ASCII case so we know the fallback did not get slower.
   
   The `thread_local!` buffers grow to the longest string ever seen on that 
thread and never shrink. On a pool of executor threads that could retain a fair 
amount of memory after one pathological row. Is it worth calling `shrink_to` 
when the buffer is far larger than the current need?
   


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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to