手動メモリ管理よりも高速なガベージコレクションのデモンストレーション


23

私は、ガベージコレクション(理論的には)手動のメモリ管理よりも高速である可能性があることを、多くの場所で読んでいます(実際、自分で書きました)。

ただし、表示することは、伝えることよりもはるかに困難です。この効果が実際に作用することを示すコードを
実際に見たことはありません。

このパフォーマンスの利点を実証するコードを誰かが持っていますか(またはどこにあるかを知っていますか)?


5
GCの問題点は、比較するために、右の変数を分離するのは難しいではないに言及し、2つのランが大幅に異なる結果を持つことができますので、ほとんどの実装が決定論的ではないということです
ラチェットフリーク

@ratchetfreak:70%の確率で(たとえば)速いだけの例を知っているなら、それでもいいです。少なくともスループットの観点から、この2つを比較する何らかの方法が必要です(おそらく遅延は機能しません)。
Mehrdad

1
さて、GCに手動で行ったことよりも優位なことはいつでも手動で行うことができるため、これは少し注意が必要です。おそらく、これを「標準」の手動メモリ管理ツール(malloc()/ free()、所有ポインタ、refcountとの共有ポインタ、弱いポインタ、カスタムアロケータなし)に制限する方が良いでしょうか?または、カスタムアロケーター(想定するプログラマーの種類に応じて、より現実的または現実的でない場合があります)を許可する場合は、それらのアロケーターにかける労力を制限します。それ以外の場合、「この場合のGCの動作をコピーする」手動戦略は、常に少なくともGCと同じくらい高速です。

1
「GCが行うことをコピーする」とは、「独自のGCを構築する」という意味ではありません(ただし、GCのオプションサポートを導入するC ++ 11以降では理論的に可能であることに注意してください)。同じコメントで以前に述べたように、「GCが手動で行ったものよりも優位に立つようにする」ことを意味しました。たとえば、チェイニーのような圧縮がこのアプリケーションに大いに役立つ場合、ポインタ修正を処理するカスタムスマートポインタを使用して、同様の割り当て+圧縮スキームを手動で実装できます。また、シャドウスタックなどの手法を使用すると、CまたはC ++でルートを見つけることができますが、余分な作業が必要になります。

1
@Ike:大丈夫です。なぜ私が質問したのかを見てください?それが私の質問のすべてのポイントでした-人々理にかなっているあらゆる種類の説明を思い付きますが、実際に正しいと言うことを証明するデモを提供するように頼むと誰もがつまずきます。この質問の全体のポイントは、これが実際に実際に起こり得ることを一度だけ示すことでした。
-Mehrdad

回答:


26

http://blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspxを参照し、すべてのリンクをたどってRico Mariani対Raymond Chen(Microsoftの非常に有能なプログラマー)が決着をつける。レイモンドは管理されていないものを改善し、リコは管理されたもので同じことを最適化することで対応します。

基本的に最適化の労力はゼロで、管理バージョンはマニュアルよりも何倍も早く起動しました。最終的にマニュアルはマネージドを打ち負かしましたが、ほとんどのプログラマーが行きたくないレベルに最適化することによってのみです。すべてのバージョンで、マニュアルのメモリ使用量は管理対象よりも大幅に優れていました。


実際の例として引用のための1 のコードと C ++の構造(などの適切な使用があるが:) swap)ではないということは難しいですが、おそらく非常に簡単に性能面があなたを得るでしょう...
Mehrdad

5
パフォーマンスでレイモンド・チェンをしのぐことができるかもしれません。私は彼が病気であるために彼がそれから出ない限り、私は何度も一生懸命働いており、幸運になったと確信しています。彼があなたが選んだであろう解決策を選ばなかった理由がわかりません。彼にはその理由があると確信しています
13

