web-dev-qa-db-ja.com

メモリ割り当て/割り当て解除のボトルネック?

典型的な実際のプログラムでのメモリの割り当て/割り当て解除のボトルネックはどれくらいですか?パフォーマンスが通常重要となるあらゆるタイプのプログラムからの回答を歓迎します。 malloc/free/garbage collectionのまともな実装は、いくつかのコーナーケースでボトルネックになるほど高速であるか、またはほとんどのパフォーマンスが重要なソフトウェアは、メモリ割り当ての量を抑えようとするか、高速のmalloc/free /ガベージコレクションの実装?

注:私はここでnotリアルタイムのものについて話しています。パフォーマンスが重要とは、スループットが重要であるということですが、レイテンシは必ずしもそうではありません。

編集:mallocについて言及しましたが、この質問はnotであり、C/C++に固有のものです。

45
dsimcha

これは重要です。特に、断片化が大きくなり、アロケーターが、要求する隣接領域のより大きなヒープ全体をより強力に狩る必要があるためです。ほとんどのパフォーマンス重視のアプリケーションは、通常、独自の固定サイズのブロックアロケーターを作成します(たとえば、一度に16MBのメモリをOSに要求してから、4kb、16kbなどの固定ブロックに分割する)。この問題を回避します。

ゲームでは、malloc()/ free()の呼び出しがCPUの15%を消費する(不十分に記述された製品の場合)、または慎重に記述および最適化されたブロックアロケーターを使用すると、わずか5%を消費します。ゲームは60ヘルツの一貫したスループットが必要であることを考えると、ガベージコレクターがときどき実行されている間、ゲームを500ミリ秒間停止させることは現実的ではありません。

36
Crashworks

現在、ほぼすべての高性能アプリケーションはスレッドを使用して並列計算を利用する必要があります。これは、C/C++アプリケーションを作成するときに、実際のメモリ割り当ての速度が大幅に向上する場所です。

CまたはC++アプリケーションでは、malloc/newはすべての操作でグローバルヒープをロックする必要があります。競合がない場合でも、ロックは自由とはほど遠く、可能な限り回避する必要があります。

スレッド化は最初から設計されており、メモリアロケータはスレッドごとのプールから機能するため、JavaとC#の方が優れています。これはC/C++でも実行できますが、自動ではありません。

21
Zan Lynx

まず、mallocについて説明したので、CまたはC++について話していると思います。

メモリの割り当てと割り当て解除は、実際のプログラムでは重大なボトルネックになる傾向があります。メモリを割り当てたり割り当て解除したりすると、多くのことが「内部」で行われますが、そのすべてがシステム固有です。メモリは実際に移動またはデフラグされ、ページは再編成される場合があります。プラットフォームに依存しない方法で影響を知る方法はありません。一部のシステム(多くのゲームコンソールなど)もメモリの最適化を行わないため、これらのシステムでは、メモリが断片化するとメモリ不足エラーが発生し始めます。

典型的な回避策は、できるだけ多くのメモリを事前に割り当て、プログラムが終了するまでそのままにしておくことです。そのメモリを使用して、大きなモノリシックデータセットを格納するか、メモリプールの実装を使用して、チャンクに分割することができます。多くのC/C++標準ライブラリの実装では、この理由のために、一定量のメモリプーリングが行われます。

ただし、2つの方法はありません。時間に敏感なC/C++プログラムがある場合、大量のメモリの割り当て/割り当て解除を行うとパフォーマンスが低下します。

11
MattK

一般に、メモリ割り当てのコストは、mostアプリケーションでのロック競合、アルゴリズムの複雑さ、またはその他のパフォーマンスの問題によっておそらく小さくなります。一般に、これはおそらく私が心配するパフォーマンスの問題のトップ10には含まれないと思います。

現在、非常に大きなメモリのチャンクを取得することが問題になる可能性があります。そして、メモリをつかんでも適切に取り除かないことは、私が心配することです。

JavaおよびJVMベースの言語では、新しいオブジェクトは非常に高速です。

以下に、関連性の高いリンクへの参照を下に付けて、彼のことを知っている人によるきちんとした記事を示します。 http://www.ibm.com/developerworks/Java/library/j-jtp09275.html

8
Alex Miller

Java(および適切なGC実装を備えた他の言語)では、オブジェクトの割り当ては非常に安価です。SunJVMでは、10 CPUサイクルしか必要ありません。C/ c ++のmallocははるかに高価です、それはより多くの仕事をしなければならないからといって。

