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

   Thanks for the review. The P2 is right, and I reproduced all three 
mechanisms against the Arrow
   18.3 sources before changing anything:
   
   - `ArrayImporter.java:58` allocates the owning `ArrowArray` with 
`ArrowArray.allocateNew(allocator)`,
     so every import charges a JVM-created struct to the import allocator.
   - `BitVectorHelper.loadValidityBuffer:328` does 
`allocator.buffer(getValidityBufferSize(valueCount))`
     when the source validity buffer is absent and the field node is all-valid 
or all-null. For 4096
     rows that is 512 bytes, which with the struct matches the 640 bytes you 
measured.
   - `Data.exportVector`'s allocator is documented as being "for allocating C 
data interface fields"
     and only constructs an `ArrayExporter`; it never re-owns the vector's 
buffers. So a UDF result
     allocated from `inputs.head.getAllocator()` stays charged to the import 
allocator for as long as
     the export holds it.
   
   So the docs claim that the difference is the Arrow memory the JVM allocated 
itself was too strong.
   Two changes in response.
   
   **Narrowed the claim.** `jvm_arrow_imported` is now described as what the 
FFI import path holds,
   which is mostly imported buffers plus a little JVM-allocated import 
overhead, making
   `jvm_arrow_allocated - jvm_arrow_imported` a close lower bound rather than 
an exact split. The
   tracing guide names both contaminants with their magnitudes, and states that 
the two counters are
   separate reads of process-wide state, so they are neither an atomic 
per-query balance nor a measure
   of RSS. The scaladoc on `CometArrowImportAllocator` and 
`Tracing.logArrowMemory` says the same.
   Worth noting the direction: these bytes make the reported JVM-own figure an 
underestimate, so the
   unaccounted memory this PR exists to surface is marginally larger than the 
counter suggests, not
   smaller.
   
   **Fixed the UDF case,** since that one is both the largest and avoidable. 
`CometUdfBridge` now
   transfers the result to the root allocator before export when the UDF 
allocated it elsewhere.
   `TransferPair.transfer()` re-parents buffer ownership through
   `referenceManager.transferOwnership(...)` without copying the payload, so 
this costs an ownership
   move rather than a copy, and the transferred vector is closed alongside the 
emptied original.
   
   **Tests,** both of the cases you asked for:
   
   - A characterization test in `NativeUtilSuite` asserting the import 
allocator holds strictly more
     than the sum of the imported buffers, which pins the struct and validity 
overhead rather than
     pretending it is absent.
   - A regression test in `CometUdfBridgeSuite` with a UDF that allocates its 
output from
     `inputs.head.getAllocator()`. It asserts before the exported structs are 
released, since that is
     the window in which the export keeps the buffers alive and the miscounting 
is observable. It
     failed at 32768 bytes charged to the import allocator before the fix and 
passes after it.
   
   I also added the import child to the allocator inventory in the memory 
management guide, as
   suggested.
   
   On making the child exclusively foreign: I looked at it and decided against 
it. Both the struct and
   the validity synthesis happen inside Arrow's own import path using the 
single allocator we pass, so
   excluding them would mean vendoring further into Arrow's importer and 
re-deriving that logic, which
   would be fragile across Arrow upgrades for a few hundred bytes per batch. 
Narrowing the claim and
   removing the one unbounded contaminant seemed the better trade, but I am 
happy to revisit if you
   disagree.
   
   On the performance question: tracing is off by default, so the added JNI 
calls only occur when it is
   enabled. A traced TPC-H SF100 run on 2 executors x 8 cores (Spark 4.1.1, 16g 
off-heap, build with
   `jemalloc,alloc-accounting`) completed its iteration in 208.6 s against a 
roughly 220 s untraced
   baseline on the same machine, so the two extra calls per sample are inside 
run-to-run variation. For
   reference, that run measured peak `native_allocated` 2193.7 MB, 
`jvm_arrow_allocated` 144.0 MB and
   `jvm_arrow_imported` 53.7 MB, with 0 of 16970 paired samples showing 
imported exceeding allocated.
   


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