sunchao commented on code in PR #5493:
URL: https://github.com/apache/datafusion-comet/pull/5493#discussion_r3926724407


##########
spark/src/main/java/org/apache/spark/shuffle/comet/CometBoundedShuffleMemoryAllocator.java:
##########
@@ -123,6 +147,122 @@ public synchronized MemoryBlock allocate(long required) {
     return allocateMemoryBlock(size);
   }
 
+  /**
+   * Like {@link #allocate(long)}, but waits for other tasks of this shared 
pool to free memory,
+   * mirroring how Spark's unified memory manager blocks a task until memory 
becomes available.
+   * Callers must first spill buffered data they can cheaply release; memory 
this thread still
+   * retains (e.g. the sorter's pointer array or sibling writers' pages) is 
included in the liveness
+   * checks below. The wait fails fast when it can never succeed: when the 
request does not fit next
+   * to the requester's retained memory, or when all allocated memory is 
retained by blocked threads
+   * and none of their requests fits in the free pool. Because the holders it 
depends on may in turn
+   * be blocked on resources outside this pool that only a task waiting here 
can release, the wait
+   * is also bounded by `spark.comet.shuffle.jvm.memoryWaitTimeout`, after 
which the managed
+   * allocation error is thrown and Spark's task retry can recover. Task 
cancellation or Java
+   * interruption aborts the wait.
+   */
+  @Override
+  public synchronized MemoryBlock allocateBlocking(long required) {
+    long memoryWaitTimeoutMs =
+        (long) 
CometConf$.MODULE$.COMET_SHUFFLE_JVM_MEMORY_WAIT_TIMEOUT().get();

Review Comment:
   Could this lookup move into the first actual wait? `allocateBlocking` can 
succeed immediately after the caller spills, so that path does not need to 
resolve the timeout.
   
   Across two JVM forks, the current successful allocation/free path measured 
329–336 ns and 432 B of Java heap per pair. The prototype with a capacity check 
and deferred lookup measured 110–126 ns and 112 B. The capacity-check-only 
control retained roughly the current cost, which points to the eager 
configuration lookup as the avoidable work on this path. These are component 
measurements and the absolute cost is small per page.
   
   Read the setting once per waiting request, from that task's configuration, 
so this preserves the per-task timeout fix already made in this PR.



##########
spark/src/main/java/org/apache/spark/shuffle/comet/CometBoundedShuffleMemoryAllocator.java:
##########
@@ -123,6 +147,122 @@ public synchronized MemoryBlock allocate(long required) {
     return allocateMemoryBlock(size);
   }
 
+  /**
+   * Like {@link #allocate(long)}, but waits for other tasks of this shared 
pool to free memory,
+   * mirroring how Spark's unified memory manager blocks a task until memory 
becomes available.
+   * Callers must first spill buffered data they can cheaply release; memory 
this thread still
+   * retains (e.g. the sorter's pointer array or sibling writers' pages) is 
included in the liveness
+   * checks below. The wait fails fast when it can never succeed: when the 
request does not fit next
+   * to the requester's retained memory, or when all allocated memory is 
retained by blocked threads
+   * and none of their requests fits in the free pool. Because the holders it 
depends on may in turn
+   * be blocked on resources outside this pool that only a task waiting here 
can release, the wait
+   * is also bounded by `spark.comet.shuffle.jvm.memoryWaitTimeout`, after 
which the managed
+   * allocation error is thrown and Spark's task retry can recover. Task 
cancellation or Java
+   * interruption aborts the wait.
+   */
+  @Override
+  public synchronized MemoryBlock allocateBlocking(long required) {
+    long memoryWaitTimeoutMs =
+        (long) 
CometConf$.MODULE$.COMET_SHUFFLE_JVM_MEMORY_WAIT_TIMEOUT().get();
+    long size = Math.max(pageSize, required);
+    Thread self = Thread.currentThread();
+    TaskContext taskContext = TaskContext.get();
+    long waitStart = 0;
+    long lastLog = 0;
+    try {
+      while (true) {
+        if (taskContext != null) {
+          taskContext.killTaskIfInterrupted();
+        }
+        try {
+          return allocateMemoryBlock(size);
+        } catch (SparkOutOfMemoryError e) {

Review Comment:
   Could the retry path use a private allocation helper that reports 
pool-capacity shortage without constructing an exception? Each small free can 
wake every waiter even when their requests still do not fit, so these retries 
construct and format a new `SparkOutOfMemoryError`, including its stack trace, 
under the shared allocator monitor.
   
   In the controlled allocator experiment described in the review body, a 
capacity-check prototype reduced waiter Java heap allocation over 1.2 seconds 
from 29.5 MiB to 0.22 MiB with 8 waiters, and from 54.2 MiB to 0.49 MiB with 32 
waiters.
   
   A private nonthrowing helper could serve both APIs: immediate allocation 
converts a capacity miss to the managed error, while blocking allocation 
creates that error only when it must fail. Keep the existing ownership, 
liveness, cancellation, timeout and notification behavior, and preserve 
oversized-request, page-table and native-allocation errors.



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