プレーン C の malloc()/realloc()/free() を使用して、独自の VkAllocatorCallback を実装しました。これは単純な実装であり、配置パラメーターを完全に無視します。64 ビット OS の 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 ビット OS では、 Vulkan が実際には malloc() のデフォルトよりも低い位置合わせでメモリを要求していることがわかります。
最終的な考え:
見つかったすべての VkAllocatorCallback* pAllocator を NULL に設定するだけで、最後まで幸せに過ごすことができます ;) おそらく、Vulkan のデフォルトのアロケータは、すでに自分よりもうまく機能しています。
しかし...
Vulkan の利点のハイライトの 1 つは、開発者がメモリ管理を含むすべてを制御できることです。クロノスのプレゼンテーション、スライド 6