This is an automated email from the ASF dual-hosted git repository.
hongze pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-gluten.git
The following commit(s) were added to refs/heads/main by this push:
new ee99bf0082 [VL] Acquire off-heap global memory via the new API for
off-heap broadcast exchange (#9075)
ee99bf0082 is described below
commit ee99bf008204296958611966042dfaa8bdda072d
Author: Hongze Zhang <[email protected]>
AuthorDate: Fri Mar 21 19:19:05 2025 +0000
[VL] Acquire off-heap global memory via the new API for off-heap broadcast
exchange (#9075)
---
.../apache/spark/memory/GlobalOffHeapMemory.scala | 22 +++++++++++++--
.../execution/unsafe/UnsafeBytesBufferArray.scala | 32 +++++++++++++---------
.../unsafe/UnsafeColumnarBuildSideRelation.scala | 17 +++---------
.../spark/memory/GlobalOffHeapMemorySuite.scala | 2 +-
.../UnsafeColumnarBuildSideRelationTest.scala | 7 +----
5 files changed, 45 insertions(+), 35 deletions(-)
diff --git
a/backends-velox/src/main/scala/org/apache/spark/memory/GlobalOffHeapMemory.scala
b/backends-velox/src/main/scala/org/apache/spark/memory/GlobalOffHeapMemory.scala
index cfaf797869..3e25fdfc74 100644
---
a/backends-velox/src/main/scala/org/apache/spark/memory/GlobalOffHeapMemory.scala
+++
b/backends-velox/src/main/scala/org/apache/spark/memory/GlobalOffHeapMemory.scala
@@ -24,9 +24,26 @@ import org.apache.spark.storage.BlockId
import java.lang.reflect.Field
import java.util.UUID
+/**
+ * API #acuqire is for reserving some global off-heap memory from Spark memory
manager. Once
+ * reserved, Spark tasks will have less off-heap memory to use because of the
reservation.
+ *
+ * Note the API #acuqire doesn't trigger spills on Spark tasks although OOM
may be encountered.
+ *
+ * The utility internally relies on the Spark storage memory pool. As Spark
doesn't expect trait
+ * BlockId to be extended by user, TestBlockId is chosen for the storage
memory reservations.
+ */
object GlobalOffHeapMemory {
private val FIELD_MEMORY_MANAGER: Field = {
- val f = classOf[TaskMemoryManager].getDeclaredField("memoryManager")
+ val f =
+ try {
+ classOf[TaskMemoryManager].getDeclaredField("memoryManager")
+ } catch {
+ case e: Exception =>
+ throw new GlutenException(
+ "Unable to find field TaskMemoryManager#memoryManager via
reflection",
+ e)
+ }
f.setAccessible(true)
f
}
@@ -38,7 +55,7 @@ object GlobalOffHeapMemory {
MemoryMode.OFF_HEAP)
}
- def free(numBytes: Long): Unit = {
+ def release(numBytes: Long): Unit = {
memoryManager().releaseStorageMemory(numBytes, MemoryMode.OFF_HEAP)
}
@@ -49,6 +66,7 @@ object GlobalOffHeapMemory {
}
val tc = TaskContext.get()
if (tc != null) {
+ // This may happen in test code that mocks the task context without
booting up SparkEnv.
return
FIELD_MEMORY_MANAGER.get(tc.taskMemoryManager()).asInstanceOf[MemoryManager]
}
throw new GlutenException(
diff --git
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeBytesBufferArray.scala
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeBytesBufferArray.scala
index aafc76ac08..ecfb777178 100644
---
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeBytesBufferArray.scala
+++
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeBytesBufferArray.scala
@@ -16,11 +16,15 @@
*/
package org.apache.spark.sql.execution.unsafe
+import org.apache.gluten.exception.GlutenException
+
+import org.apache.spark.SparkEnv
import org.apache.spark.annotation.Experimental
import org.apache.spark.internal.Logging
-import org.apache.spark.memory.{MemoryConsumer, MemoryMode, TaskMemoryManager}
+import org.apache.spark.memory.GlobalOffHeapMemory
import org.apache.spark.unsafe.Platform
import org.apache.spark.unsafe.array.LongArray
+import org.apache.spark.unsafe.memory.MemoryAllocator
/**
* Used to store broadcast variable off-heap memory for broadcast variable.
The underlying data
@@ -35,14 +39,8 @@ import org.apache.spark.unsafe.array.LongArray
*/
// scalastyle:off no.finalize
@Experimental
-case class UnsafeBytesBufferArray(
- arraySize: Int,
- bytesBufferLengths: Array[Int],
- totalBytes: Long,
- tmm: TaskMemoryManager)
- extends MemoryConsumer(tmm, MemoryMode.OFF_HEAP)
- with Logging {
-
+case class UnsafeBytesBufferArray(arraySize: Int, bytesBufferLengths:
Array[Int], totalBytes: Long)
+ extends Logging {
{
assert(
arraySize == bytesBufferLengths.length,
@@ -50,6 +48,7 @@ case class UnsafeBytesBufferArray(
"not equal to buffer lengths!")
assert(totalBytes >= 0, "Unsafe buffer array total bytes can't be
negative!")
}
+ private val allocatedBytes = (totalBytes + 7) / 8 * 8
/**
* A single array to store all bytesBufferArray's value, it's inited once
when first time get
@@ -64,8 +63,6 @@ case class UnsafeBytesBufferArray(
bytesBufferLengths.init.scanLeft(0)(_ + _)
}
- override def spill(l: Long, memoryConsumer: MemoryConsumer): Long = 0L
-
/**
* Put bytesBuffer at specified array index.
*
@@ -79,7 +76,16 @@ case class UnsafeBytesBufferArray(
assert(bytesBuffer.length == bytesBufferLengths(index))
// first to allocate underlying long array
if (null == longArray && index == 0) {
- longArray = allocateArray((totalBytes + 7) / 8)
+ if (!GlobalOffHeapMemory.acquire(allocatedBytes)) {
+ val memoryManager = SparkEnv.get.memoryManager
+ val offHeapMemoryTotal =
+ memoryManager.maxOffHeapStorageMemory +
memoryManager.offHeapExecutionMemoryUsed
+ throw new GlutenException(
+ s"Spark off-heap memory is exhausted." +
+ s" Storage: ${memoryManager.offHeapStorageMemoryUsed} /
$offHeapMemoryTotal," +
+ s" execution: ${memoryManager.offHeapExecutionMemoryUsed} /
$offHeapMemoryTotal")
+ }
+ longArray = new
LongArray(MemoryAllocator.UNSAFE.allocate(allocatedBytes))
}
Platform.copyMemory(
@@ -132,8 +138,8 @@ case class UnsafeBytesBufferArray(
override def finalize(): Unit = {
try {
if (longArray != null) {
- freeArray(longArray)
longArray = null
+ GlobalOffHeapMemory.release(allocatedBytes)
}
} finally {
super.finalize()
diff --git
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala
index 47f659d189..09ff5dabd9 100644
---
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala
+++
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala
@@ -25,10 +25,8 @@ import org.apache.gluten.sql.shims.SparkShimLoader
import org.apache.gluten.utils.ArrowAbiUtil
import org.apache.gluten.vectorized.{ColumnarBatchSerializerJniWrapper,
NativeColumnarToRowJniWrapper}
-import org.apache.spark.{SparkEnv, TaskContext}
import org.apache.spark.annotation.Experimental
import org.apache.spark.internal.Logging
-import org.apache.spark.memory.TaskMemoryManager
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions.{Attribute, Expression,
UnsafeProjection, UnsafeRow}
import org.apache.spark.sql.catalyst.plans.physical.{BroadcastMode,
IdentityBroadcastMode}
@@ -78,8 +76,7 @@ case class UnsafeColumnarBuildSideRelation(
UnsafeBytesBufferArray(
bytesBufferArray.length,
bytesBufferArray.map(_.length),
- bytesBufferArray.map(_.length.toLong).sum,
- TaskContext.get().taskMemoryManager
+ bytesBufferArray.map(_.length.toLong).sum
),
mode
)
@@ -123,15 +120,12 @@ case class UnsafeColumnarBuildSideRelation(
// scalastyle:off
/**
- * This is used in Broadcast, shared by multiple tasks, we use off-heap
memory to reduce on-heap
- * pressure Similar to
+ * We use off-heap memory to reduce on-heap pressure Similar to
*
https://github.com/apache/spark/blob/master/sql/core/src/main/scala/org/apache/spark/sql/execution/joins/HashedRelation.scala#L389-L410
*/
// scalastyle:on
- val taskMemoryManager = new TaskMemoryManager(SparkEnv.get.memoryManager,
0)
- batches =
- UnsafeBytesBufferArray(totalArraySize, bytesBufferLengths, totalBytes,
taskMemoryManager)
+ batches = UnsafeBytesBufferArray(totalArraySize, bytesBufferLengths,
totalBytes)
for (i <- 0 until totalArraySize) {
val length = bytesBufferLengths(i)
@@ -148,10 +142,7 @@ case class UnsafeColumnarBuildSideRelation(
val bytesBufferLengths = kryo.readObject(in, classOf[Array[Int]])
val totalBytes = in.readLong()
- val taskMemoryManager = new TaskMemoryManager(SparkEnv.get.memoryManager,
0)
-
- batches =
- UnsafeBytesBufferArray(totalArraySize, bytesBufferLengths, totalBytes,
taskMemoryManager)
+ batches = UnsafeBytesBufferArray(totalArraySize, bytesBufferLengths,
totalBytes)
for (i <- 0 until totalArraySize) {
val length = bytesBufferLengths(i)
diff --git
a/backends-velox/src/test/scala/org/apache/spark/memory/GlobalOffHeapMemorySuite.scala
b/backends-velox/src/test/scala/org/apache/spark/memory/GlobalOffHeapMemorySuite.scala
index 2a25d09d3b..9ac890e87a 100644
---
a/backends-velox/src/test/scala/org/apache/spark/memory/GlobalOffHeapMemorySuite.scala
+++
b/backends-velox/src/test/scala/org/apache/spark/memory/GlobalOffHeapMemorySuite.scala
@@ -95,7 +95,7 @@ class GlobalOffHeapMemorySuite extends AnyFunSuite with
BeforeAndAfterAll {
Collections.emptyMap())
Assert.assertTrue(GlobalOffHeapMemory.acquire(300))
Assert.assertEquals(100, consumer.borrow(200))
- GlobalOffHeapMemory.free(10)
+ GlobalOffHeapMemory.release(10)
Assert.assertEquals(10, consumer.borrow(50))
}
}
diff --git
a/backends-velox/src/test/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelationTest.scala
b/backends-velox/src/test/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelationTest.scala
index f47c8bd562..9aed31476e 100644
---
a/backends-velox/src/test/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelationTest.scala
+++
b/backends-velox/src/test/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelationTest.scala
@@ -17,7 +17,6 @@
package org.apache.spark.sql.execution.unsafe;
import org.apache.spark.{SparkConf, SparkEnv}
-import org.apache.spark.memory.{TaskMemoryManager, UnifiedMemoryManager}
import org.apache.spark.serializer.{JavaSerializer, KryoSerializer}
import org.apache.spark.sql.catalyst.expressions.AttributeReference
import org.apache.spark.sql.catalyst.plans.physical.IdentityBroadcastMode
@@ -37,9 +36,6 @@ class UnsafeColumnarBuildSideRelationTest extends
SharedSparkSession {
override def beforeAll(): Unit = {
super.beforeAll()
- val taskMemoryManager = new TaskMemoryManager(
- new UnifiedMemoryManager(SparkEnv.get.conf, Long.MaxValue, Long.MaxValue
/ 2, 1),
- 0)
val a = AttributeReference("a", StringType, nullable = false, null)()
val output = Seq(a)
val totalArraySize = 1
@@ -48,8 +44,7 @@ class UnsafeColumnarBuildSideRelationTest extends
SharedSparkSession {
val bytesArray = UnsafeBytesBufferArray(
1,
perArraySize,
- 10,
- taskMemoryManager
+ 10
)
bytesArray.putBytesBuffer(0, "1234567890".getBytes())
unsafeRelWithIdentityMode = UnsafeColumnarBuildSideRelation(
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]