This is an automated email from the ASF dual-hosted git repository.

agrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/main by this push:
     new c32bf0c9 chore: Simplify CometShuffleMemoryAllocator to use Spark 
unified memory allocator (#1063)
c32bf0c9 is described below

commit c32bf0c949e7ab4a99e5a98d07639812267c97ee
Author: Liang-Chi Hsieh <[email protected]>
AuthorDate: Wed Nov 13 22:18:56 2024 -0800

    chore: Simplify CometShuffleMemoryAllocator to use Spark unified memory 
allocator (#1063)
---
 .../main/scala/org/apache/comet/CometConf.scala    |  19 ++-
 docs/source/user-guide/configs.md                  |   1 -
 .../shuffle/comet/CometShuffleMemoryAllocator.java | 182 +++++----------------
 .../comet/CometShuffleMemoryAllocatorTrait.java    |  41 +++++
 ...r.java => CometTestShuffleMemoryAllocator.java} |  35 ++--
 .../shuffle/sort/CometShuffleExternalSorter.java   |   3 +-
 .../execution/shuffle/CometDiskBlockWriter.java    |  10 +-
 .../sql/comet/execution/shuffle/SpillWriter.java   |   8 +-
 .../apache/comet/CometSparkSessionExtensions.scala |  16 +-
 .../comet/exec/CometColumnarShuffleSuite.scala     |  10 ++
 10 files changed, 156 insertions(+), 169 deletions(-)

diff --git a/common/src/main/scala/org/apache/comet/CometConf.scala 
b/common/src/main/scala/org/apache/comet/CometConf.scala
index 7450d27a..b602d7cf 100644
--- a/common/src/main/scala/org/apache/comet/CometConf.scala
+++ b/common/src/main/scala/org/apache/comet/CometConf.scala
@@ -322,8 +322,10 @@ object CometConf extends ShimCometConf {
 
   val COMET_COLUMNAR_SHUFFLE_MEMORY_SIZE: OptionalConfigEntry[Long] =
     conf("spark.comet.columnar.shuffle.memorySize")
+      .internal()
       .doc(
-        "The optional maximum size of the memory used for Comet columnar 
shuffle, in MiB. " +
+        "Test-only config. This is only used to test Comet shuffle with Spark 
tests. " +
+          "The optional maximum size of the memory used for Comet columnar 
shuffle, in MiB. " +
           "Note that this config is only used when 
`spark.comet.exec.shuffle.mode` is " +
           "`jvm`. Once allocated memory size reaches this config, the current 
batch will be " +
           "flushed to disk immediately. If this is not configured, Comet will 
use " +
@@ -335,8 +337,10 @@ object CometConf extends ShimCometConf {
 
   val COMET_COLUMNAR_SHUFFLE_MEMORY_FACTOR: ConfigEntry[Double] =
     conf("spark.comet.columnar.shuffle.memory.factor")
+      .internal()
       .doc(
-        "Fraction of Comet memory to be allocated per executor process for 
Comet shuffle. " +
+        "Test-only config. This is only used to test Comet shuffle with Spark 
tests. " +
+          "Fraction of Comet memory to be allocated per executor process for 
Comet shuffle. " +
           "Comet memory size is specified by `spark.comet.memoryOverhead` or " 
+
           "calculated by `spark.comet.memory.overhead.factor` * 
`spark.executor.memory`.")
       .doubleConf
@@ -345,6 +349,17 @@ object CometConf extends ShimCometConf {
         "Ensure that Comet shuffle memory overhead factor is a double greater 
than 0")
       .createWithDefault(1.0)
 
+  val COMET_COLUMNAR_SHUFFLE_UNIFIED_MEMORY_ALLOCATOR_IN_TEST: 
ConfigEntry[Boolean] =
+    conf("spark.comet.columnar.shuffle.unifiedMemoryAllocatorTest")
+      .doc("Whether to use Spark unified memory allocator for Comet columnar 
shuffle in tests." +
+        "If not configured, Comet will use a test-only memory allocator for 
Comet columnar " +
+        "shuffle when Spark test env detected. The test-ony allocator is 
proposed to run with " +
+        "Spark tests as these tests require on-heap memory configuration. " +
+        "By default, this config is false.")
+      .internal()
+      .booleanConf
+      .createWithDefault(false)
+
   val COMET_COLUMNAR_SHUFFLE_BATCH_SIZE: ConfigEntry[Int] =
     conf("spark.comet.columnar.shuffle.batch.size")
       .internal()
diff --git a/docs/source/user-guide/configs.md 
b/docs/source/user-guide/configs.md
index dc97e3c5..69da7922 100644
--- a/docs/source/user-guide/configs.md
+++ b/docs/source/user-guide/configs.md
@@ -29,7 +29,6 @@ Comet provides the following configuration settings.
 | spark.comet.columnar.shuffle.async.enabled | Whether to enable asynchronous 
shuffle for Arrow-based shuffle. | false |
 | spark.comet.columnar.shuffle.async.max.thread.num | Maximum number of 
threads on an executor used for Comet async columnar shuffle. This is the upper 
bound of total number of shuffle threads per executor. In other words, if the 
number of cores * the number of shuffle threads per task 
`spark.comet.columnar.shuffle.async.thread.num` is larger than this config. 
Comet will use this config as the number of shuffle threads per executor 
instead. | 100 |
 | spark.comet.columnar.shuffle.async.thread.num | Number of threads used for 
Comet async columnar shuffle per shuffle task. Note that more threads means 
more memory requirement to buffer shuffle data before flushing to disk. Also, 
more threads may not always improve performance, and should be set based on the 
number of cores available. | 3 |
-| spark.comet.columnar.shuffle.memory.factor | Fraction of Comet memory to be 
allocated per executor process for Comet shuffle. Comet memory size is 
specified by `spark.comet.memoryOverhead` or calculated by 
`spark.comet.memory.overhead.factor` * `spark.executor.memory`. | 1.0 |
 | spark.comet.convert.csv.enabled | When enabled, data from Spark (non-native) 
CSV v1 and v2 scans will be converted to Arrow format. Note that to enable 
native vectorized execution, both this config and 'spark.comet.exec.enabled' 
need to be enabled. | false |
 | spark.comet.convert.json.enabled | When enabled, data from Spark 
(non-native) JSON v1 and v2 scans will be converted to Arrow format. Note that 
to enable native vectorized execution, both this config and 
'spark.comet.exec.enabled' need to be enabled. | false |
 | spark.comet.convert.parquet.enabled | When enabled, data from Spark 
(non-native) Parquet v1 and v2 scans will be converted to Arrow format. Note 
that to enable native vectorized execution, both this config and 
'spark.comet.exec.enabled' need to be enabled. | false |
diff --git 
a/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocator.java
 
b/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocator.java
index 2837fa36..54e349c1 100644
--- 
a/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocator.java
+++ 
b/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocator.java
@@ -20,157 +20,75 @@
 package org.apache.spark.shuffle.comet;
 
 import java.io.IOException;
-import java.util.BitSet;
 
 import org.apache.spark.SparkConf;
 import org.apache.spark.memory.MemoryConsumer;
 import org.apache.spark.memory.MemoryMode;
-import org.apache.spark.memory.SparkOutOfMemoryError;
 import org.apache.spark.memory.TaskMemoryManager;
-import org.apache.spark.sql.internal.SQLConf;
-import org.apache.spark.unsafe.array.LongArray;
 import org.apache.spark.unsafe.memory.MemoryBlock;
-import org.apache.spark.unsafe.memory.UnsafeMemoryAllocator;
+import org.apache.spark.util.Utils;
 
-import org.apache.comet.CometSparkSessionExtensions$;
+import org.apache.comet.CometConf$;
 
 /**
  * A simple memory allocator used by `CometShuffleExternalSorter` to allocate 
memory blocks which
- * store serialized rows. We don't rely on Spark memory allocator because we 
need to allocate
- * off-heap memory no matter memory mode is on-heap or off-heap. This 
allocator is configured with
- * fixed size of memory, and it will throw `SparkOutOfMemoryError` if the 
memory is not enough.
- *
- * <p>Some methods are copied from 
`org.apache.spark.unsafe.memory.TaskMemoryManager` with
- * modifications. Most modifications are to remove the dependency on the 
configured memory mode.
+ * store serialized rows. This class is simply an implementation of 
`MemoryConsumer` that delegates
+ * memory allocation to the `TaskMemoryManager`. This requires that the 
`TaskMemoryManager` is
+ * configured with `MemoryMode.OFF_HEAP`, i.e. it is using off-heap memory.
  */
-public final class CometShuffleMemoryAllocator extends MemoryConsumer {
-  private final UnsafeMemoryAllocator allocator = new UnsafeMemoryAllocator();
-
-  private final long pageSize;
-  private final long totalMemory;
-  private long allocatedMemory = 0L;
-
-  /** The number of bits used to address the page table. */
-  private static final int PAGE_NUMBER_BITS = 13;
-
-  /** The number of entries in the page table. */
-  private static final int PAGE_TABLE_SIZE = 1 << PAGE_NUMBER_BITS;
-
-  private final MemoryBlock[] pageTable = new MemoryBlock[PAGE_TABLE_SIZE];
-  private final BitSet allocatedPages = new BitSet(PAGE_TABLE_SIZE);
+public final class CometShuffleMemoryAllocator extends 
CometShuffleMemoryAllocatorTrait {
+  private static CometShuffleMemoryAllocatorTrait INSTANCE;
 
-  private static final int OFFSET_BITS = 51;
-  private static final long MASK_LONG_LOWER_51_BITS = 0x7FFFFFFFFFFFFL;
-
-  private static CometShuffleMemoryAllocator INSTANCE;
-
-  public static synchronized CometShuffleMemoryAllocator getInstance(
+  /**
+   * Returns the singleton instance of `CometShuffleMemoryAllocator`. This 
method should be used
+   * instead of the constructor to ensure that only one instance of 
`CometShuffleMemoryAllocator` is
+   * created. For Spark tests, this returns `CometTestShuffleMemoryAllocator` 
which is a test-only
+   * allocator that should not be used in production.
+   */
+  public static CometShuffleMemoryAllocatorTrait getInstance(
       SparkConf conf, TaskMemoryManager taskMemoryManager, long pageSize) {
-    if (INSTANCE == null) {
-      INSTANCE = new CometShuffleMemoryAllocator(conf, taskMemoryManager, 
pageSize);
+    boolean isSparkTesting = Utils.isTesting();
+    boolean useUnifiedMemAllocator =
+        (boolean)
+            
CometConf$.MODULE$.COMET_COLUMNAR_SHUFFLE_UNIFIED_MEMORY_ALLOCATOR_IN_TEST().get();
+
+    if (isSparkTesting && !useUnifiedMemAllocator) {
+      synchronized (CometShuffleMemoryAllocator.class) {
+        if (INSTANCE == null) {
+          // CometTestShuffleMemoryAllocator handles pages by itself so it can 
be a singleton.
+          INSTANCE = new CometTestShuffleMemoryAllocator(conf, 
taskMemoryManager, pageSize);
+        }
+      }
+      return INSTANCE;
+    } else {
+      if (taskMemoryManager.getTungstenMemoryMode() != MemoryMode.OFF_HEAP) {
+        throw new IllegalArgumentException(
+            "CometShuffleMemoryAllocator should be used with off-heap "
+                + "memory mode, but got "
+                + taskMemoryManager.getTungstenMemoryMode());
+      }
+
+      // CometShuffleMemoryAllocator stores pages in TaskMemoryManager which 
is not singleton,
+      // but one instance per task. So we need to create a new instance for 
each task.
+      return new CometShuffleMemoryAllocator(taskMemoryManager, pageSize);
     }
-
-    return INSTANCE;
   }
 
-  CometShuffleMemoryAllocator(SparkConf conf, TaskMemoryManager 
taskMemoryManager, long pageSize) {
+  CometShuffleMemoryAllocator(TaskMemoryManager taskMemoryManager, long 
pageSize) {
     super(taskMemoryManager, pageSize, MemoryMode.OFF_HEAP);
-    this.pageSize = pageSize;
-    this.totalMemory =
-        CometSparkSessionExtensions$.MODULE$.getCometShuffleMemorySize(conf, 
SQLConf.get());
-  }
-
-  public synchronized long acquireMemory(long size) {
-    if (allocatedMemory >= totalMemory) {
-      throw new SparkOutOfMemoryError(
-          "Unable to acquire "
-              + size
-              + " bytes of memory, current usage "
-              + "is "
-              + allocatedMemory
-              + " bytes and max memory is "
-              + totalMemory
-              + " bytes");
-    }
-    long allocationSize = Math.min(size, totalMemory - allocatedMemory);
-    allocatedMemory += allocationSize;
-    return allocationSize;
   }
 
   public long spill(long l, MemoryConsumer memoryConsumer) throws IOException {
+    // JVM shuffle writer does not support spilling for other memory consumers
     return 0;
   }
 
-  public synchronized LongArray allocateArray(long size) {
-    long required = size * 8L;
-    MemoryBlock page = allocate(required);
-    return new LongArray(page);
-  }
-
-  public synchronized void freeArray(LongArray array) {
-    if (array == null) {
-      return;
-    }
-    free(array.memoryBlock());
-  }
-
-  public synchronized MemoryBlock allocatePage(long required) {
-    long size = Math.max(pageSize, required);
-    return allocate(size);
-  }
-
-  private synchronized MemoryBlock allocate(long required) {
-    if (required > TaskMemoryManager.MAXIMUM_PAGE_SIZE_BYTES) {
-      throw new TooLargePageException(required);
-    }
-
-    long got = acquireMemory(required);
-
-    if (got < required) {
-      allocatedMemory -= got;
-
-      throw new SparkOutOfMemoryError(
-          "Unable to acquire "
-              + required
-              + " bytes of memory, got "
-              + got
-              + " bytes. Available: "
-              + (totalMemory - allocatedMemory));
-    }
-
-    int pageNumber = allocatedPages.nextClearBit(0);
-    if (pageNumber >= PAGE_TABLE_SIZE) {
-      allocatedMemory -= got;
-
-      throw new IllegalStateException(
-          "Have already allocated a maximum of " + PAGE_TABLE_SIZE + " pages");
-    }
-
-    MemoryBlock block = allocator.allocate(got);
-
-    block.pageNumber = pageNumber;
-    pageTable[pageNumber] = block;
-    allocatedPages.set(pageNumber);
-
-    return block;
+  public synchronized MemoryBlock allocate(long required) {
+    return this.allocatePage(required);
   }
 
   public synchronized void free(MemoryBlock block) {
-    if (block.pageNumber == MemoryBlock.FREED_IN_ALLOCATOR_PAGE_NUMBER) {
-      // Already freed block
-      return;
-    }
-    allocatedMemory -= block.size();
-
-    pageTable[block.pageNumber] = null;
-    allocatedPages.clear(block.pageNumber);
-    block.pageNumber = MemoryBlock.FREED_IN_TMM_PAGE_NUMBER;
-
-    allocator.free(block);
-  }
-
-  public synchronized long getAvailableMemory() {
-    return totalMemory - allocatedMemory;
+    this.freePage(block);
   }
 
   /**
@@ -178,21 +96,11 @@ public final class CometShuffleMemoryAllocator extends 
MemoryConsumer {
    * method assumes that the page number is valid.
    */
   public long getOffsetInPage(long pagePlusOffsetAddress) {
-    long offsetInPage = decodeOffset(pagePlusOffsetAddress);
-    int pageNumber = TaskMemoryManager.decodePageNumber(pagePlusOffsetAddress);
-    assert (pageNumber >= 0 && pageNumber < PAGE_TABLE_SIZE);
-    MemoryBlock page = pageTable[pageNumber];
-    assert (page != null);
-    return page.getBaseOffset() + offsetInPage;
-  }
-
-  public long decodeOffset(long pagePlusOffsetAddress) {
-    return pagePlusOffsetAddress & MASK_LONG_LOWER_51_BITS;
+    return taskMemoryManager.getOffsetInPage(pagePlusOffsetAddress);
   }
 
   public long encodePageNumberAndOffset(int pageNumber, long offsetInPage) {
-    assert (pageNumber >= 0);
-    return ((long) pageNumber) << OFFSET_BITS | offsetInPage & 
MASK_LONG_LOWER_51_BITS;
+    return TaskMemoryManager.encodePageNumberAndOffset(pageNumber, 
offsetInPage);
   }
 
   public long encodePageNumberAndOffset(MemoryBlock page, long offsetInPage) {
diff --git 
a/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocatorTrait.java
 
b/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocatorTrait.java
new file mode 100644
index 00000000..6831396b
--- /dev/null
+++ 
b/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocatorTrait.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.spark.shuffle.comet;
+
+import org.apache.spark.memory.MemoryConsumer;
+import org.apache.spark.memory.MemoryMode;
+import org.apache.spark.memory.TaskMemoryManager;
+import org.apache.spark.unsafe.memory.MemoryBlock;
+
+/** The base class for Comet JVM shuffle memory allocators. */
+public abstract class CometShuffleMemoryAllocatorTrait extends MemoryConsumer {
+  protected CometShuffleMemoryAllocatorTrait(
+      TaskMemoryManager taskMemoryManager, long pageSize, MemoryMode mode) {
+    super(taskMemoryManager, pageSize, mode);
+  }
+
+  public abstract MemoryBlock allocate(long required);
+
+  public abstract void free(MemoryBlock block);
+
+  public abstract long getOffsetInPage(long pagePlusOffsetAddress);
+
+  public abstract long encodePageNumberAndOffset(MemoryBlock page, long 
offsetInPage);
+}
diff --git 
a/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocator.java
 
b/spark/src/main/java/org/apache/spark/shuffle/comet/CometTestShuffleMemoryAllocator.java
similarity index 87%
copy from 
spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocator.java
copy to 
spark/src/main/java/org/apache/spark/shuffle/comet/CometTestShuffleMemoryAllocator.java
index 2837fa36..084e82b2 100644
--- 
a/spark/src/main/java/org/apache/spark/shuffle/comet/CometShuffleMemoryAllocator.java
+++ 
b/spark/src/main/java/org/apache/spark/shuffle/comet/CometTestShuffleMemoryAllocator.java
@@ -42,8 +42,13 @@ import org.apache.comet.CometSparkSessionExtensions$;
  *
  * <p>Some methods are copied from 
`org.apache.spark.unsafe.memory.TaskMemoryManager` with
  * modifications. Most modifications are to remove the dependency on the 
configured memory mode.
+ *
+ * <p>This allocator is test-only and should not be used in production. It is 
used to test Comet JVM
+ * shuffle and execution with Spark tests which basically require on-heap 
memory configuration.
+ * Thus, this allocator is used to allocate separate off-heap memory 
allocation for Comet JVM
+ * shuffle and execution apart from Spark's on-heap memory configuration.
  */
-public final class CometShuffleMemoryAllocator extends MemoryConsumer {
+public final class CometTestShuffleMemoryAllocator extends 
CometShuffleMemoryAllocatorTrait {
   private final UnsafeMemoryAllocator allocator = new UnsafeMemoryAllocator();
 
   private final long pageSize;
@@ -62,25 +67,17 @@ public final class CometShuffleMemoryAllocator extends 
MemoryConsumer {
   private static final int OFFSET_BITS = 51;
   private static final long MASK_LONG_LOWER_51_BITS = 0x7FFFFFFFFFFFFL;
 
-  private static CometShuffleMemoryAllocator INSTANCE;
+  private static CometTestShuffleMemoryAllocator INSTANCE;
 
-  public static synchronized CometShuffleMemoryAllocator getInstance(
+  CometTestShuffleMemoryAllocator(
       SparkConf conf, TaskMemoryManager taskMemoryManager, long pageSize) {
-    if (INSTANCE == null) {
-      INSTANCE = new CometShuffleMemoryAllocator(conf, taskMemoryManager, 
pageSize);
-    }
-
-    return INSTANCE;
-  }
-
-  CometShuffleMemoryAllocator(SparkConf conf, TaskMemoryManager 
taskMemoryManager, long pageSize) {
     super(taskMemoryManager, pageSize, MemoryMode.OFF_HEAP);
     this.pageSize = pageSize;
     this.totalMemory =
         CometSparkSessionExtensions$.MODULE$.getCometShuffleMemorySize(conf, 
SQLConf.get());
   }
 
-  public synchronized long acquireMemory(long size) {
+  private synchronized long _acquireMemory(long size) {
     if (allocatedMemory >= totalMemory) {
       throw new SparkOutOfMemoryError(
           "Unable to acquire "
@@ -103,7 +100,7 @@ public final class CometShuffleMemoryAllocator extends 
MemoryConsumer {
 
   public synchronized LongArray allocateArray(long size) {
     long required = size * 8L;
-    MemoryBlock page = allocate(required);
+    MemoryBlock page = allocateMemoryBlock(required);
     return new LongArray(page);
   }
 
@@ -114,17 +111,17 @@ public final class CometShuffleMemoryAllocator extends 
MemoryConsumer {
     free(array.memoryBlock());
   }
 
-  public synchronized MemoryBlock allocatePage(long required) {
+  public synchronized MemoryBlock allocate(long required) {
     long size = Math.max(pageSize, required);
-    return allocate(size);
+    return allocateMemoryBlock(size);
   }
 
-  private synchronized MemoryBlock allocate(long required) {
+  private synchronized MemoryBlock allocateMemoryBlock(long required) {
     if (required > TaskMemoryManager.MAXIMUM_PAGE_SIZE_BYTES) {
       throw new TooLargePageException(required);
     }
 
-    long got = acquireMemory(required);
+    long got = _acquireMemory(required);
 
     if (got < required) {
       allocatedMemory -= got;
@@ -169,10 +166,6 @@ public final class CometShuffleMemoryAllocator extends 
MemoryConsumer {
     allocator.free(block);
   }
 
-  public synchronized long getAvailableMemory() {
-    return totalMemory - allocatedMemory;
-  }
-
   /**
    * Returns the offset in the page for the given page plus base offset 
address. Note that this
    * method assumes that the page number is valid.
diff --git 
a/spark/src/main/java/org/apache/spark/shuffle/sort/CometShuffleExternalSorter.java
 
b/spark/src/main/java/org/apache/spark/shuffle/sort/CometShuffleExternalSorter.java
index ed3e2be6..cc449557 100644
--- 
a/spark/src/main/java/org/apache/spark/shuffle/sort/CometShuffleExternalSorter.java
+++ 
b/spark/src/main/java/org/apache/spark/shuffle/sort/CometShuffleExternalSorter.java
@@ -38,6 +38,7 @@ import org.apache.spark.memory.TaskMemoryManager;
 import org.apache.spark.shuffle.ShuffleWriteMetricsReporter;
 import org.apache.spark.shuffle.comet.CometShuffleChecksumSupport;
 import org.apache.spark.shuffle.comet.CometShuffleMemoryAllocator;
+import org.apache.spark.shuffle.comet.CometShuffleMemoryAllocatorTrait;
 import org.apache.spark.shuffle.comet.TooLargePageException;
 import org.apache.spark.sql.comet.execution.shuffle.CometUnsafeShuffleWriter;
 import org.apache.spark.sql.comet.execution.shuffle.ShuffleThreadPool;
@@ -110,7 +111,7 @@ public final class CometShuffleExternalSorter implements 
CometShuffleChecksumSup
   // The memory allocator for this sorter. It is used to allocate/free memory 
pages for this sorter.
   // Because we need to allocate off-heap memory regardless of configured 
Spark memory mode
   // (on-heap/off-heap), we need a separate memory allocator.
-  private final CometShuffleMemoryAllocator allocator;
+  private final CometShuffleMemoryAllocatorTrait allocator;
 
   /** Whether to write shuffle spilling file in async mode */
   private final boolean isAsync;
diff --git 
a/spark/src/main/java/org/apache/spark/sql/comet/execution/shuffle/CometDiskBlockWriter.java
 
b/spark/src/main/java/org/apache/spark/sql/comet/execution/shuffle/CometDiskBlockWriter.java
index f793874d..dcb9d99d 100644
--- 
a/spark/src/main/java/org/apache/spark/sql/comet/execution/shuffle/CometDiskBlockWriter.java
+++ 
b/spark/src/main/java/org/apache/spark/sql/comet/execution/shuffle/CometDiskBlockWriter.java
@@ -41,6 +41,7 @@ import org.apache.spark.serializer.SerializationStream;
 import org.apache.spark.serializer.SerializerInstance;
 import org.apache.spark.shuffle.ShuffleWriteMetricsReporter;
 import org.apache.spark.shuffle.comet.CometShuffleMemoryAllocator;
+import org.apache.spark.shuffle.comet.CometShuffleMemoryAllocatorTrait;
 import org.apache.spark.shuffle.sort.RowPartition;
 import org.apache.spark.sql.catalyst.expressions.UnsafeRow;
 import org.apache.spark.sql.types.StructType;
@@ -87,7 +88,7 @@ public final class CometDiskBlockWriter {
   static final int MAXIMUM_PAGE_SIZE_BYTES = 1 << 27;
 
   /** The Comet allocator used to allocate pages. */
-  private final CometShuffleMemoryAllocator allocator;
+  private final CometShuffleMemoryAllocatorTrait allocator;
 
   /** The serializer used to write rows to memory page. */
   private final SerializerInstance serializer;
@@ -435,12 +436,17 @@ public final class CometDiskBlockWriter {
               }
             });
 
+        long totalFreed = 0;
         for (CometDiskBlockWriter writer : currentWriters) {
           // Force to spill the writer in a synchronous way, otherwise, we may 
not be able to
           // acquire enough memory.
+          long used = writer.getActiveMemoryUsage();
+
           writer.doSpill(true);
 
-          if (allocator.getAvailableMemory() >= required) {
+          totalFreed += used;
+
+          if (totalFreed >= required) {
             break;
           }
         }
diff --git 
a/spark/src/main/java/org/apache/spark/sql/comet/execution/shuffle/SpillWriter.java
 
b/spark/src/main/java/org/apache/spark/sql/comet/execution/shuffle/SpillWriter.java
index cc8c04fd..3dc86b05 100644
--- 
a/spark/src/main/java/org/apache/spark/sql/comet/execution/shuffle/SpillWriter.java
+++ 
b/spark/src/main/java/org/apache/spark/sql/comet/execution/shuffle/SpillWriter.java
@@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.spark.memory.SparkOutOfMemoryError;
 import org.apache.spark.shuffle.ShuffleWriteMetricsReporter;
-import org.apache.spark.shuffle.comet.CometShuffleMemoryAllocator;
+import org.apache.spark.shuffle.comet.CometShuffleMemoryAllocatorTrait;
 import org.apache.spark.shuffle.sort.RowPartition;
 import org.apache.spark.sql.types.StructType;
 import org.apache.spark.unsafe.memory.MemoryBlock;
@@ -62,7 +62,7 @@ public abstract class SpillWriter {
   // The memory allocator for this sorter. It is used to allocate/free memory 
pages for this sorter.
   // Because we need to allocate off-heap memory regardless of configured 
Spark memory mode
   // (on-heap/off-heap), we need a separate memory allocator.
-  protected CometShuffleMemoryAllocator allocator;
+  protected CometShuffleMemoryAllocatorTrait allocator;
 
   protected Native nativeLib;
 
@@ -134,7 +134,7 @@ public abstract class SpillWriter {
         || pageCursor + required > currentPage.getBaseOffset() + 
currentPage.size()) {
       // TODO: try to find space in previous pages
       try {
-        currentPage = allocator.allocatePage(required);
+        currentPage = allocator.allocate(required);
       } catch (SparkOutOfMemoryError error) {
         try {
           // Cannot allocate enough memory, spill
@@ -155,7 +155,7 @@ public abstract class SpillWriter {
   public void initialCurrentPage(int required) {
     assert (currentPage == null);
     try {
-      currentPage = allocator.allocatePage(required);
+      currentPage = allocator.allocate(required);
     } catch (SparkOutOfMemoryError e) {
       logger.error("Unable to acquire {} bytes of memory", required);
       throw e;
diff --git 
a/spark/src/main/scala/org/apache/comet/CometSparkSessionExtensions.scala 
b/spark/src/main/scala/org/apache/comet/CometSparkSessionExtensions.scala
index 952ef39e..6a5c0efe 100644
--- a/spark/src/main/scala/org/apache/comet/CometSparkSessionExtensions.scala
+++ b/spark/src/main/scala/org/apache/comet/CometSparkSessionExtensions.scala
@@ -1162,8 +1162,22 @@ object CometSparkSessionExtensions extends Logging {
     }
   }
 
+  private[comet] def isOffHeapEnabled(conf: SQLConf): Boolean =
+    conf.contains("spark.memory.offHeap.enabled") &&
+      conf.getConfString("spark.memory.offHeap.enabled").toBoolean
+
+  // Copied from org.apache.spark.util.Utils which is private to Spark.
+  private[comet] def isTesting: Boolean = {
+    System.getenv("SPARK_TESTING") != null || 
System.getProperty("spark.testing") != null
+  }
+
+  // Check whether Comet shuffle is enabled:
+  // 1. `COMET_EXEC_SHUFFLE_ENABLED` is true
+  // 2. `spark.shuffle.manager` is set to `CometShuffleManager`
+  // 3. Off-heap memory is enabled || Spark/Comet unit testing
   private[comet] def isCometShuffleEnabled(conf: SQLConf): Boolean =
-    COMET_EXEC_SHUFFLE_ENABLED.get(conf) && isCometShuffleManagerEnabled(conf)
+    COMET_EXEC_SHUFFLE_ENABLED.get(conf) && isCometShuffleManagerEnabled(conf) 
&&
+      (isOffHeapEnabled(conf) || isTesting)
 
   private[comet] def getCometShuffleNotEnabledReason(conf: SQLConf): 
Option[String] = {
     if (!COMET_EXEC_SHUFFLE_ENABLED.get(conf)) {
diff --git 
a/spark/src/test/scala/org/apache/comet/exec/CometColumnarShuffleSuite.scala 
b/spark/src/test/scala/org/apache/comet/exec/CometColumnarShuffleSuite.scala
index ecc056dd..a2c2f665 100644
--- a/spark/src/test/scala/org/apache/comet/exec/CometColumnarShuffleSuite.scala
+++ b/spark/src/test/scala/org/apache/comet/exec/CometColumnarShuffleSuite.scala
@@ -40,6 +40,7 @@ import org.apache.comet.CometConf
 abstract class CometColumnarShuffleSuite extends CometTestBase with 
AdaptiveSparkPlanHelper {
   protected val adaptiveExecutionEnabled: Boolean
   protected val numElementsForceSpillThreshold: Int = 10
+  protected val useUnifiedMemoryAllocator: Boolean = true
 
   override protected def sparkConf: SparkConf = {
     val conf = super.sparkConf
@@ -57,6 +58,8 @@ abstract class CometColumnarShuffleSuite extends 
CometTestBase with AdaptiveSpar
         CometConf.COMET_COLUMNAR_SHUFFLE_SPILL_THRESHOLD.key -> 
numElementsForceSpillThreshold.toString,
         CometConf.COMET_EXEC_ENABLED.key -> "false",
         CometConf.COMET_SHUFFLE_MODE.key -> "jvm",
+        CometConf.COMET_COLUMNAR_SHUFFLE_UNIFIED_MEMORY_ALLOCATOR_IN_TEST.key 
->
+          useUnifiedMemoryAllocator.toString,
         CometConf.COMET_EXEC_SHUFFLE_ENABLED.key -> "true",
         CometConf.COMET_COLUMNAR_SHUFFLE_MEMORY_SIZE.key -> "1536m") {
         testFun
@@ -968,6 +971,13 @@ abstract class CometColumnarShuffleSuite extends 
CometTestBase with AdaptiveSpar
   }
 }
 
+class CometTestMemoryAllocatorShuffleSuite extends CometColumnarShuffleSuite {
+  override protected val asyncShuffleEnable: Boolean = false
+  override protected val adaptiveExecutionEnabled: Boolean = true
+  // Explicitly test with `CometTestShuffleMemoryAllocator`
+  override protected val useUnifiedMemoryAllocator: Boolean = false
+}
+
 class CometAsyncShuffleSuite extends CometColumnarShuffleSuite {
   override protected val asyncShuffleEnable: Boolean = true
 


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

Reply via email to