我使用普通 C 的 malloc()/realloc()/free() 实现了我自己的 VkAllocatorCallback。这是一个幼稚的实现,完全忽略了对齐参数。考虑到 64 位操作系统中的 malloc 总是返回具有 16(!)字节对齐的指针,这是相当大的对齐,这在我的测试中不会成为问题。请参阅参考资料。
为了信息的完整性,16 字节对齐也是 8/4/2 字节对齐。
我的代码如下:
/**
* PFN_vkAllocationFunction implementation
*/
void* allocationFunction(void* pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope){
printf("pAllocator's allocationFunction: <%s>, size: %u, alignment: %u, allocationScope: %d",
(USER_TYPE)pUserData, size, alignment, allocationScope);
// the allocation itself - ignore alignment, for while
void* ptr = malloc(size);//_aligned_malloc(size, alignment);
memset(ptr, 0, size);
printf(", return ptr* : 0x%p \n", ptr);
return ptr;
}
/**
* The PFN_vkFreeFunction implementation
*/
void freeFunction(void* pUserData, void* pMemory){
printf("pAllocator's freeFunction: <%s> ptr: 0x%p\n",
(USER_TYPE)pUserData, pMemory);
// now, the free operation !
free(pMemory);
}
/**
* The PFN_vkReallocationFunction implementation
*/
void* reallocationFunction(void* pUserData, void* pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope){
printf("pAllocator's REallocationFunction: <%s>, size %u, alignment %u, allocationScope %d \n",
(USER_TYPE)pUserData, size, alignment, allocationScope);
return realloc(pOriginal, size);
}
/**
* PFN_vkInternalAllocationNotification implementation
*/
void internalAllocationNotification(void* pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope){
printf("pAllocator's internalAllocationNotification: <%s>, size %uz, alignment %uz, allocationType %uz, allocationScope %s \n",
(USER_TYPE)pUserData,
size,
allocationType,
allocationScope);
}
/**
* PFN_vkInternalFreeNotification implementation
**/
void internalFreeNotification(void* pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope){
printf("pAllocator's internalFreeNotification: <%s>, size %uz, alignment %uz, allocationType %d, allocationScope %s \n",
(USER_TYPE)pUserData, size, allocationType, allocationScope);
}
/**
* Create Pallocator
* @param info - String for tracking Allocator usage
*/
static VkAllocationCallbacks* createPAllocator(const char* info){
VkAllocationCallbacks* m_allocator = (VkAllocationCallbacks*)malloc(sizeof(VkAllocationCallbacks));
memset(m_allocator, 0, sizeof(VkAllocationCallbacks));
m_allocator->pUserData = (void*)info;
m_allocator->pfnAllocation = (PFN_vkAllocationFunction)(&allocationFunction);
m_allocator->pfnReallocation = (PFN_vkReallocationFunction)(&reallocationFunction);
m_allocator->pfnFree = (PFN_vkFreeFunction)&freeFunction;
m_allocator->pfnInternalAllocation = (PFN_vkInternalAllocationNotification)&internalAllocationNotification;
m_allocator->pfnInternalFree = (PFN_vkInternalFreeNotification)&internalFreeNotification;
// storePAllocator(m_allocator);
return m_allocator;
}
`
我使用来自 VulkanSDK 的 Cube.c 示例来测试我的代码和假设。修改版本可在此处获取GitHub
输出示例:
pAllocator's allocationFunction: <Device>, size: 800, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061ECE40
pAllocator's allocationFunction: <RenderPass>, size: 128, alignment: 8, allocationScope: 1, return ptr* : 0x000000000623FAB0
pAllocator's allocationFunction: <ShaderModule>, size: 96, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061F2C30
pAllocator's allocationFunction: <ShaderModule>, size: 96, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061F8790
pAllocator's allocationFunction: <PipelineCache>, size: 152, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061F2590
pAllocator's allocationFunction: <Device>, size: 424, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061F8EB0
pAllocator's freeFunction: <ShaderModule> ptr: 0x00000000061F8790
pAllocator's freeFunction: <ShaderModule> ptr: 0x00000000061F2C30
pAllocator's allocationFunction: <Device>, size: 3448, alignment: 8, allocationScope: 1, return ptr* : 0x000000000624D260
pAllocator's allocationFunction: <Device>, size: 3448, alignment: 8, allocationScope: 1, return ptr* : 0x0000000006249A80
结论:
用户实现了 PFN_vkAllocationFunction、PFN_vkReallocationFunction、PFN_vkFreeFunction 真正代表 Vulkan执行 malloc/realoc/free 操作。不确定他们是否执行所有分配,因为 Vulkan可能会自行选择分配/释放某些部分。
我的实现提供的输出显示,在我的 Win 7-64/NVidia 中,请求的典型对齐是 8 个字节。这表明存在优化空间,例如托管内存,您可以在其中获取大量内存并为您的 Vulkan 应用程序(内存池)进行子分配。它可以*减少内存使用量(想想每个分配块之前的 8 个字节和之后的 8 个字节)。它也可能更快,因为 malloc() 调用可以持续更长的时间,而不是指向您自己的已分配内存池的直接指针。
至少对于我当前的 Vulkan 驱动程序,PFN_vkInternalAllocationNotification 和 PFN_vkInternalFreeNotification 不会运行。可能是我的 NVidia 驱动程序中的一个错误。稍后我会检查我的 AMD。
*pUserData 用于调试信息和/或管理。实际上,您可以使用它来传递一个 C++ 对象,并在那里完成所有需要的性能工作。这是一种显而易见的信息,但您可以为每个调用或 VkCreateXXX 对象更改它。
您可以对所有应用程序使用单个通用的 VkAllocatorCallBack 分配器,但我想使用自定义分配器可能会带来更好的结果。在我的测试中,VkSemaphore 创建显示了在小块(72 字节)的密集分配/释放上的典型模式,这可以通过在自定义分配器中重用内存上的先前块来解决。malloc()/free() 已经在可能的情况下重用了内存,但尝试使用我们自己的内存管理器很诱人,至少对于短暂的小内存块是这样。
内存对齐可能是实现 VkAllocationCallback 的一个问题(没有可用的 _aligned_realoc 函数,只有 _aligned_malloc 和 _aligned_free)。但前提是 Vulkan 请求的对齐大于malloc 的默认值(x86 为 8 字节,AMD64 为 16 字节等,必须检查 ARM 默认值)。但到目前为止,至少在 64 位操作系统上,看到 Vulkan 实际上请求的内存对齐方式低于 malloc() 默认值。
最后的想法:
只需将您找到的所有 VkAllocatorCallback* pAllocator 设置为 NULL,您就可以一直快乐地生活;)可能 Vulkan 的默认分配器已经比您自己做得更好了。
但...
Vulkan 的一大亮点是开发人员可以控制一切,包括内存管理。Khronos 演示文稿,幻灯片 6