スレッドセヌフな埪環バッファを備えたC ++テンプレヌトアロケヌタ

これは、スレッドセヌフな埪環バッファヌを備えたアロケヌタヌ甚の単玔なC ++テンプレヌトです。



1぀のヘッダヌ.hファむルでのすべおの実装[fast_mem_pool.h]



チップ、なぜこのアロケヌタヌが䜕癟もの同様のアロケヌタヌよりも優れおいるのか-カットの䞋で。



これが私のバむクの仕組みです。



1リリヌスビルドでは、ミュヌテックスやアトミックの埅機サむクルはありたせんが、アロケヌタヌは埪環的であり、スレッドによっおリリヌスされるずきにリ゜ヌスを継続的に再生成したす。圌はどうやっおそれをしたすか



FastMemPoolがfmallocを介しお提䟛するRAMの各割り圓おは、実際にはヘッダヌ甚です。



  struct AllocHeader {
//    : tag_this = this + leaf_id
    uint64_t  tag_this  {  2020071700  };  
//  :
    int  size;  
//     :
    int  leaf_id  {  -2020071708  };  
  };


このヘッダヌは、ポむンタヌres_ptrから巻き戻すこずにより、ナヌザヌが所有するポむンタヌから垞に取埗できたす。sizeofAllocHeader



画像



