web-dev-qa-db-ja.com

カスタムヒープアロケーター

ほとんどのプログラムは、関数型プログラミング言語が古いオブジェクトを変更するよりも新しいオブジェクトを割り当てることを好み、ガベージコレクターが解放することを心配する程度まで、ヒープの割り当てについて非常にカジュアルである可能性があります。

ただし、組み込みプログラミングのサイレントセクターでは、メモリとハードリアルタイムの制約により、ヒープ割り当てをまったく使用できないアプリケーションが数多くあります。処理される各タイプのオブジェクトの数は仕様の一部であり、すべてが静的に割り当てられます。

ゲームプログラミング(少なくともハードウェアのプッシュに意欲的なゲームでは)は、その中間になることがあります。動的割り当てを使用できますが、アロケーターをブラックボックスとして処理できない十分なメモリとソフトリアルタイム制約があります、ガベージコレクションはもちろんのこと、カスタムアロケータを使用する必要があります。これが、ゲーム業界でC++が依然として広く使用されている理由の1つです。 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html のようなことができます

その中間の領域には他にどのようなドメインがありますか?ゲームとは別に、カスタムアロケーターは頻繁に使用されますか?

9
rwallace

パフォーマンスを重視するクリティカルパスを持つアプリケーションがある場合は常に、メモリの処理方法を気にする必要があります。ほとんどのエンドユーザーのクライアント側アプリケーションは、主要なイベント駆動型であり、ほとんどのイベントはユーザーとの対話から発生するため、このカテゴリには分類されません。

ただし、多くのバックエンドソフトウェアは、多くのクライアント、より多くのトランザクション、より多くのデータソースを処理するためにスケールアップできるため、メモリの処理方法にいくらか焦点を合わせる必要があります。限界を押し広げると、ソフトウェアがどのようにメモリを使用し、ソフトウェアに合わせたカスタム割り当てスキームを作成するかを分析し始めることができます。

いくつか例を挙げましょう...私の最初の会社では、歴史的パッケージ、プロセス制御データの収集/保存/アーカイブに責任のあるソフトウェア(工場、原子力発電所、数千万のセンサーを備えた石油精製所など)に取り組みました。そのデータを保存します)。 Historianがより多くのデータを処理するのを妨げるパフォーマンスのボトルネックを分析したときはいつでも、ほとんどの場合、問題はメモリの処理方法にありました。絶対に必要な場合を除いて、malloc/freeが呼び出されないようにするために、長い時間をかけてきました。

現在の仕事では、監視ビデオデジタルレコーダーと分析パッケージに取り組んでいます。 30 fpsでは、各チャネルは33ミリ秒ごとにビデオフレームを受信します。私たちが販売しているハードウェアでは、100チャンネルのビデオを簡単に録画できます。つまり、クリティカルパス(ネットワークコール=>キャプチャコンポーネント=>レコーダー管理ソフトウェア=>ストレージコンポーネント=>ディスク)に動的メモリ割り当てがないことを確認する別のケースです。カスタムフレームアロケーターがあります。これには、バッファーの固定サイズバケットが含まれ、LIFOを使用して以前に割り当てられたバッファーを再利用します。600Kbのストレージが必要な場合、1024Kbバッファーになり、無駄になります。スペースですが、各割り当てが非常に短期間である私たちの使用のために特別に調整されているため、ヒープAPIを呼び出すことなく、バッファーが使用され、解放され、次のチャネルに再利用されるため、非常にうまく機能します。

私が説明した種類のアプリケーション(大量のデータをAからBに移動し、多数のクライアント要求を処理する)では、ヒープに行き来して、CPUパフォーマンスのボトルネックの主な原因になります。ヒープの断片化を最小限に抑えることは副次的な利点ですが、私が知る限り、ほとんどの最新のOSはすでに実装しています 低断片化ヒープ (少なくともWindowsは知っているので、他の人がそうすることを願っています)上手)。個人的には、これらのタイプの環境で12年以上働いていて、ヒープに関連するCPU使用率の問題がかなり頻繁に見られましたが、実際に断片化されたヒープに実際に影響を受けたシステムを見たことはありません。

4
DXM

ビデオ処理、VFX、オペレーティングシステムなど。効率的な割り当てを実現するために、データ構造とアロケータを分離する必要はありません。

たとえば、オクツリー内の効率的なツリーノードの割り当てをオクツリー自体から分離し、外部アロケータに依存するために、多くの追加の複雑さが導入されています。変更する理由の数が増えるわけではないため、これらの2つの懸念事項を融合してoctreeが一度に連続して多くのノードを割り当てるのはSRPの違反ではありません。実際には、減少する可能性があります。

たとえば、C++では、標準のコンテナが外部アロケータに依存することによる遅延副作用の1つにより、std::mapstd::listのようなリンクされた構造は、ベンチマークのため、C++コミュニティではほとんど役に立たないと見なされていますこれらのデータ構造が一度に1つのノードを割り当てる間、std::allocatorに対してそれらを使用します。もちろん、その場合、リンクされた構造のパフォーマンスは低下しますが、リンクされた構造へのノードの効率的な割り当てが、アロケーターではなくデータ構造の責任と見なされた場合、状況は大きく異なります。メモリー追跡/プロファイリングなどの他の理由でカスタム割り当てを使用する可能性がありますが、ノードを一度に1つずつ割り当てようとするときに、アロケーターに依存してリンクされた構造を効率化すると、デフォルトでそれらすべてが非常に非効率になります。これは、リンクされた構造がフリーリストのようなカスタムアロケーターを必要とすることで十分に効率的であり、キャッシュミスが左右にトリガーされないようにするというよく知られた警告があれば問題ありません。はるかに実際的に適用できるのはstd::list<T, BlockSize, Alloc>のようなものである可能性があります。ここで、BlockSizeは、フリーリストに一度に割り当てる隣接ノードの数を示します(1を指定すると、事実上std::listになります。今です)。

しかし、そのような警告はありません。それは、リンクされたリストが役に立たないというカルトマントラをエコーするブロックヘッドのコミュニティ全体につながります。

4
user204677

カスタムアロケーターが必要になる可能性があるもう1つの領域は、 ヒープの断片化 を防ぐことです。時間の経過とともに、ヒープは断片化された小さなオブジェクトをヒープ全体に割り当てる可能性があります。プログラムがヒープメモリを一緒に保持できない場合、プログラムがより大きなオブジェクトを割り当てようとすると、既存の断片化されたヒープの間に空きブロックが見つからないため、システムからより多くのメモリを要求する必要があります(小さすぎるオブジェクトが邪魔です)。プログラムの総メモリ使用量は時間の経過とともに増加し、メモリの追加ページを不必要に消費します。したがって、これは長期間実行することが予想されるプログラム(データベース、サーバーなど)にとってかなり大きな問題です。

ゲームとは別に、カスタムアロケーターは頻繁に使用されますか?

Facebook

jemalloc をチェックしてください。Facebookがヒープのパフォーマンスを向上させ、断片化を減らすために使用し始めていることを確認してください。

3
Doug T.