ここにレイモンドのコードをコピーし、比較するためにここに自分のバージョンを書きました。テキストファイルを含むZIPファイルはこちらです。私のコンピューターでは、私のものは14ミリ秒で、レイモンドのは21ミリ秒で実行されます。私が何か間違ったことをしない限り(可能な場合)、メモリマップファイルまたはカスタムメモリプール(彼が使用した)を使用しなくても、彼の215行のコードは48行の実装よりも50%遅くなります。私の長さはC#バージョンの半分です。私は間違っていましたか、それとも同じことを観察していますか?
Mehrdad

1
@Mehrdadこのラップトップでgccの古いコピーを取り出して、あなたのコードも彼のコードもコンパイルせず、もちろんそれで何もしません。私がWindowsにいないという事実は、おそらくそれを説明しています。しかし、数字とコードが正しいと仮定しましょう。彼らは10年前のコンパイラとコンピュータで同じパフォーマンスを発揮しますか?(ブログが書かれた時期を見てください。)たぶんそうではありません。彼ら(Cプログラマー)がC ++を適切に使用する方法などを知らなかったと仮定しましょう。
btilly

1
マネージドメモリに変換して高速化できる合理的なC ++プログラムが残っています。ただし、C ++バージョンを最適化して、さらに高速化することができます。私たち全員が同意しているのは、マネージコードがアンマネージコードよりも速い場合に常に発生する一般的なパターンです。ただし、管理されたバージョンではより高速であった、優れたプログラマーによる妥当なコードの具体例がまだあります。
btilly

5

経験則では、無料のランチはありません。

GCは、手動のメモリ管理の頭痛を取り除き、ミスをする可能性を減らします。特定のGC戦略が問題の最適な解決策である状況がいくつかありますが、その場合、それを使用してもペナルティはありません。しかし、他のソリューションがより高速になる他のものがあります。低いレベルから高い抽象化をいつでもシミュレートできますが、他の方法ではできないため、一般的な場合、高い抽象化が低い抽象化よりも速くなる方法がないことを効果的に証明できます。

GC 、手動メモリ管理の特殊なケースです

手動でより良いパフォーマンスを得るには、多くの作業やエラーが発生する可能性がありますが、それは別の話です。


1
それは私には意味がありません。具体的な例をいくつか示します。1)本番GCのアロケーターと書き込みバリアは手書きのアセンブラーです。これは、Cが非効率であるためCからどのように勝つか、2)テールコールの除去が最適化の例であるためです。 Cコンパイラでは実行されないため、Cで実行できない高レベル(機能)言語で実行されます。スタックウォーキングは、高レベル言語でCレベル以下で実行される別の例です。
ジョンハロップ

2
1)コメントする特定のコードを確認する必要がありますが、アセンブラーで手書きのアロケーター/バリアーが速い場合は、手書きのアセンブラーを使用します。それがGCとどう関係するのかわかりません。2)ここを見てください:stackoverflow.com/a/9814654/441099重要なのは、非GC言語が末尾再帰の除去を行えるかどうかではありません。重要なのは、コードをできるだけ高速または高速に変換できることです。特定の言語のコンパイラがこれを自動的に行うことができるかどうかは、利便性の問題です。十分に低い抽象化では、必要に応じていつでも自分でこれを行うことができます。
ガイサートン

1
Cでのその末尾呼び出しの例は、それ自体を呼び出す関数の特別な場合にのみ機能します。Cは、相互に呼び出す関数の末尾の一般的なケースを処理できません。アセンブラーに落とし込み、開発のための無限の時間を想定するのはチューリングターピットです。
ジョンハロップ

3

GCが手動の方法よりも無限に効率的である人工的な状況を構築するのは簡単です-ガベージコレクターに「ルート」が1つだけあり、すべてがガベージであるように調整するだけで、GCの手順は即座に完了します。

考えてみると、それはプロセスに割り当てられたメモリをガベージコレクションするときに使用されるモデルです。プロセスは死に、メモリはすべてゴミになり、完了です。実用的な用語であっても、トレースを残さずに開始、実行、および終了するプロセスは、永久に開始して実行するプロセスよりも効率的です。

ガベージコレクションを使用する言語で記述された実用的なプログラムの場合、ガベージコレクションの利点は速度ではなく、正確性と単純さです。