ただし、Java=内のオブジェクトの割り当ても非常に安価です。Webアプリケーションの多くのユーザーが並行して行うと、ガベージコレクターの実行がさらにトリガーされるため、パフォーマンスの問題が発生する可能性があります。したがって、 Javaでの割り当ての間接的なコストは、GCによって行われた割り当て解除によって発生します。これらのコストは、セットアップ(メモリの量)に大きく依存するため、定量化が困難です。あなたの申請。

5
kohlerm

A Java VMは、アプリケーションコードが行っていることとはほぼ無関係に、オペレーティングシステムからメモリを要求して解放します。これにより、メモリを取得して解放することができます。大きなメモリチャンクは、手動のメモリ管理で得られるように、小さな個々の操作で実行するよりもはるかに効率的です。

この記事 は2005年に書かれており、JVMスタイルのメモリ管理はすでに進んでいます。それ以来、状況は改善しました。

Java言語、またはC/C++のいずれか)高速のraw割り当てパフォーマンスを誇る言語はどれですか?答えは驚くかもしれません-最新のJVMでの割り当ては、最高のパフォーマンスのmalloc実装よりもはるかに高速です。一般的なコードHotSpot 1.4.2以降のnew Object()のパスは約10のマシンインストラクション(Sunが提供するデータ。参考文献を参照)ですが、Cで最もパフォーマンスの高いmalloc実装には、1コールあたり平均60〜100のインストラクションが必要です(Detlefsなど。al .;参考文献を参照してください。また、割り当てのパフォーマンスは全体的なパフォーマンスの重要な要素ではありません。PerlやGhostscriptなどの多くの実際のCおよびC++プログラムは、総実行時間の20〜30%をmallocとfree-正常なアプリケーションの割り当てとガベージコレクションのオーバーヘッドよりもはるかに多くJavaアプリケーション。

4
skaffman

私が以前に答えたのは知っていますが、それはあなたの質問に対する答えではなく、他の答えに対する答えでした。

直接お話しするのであれば、私が正しく理解していれば、パフォーマンスユースケースの基準はスループットです。

これは私にとって、ほとんど例外なく [〜#〜] numa [〜#〜]awareallocators を参照する必要があることを意味します。

以前の参照はありません。 IBM JVMペーパー、Microquill C、Sun JVM。この点をカバーして、少なくともAMD ABIでは、NUMAが卓越したメモリCPUガバナーであるという点で、今日のアプリケーションに非常に疑わしいと思います。

手を下げて;現実の世界、偽の世界、どんな世界でも... NUMA対応のメモリ要求/使用テクノロジーはより高速です。残念ながら、私は現在Windowsを実行しており、Linuxで利用可能な「numastat」を見つけられませんでした。

私の friend は、FreeBSDカーネルの実装でこれについて深く written を持っています。

私がその場で、通常は非常に大量のローカルノードのメモリリクエストをリモートノードの上に表示できることを残念に思います(明らかなパフォーマンススループット利点)、自分でベンチマークを付けることができます。パフォーマンス特性は非常に具体的であるため、これはおそらく実行する必要があることです。

多くの点で、少なくとも以前の5.x VMWAREは、少なくともリモートノードから頻繁に要求されるNUMAのページを利用しないために、かなり不十分にフェアリングされたことを知っています。ただし、メモリのコンパートメント化やコンテナ化に関しては、VMは非常にユニークな獣です。

私が引用したリファレンスの1つは、AMD ABIに対するMicrosoftのAPI実装です。これには、ユーザーランドアプリケーション開発者が利用するNUMA割り当て専用インターフェイスがあります;)

これがかなり最近の analysis 、視覚的、そしてすべてです。4つの異なるヒープ実装を比較する一部のブラウザーアドオン開発者からのものです。当然、彼ら 開発済み が一番上になります(奇妙なことに、テストを行う人が最も高いスコアを示すことがよくあります)。

彼らはいくつかの方法で定量化可能にカバーしますが、少なくともそれらのユースケースでは、空間/時間間の正確なトレードオフは何か、一般にLFHを識別しました(そうそう、LFHは単に標準ヒープの単なるモードにすぎません)。または同様に設計されたアプローチは、本質的にかなり多くのメモリを消費しますが、時間が経つと、使用するメモリが少なくなる可能性があります...接頭辞もきちんとしています...

ただし、一般的なワークロードに基づいてHEAP実装を選択することは、それを十分に理解した上で選択することをお勧めします;)ただし、ニーズを十分に理解するには、これらの確率を最適化する前に、まず基本的な操作が正しいことを確認してください;)

3

