Note:本文要求讀者對UnifiedMemoryManager的原理有基本的了解。希望讀者能與作者一起探討。
本文試圖分析UnifiedMemoryManager中的maybeGrowExecutionPool()方法。事實上,該方法是UnifiedMemoryManager的 acquireExecutionMemory()方法的內(nèi)部方法。但個人覺得,理解清楚該方法有助于更好地理解UnifiedMemoryManager的工作原理。
先來看看該方法的全部源碼:
def maybeGrowExecutionPool(extraMemoryNeeded: Long): Unit = {
if (extraMemoryNeeded > 0) {
// There is not enough free memory in the execution pool, so try to reclaim memory from
// storage. We can reclaim any free memory from the storage pool. If the storage pool
// has grown to become larger than `storageRegionSize`, we can evict blocks and reclaim
// the memory that storage has borrowed from execution.
val memoryReclaimableFromStorage = math.max(
storagePool.memoryFree,
storagePool.poolSize - storageRegionSize)
if (memoryReclaimableFromStorage > 0) {
// Only reclaim as much space as is necessary and available:
val spaceToReclaim = storagePool.freeSpaceToShrinkPool(
math.min(extraMemoryNeeded, memoryReclaimableFromStorage))
storagePool.decrementPoolSize(spaceToReclaim)
executionPool.incrementPoolSize(spaceToReclaim)
}
}
}
首先,該方法何時被調(diào)用?
當(dāng)execution pool的空閑內(nèi)存不夠用時,則該方法會被調(diào)用。從該方法的名字就可以得知,該方法試圖增長execution pool的大小。
那么,如何增長execution pool呢?
通過閱讀方法中的注釋,我們可以看到有兩種方式:
- storage pool中有空閑內(nèi)存,則借用storage pool中的空閑內(nèi)存
- storage pool的大小超過了
storageRegionSize,則驅(qū)逐存儲在storage pool中的blocks,來回收storage pool從execution pool中借走的內(nèi)存。
結(jié)合上述兩種情況,本文針對該方法中的max()部分,展開更具體的討論。在不同的情況下,execution pool能使用的究竟是哪部分內(nèi)存?
val memoryReclaimableFromStorage = math.max(
storagePool.memoryFree,
storagePool.poolSize - storageRegionSize)
1.storage pool沒有空閑內(nèi)存可用。此時,有兩種情況:
a) storage poolSize <= storageRegionSize: execution pool不能驅(qū)逐 storage pool中的blocks來獲取更多的內(nèi)存。此時,execution pool只能等待內(nèi)存主動釋放,來獲取更多內(nèi)存。
對應(yīng)于max: memoryFree = 0, poolSize - storageRegionSize <= 0
b) storage poolSize > storageRegionSize: execution pool可以驅(qū)逐storage pool中超過storageRegionSize的那部分內(nèi)存中的blocks,來回收之前被storage pool借走的內(nèi)存。
對應(yīng)于max: memoryFree = 0, poolSize - storageRegionSize > 0
2.storage pool有空閑內(nèi)存可用。此時情況復(fù)雜些:
a) storage poolSize <= storageRegionSize: execution pool只能借用storage pool的空閑內(nèi)存。
對應(yīng)與max: memoryFree > 0, poolSize - storageRegionSize < 0
b) storage poolSize > storageRegionSize:此時又有兩種情況:
b.1) storage pool usedMemory <= storageRegionSize:
其中,usedMemory = poolSize - memoryFree。則,可得memoryFree >= poolSize - storageRegionSize。此時,execution pool的可用內(nèi)存僅有storage pool的空閑內(nèi)存。
b.2) storage pool usedMemory > storageRegionSize:
顯然,此時memoryFree < poolSize - storageRegionSize。此時,execution pool最多可以利用的內(nèi)存包括storage pool的空閑內(nèi)存以及通過驅(qū)逐storage pool超過storageRegionSize的存儲blocks而回收的內(nèi)存。當(dāng)然,spark會優(yōu)先使用空閑內(nèi)存。當(dāng)空閑內(nèi)存不夠時,再通過驅(qū)逐storage pool的blocks回收內(nèi)存。
總結(jié):可見,并不是當(dāng)storage poolSize > storageRegionSize的時候,execution pool就會去驅(qū)逐storage pool中的blocks或者storage pool就沒有空閑內(nèi)存可用。而當(dāng)storage poolSize < storageRegionSize的時候,
execution pool必然不能驅(qū)逐storage pool中的blocks。