Add ability to disable command memory pooling using MVK_CONFIG_USE_COMMAND_POOLING

environment variable.

Add ability to track MVKObjectPool allocation and pool residency counts.
This commit is contained in:
Bill Hollings 2020-03-11 17:50:49 -04:00
parent bf4d121897
commit 30658f05f5
7 changed files with 117 additions and 69 deletions

View File

@ -24,6 +24,9 @@ Released TBD
within a single `MTLRenderEncoder`. within a single `MTLRenderEncoder`.
- Increase value of `VkPhysicalDeviceLimits::minStorageBufferOffsetAlignment` - Increase value of `VkPhysicalDeviceLimits::minStorageBufferOffsetAlignment`
to `16` to avoid Metal validation assertions. to `16` to avoid Metal validation assertions.
- Add ability to disable command memory pooling using `MVK_CONFIG_USE_COMMAND_POOLING`
environment variable.

View File

@ -154,6 +154,15 @@ typedef unsigned long MTLLanguageVersion;
* be dynamically allocated in application memory when the descriptor set itself is allocated. * be dynamically allocated in application memory when the descriptor set itself is allocated.
* This setting is disabled by default, and MoltenVK will dynamically allocate descriptors * This setting is disabled by default, and MoltenVK will dynamically allocate descriptors
* when the containing descriptor set is allocated. * when the containing descriptor set is allocated.
*
* 8. The MVK_CONFIG_USE_COMMAND_POOLING runtime environment variable or MoltenVK compile-time
* build setting controls whether MoltenVK should use pools to manage memory used when
* adding commands to command buffers. If this environment variable is enabled, MoltenVK
* will use a pool to hold command resources for reuse during command execution. If this
* environment variable is disabled, command memory is allocated and destroyed each time
* a command is executed. This is a classic time-space trade off. When command pooling is
* active, the memory in the pool can be cleared via a call to the vkTrimCommandPoolKHR()
* command. This setting is enabled by default, and MoltenVK will pool command memory.
*/ */
typedef struct { typedef struct {

View File

@ -182,7 +182,9 @@ public:
/** Resets the command pool. */ /** Resets the command pool. */
VkResult reset( VkCommandPoolResetFlags flags); VkResult reset( VkCommandPoolResetFlags flags);
MVKCommandPool(MVKDevice* device, const VkCommandPoolCreateInfo* pCreateInfo); MVKCommandPool(MVKDevice* device,
const VkCommandPoolCreateInfo* pCreateInfo,
bool usePooling);
~MVKCommandPool() override; ~MVKCommandPool() override;

View File

@ -134,57 +134,58 @@ void MVKCommandPool::trim() {
#pragma mark Construction #pragma mark Construction
MVKCommandPool::MVKCommandPool(MVKDevice* device, MVKCommandPool::MVKCommandPool(MVKDevice* device,
const VkCommandPoolCreateInfo* pCreateInfo) : const VkCommandPoolCreateInfo* pCreateInfo,
bool usePooling) :
MVKVulkanAPIDeviceObject(device), MVKVulkanAPIDeviceObject(device),
_queueFamilyIndex(pCreateInfo->queueFamilyIndex), _queueFamilyIndex(pCreateInfo->queueFamilyIndex),
_commandBufferPool(device), _commandBufferPool(device, usePooling),
_commandEncodingPool(this), _commandEncodingPool(this),
_cmdPipelineBarrierPool(this), _cmdPipelineBarrierPool(this, usePooling),
_cmdBindPipelinePool(this), _cmdBindPipelinePool(this, usePooling),
_cmdBeginRenderPassPool(this), _cmdBeginRenderPassPool(this, usePooling),
_cmdNextSubpassPool(this), _cmdNextSubpassPool(this, usePooling),
_cmdExecuteCommandsPool(this), _cmdExecuteCommandsPool(this, usePooling),
_cmdEndRenderPassPool(this), _cmdEndRenderPassPool(this, usePooling),
_cmdBindDescriptorSetsPool(this), _cmdBindDescriptorSetsPool(this, usePooling),
_cmdSetViewportPool(this), _cmdSetViewportPool(this, usePooling),
_cmdSetScissorPool(this), _cmdSetScissorPool(this, usePooling),
_cmdSetLineWidthPool(this), _cmdSetLineWidthPool(this, usePooling),
_cmdSetDepthBiasPool(this), _cmdSetDepthBiasPool(this, usePooling),
_cmdSetBlendConstantsPool(this), _cmdSetBlendConstantsPool(this, usePooling),
_cmdSetDepthBoundsPool(this), _cmdSetDepthBoundsPool(this, usePooling),
_cmdSetStencilCompareMaskPool(this), _cmdSetStencilCompareMaskPool(this, usePooling),
_cmdSetStencilWriteMaskPool(this), _cmdSetStencilWriteMaskPool(this, usePooling),
_cmdSetStencilReferencePool(this), _cmdSetStencilReferencePool(this, usePooling),
_cmdBindVertexBuffersPool(this), _cmdBindVertexBuffersPool(this, usePooling),
_cmdBindIndexBufferPool(this), _cmdBindIndexBufferPool(this, usePooling),
_cmdDrawPool(this), _cmdDrawPool(this, usePooling),
_cmdDrawIndexedPool(this), _cmdDrawIndexedPool(this, usePooling),
_cmdDrawIndirectPool(this), _cmdDrawIndirectPool(this, usePooling),
_cmdDrawIndexedIndirectPool(this), _cmdDrawIndexedIndirectPool(this, usePooling),
_cmdCopyImagePool(this), _cmdCopyImagePool(this, usePooling),
_cmdBlitImagePool(this), _cmdBlitImagePool(this, usePooling),
_cmdResolveImagePool(this), _cmdResolveImagePool(this, usePooling),
_cmdFillBufferPool(this), _cmdFillBufferPool(this, usePooling),
_cmdUpdateBufferPool(this), _cmdUpdateBufferPool(this, usePooling),
_cmdCopyBufferPool(this), _cmdCopyBufferPool(this, usePooling),
_cmdBufferImageCopyPool(this), _cmdBufferImageCopyPool(this, usePooling),
_cmdClearAttachmentsPool(this), _cmdClearAttachmentsPool(this, usePooling),
_cmdClearImagePool(this), _cmdClearImagePool(this, usePooling),
_cmdBeginQueryPool(this), _cmdBeginQueryPool(this, usePooling),
_cmdEndQueryPool(this), _cmdEndQueryPool(this, usePooling),
_cmdWriteTimestampPool(this), _cmdWriteTimestampPool(this, usePooling),
_cmdResetQueryPoolPool(this), _cmdResetQueryPoolPool(this, usePooling),
_cmdCopyQueryPoolResultsPool(this), _cmdCopyQueryPoolResultsPool(this, usePooling),
_cmdPushConstantsPool(this), _cmdPushConstantsPool(this, usePooling),
_cmdDispatchPool(this), _cmdDispatchPool(this, usePooling),
_cmdDispatchIndirectPool(this), _cmdDispatchIndirectPool(this, usePooling),
_cmdPushDescriptorSetPool(this), _cmdPushDescriptorSetPool(this, usePooling),
_cmdPushSetWithTemplatePool(this), _cmdPushSetWithTemplatePool(this, usePooling),
_cmdDebugMarkerBeginPool(this), _cmdDebugMarkerBeginPool(this, usePooling),
_cmdDebugMarkerEndPool(this), _cmdDebugMarkerEndPool(this, usePooling),
_cmdDebugMarkerInsertPool(this), _cmdDebugMarkerInsertPool(this, usePooling),
_cmdSetResetEventPool(this), _cmdSetResetEventPool(this, usePooling),
_cmdWaitEventsPool(this) _cmdWaitEventsPool(this, usePooling)
// when extending be sure to add to trim() as well // when extending be sure to add to trim() as well
{} {}

View File

@ -666,16 +666,6 @@ public:
/** Performance statistics. */ /** Performance statistics. */
MVKPerformanceStatistics _performanceStatistics; MVKPerformanceStatistics _performanceStatistics;
// Indicates whether semaphores should use a MTLFence if available.
// Set by the MVK_ALLOW_METAL_FENCES environment variable if MTLFences are available.
// This should be a temporary fix after some repair to semaphore handling.
bool _useMTLFenceForSemaphores;
// Indicates whether semaphores should use a MTLEvent if available.
// Set by the MVK_ALLOW_METAL_EVENTS environment variable if MTLEvents are available.
// This should be a temporary fix after some repair to semaphore handling.
bool _useMTLEventForSemaphores;
#pragma mark Construction #pragma mark Construction
@ -724,6 +714,9 @@ protected:
id<MTLBuffer> _globalVisibilityResultMTLBuffer; id<MTLBuffer> _globalVisibilityResultMTLBuffer;
uint32_t _globalVisibilityQueryCount; uint32_t _globalVisibilityQueryCount;
std::mutex _vizLock; std::mutex _vizLock;
bool _useMTLFenceForSemaphores;
bool _useMTLEventForSemaphores;
bool _useCommandPooling;
}; };

View File

@ -2395,7 +2395,7 @@ void MVKDevice::destroyRenderPass(MVKRenderPass* mvkRP,
MVKCommandPool* MVKDevice::createCommandPool(const VkCommandPoolCreateInfo* pCreateInfo, MVKCommandPool* MVKDevice::createCommandPool(const VkCommandPoolCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator) { const VkAllocationCallbacks* pAllocator) {
return new MVKCommandPool(this, pCreateInfo); return new MVKCommandPool(this, pCreateInfo, _useCommandPooling);
} }
void MVKDevice::destroyCommandPool(MVKCommandPool* mvkCmdPool, void MVKDevice::destroyCommandPool(MVKCommandPool* mvkCmdPool,
@ -2650,16 +2650,30 @@ void MVKDevice::initPhysicalDevice(MVKPhysicalDevice* physicalDevice, const VkDe
_pProperties = &_physicalDevice->_properties; _pProperties = &_physicalDevice->_properties;
_pMemoryProperties = &_physicalDevice->_memoryProperties; _pMemoryProperties = &_physicalDevice->_memoryProperties;
// Indicates whether semaphores should use a MTLFence if available.
// Set by the MVK_ALLOW_METAL_FENCES environment variable if MTLFences are available.
// This should be a temporary fix after some repair to semaphore handling.
_useMTLFenceForSemaphores = false; _useMTLFenceForSemaphores = false;
if (_pMetalFeatures->fences) { if (_pMetalFeatures->fences) {
MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLFenceForSemaphores, MVK_ALLOW_METAL_FENCES); MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLFenceForSemaphores, MVK_ALLOW_METAL_FENCES);
} }
// Indicates whether semaphores should use a MTLEvent if available.
// Set by the MVK_ALLOW_METAL_EVENTS environment variable if MTLEvents are available.
// This should be a temporary fix after some repair to semaphore handling.
_useMTLEventForSemaphores = false; _useMTLEventForSemaphores = false;
if (_pMetalFeatures->events) { if (_pMetalFeatures->events) {
MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLEventForSemaphores, MVK_ALLOW_METAL_EVENTS); MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLEventForSemaphores, MVK_ALLOW_METAL_EVENTS);
} }
MVKLogInfo("Using %s for Vulkan semaphores.", _useMTLFenceForSemaphores ? "MTLFence" : (_useMTLEventForSemaphores ? "MTLEvent" : "emulation")); MVKLogInfo("Using %s for Vulkan semaphores.", _useMTLFenceForSemaphores ? "MTLFence" : (_useMTLEventForSemaphores ? "MTLEvent" : "emulation"));
#ifndef MVK_CONFIG_USE_COMMAND_POOLING
# define MVK_CONFIG_USE_COMMAND_POOLING 1
#endif
_useCommandPooling = MVK_CONFIG_USE_COMMAND_POOLING;
MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useCommandPooling, MVK_CONFIG_USE_COMMAND_POOLING);
#if MVK_MACOS #if MVK_MACOS
// If we have selected a high-power GPU and want to force the window system // If we have selected a high-power GPU and want to force the window system
// to use it, force the window system to use a high-power GPU by calling the // to use it, force the window system to use a high-power GPU by calling the

