4

私はVulkan Memory Allocation - Memory Hostを読んでおり、単純な malloc/realloc/free 関数を使用して VkAllocationCallbacks を実装できるようです。

typedef struct VkAllocationCallbacks {
   void*                                   pUserData;
   PFN_vkAllocationFunction                pfnAllocation;
   PFN_vkReallocationFunction              pfnReallocation;
   PFN_vkFreeFunction                      pfnFree;
   PFN_vkInternalAllocationNotification    pfnInternalAllocation;
   PFN_vkInternalFreeNotification          pfnInternalFree;
} VkAllocationCallbacks;

しかし、独自の vkAllocationCallback を実装する理由として考えられるのは、次の 2 つだけです。

  • Vulkan API によるメモリ使用量のログと追跡。
  • 一種のヒープ メモリ管理を実装します。これは、何度も何度も使用および再利用される大量のメモリです。明らかに、これはやり過ぎであり、マネージ メモリと同じ種類の問題 (Java JVM の場合) に悩まされる可能性があります。

ここで何か不足していますか?vkAllocationCallbacks を実装する価値があるのは、どのような種類のアプリケーションですか?

4

3 に答える 3

7

仕様から: 「ほとんどのメモリ割り当てはクリティカル パスから外れているため、これはパフォーマンス機能として意図されたものではありません。むしろ、これは特定の組み込みシステムで、デバッグの目的で役立つ場合があります (たとえば、すべてのホスト割り当ての後にガード ページを配置する)。またはメモリ割り当てログ用。」

組み込みシステムでは、開始時にすべてのメモリを取得している可能性があるため、タンクに何も残っていない可能性があるため、ドライバーが malloc を呼び出す必要はありません。ガード ページとメモリ ログ (デバッグ ビルドのみ) は、用心深い/好奇心旺盛なユーザーに役立ちます。

どこかのスライドを読みました (場所を思い出せません、申し訳ありません)。malloc/realloc/free にフィードスルーするだけの割り当てコールバックは絶対に実装すべきではないということを読みました。たとえば、小さな割り当てをプールに統合するなど)。

割り当てコールバックを実装する必要があるかどうかわからない場合は、割り当てコールバックを実装する必要はなく、実装する必要があるかもしれないと心配する必要もないと思います。

それらは、特定のユースケースと、本当にすべてを管理したい人のためにあると思います。

于 2016-04-29T19:39:59.727 に答える
4

この回答は、他の回答の一部の情報を明確にして修正する試みです...

何をするにしても、Vulkan アロケーターに malloc/free/realloc を使用しないでください。Vulkan は、アラインされたメモリ コピーを使用してメモリを移動できます。アラインされていない割り当てを使用すると、メモリが破損し、悪いことが起こります。腐敗も明らかな形で現れないかもしれません。代わりに、posix のaligned_alloc/aligned_free/aligned_reallocを使用してください。それらは、ほとんどのシステムの「malloc.h」にあります。(windows では _aligned_alloc,ect を使用します) 関数aligned_realloc はよく知られていませんが、そこにあります (そして何年も前から存在しています)。ところで、私のテストカードの割り当てには、いたるところにアライメントリクエストがありました。

アプリケーション固有のアロケーターを Vulkan に渡すことについて明らかでないことの 1 つは、少なくとも一部の Vulkan オブジェクトがアロケーターを「記憶」していることです。たとえば、vkcreateinstance 関数にアロケーターを渡しましたが、他のオブジェクト (アロケーターにも nullptr を渡していた) を割り当てるときに、アロケーターからメッセージが送られてくるのを見て非常に驚きました。vulkan インスタンスとやり取りするオブジェクトによって、インスタンスが追加の割り当てを行う可能性があるため、考えるのをやめたとき、それは理にかなっています。

個々のアロケーターを作成して特定の割り当てタスクに合わせて調整できるため、これはすべて Vulkan のパフォーマンスに影響します。これは、プロセスの起動時間に影響を与える可能性があります。しかし、より重要なことは、たとえばインスタンスの割り当てを互いに近くに配置する「ブロック」アロケーターは、キャッシュの一貫性を高める可能性があるため、全体的なパフォーマンスに影響を与える可能性があることです。(割り当てをメモリ全体に分散させるのではなく) この種のパフォーマンスの「強化」は非常に推測に基づくものですが、慎重に調整されたアプリケーションが影響を与える可能性があることを認識しています。(言うまでもなく、Vulkan にはもっと注目に値する多くのパフォーマンス クリティカル パスがあります。)

Vulkan のビルトイン アロケーター (私のテスト カード) に比べてパフォーマンスが非常に悪いため、aligned_alloc クラスの関数を「リリース」アロケーターとして使用しようとしないでください。単純なプログラムでも、Vulkan のアロケーターと比較すると、非常に顕著なパフォーマンスの違いがありました。(申し訳ありませんが、タイミング情報を収集していませんでしたが、長い起動時間に繰り返し座っているつもりはありませんでした。)

デバッグに関しては、平凡な古い printf のような単純なものでさえ、アロケーター内で啓発することができます。簡易統計の集計も簡単に追加できます。ただし、深刻なパフォーマンスの低下が予想されます。これらは、手の込んだデバッグ アロケータを作成したり、さらに別のデバッグ レイヤーを追加したりしなくても、デバッグ フックとしても役立ちます。

ところで...私のテストカードは、リリースドライバーを使用したnvidiaでした

于 2018-06-14T22:01:38.070 に答える
2

プレーン 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

于 2016-05-01T23:59:10.810 に答える