9

C++ を使用して作成された大規模なサーバー アプリケーションに取り組んでいます。このサーバーは、おそらく数か月間、再起動せずに実行する必要があります。時間の経過とともにメモリ消費量が増加するため、断片化はすでに疑わしい問題です。これまでの測定は、プライベート バイトと仮想バイトを比較し、これら 2 つの数値の違いを分析することでした。

断片化に対する私の一般的なアプローチは、分析に任せることです。一般的なパフォーマンスやメモリの最適化など、他のことについても同じように考えています。分析と証明で変更をバックアップする必要があります。

コードのレビューやディスカッション中に、メモリの断片化が最初に発生する問題の 1 つであることによく気づきます。現在、それに対する大きな恐怖があり、事前に「断片化を防ぐ」ための大きなイニシアチブがあるようです. メモリの断片化の問題を軽減または防止するのに有利と思われるコードの変更が要求されます。これらは私には時期尚早の最適化のように見えるので、すぐにこれらに反対する傾向があります。コードのクリーンさ/読みやすさ/保守性などを犠牲にします。これらの変化を満たすために。

たとえば、次のコードを見てください。

std::stringstream s;
s << "This" << "Is" << "a" << "string";

上記で、stringstream がここで行う割り当ての数は定義されていません。4 つの割り当て、または 1 つの割り当てである可能性があります。したがって、それだけに基づいて最適化することはできませんが、一般的なコンセンサスは、固定バッファーを使用するか、何らかの方法でコードを変更して、潜在的に少ない割り当てを使用することです。ここで文字列ストリームがメモリの問題の大きな原因になっているとは思えませんが、間違っているかもしれません。

上記のコードに対する一般的な改善提案は、次の行に沿っています。

std::stringstream s;
s << "This is a string"; // Combine it all to 1 line, supposedly less allocations?

また、可能な限りスタック オーバー ヒープを使用するという大きな推進力もあります。

このようにメモリの断片化を先取りすることは可能ですか、それともこれは単なる誤った安心感ですか?

4

6 に答える 6

14

断片化を少なくする必要があることを事前に知っていて、断片化が実際の問題であることを事前に測定しており、コードのどのセグメントが関連しているかを事前に知っている場合、それは時期尚早の最適化ではありません。パフォーマンスは必要条件ですが、盲目的な最適化はどのような状況でも良くありません。

ただし、優れたアプローチは、オブジェクト プールやメモリ アリーナなど、断片化のないカスタム アロケータを使用することです。たとえば、物理エンジンでは、すべてのティックごとの割り当てにメモリ アリーナを使用し、最後にそれを空にすることができます。これはばかばかしいほど高速であるだけでなく ( _allocaVS2010 よりもさらに高速です)、非常にメモリ効率が高く、断片化も少なくなります。

于 2012-05-17T03:54:40.147 に答える
6

メモリの断片化をアルゴリズム レベルで検討することは、まったく合理的です。不必要なヒープの割り当てと解放のコストを回避するために、小さな固定サイズのオブジェクトをスタックに割り当てることも合理的です。ただし、コードのデバッグ、分析、または保守を困難にするものはすべて一線を画します。

私はまた、単に間違っている提案がたくさんあることも心配しています. おそらく、人々が「メモリの断片化を回避するために」行うべきだと一般的に言うことの 1/2 は、まったく効果がなく、残りのかなりの部分が有害である可能性があります。

典型的な最新のコンピューティング ハードウェア上で最も現実的で長時間実行されるサーバー タイプのアプリケーションの場合、ユーザー空間の仮想メモリの断片化は、単純で単純なコーディングでは問題になりません。

于 2012-05-17T03:54:26.640 に答える
1

実際に遭遇する前にメモリの断片化について多くの懸念を抱くことは、明らかに時期尚早の最適化です。最初の設計ではあまり考慮しませんでした。適切なカプセル化のようなことはより重要です (必要に応じて後でメモリ表現を変更できるようになるため)。