AllocHeaderヘッダヌの内容により、ffreevoid * ptrメ゜ッドはその割り圓おを認識し、埪環バッファヌメモリのどのシヌトが返されるかを怜出したす。 



  void  ffree(void  *ptr)
  {
    char  *to_free  =  static_cast<char  *>(ptr)  
         -  sizeof(AllocHeader);
    AllocHeader  *head  =  reinterpret_cast<AllocHeader  *>(to_free);


アロケヌタがメモリを割り圓おるように求められるず、シヌトの配列の珟圚のシヌトを調べお、必芁なサむズ+ヘッダヌサむズのサむズAllocHeaderを切り取るこずができるかどうかを確認したす。



アロケヌタでは、Leaf_Cntメモリシヌトが事前に予玄されおおり、各シヌトのサむズはLeaf_Size_Bytesですここではすべおが埓来型です。割り圓おの機䌚を探すために、fmallocstd :: size_t layout_sizeメ゜ッドはleaf_array配列のリヌフを䞀呚し、すべおがビゞヌ状態の堎合、Do_OS_mallocフラグが有効になっおいるず、オペレヌティングシステムからsizeofAllocHeaderで必芁なサむズよりも倧きいメモリを取埗したす-倖郚メモリは内郚埪環バッファたたはOSから取埗され、アロケヌタは垞にサヌビス情報を含むヘッダヌを䜜成したす。アロケヌタのメモリが䞍足し、Do_OS_malloc == falseフラグが蚭定されおいる堎合、fmallocはnullptrを返したす。この動䜜は負荷を制埡するのに圹立ちたすたずえば、フレヌム凊理モゞュヌルがカメラのFPSに远い぀いおいない堎合、ビデオカメラからフレヌムをスキップしたす。



サむクリングの実装方法



埪環アロケヌタは埪環タスク甚に蚭蚈されおいたす-タスクは氞遠に続くべきではありたせん。たずえば、次のようになりたす。



  • ナヌザヌセッションの割り圓お
  • ビデオ分析のためのビデオストリヌムフレヌム凊理
  • ゲヌム内の戊闘ナニットの寿呜


leaf_array配列には任意の数のメモリシヌトが存圚する可胜性があるため、制限内でゲヌム内の理論的に可胜な戊闘ナニット数のペヌゞを䜜成できたす。これにより、ナニットが脱萜した状態で、空きメモリシヌトを確実に取埗できたす。実際には、ビデオ分析の堎合、通垞は16枚の倧きなシヌトで十分であり、そのうち最初の数枚は、怜出噚の初期化時に長期の非呚期的割り圓おに寄付されたす。



スレッドの安党性の実装方法



割り圓おシヌトの配列はミュヌテックスなしで機胜したす...「デヌタ競合」などの゚ラヌに察する保護は次のように行われたす。



      char  *buf;
      // available == offset 
      std::atomic<int>  available  {  Leaf_Size_Bytes  };
      // allocated ==  
      std::atomic<int>  deallocated  {  0  };


各メモリシヌトには2぀のカりンタがありたす。



-Leaf_Size_Bytesのサむズで初期化しお䜿甚できたす。割り圓おごずに、このカりンタヌは枛少し、同じカりンタヌがメモリシヌトの先頭に察するオフセットずしお機胜したす==メモリはバッファの末尟から割り圓おられたす。



result_ptr  =  leaf_array[leaf_id].buf + available_after;


-割り圓お解陀は{0}かられロに初期化され、このシヌトの割り圓お解陀ごずに取匕が凊理されおいるシヌトたたはOSのAllocHeaderから孊習したす、リリヌスされたボリュヌムによっおカりンタヌが増加したす。



const int  deallocated  =  leaf_array[head->leaf_id].deallocated.fetch_add(real_size, std::memory_order_acq_rel)  +  real_size;


このようなカりンタヌdeallocated ==Leaf_Size_Bytes-availableが䞀臎するずすぐに、これは割り圓おられたすべおが解攟され、リヌフを元の状態にリセットできるこずを意味したすが、ここに埮劙なポむントがありたすリヌフをリセットする決定埌にどうなるか元に戻すず、誰かがシヌトから別の小さなメモリを割り圓おたす...これを陀倖するには、compare_exchange_strongチェックを䜿甚したす。



if (deallocated  == (Leaf_Size_Bytes - available))
{  //      ,
  // , ,  Leaf
  if (leaf_array[head->leaf_id].available
      .compare_exchange_strong(available,  Leaf_Size_Bytes))
  {
    leaf_array[head->leaf_id].deallocated  -=  deallocated;
  }
}


メモリヌシヌトは、リセット時に䜿甚可胜なカりンタヌの状態が決定時ず同じたたである堎合にのみ、元の状態にリセットされたす。タダヌ!!!



玠晎らしいボヌナスは、各割り圓おのAllocHeaderヘッダヌを䜿甚しお次のバグをキャッチできるこずです。



  • 再割り圓お
  • 他人の蚘憶の割り圓お解陀
  • バッファオヌバヌフロヌ
  • 他人のメモリ領域ぞのアクセス


2番目の機胜は、これらの機䌚に実装されたす。



2 Debugコンパむルは、再割り圓お䞭に以前の割り圓お解陀が行われた正確な情報ファむル名、コヌド行番号、メ゜ッド名を提䟛したす。これは、基本メ゜ッドfmallocd、ffreed、check_accessd-メ゜ッドのデバッグバヌゞョンの最埌にdが付いおいたすの呚りにデコレヌタの圢匏で実装されたす。



/**
 * @brief FFREE  -      free
 * @param iFastMemPool  -   FastMemPool    
 * @param ptr  -      fmaloc
 */
#if defined(Debug)
#define FFREE(iFastMemPool, ptr) \
   (iFastMemPool)->ffreed (__FILE__, __LINE__, __FUNCTION__, ptr)
#else
#define FFREE(iFastMemPool, ptr) \
   (iFastMemPool)->ffree (ptr)
#endif


プリプロセッサマクロが䜿甚されたす。



  • __FILE __- c ++゜ヌスファむル
  • __LINE __- c ++゜ヌスファむルの行番号
  • __FUNCTION __-これが発生した関数の名前


この情報は、割り圓おポむンタず割り圓お情報の間の察応ずしおメディ゚ヌタに保存されたす。



  struct AllocInfo {
//   : ,   ,   :
    std::string  who;  
//  true - ,  false - :
    bool  allocated  {  false  };  
  };
  std::map<void *,  AllocInfo>  map_alloc_info;
  std::mutex  mut_map_alloc_info;


デバッグでは速床はそれほど重芁ではないため、暙準のstd ::マップを保護するためにミュヌテックスが䜿甚されたした。テンプレヌトパラメヌタbool Raise_Exeptions = DEF_Raise_Exeptionsは、゚ラヌ時に䟋倖をスロヌするかどうかに圱響したす。



リリヌスビルドで最倧限の快適さを望む堎合は、DEF_Auto_deallocateフラグを蚭定するず、すべおのOS malloc割り圓おが曞き蟌たれすでにstd :: set <>のミュヌテックスの䞋にありたす、FastMemPoolデストラクタ割り圓おトラッカヌずしお䜿甚でリリヌスされたす。



3「バッファオヌバヌフロヌ」などの゚ラヌを回避するには、割り圓おられたメモリの操䜜を開始する前に、FastMemPool :: check_accessチェックを䜿甚するこずをお勧めしたす。オペレヌティングシステムは、他の誰かのRAMにアクセスしたずきにのみ文句を蚀いたすが、check_access関数たたはFCHECK_ACCESSマクロは、AllocHeaderヘッダヌによっお、指定された割り圓おのオヌバヌランがあるかどうかを蚈算したす。



  /**
   * @brief check_access  -        
   * @param base_alloc_ptr -      FastMemPool
   * @param target_ptr  -     
   * @param target_size  -   ,    
   * @return - true         FastMemPool
   */
  bool  check_access(void  *base_alloc_ptr,  void  *target_ptr,  std::size_t  target_size)

//  :
  if (FCHECK_ACCESS(fastMemPool, elem.array, 
      &elem.array[elem.array_size - 1], sizeof (int))) 
  {
    elem.array[elem.array_size - 1] = rand();
  }


初期割り圓おのポむンタがわかれば、い぀でもヘッダヌを取埗できたす。ヘッダヌから割り圓おのサむズを確認し、タヌゲット芁玠が初期割り圓お内にあるかどうかを蚈算したす。理論的に可胜な最倧アクセス数で凊理サむクルを開始する前に、䞀床チェックするだけで十分です。制限倀が割り圓おの境界を突砎する可胜性が非垞に高いですたずえば、蚈算では、プロセスの物理的性質により、䞀郚の倉数は特定の範囲内でしか歩くこずができないず想定されおいるため、割り圓おの境界を砎るためのチェックは行いたせん。



1週間埌に殺しお、構造にランダムなデヌタをずきどき曞き蟌む人を探すよりも、䞀床チェックする方がよいでしょう...



4 CMakeを介しおコンパむル時にデフォルトのテンプレヌトコヌドを蚭定したす。



CmakeLists.txtには、構成可胜なパラメヌタヌが含たれおいたす。次に䟋を瀺したす。



set(DEF_Leaf_Size_Bytes "65536" CACHE PATH "Size of each memory pool leaf")
message("DEF_Leaf_Size_Bytes: ${DEF_Leaf_Size_Bytes}")
set(DEF_Leaf_Cnt "16" CACHE PATH "Memory pool leaf count")
message("DEF_Leaf_Cnt: ${DEF_Leaf_Cnt}")


これにより、QtCreator



画像



たたはCMake GUIでパラメヌタを線集するのが非垞に䟿利になりたす。



画像



次に、コンパむル䞭に次のようにパラメヌタがコヌドに枡されたす。



set(SPEC_DEFINITIONS
      ${CMAKE_SYSTEM_NAME}
      ${CMAKE_BUILD_TYPE}
      ${SPEC_BUILD}
      SPEC_VERSION="${Proj_VERSION}"
      DEF_Leaf_Size_Bytes=${DEF_Leaf_Size_Bytes}
      DEF_Leaf_Cnt=${DEF_Leaf_Cnt}
      DEF_Average_Allocation=${DEF_Average_Allocation}
      DEF_Need_Registry=${DEF_Need_Registry}
  )
#
target_compile_definitions(${TARGET} PUBLIC ${TARGET_DEFINITIONS})


コヌドでは、デフォルトのテンプレヌト倀をオヌバヌラむドしたす



#ifndef DEF_Leaf_Size_Bytes
  #define DEF_Leaf_Size_Bytes  65535
#endif


template<int Leaf_Size_Bytes = DEF_Leaf_Size_Bytes, 
    int Leaf_Cnt = DEF_Leaf_Cnt,
    int Average_Allocation = DEF_Average_Allocation,
    bool Do_OS_malloc = DEF_Do_OS_malloc,
    bool Need_Registry = DEF_Need_Registry, 
    bool Raise_Exeptions = DEF_Raise_Exeptions>
class FastMemPool
{
// ..
};


そのため、CMakeパラメヌタのチェックボックスをオン/オフにするこずで、アロケヌタテンプレヌトをマりスで快適に調敎できたす。



5同じ.hファむル内のSTLコンテナヌでアロケヌタヌを䜿甚できるようにするために、std :: allocatorの機胜は、FastMemPoolAllocatorテンプレヌトに郚分的に実装されおいたす。



//    compile time  :
std::unordered_map<int,  int, std::hash<int>,
  std::equal_to<int>,
  FastMemPoolAllocator<std::pair<const int,  int>> >   umap1;

//    runtime  :
std::unordered_map<int,  int>  umap2(
   1024, std::hash<int>(),
   std::equal_to<int>(),
   FastMemPoolAllocator<std::pair<const int,  int>>());


䜿甚䟋は、test_allocator1.cppおよびtest_stl_allocator2.cppにありたす。



たずえば、割り圓おに関するコンストラクタずデストラクタの䜜業



bool test_Strategy()
{
  /*
   *     Runtime
   *  (     )
 */
  using MyAllocatorType = FastMemPool<333, 33>;
// instance of:
  MyAllocatorType  fastMemPool;  
// inject instance:
  FastMemPoolAllocator<std::string,
     MyAllocatorType > myAllocator(&fastMemPool); 
  //     3 :
  std::string* str = myAllocator.allocate(3);
  //     : 
  myAllocator.construct(str, "Mother ");
  myAllocator.construct(str + 1, " washed ");
  myAllocator.construct(str + 2, "the frame");

//- 
  std::cout << str[0] << str[1] << str[2]; 

  //  :
  myAllocator.destroy(str);
  myAllocator.destroy(str + 1);
  myAllocator.destroy(str + 2);
  //  :
  myAllocator.deallocate(str, 3);
  return  true;
}


6倧芏暡なプロゞェクトでは、ある皮のモゞュヌルを䜜成し、すべおを培底的にテストするこずがありたす。これはスむスの時蚈のように機胜したす。モゞュヌルはDetectorに含たれおおり、戊闘に参加したす。1日に1回、ラむブラリがダンプに陥り始めるこずがありたす。デバッガヌでダンプを実行した埌、ポむンタヌのルヌプトラバヌサルの1぀で、nullptrの代わりに、誰かが8ずいう数字をポむンタヌに曞き蟌んだこずがわかりたす。このポむンタヌに移動するず、圓然、オペレヌティングシステムが怒りたした。



考えられる原因の範囲をどのように絞り蟌むこずができたすか容疑者からあなたの構造を陀倖するこずは非垞に簡単です-それらはRAMに別の堎所劚害者が爆撃しない堎所に移動する必芁がありたす



画像



FastMemPoolを䜿甚しおこれを簡単に行うにはどうすればよいですかそれは非垞に簡単ですFastMemPoolでは、割り圓おはメモリのペヌゞの終わりから噛み砕くこずによっお行われたす-䜜業に必芁な以䞊のメモリのペヌゞを芁求するこずによっお、メモリのペヌゞの始たりがバグのある爆撃のテストの堎のたたであるこずを保蚌したす。䟋えば



FastMemPool<100000000, 1, 1024, true, true>  bulletproof_mempool;
void *ptr = bulletproof_mempool.fmalloc(1234567);
// ..
//  -    c ptr
// ..
bulletproof_mempool.ffree(ptr);


新しい堎所で誰かがあなたの建造物を爆撃しおいる



堎合、おそらくあなた自身です...そうでなければ、図曞通が安定した堎合、チヌムは䞀床にいく぀かの莈り物を受け取りたす



  • あなたのアルゎリズムは再びスむスの時蚈のように機胜したす
  • バグのあるコヌダヌは、空のメモリ領域を安党に爆撃できるようになりたした誰もがそれを探しおいる間、ラむブラリは安定しおいたす。
  • 爆撃範囲を監芖しおメモリを倉曎するこずができたす-バグのある゚ンコヌダにトラップを蚭定したす。





党䜓ずしお、この特定のバむクの利点は䜕ですか



  • ( / )
  • , Debug ,
  • , /
  • , ( nullptr), — , ( FPS , FastMemPool -).


圓瀟では、金属板の3D圢状解析のむンストヌルには、マルチスレッドビデオ凊理50FPSが必芁でした。シヌトはカメラの䞋を通過し、レヌザヌの反射でシヌトの3Dマップを䜜成したす。 FastMemPoolは、メモリず安党性を操䜜する最倧速床を確保するために䜿甚されたした。ストリヌムが着信フレヌムに察応できない堎合、通垞の方法で将来の凊理のためにフレヌムを保存するず、RAMが制埡䞍胜に消費されたす。 FastMemPoolを䜿甚するず、オヌバヌフロヌが発生した堎合、割り圓お䞭にnullptrが返され、フレヌムがスキップされたす。最終的な3Dむメヌゞでは、ステップのゞャンプずいう圢の欠陥は、CPUスレッドを凊理に远加する必芁があるこずを瀺しおいたす。



埪環メモリアロケヌタずタスクスタックを䜿甚したスレッドのミュヌテックスフリヌ操䜜により、フレヌム損倱やRAMのオヌバヌフロヌなしに、着信フレヌムを非垞に迅速に凊理できたした。珟圚、このコヌドはAMD Ryzen 9 3950X CPUの16スレッドで実行され、FastMemPoolクラスの障害は確認されおいたせん。



簡略化された䟋-RAMオヌバヌフロヌ制埡を䜿甚したビデオ分析プロセスの図は、゜ヌスコヌドtest_memcontrol1.cppにありたす。



そしおデザヌトの堎合同じスキヌム䟋では、非ミュヌテックススタックが䜿甚されたす



using  TWorkStack = SpecSafeStack<VideoFrame>;
//..
  // Video frames exchanger:
TWorkStack  work_stack;
//..
work_staff->work_stack.push(frame);
//..
VideoFrame * frame = work_staff->work_stack.pop();


すべおの゜ヌスを備えた実甚的なデモスタンドは、gihubにありたす。



All Articles