人為的な例を簡単に作成できる場合は、簡単な例を示していただけますか?
Mehrdad

1
@Mehrdad彼は簡単なものを説明しました。終了する前にGCバージョンがガベージ実行に失敗するプログラムを作成します。手動でメモリ管理されたバージョンは、明示的に追跡および解放しているため、遅くなります。
btilly

3
@btilly:「GCバージョンが終了前にガベージランを実行できないプログラムを作成します。」...そもそもガベージコレクションを行わないと、GCが機能しないためにメモリリークが発生しますが、GC が存在することによるパフォーマンスの向上ではありません。これabort()は、プログラムが終了する前にC ++を呼び出すようなものです。これは無意味な比較です。あなたはガベージコレクションさえしていません、ただメモリリークをさせているだけです。最初からガベージコレクションを行っていない場合、ガベージコレクションの速度が速い(または遅い)とは言えません
...-Mehrdad

極端な例を挙げると、独自のヒープとヒープ管理を備えた完全なシステムを定義する必要があります。これは、すばらしい学生プロジェクトですが、このマージンに収まるには大きすぎます。非gcメモリ管理メソッドにストレスを与えるように設計された方法で、ランダムなサイズの配列を割り当ておよび割り当て解除するプログラムを作成することで、かなりうまくいくでしょう。
ddyer

3
@Mehrdadそうではありません。シナリオは、GCバージョンが実行されたはずのしきい値に決して到達しなかったことであり、別のデータセットで正しく実行できなかったことではありません。これは、最終的なパフォーマンスの良い予測子ではありませんが、GCバージョンにとっては非常に良いことです。
btilly

2

GCは単なるメモリ管理戦略ではありません。また、言語およびランタイム環境の設計全体に要求があり、コスト(および利点)がかかります。たとえば、GCをサポートする言語は、ガベージコレクターからポインターを非表示にできない形式にコンパイルする必要があります。通常は、慎重に管理されたシステムプリミティブを除き、ポインターを構築できません。もう1つの考慮事項は、GCが完了するまで実行を許可する必要があるいくつかのステップを課すため、応答時間の保証を維持することの難しさです。

したがって、ガベージコレクションされた言語があり、同じシステムで手動で管理されたメモリと速度を比較する場合、ガベージコレクションを使用していない場合でも、ガベージコレクションをサポートするためにオーバーヘッドを支払う必要があります。


2

より速いのは疑わしい。ただし、ハードウェアがサポートされている場合、超高速、感知できない、または高速になる可能性があります。LISPマシン用のそのような設計はずっと前にありました。メインCPUがそこにあることを知らないように、GCをハードウェアのメモリサブシステムに組み込みました。後の多くの設計と同様に、GCは一時停止をほとんどまたはまったく必要とせずにメインプロセッサと同時に実行されました。より近代的な設計は、専用のプロセッサと一時停止のないGCを使用するJVMよりも高速にJavaコードを実行するAzul Systems Vega 3マシンです。GC(またはJava)の速度を知りたい場合は、Googleを使用してください。


2

私はこれについてかなりの仕事をして、ここでそのいくつかを説明しました。Boehm GCをC ++でベンチマークし、C ++で記述されたカスタムマーク領域GCと、リストベースのn-queensソルバーを実行するOCamlのストックGC mallocを使用して割り当てますが、解放はしませんfree。OCamlのGCはすべてのケースで高速でした。C ++およびOCamlプログラムは、同じ割り当てを同じ順序で実行するように意図的に作成されています。

もちろん、プログラムを書き換えて、64ビット整数のみを使用し、割り当てを使用せずに問題を解決できます。より高速であると、演習のポイントが無効になります(これは、C ++で構築されたプロトタイプを使用して取り組んでいた新しいGCアルゴリズムのパフォーマンスを予測することでした)。