一方、不必要な割り当てを避け、可能な場合は動的割り当ての代わりにローカル変数を使用するのは良い設計です。断片化の理由だけでなく、プログラムの単純さの理由でもあります。C++ は一般に値セマンティクスを好む傾向があり、値セマンティクス (コピーと代入) を使用するプログラムは、参照セマンティクス (動的割り当てとポインターの受け渡し) を使用するプログラムよりも自然です。

于 2012-05-17T07:50:06.600 に答える
1

時期尚早の最適化ではなく、ベストプラクティス以上のものだと思います。テスト スイートがある場合は、一連のメモリ テストを作成して、たとえば夜間にメモリやパフォーマンスなどを実行および測定できます。レポートを読んで、可能であればいくつかのエラーを修正できます。

小規模な最適化の問題は、コードを別のものに変更することですが、ビジネス ロジックは同じです。通常の for よりも高速なため、逆の for ループを使用するのと同じです。単体テストはおそらく、副作用なしでいくつかのポイントを最適化するように導きます。

于 2012-05-17T03:52:46.370 に答える
0

実際に断片化の問題に遭遇する前に断片化の問題を解決するべきではないと思いますが、同時に、メモリの断片化の問題に対するそのような解決策を簡単に統合できるようにソフトウェアを設計する必要があります。そして、ソリューションはカスタム メモリ アロケータであるため、コード (コンテナのオペレータ new/delete および Allocator クラス) へのプラグインは、config.h ファイルのどこかでコードの 1 行を変更することによって行う必要があり、絶対に変更しないことを意味します。すべてのコンテナなどのすべてのインスタンス化を通過します。これを裏付けるもう 1 つのポイントは、現在のすべての複雑なソフトウェアの 99% がマルチスレッドであり、異なるスレッドからのメモリ割り当てが同期の問題や、場合によっては偽共有につながることです。これらの問題に対する答えは、やはりカスタム メモリ アロケータです。

そのため、設計がカスタム アロケータをサポートしている場合は、「断片化の解消」として販売されているコード変更を受け入れるべきではありません。アプリのプロファイリングを行い、パッチによって DTLB または LLC ミスの数が実際に減少することを確認するまでは、受け入れないでください。データをより適切にパッキングします。ただし、設計でカスタム アロケータが許可されていない場合は、他の「メモリの断片化を排除する」コード変更を行う前に、これを最初のステップとして実装する必要があります。

内部設計について私が覚えていることから、Threading Building Blocks スケーラブル アロケーターは、メモリ割り当てのスケーラビリティの向上とメモリの断片化の減少の両方を試みることができました。

もう 1 つの小さなポイント: stringstream の割り当てと、割り当てを可能な限りまとめるポリシーを使用して作成している例 - 私の理解では、場合によっては、この問題を解決するのではなく、メモリの断片化が発生する可能性があります。すべての割り当てをまとめてパックすると、大きな連続したメモリ チャンクを要求するようになり、最終的に分散してしまう可能性があり、他の同様の大きなチャンク リクエストではギャップを埋めることができなくなります。

于 2012-05-17T13:18:14.727 に答える
-3

私が言及したいもう1つのポイントは、ある種のガベージコレクターを試してみないでください。特定のしきい値の後または特定の期間の後に呼び出すことができます。ガベージ コレクターは、一定のしきい値を超えると、未使用のメモリを自動的に収集します。

また、断片化に関しては、さまざまなタイプのオブジェクトに何らかのタイプのストレージを割り当て、コードでそれらを管理するようにしてください。

つまり、5 種類のオブジェクト (クラス A、B、C、D、および E) があるとします。たとえば、cacheA、cacheB...cacheE のように、最初に各タイプの 1000 個のオブジェクトに最初にスペースを割り当てることができます。

そのため、malloc と new の多くの呼び出しを避けることができ、断片化も非常に少なくなります。また、 cacheA 、 cacheB などから割り当てられる myAlloc のようなものを実装する必要があるだけなので、コードは以前と同じように読み取り可能になります...

于 2012-05-17T05:49:15.337 に答える