web-dev-qa-db-ja.com

C#のスタックサイズが正確に1 MBなのはなぜですか?

今日のPCには大量の物理的なRAMがありますが、それでもC#のスタックサイズは32ビットプロセスで1 MB、64ビットプロセスで4 MBです( スタック容量C# )で。

CLRのスタックサイズがまだそれほど制限されているのはなぜですか?

そして、なぜそれはちょうど1 MB(4 MB)である(2 MBや512 KBではない)のか?これらの金額を使用することにしたのはなぜですか?

私はその決定の背後にある考察と理由に興味があります。

84
Nikolay Kostov

enter image description here

あなたはその選択をした男を見ています。 David Cutlerと彼のチームは、デフォルトのスタックサイズとして1メガバイトを選択しました。 .NETやC#とは何の関係もありません。WindowsNTを作成したときにこれは明確にされています。プログラムのEXEヘッダーまたはCreateThread()winapi呼び出しでスタックサイズが明示的に指定されていない場合、1メガバイトが選択されます。これは通常の方法で、ほとんどのプログラマーはOSに任せてサイズを選択します。

この選択はおそらくWindows NTの設計よりも前のものであり、この点については歴史があまりにも曖昧です。カトラーがそれについて本を書いてくれればいいのですが、彼は作家ではありません。彼はコンピューターの動作に非常に大きな影響を与えてきました。彼の最初のOS設計はRSX-11M、DECコンピューター(Digital Equipment Corporation)用の16ビットオペレーティングシステムでした。これは、8ビットマイクロプロセッサ用の最初の適切なOSであるGary KildallのCP/Mに大きな影響を与えました。 MS-DOSに大きな影響を与えました。

彼の次の設計は、仮想メモリをサポートする32ビットプロセッサ用のオペレーティングシステムであるVMSでした。とても成功しました。彼の次のものは、会社が崩壊し始めた頃にDECによってキャンセルされ、安価なPCハードウェアと競合することができませんでした。マイクロソフトをキュー、彼らは彼に彼が拒否することができなかった申し出をしました。彼の同僚の多くも参加しました。彼らはWindows NTとして知られているVMS v2で働いていました。 DECはそれについて怒って、お金はそれを解決するために手を変えました。 VMSが既に1メガバイトを選択したかどうかはわかりませんが、RSX-11については十分に知っているだけです。ありそうなことではありません。

十分な歴史。 1メガバイトはlotです。実際のスレッドが消費するのは数キロバイトを超えることはめったにありません。したがって、メガバイトは実際にはかなり無駄です。ただし、デマンドページ仮想メモリオペレーティングシステムでは余裕があるため、メガバイトは仮想メモリにすぎません。 4096バイトごとに1つずつ、プロセッサに番号を付けます。実際にアドレスを指定するまで、実際に物理メモリ(RAM)を使用することはありません。

.NETプログラムでは、ネイティブプログラムに対応するために元々1メガバイトのサイズが選択されていたため、これは非常に過剰です。大きなスタックフレームを作成する傾向があり、文字列とバッファ(配列)もスタックに格納します。マルウェア攻撃ベクトルであることで悪名高いバッファオーバーフローは、プログラムをデータで操作する可能性があります。 .NETプログラムの動作方法ではなく、文字列と配列がGCヒープに割り当てられ、インデックス作成がチェックされます。 C#でスタック上のスペースを割り当てる唯一の方法は、安全でないstackallocキーワードを使用することです。

.NETでのスタックの唯一の重要な使用法は、ジッターによるものです。スレッドのスタックを使用して、MSILをマシンコードにジャストインタイムでコンパイルします。必要なスペースを見たことも確認したこともありません。コードの性質とオプティマイザーが有効かどうかによって異なりますが、数十キロバイトは大まかな推測です。それ以外の場合は、このWebサイトがその名前を取得した方法です。NETプログラムでのスタックオーバーフローは非常に致命的です。例外をキャッチしようとするコードを確実にJITするのに十分なスペース(3キロバイト未満)が残っていません。デスクトップへのKaboomが唯一のオプションです。