私は長年、業界で実際のC ++コードをマネージ言語に移植してきました。ほとんどすべてのケースで、パフォーマンスの大幅な向上が見られましたが、その多くは、GCが手動のメモリ管理に勝っていることによるものと思われます。実用的な制限は、マイクロベンチマークで達成できることではなく、期限とGCベースの言語が非常に大きな生産性の改善を提供する前に達成できることです。私は今でも組み込みデバイス(マイクロコントローラー)でCとC ++を使用していますが、それも今では変わりつつあります。


+1ありがとう。ベンチマークコードはどこで確認して実行できますか?
Mehrdad

コードはその場所に散らばっています。:私はここにマーク領域バージョン掲載groups.google.com/d/msg/...
ジョン・ハロップ

1
スレッドセーフとスレッドセーフの両方の結果があります。
ジョンハロップ

1
@Mehrdad:「このような潜在的なエラーの原因を排除しましたか?」はい。OCamlには、エスケープ分析などの最適化を行わない非常にシンプルなコンパイルモデルがあります。OCamlのクロージャの表現は、実際にはC ++ソリューションよりも大幅に遅いため、C ++とList.filter同様にカスタムを実際に使用する必要があります。しかし、はい、確かに、一部のRC操作は省略できます。しかし、私が実際に目にする最大の問題は、人々が大規模な産業用コードベースでそのような最適化を手作業で実行する時間がないことです。
ジョンハロップ

2
そのとおり。C ++のボトルネックは、コードを書くこと以外の追加作業ではありません。コードのメンテナンスは。この種の偶発的な複雑さでコードを維持するのは悪夢です。ほとんどの産業用コードベースは、数百万行のコードです。あなたはただそれに対処する必要はありません。shared_ptr並行性のバグを修正するためだけにすべてを変換する人を見てきました。コードは非常に遅くなりますが、今は動作します。
ジョンハロップ

-1

このような例には、手動によるメモリ割り当てスキームが必ず必要です。

最高のガベージコレクターを想定してくださいGC。内部には、メモリを割り当てるメソッド、解放できるメモリを決定するメソッド、最終的に解放するメソッドがあります。一緒にこれらのすべてよりも時間がかかりGCます。いくつかの他のメソッドで時間を費やしていGCます。

ここで、と同じ割り当てメカニズムを使用し、GCそのfree()呼び出しがと同じメソッドによって解放されるメモリを確保するだけの手動アロケータを考えてみましょうGC。スキャンフェーズはなく、他の方法もありません。必然的に時間がかかりません。


2
ガベージコレクターは、多くのオブジェクトを解放することができますが、各オブジェクトの後にメモリを有用な状態にする必要はありません。特定の条件を満たすすべてのアイテムを配列リストから削除するタスクを検討してください。N項目リストから単一の項目を削除するのはO(N)です。N個のリストからM個のアイテムを削除します。一度に1個はO(M * N)です。ただし、リストの1回のパスで基準を満たすすべてのアイテムを削除するのはO(1)です。
supercat

@supercat:freeバッチも収集できます。(唯一の理由は、リスト・トラバーサル自体の場合はもちろんの基準を満たすすべての項目を削除すると、まだO(N)である)
MSalters

基準を満たすすべてのアイテムを削除することは、少なくとも O(N)です。あなたは正しいよfreeGCは、まだいくつかの状況で先に出てくることができますが、各メモリ項目は、それに関連付けられた旗を持っていた場合は、バッチ・コレクトモードで動作することができます。N個のセットからL個の異なるアイテムを識別するM個の参照がある場合、参照が存在しないすべての参照を削除して残りを統合する時間は、O(N)ではなくO(M)です。Mの追加スペースがある場合、スケーリング定数は非常に小さくなります。さらに、非スキャンGCシステムのコンパクト化には...が必要です。
supercat14年

@supercat:まあ、最初のコメントの最後の文が述べているように、確かにO(1)ではありません。
MSalters

1
@MSalters:「そして、決定論的スキームが保育園を持つことを妨げるものは何ですか?」なし。OCamlのトレースガベージコレクターは確定的であり、ナーサリを使用します。しかし、それは「手動」ではなく、「決定論的」という言葉を誤用していると思います。
ジョンハロップ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.