これが、c/c ++のメモリ割り当てシステムが最適に機能する場所です。デフォルトの割り当て方法はほとんどの場合問題ありませんが、必要に応じて変更できます。 GCシステムでは、割り当て戦略を変更するためにできることは多くありません。もちろん、支払うべき代償があり、それは割り当てを追跡し、それらを正しく解放する必要があります。 C++はこれをさらに進め、新しい演算子を使用してクラスごとに割り当て戦略を指定できます。

class AClass
{
public:
  void *operator new (size_t size); // this will be called whenever there's a new AClass
   void *operator new [] (size_t size); // this will be called whenever there's a new AClass []
  void operator delete (void *memory); // if you define new, you really need to define delete as well
  void operator delete [] (void *memory);define delete as well
};

STLテンプレートの多くでは、カスタムアロケーターも定義できます。

最適化に関するすべてのことと同様に、独自のアロケータを作成する前に、実行時の分析を通じて、メモリ割り当てが本当にボトルネックであるかどうかを最初に判断する必要があります。

3
Skizz

パフォーマンスの観点からのメモリの割り当てと解放は、比較的コストのかかる操作です。最新のオペレーティングシステムでの呼び出しは、オペレーティングシステムが仮想メモリ、ページング/マッピング、実行保護などを処理できるように、カーネルまで到達する必要があります。

一方、ほとんどすべての最近のプログラミング言語は、事前に割り当てられたバッファで機能する「アロケータ」の背後にこれらの操作を隠しています。

この概念は、スループットを重視するほとんどのアプリケーションでも使用されます。

3
Kosi2801

MicroQuill SmartHeap Technical Specification によると、「一般的なアプリケーション[...]は、総実行時間の40%をメモリの管理に費やしています」。この数値を上限として取ることができます。個人的には、典型的なアプリケーションは、メモリの割り当て/割り当て解除の実行時間の10〜15%を費やすと感じています。シングルスレッドアプリケーションのボトルネックになることはめったにありません。

マルチスレッドのC/C++アプリケーションでは、ロックの競合により、標準アロケーターが問題になります。ここから、よりスケーラブルなソリューションを探し始めます。ただし、覚えておいてください アムダールの法則

2
Constantin

その他はC/C++をカバーしているので、.NETについて少しだけ情報を追加します。

.NETでは、ヒープのジェネレーションゼロ部分でメモリを取得するだけの問題であるため、一般的にヒープ割り当ては非常に高速です。明らかにこれは永遠に続くことはできません。これはガベージコレクションの出番です。メモリの圧縮中にユーザースレッドを中断する必要があるため、ガベージコレクションはアプリケーションのパフォーマンスに大きな影響を与える可能性があります。完全な収集が少ないほど良いです。

.NETのガベージコレクタのワークロードに影響を与えるためにできることはさまざまです。一般に、メモリ参照が多い場合、ガベージコレクターはより多くの作業を行う必要があります。例えば。ノード間の参照の代わりに隣接行列を使用してグラフを実装することにより、ガベージコレクターはより少ない参照を分析する必要があります。

それがアプリケーションで実際に重要であるかどうかは、いくつかの要因に依存します。そのような最適化に進む前に、実際のデータでアプリケーションをプロファイルする必要があります。

1
Brian Rasmussen

マイクロソフトヒープについて言えば、ほとんどすべての人がoffベースです。同期化は断片化と同じように簡単に処理されます。

現在の優先ヒープはLFHです([〜#〜] low [〜#〜][〜#〜] fragmentation [〜#〜]HEAP)、Vista + OSのデフォルトであり、XPでgflagを介して構成することができ、問題はほとんどありません

ロック/ブロッキング/競合/バス帯域幅の問題を回避するのは簡単です。

HEAP_NO_SERIALIZE

heapAllocまたはHeapCreate中のオプション。これにより、連動待機に入らずにヒープを作成/使用できます。

HeapCreateを使用していくつかのヒープを作成し、おそらくmallocx(enum my_heaps_set、size_t);マクロを定義することをお勧めします。

もちろん問題ありません。適切に設定するには、無料で再割り当てする必要があります。凝ったものにしたい場合は、ポインターのアドレスを評価するか、mallocがスレッドIDに基づいて使用するヒープを識別できるようにロジックを追加し、ビルドすることで、それ自体のヒープハンドルをfree/reallocで自動検出します。スレッドごとのヒープと共有グローバルヒープ/プールの階層。

Heap * APIは、malloc/newによって内部的に呼び出されます。

動的な memory management の問題に関する素晴らしい記事を、さらにすばらしい references とともに示します。ヒープアクティビティを計測および分析する。

1