最後に重要なことですが、.NETプログラムはスタックでかなり非生産的なことをします。 CLRは、スレッドのスタックをcommitします。これは高価なWordです。つまり、スタックのサイズを予約するだけでなく、オペレーティングシステムのページングファイルにスペースが予約されていることを確認し、必要に応じてスタックを常に交換できるようにします。コミットに失敗すると致命的なエラーになり、プログラムを無条件に終了します。それは、非常に少ないRAMが非常に多くのプロセスを実行するマシンでのみ発生します。そのようなマシンは、プログラムが死に始める前に糖蜜に変わります。 F1レースカーのように動作するようにプログラムを調整する <disableCommitThreadStack> .configファイルの要素。

Fwiw、Cutlerはオペレーティングシステムの設計を停止しませんでした。その写真は、彼がAzureで働いていたときに作成されました。


更新、.NETがスタックをコミットしなくなったことに気付きました。いつ、なぜこれが起こったのか正確にはわかりませんが、チェックしてから長すぎます。この設計変更は、.NET 4.5のどこかで起こったと推測しています。かなり賢明な変化。

179
Hans Passant

デフォルトの予約済みスタックサイズはリンカーによって指定され、リンク時にPE値を変更するか、dwStackSize WinAPIにCreateThreadパラメーターを指定して個々のスレッドを変更することにより、開発者がオーバーライドできます。関数。

デフォルトのスタックサイズ以上の初期スタックサイズでスレッドを作成した場合、最も近い1 MBの倍数に切り上げられます。

32ビットプロセスで1 MB、64ビットで4 MBに等しいのはなぜですか? Windowsを設計した開発者に尋ねるか、誰かがあなたの質問に答えるまで待つべきだと思います。

おそらくマーク・ルシノヴィッチはそれを知っており、あなたは contact 彼にできる。この情報は、彼の article ではなく、スタックに関する以下の情報を説明している第6版より前の彼のWindows Internals本で見つけることができます。あるいは、Windowsの内部構造とその歴史について興味深いことを書いているレイモンドチェンは、理由を知っているかもしれません。彼もあなたの質問に答えることができますが、提案を Suggestion Box に投稿する必要があります。

しかし、現時点では、MSDN、Mark、およびRaymondのブログを使用して、Microsoftがこれらの値を選択した理由をいくつか説明します。

デフォルトではこれらの値が設定されています。これはおそらく、初期のPCが遅く、スタック上のメモリの割り当てがヒープ内のメモリの割り当てよりもはるかに高速だったためです。また、スタック割り当てははるかに安価であったため、使用されましたが、より大きなスタックサイズが必要でした。

そのため、この値は、ほとんどのアプリケーションにとって最適な予約スタックサイズでした。多くのネストされた呼び出しを行い、呼び出し関数に構造体を渡すためにスタックにメモリを割り当てることができるため、最適です。同時に、多くのスレッドを作成できます。

現在、これらの値は、下位互換性のために主に使用されます。これは、WinAPI関数にパラメーターとして渡される構造体が依然としてスタックに割り当てられているためです。ただし、スタック割り当てを使用していない場合、スレッドのスタック使用量はデフォルトの1 MBより大幅に少なくなり、Hans Passantが述べたように無駄になります。これを防ぐために、OSはスタックの最初のページ(4 KB)のみをコミットします(アプリケーションのPEヘッダーで他のページが指定されていない場合)。他のページはオンデマンドで割り当てられます。

一部のアプリケーションは、予約済みのアドレス空間をオーバーライドし、メモリ使用量を最適化するために最初にコミットします。例として、IISネイティブプロセスのスレッドの最大スタックサイズは256 KBです( KB932909 )。デフォルト値のこの減少は 推奨 Microsoft:

できる限り小さいスタックサイズを選択し、スレッドまたはファイバーを確実に実行するために必要なスタックをコミットすることが最善です。スタック用に予約されているすべてのページを他の目的に使用することはできません。

ソース:

  1. スレッドスタックサイズ(Microsoft Docs)
  2. Windowsの限界を押し広げる:プロセスとスレッド(Mark Russinovich)
  3. デフォルトでは、ネイティブで作成されるスレッドの最大スタックサイズIISプロセスは256 KB(KB932909)
3
Yoh Deadfall