View File

@ -23,6 +23,9 @@
#include <mutex> #include <mutex>
#pragma mark -
#pragma mark MVKLinkableMixin
/** /**
* Instances of sublcasses of this mixin can participate in a typed linked list or pool. * Instances of sublcasses of this mixin can participate in a typed linked list or pool.
* A simple implementation of the CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). * A simple implementation of the CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern).
@ -47,6 +50,13 @@ protected:
#pragma mark - #pragma mark -
#pragma mark MVKObjectPool #pragma mark MVKObjectPool
/** Track pool stats. */
typedef struct {
uint64_t created = 0;
uint64_t alive = 0;
uint64_t resident = 0;
} MVKObjectPoolCounts;
/** /**
* Manages a pool of instances of a particular object type. * Manages a pool of instances of a particular object type.
* *
@ -80,9 +90,13 @@ public:
* aquireObject() and returnObject() must be made from the same thread. * aquireObject() and returnObject() must be made from the same thread.
*/ */
T* acquireObject() { T* acquireObject() {
T* obj = VK_NULL_HANDLE; T* obj = nullptr;
if (_isPooling) { obj = nextObject(); } if (_isPooling) { obj = nextObject(); }
if ( !obj ) { obj = newObject(); } if ( !obj ) {
obj = newObject();
_counts.created++;
_counts.alive++;
}
return obj; return obj;
} }
@ -102,11 +116,12 @@ public:
if (_isPooling) { if (_isPooling) {
if (_tail) { _tail->_next = obj; } if (_tail) { _tail->_next = obj; }
obj->_next = VK_NULL_HANDLE; obj->_next = nullptr;
_tail = obj; _tail = obj;
if ( !_head ) { _head = obj; } if ( !_head ) { _head = obj; }
_counts.resident++;
} else { } else {
obj->destroy(); destroyObject(obj);
} }
} }
@ -122,12 +137,15 @@ public:
returnObject(obj); returnObject(obj);
} }
/** Clears all the objects from this pool, deleting each one. This method is thread-safe. */ /** Clears all the objects from this pool, destroying each one. This method is thread-safe. */
void clear() { void clear() {
std::lock_guard<std::mutex> lock(_lock); std::lock_guard<std::mutex> lock(_lock);
while ( T* obj = nextObject() ) { obj->destroy(); } while ( T* obj = nextObject() ) { destroyObject(obj); }
} }
/** Returns the current counts. */
MVKObjectPoolCounts getCounts() { return _counts; }
/** /**
* Configures this instance to either use pooling, or not, depending on the * Configures this instance to either use pooling, or not, depending on the
* value of isPooling, which defaults to true if not indicated explicitly. * value of isPooling, which defaults to true if not indicated explicitly.
@ -146,9 +164,10 @@ protected:
T* nextObject() { T* nextObject() {
T* obj = _head; T* obj = _head;
if (obj) { if (obj) {
_head = (T*)obj->_next; // Will be null for last object in pool _head = (T*)obj->_next; // Will be null for last object in pool
if ( !_head ) { _tail = VK_NULL_HANDLE; } // If last, also clear tail if ( !_head ) { _tail = nullptr; } // If last, also clear tail
obj->_next = VK_NULL_HANDLE; // Objects in the wild should never think they are still part of this pool obj->_next = nullptr; // Objects in the wild should never think they are still part of this pool
_counts.resident--;
} }
return obj; return obj;
} }
@ -156,9 +175,16 @@ protected:
/** Returns a new instance of the type of object managed by this pool. */ /** Returns a new instance of the type of object managed by this pool. */
virtual T* newObject() = 0; virtual T* newObject() = 0;
/** Destroys the object. */
void destroyObject(T* obj) {
obj->destroy();
_counts.alive--;
}
std::mutex _lock; std::mutex _lock;
T* _head = nullptr; T* _head = nullptr;
T* _tail = nullptr; T* _tail = nullptr;
bool _isPooling; bool _isPooling;
MVKObjectPoolCounts _counts;
}; };