C#およびCLR(v2.0)に関する興味深いメモ





メモの第2部を.NETに投稿するときが来ました。



構成ファイルには復venが必要です。

* .CONFIG-アプリケーションを本当に設定できますか?



タイツ
Mark R. "Procmon.exe"から有名なユーティリティを起動し、テストウィンドウアプリケーションを起動してすぐに閉じ、イベントの収集を停止します。 受信したログで、アプリケーションを名前でフィルタリングします(Include)。 表示される内容は次のとおりです。



1)   config: 22:09:36.0364337 WindowsFormsApplication1.exe 7388 QueryOpen S:\WindowsFormsApplication1.exe.config NAME NOT FOUND 2)   INI: 22:09:36.0366595 WindowsFormsApplication1.exe 7388 QueryOpen S:\WindowsFormsApplication1.INI NAME NOT FOUND 3)   Local: 22:09:36.0537481 WindowsFormsApplication1.exe 7388 QueryOpen S:\WindowsFormsApplication1.exe.Local NAME NOT FOUND
      
      







PowerGUIがEXEにコンパイルされるPowerShellスクリプトの構成ファイルを使用していることを偶然発見しました(パスワードで保護したり、すぐにサービスを作成することもできます)。

ファイル自体:Untitled.exeおよびUntitled.exe.config。



 <?xml version="1.0" encoding="utf-8" ?> <configuration> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" /> <supportedRuntime version="v2.0.50727" /> </startup> <runtime> <loadFromRemoteSources enabled="true"/> </runtime> </configuration>
      
      





.INI-アセンブリを最適化する必要がないことをJITコンパイラに伝えることができます。 したがって、リリースでは、MSILを最適化でき、2つの異なるビルドを使用せずに、このファイルを介してJITを管理できます。



 [.NET Framework Debugging Control] GenerateTrackinglnfo = 1 AllowOptimize = 0
      
      





.Local- ダイナミックリンクライブラリのリダイレクト



ジョン・ロビンスのジョーク
プロセスが見える別の場所があります。

HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \現在のバージョン\イメージファイル実行オプション\



レジストリキーMyApp.EXEを作成し、その中に新しい文字列値Debuggerを作成します。ここで、デバッガへのフルパス(ある場合)を示し、calc.exeを記述します。



これで、MyApp.EXEを起動しようとすると、計算機が実際に起動します。



1)少しのC#履歴

異なるバージョンで追加されたもの
C#2.0

ジェネリック、null許容型、匿名メソッド、デリゲートの強化、反復的なyieldブロック。 部分型、静的クラス、異なるアクセス修飾子を持つプロパティ、名前空間エイリアス(ローカルでWinForms = System.Windows.Formsを使用、グローバルに-FirstAlias :: DemoおよびSecondAlias :: Demo)、プラグマディレクティブ、安全でないコードの固定サイズバッファー(固定バイトデータ[20])。



C#3.0

LINQ、自動プロパティ、配列とローカル変数の暗黙的な型指定、宣言の場所でのオブジェクトとコレクションの初期化子、匿名型。 式ラムダと式ツリー、拡張メソッド、部分メソッド。



C#4.0

名前付き引数、オプションのパラメーター、一般化されたバリエーション、動的型。



C#5.0

非同期/待機、foreachループの変更、呼び出し元コンポーネントに関する情報の属性。





2)メモリ内の参照型のインスタンスの最小サイズ。

x86およびx64の場合
空のクラスを作成します。

 class MyClass { }
      
      





32ビットでコンパイルし、Windbgのサイズを調べます。

 0:005> !do 023849bc Name: ConsoleApplication1.MyClass MethodTable: 006c39d4 EEClass: 006c1904 Size: 12(0xc) bytes -   . File: E:\...\ConsoleApplication1.exe Fields: None
      
      





64ビットでコンパイルします。

 0:003> !do 0000007c8d8465b8 Name: ConsoleApplication1.MyClass MethodTable: 00007ffa2b5c4320 EEClass: 00007ffa2b6d2548 Size: 24(0x18) bytes File: E:\...\ConsoleApplication1.exe Fields: None
      
      





最初の4バイトまたは8バイトはオブジェクトのタイトルワードであるため、小さくなりません。 同期、ガベージコレクタのオーバーヘッドの保存、ファイナライズ、ハッシュコードの保存に使用されます。 このフィールドの一部のビットは、各特定の瞬間にどの情報が格納されるかを決定します。

2番目の4バイトまたは8バイトは、メソッドテーブルへのリンクです。

データとアライメント用の3番目の4バイトまたは8バイト(たとえそれらに何もない場合でも)。

x86の参照型のインスタンスの合計最小サイズは12バイトで、x64は24バイトです。





3)メモリー内のクラスのインスタンスの非静的フィールドおよびメソッド(x64)。

1つのフィールドと自動プロパティを追加します
 class MyClass { private string _field1 = "Some string 1"; public string Field2 { get; set; } }
      
      





IL 2つのフィールドがあります。

 .field private string '<Field2>k__BackingField' .field private string _field1
      
      





そして2つの方法:

 .method public hidebysig specialname instance string get_Field2() cil managed .method public hidebysig specialname instance void set_Field2(string 'value') cil managed
      
      





誰がどこに着いたか見てみましょう:

 0:003> !do 0000005400006600 Name: ConsoleApplication1.MyClass MethodTable: 00007ffa2b5c4378 EEClass: 00007ffa2b6d2548 Size: 32(0x20) bytes File: E:\...\ConsoleApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 00007ffa89d60e08 4000002 8 System.String 0 instance 0000005400006620 _field1 00007ffa89d60e08 4000003 10 System.String 0 instance 00000054000035a0 <Field2>k__BackingField
      
      







フィールドはインスタンスに直接到達し、その最小サイズに影響しました(最初のリンクは17から24ビット(以前は空でした)で32で、2番目の25-32(シーケンスの順序を保持するために属性です)。しかし、メソッドは直接インスタンスはなく、それらへのリンクのみが存在するため、サイズに影響しませんでした。



メソッドの表を見てみましょう:

 0:003> !dumpmt -md 00007ffa2b5c4378 EEClass: 00007ffa2b6d2548 Module: 00007ffa2b5c2fc8 Name: ConsoleApplication1.MyClass mdToken: 0000000002000003 File: E:\...\ConsoleApplication1.exe BaseSize: 0x20 ComponentSize: 0x0 Slots in VTable: 7 Number of IFaces in IFaceMap: 0 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 00007ffa89ae6300 00007ffa896980e8 PreJIT System.Object.ToString() 00007ffa89b2e760 00007ffa896980f0 PreJIT System.Object.Equals(System.Object) 00007ffa89b31ad0 00007ffa89698118 PreJIT System.Object.GetHashCode() 00007ffa89b2eb50 00007ffa89698130 PreJIT System.Object.Finalize() 00007ffa2b6e0390 00007ffa2b5c4358 JIT ConsoleApplication1.MyClass..ctor() 00007ffa2b5cc130 00007ffa2b5c4338 NONE ConsoleApplication1.MyClass.get_Field2() 00007ffa2b5cc138 00007ffa2b5c4348 NONE ConsoleApplication1.MyClass.set_Field2(System.String)
      
      





そして、ここでは、コンストラクターと、.NETのインストール時にNgen自身がSystem.Objectから継承したインスタンスメソッドを除いて、どちらもまだJITコンパイルに合格していません。



この段落の終わりに、フィールドで示されるオブジェクトのサイズとともにインスタンスのフルサイズを見ていきます。

 MyClass mcClass = new MyClass(); mcClass.Field2 = "Some string 2"; 0:003> !objsize 0000005400006600 sizeof(0000005400006600) = 144 (0x90) bytes (ConsoleApplication1.MyClass)
      
      





フィールドのサイズを見て、これを確認しましょう。

 0:003> !objsize 0000005400006620 sizeof(0000005400006620) = 56 (0x38) bytes (System.String) 0:003> !objsize 00000054000035a0 sizeof(00000054000035a0) = 56 (0x38) bytes (System.String)
      
      





合計:56 + 56 + 32 = 144。





4)静的フィールドとメソッド(x64)。

タイツ
 class MyClass { private string _name = "Some string"; public static string _STR = "I'm STATIC"; public static void ImStaticMethod() { } } MyClass mcClass = new MyClass(); Console.WriteLine(MyClass._STR);
      
      





最小インスタンスサイズ(静的フィールドは考慮されません):

 0:003> !do 00000033ba2c65f8 Name: ConsoleApplication1.MyClass MethodTable: 00007ffa2b5b4370 EEClass: 00007ffa2b6c2550 Size: 24(0x18) bytes File: E:\...\ConsoleApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 00007ffa89d60e08 4000002 8 System.String 0 instance 00000033ba2c6610 _name 00007ffa89d60e08 4000003 10 System.String 0 static 00000033ba2c35a0 _STR
      
      





方式リスト:

 0:003> !dumpmt -md 00007ffa2b5b4370 EEClass: 00007ffa2b6c2550 Module: 00007ffa2b5b2fc8 Name: ConsoleApplication1.MyClass mdToken: 0000000002000003 File: E:\...\ConsoleApplication1.exe BaseSize: 0x18 ComponentSize: 0x0 Slots in VTable: 7 Number of IFaces in IFaceMap: 0 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 00007ffa89ae6300 00007ffa896980e8 PreJIT System.Object.ToString() 00007ffa89b2e760 00007ffa896980f0 PreJIT System.Object.Equals(System.Object) 00007ffa89b31ad0 00007ffa89698118 PreJIT System.Object.GetHashCode() 00007ffa89b2eb50 00007ffa89698130 PreJIT System.Object.Finalize() 00007ffa2b6d0110 00007ffa2b5b4350 JIT ConsoleApplication1.MyClass..cctor() 00007ffa2b6d03f0 00007ffa2b5b4348 JIT ConsoleApplication1.MyClass..ctor() 00007ffa2b5bc130 00007ffa2b5b4338 NONE ConsoleApplication1.MyClass.ImStaticMethod()
      
      





ConsoleApplication1.MyClass..cctor()-静的フィールドに目を向けたため、静的コンストラクターが実行されました。 型コンストラクタとも呼ばれ、正確に呼び出されたときは不明です。 静的フィールドの存在下で自動的に作成されます。 その中でアクションを実行する必要がない場合は、明示的に処方しない方が良いでしょう。 これにより、メタデータのbeforefieldinitフラグを使用して最適化が行われます。 詳細msdn.microsoft.com/ru-ru/library/dd335949.aspx



サイズを確認してください:

 0:003> !objsize 00000033ba2c65f8 sizeof(00000033ba2c65f8) = 72 (0x48) bytes (ConsoleApplication1.MyClass) 0:003> !objsize 00000033ba2c6610 sizeof(00000033ba2c6610) = 48 (0x30) bytes (System.String)
      
      





合計:24 + 48 = 72。

メソッドのような静的フィールドは、各インスタンスのコピーに保存されません。





5)親と、ガベージコレクションからインスタンスを保持する人を見つけます。

ヒープ用
3ポイントからのデータとアドレス。

 0:003> !dumpclass 00007ffa2b6c2550 Class Name: ConsoleApplication1.MyClass mdToken: 0000000002000003 File: E:\...\ConsoleApplication1.exe Parent Class: 00007ffa89684908 Module: 00007ffa2b5b2fc8 Method Table: 00007ffa2b5b4370 Vtable Slots: 4 Total Method Slots: 6 Class Attributes: 100000 Transparency: Critical NumInstanceFields: 1 NumStaticFields: 1 MT Field Offset Type VT Attr Value Name 00007ffa89d60e08 4000002 8 System.String 0 instance _name 00007ffa89d60e08 4000003 10 System.String 0 static 00000033ba2c35a0 _STR
      
      





私たちは親に行きます:

 0:003> !dumpclass 00007ffa89684908 Class Name: System.Object mdToken: 0000000002000002 File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Parent Class: 0000000000000000 -  . Module: 00007ffa89681000 Method Table: 00007ffa89d613e8 Vtable Slots: 4 Total Method Slots: a Class Attributes: 102001 Transparency: Transparent NumInstanceFields: 0 NumStaticFields: 0
      
      





mcClass = new MyClass()を保持している人:

 0:003> !gcroot 00000033ba2c65f8 Thread 3310: 00000033b81fedb0 00007ffa2b6d031f ConsoleApplication1.Program.Main rbx: -> 00000033ba2c65f8 ConsoleApplication1.MyClass
      
      





真実のように聞こえます。





6)Foreachは誰ですか。

タイツ
1. foreachを使用すると、非表示のローカル変数(ループイテレーター)が作成されます。



2. IDeachposableインターフェイスが実装されている場合、foreachステートメントはループの最後でDispose()を自動的に呼び出します。



3. GetEnumerator()、MoveNext()メソッドの呼び出し、およびCurrentプロパティの呼び出しにコンパイルされます。



4. Foreachは、yield returnと同様に、遅延反復です。たとえば、mogogigabyteファイルを一度に1行ずつ読み取り、メモリを節約する場合に非常に便利です。



5.配列のForeachは、Lengthプロパティと配列インデクサーを使用し、イテレーターオブジェクトを作成しません。



6. C#5では、foreachループ内でキャプチャされた変数が正しく機能するようになりましたが、C#3およびC#4は変数の1つのインスタンス(最後)のみをキャプチャします。





7)LINQ

クラッツ
1. LINQ to Objectは、通常のデリゲート(インプロセスクエリ)としてJITで実行され、LINQ to SQLは式ツリーを構築し、SQLまたは他の環境で既に実行しています。 ツリーはデリゲートに変換できます。



2. LINQ to ObjectsのOrderByでは、すべてのデータをロードする必要があります。



3. LINQ to ObjectsでJoin()を使用すると、右側のシーケンスがバッファリングされますが、左側ではストリームが編成されます。したがって、大きなシーケンスを小さなシーケンスに接続する必要がある場合、小さなシーケンスを正しいシーケンスとして指定すると便利です。



4. EnumType.Select(x => x)-これは縮退クエリ式と呼ばれ、結果は要素のシーケンスであり、ソース自体ではありません。これはデータの整合性の観点から重要です。 (適切に設計されたLINQデータプロバイダーに有効です。)





8)コレクション。

クリックして
リストT-配列を内部的に保存します。 新しい要素を追加するには、配列に値を設定するか、既存の配列を2倍の大きさ(文書化されていない)の新しい配列にコピーしてから値を設定します。 リストTからアイテムを削除するには、その後ろのアイテムを元の位置にコピーする必要があります。 RemoveAt()インデックスにより、削除はRemove()値よりもはるかに高速です(各要素はどこにでも比較されます)。



配列のサイズは常に固定されていますが、要素に関しては可変です。



LinkedList Tは、新しいアイテムをすばやく削除、挿入できるリンクリストです。インデックスはありませんが、効率は維持されます。



ReadOnlyDictionaryは、明示的なインターフェイス実装の背後にあるすべての可変操作を隠す単なるラッパーです。 基礎に転送されたコレクションを通じて要素を変更することが可能です。





9)オプションのメソッドパラメータ。

ネタバレ!
 void Method1( int x ) { x = 5; } IL: .method private hidebysig instance void Method1(int32 x) cil managed { // Code size 4 (0x4) .maxstack 8 IL_0000: ldc.i4.5 IL_0001: starg.sx IL_0003: ret } // end of method TestClass::Method1 void Method ( int x = 5 ) { } IL: .method private hidebysig instance void Method([opt] int32 x) cil managed { .param [1] = int32(0x00000005) // Code size 1 (0x1) .maxstack 8 IL_0000: ret } // end of method TestClass::Method
      
      





int xは定数です。 また、定数はメタデータに直接格納されます。つまり、定数を変更するには、このメソッドを使用してすべてのコードを再コンパイルする必要があります。 (2つのソース、413ページ)。



C#4以降、名前付き引数が使用されている場合、メソッドパラメーターの名前を変更すると他のコードに影響を与える可能性があります。





10).NETプラットフォーム上のアプリケーションの最適化

ヘーサークリック
1. パフォーマンスカウンター

カウンターは1秒間に数回しか更新されず、パフォーマンスモニター自体は1秒間に1回より多くカウンター値を読み取ることはできません。



2. Windows ETWのイベントトレース

これは、高性能のイベント登録フレームワークです。



ETWからイベントを読み取ります。

a) Windowsパフォーマンスツールキット

b) PerfMonitor 。 (MicrosoftのオープンソースCLRプロジェクト。)

c) PerfView 。 (Microsoftの無料のHarvester。)



3.メモリプロファイラー(組み込みのVSに加えて) CLRプロファイラー

既存のプロセス(4.0以上のCLR)に接続するか、新しいプロセスを開始することができ、メモリ割り当てとガベージコレクションのすべてのイベントを収集します。 多数のグラフを作成します。



マルチスレッドアプリケーションの誤動作の一般的なパターン





11)同期。

タイク
 lock ( obj ) { }
      
      





オンデマンドでのみ実行され、時間がかかります。 CLRは、グローバル配列「同期ブロックテーブル」に構造「同期ブロック」を作成し、弱いリンク(リサイクル用)によるロックを所有するオブジェクトへのリンクと、Win32イベントに実装されたモニターへのリンクを持っています。 同期ブロックの数値インデックスは、オブジェクトのヘッダーワードに格納されます。 同期オブジェクトが破棄された場合、同期ユニットとの接続は別のオブジェクトで再利用するために上書きされます。



しかし、すべてがそれほど単純なわけではなく、まだ薄いロックがあります。 同期ブロックがまだ作成されておらず、一方のスレッドのみがオブジェクトを所有している場合、もう一方は実行しようとして、オブジェクトヘッダーワードから所有者情報が消えるまでしばらく待機します。これが発生しない場合、シンロックは通常に変換されます。





12)パッキング。

nパッケージ
構造は次のとおりです。

 public struct Point { public int X; public int Y; } List<Point> polygon = new List<Point>(); for ( int i = 0; i < 10000000; i++ ) { polygon.Add( new Point() { X = rnd.Next(), Y = rnd.Next() } ); } Point point = new Point { X = 5, Y = 7 }; bool contains = polygon.Contains( point );
      
      





1号機を発売します。



次に、メソッドを追加します。

 public override int GetHashCode() { return (X & Y) ^ X; //  . } public override bool Equals( object obj ) { if ( !( obj is Point ) ) return false; Point other = ( Point ) obj; return X == other.X && Y == other.Y; } public bool Equals( Point other ) { return X == other.X && Y == other.Y; }
      
      





2番を開始します。



次に、インターフェースの実装を追加します(すでに適切なメソッドがあります):

 public struct Point : IEquatable<Point> { ... }
      
      





3号機を発売します。

(リストTにはIEquatable Tインターフェイスの実装はありません)



匿名タイプを試します。

 var someType = new { Prop1 = 2, Prop2 = 80000 }; var items = Enumerable.Range( 0, 10000000 ) .Select( i => new { Prop1 = i, Prop2 = i+i } ) .ToList(); items.Contains(someType);
      
      





4号機を発売します。

コンパイラーは、someTypeの型が拡張メソッドの型と同一であるため、問題がなかったことを発見しました。



まとめ
テスト結果:





そして、ILの匿名型は次のようになります。





興味があれば、メモリ内でsomeTypeはどのように見える
 var someType = new { Prop1 = 2, Prop2 = 80000 }; 0:005> !do 0000008da2745e08 Name: <>f__AnonymousType0`2[[System.Int32, mscorlib],[System.Int32, mscorlib]] MethodTable: 00007ffa2b5b4238 EEClass: 00007ffa2b6c2548 Size: 24(0x18) bytes File: E:\...\BoxingUnboxingPointList.exe Fields: MT Field Offset Type VT Attr Value Name 0...0 4000003 8 System.Int32 1 instance 2 <Prop1>i__Field 0...0 4000004 c System.Int32 1 instance 80000 <Prop2>i__Field
      
      





値タイプには、値自体(2および80,000)が格納されます。



メソッド表:

 0:005> !dumpmt -md 00007ffa2b5b4238 EEClass: 00007ffa2b6c2548 Module: 00007ffa2b5b2fc8 Name: <>f__AnonymousType0`2[[System.Int32, mscorlib],[System.Int32, mscorlib]] mdToken: 0000000002000004 File: E:\...\BoxingUnboxingPointList.exe BaseSize: 0x18 ComponentSize: 0x0 Slots in VTable: 7 Number of IFaces in IFaceMap: 0 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 0...8 0...0 NONE <>f__AnonymousType0`2[[...]].ToString() 0...0 0...8 NONE <>f__AnonymousType0`2[[...]].Equals(System.Object) 0...8 0...0 NONE <>f__AnonymousType0`2[[...]].GetHashCode() 0...0 0...0 PreJIT System.Object.Finalize() 0...0 0...8 NONE <>f__AnonymousType0`2[[...]]..ctor(Int32, Int32) 0...8 0...0 NONE <>f__AnonymousType0`2[[...]].get_Prop1() 0...0 0...8 NONE <>f__AnonymousType0`2[[...]].get_Prop2()
      
      





私はまた何か他のものを見ることを期待していました:)





13)非同期/待機

彼はより大きく
非同期-生成されたコードには表現がありません。

待機-有限状態マシン、構造。 このタイプの会議の時点までに作業の結果がすでに利用可能な場合、メソッドは結果を同期モードで引き続き処理します。 値がtrueのTask TResult.ConfigureAwaitメソッドは、継続を元のキャプチャされたコンテキストにマーシャリングしようとします。これが不要な場合は、falseを使用します。



アレクサンダーからこのトピックに関する素晴らしい無料ビデオレッスン。



また、記事「 SynchronizationContext-MSDNが失敗しとき 」の翻訳を読むことも非常に良いことです。





14)ガベージコレクション

タイク
「0」世代の固定オブジェクトを使用する場合、CLRはこの世代が古いと宣言し、自分で新しい世代を選択できます。



世代「1」は「0」からのみアクセスでき、ファイナライズされたオブジェクトは間違いなくそこにあります。



「2」世代のサイズは人為的に制限されておらず、使用可能なすべてのメモリが使用されます(WindowsはCLRと共有できます)が、GCはそれがいっぱいになるのを待たず、しきい値を使用します(これはわかりません)。



マーキングフェーズでは、ライブとしてマークされたオブジェクトはリンクを失い、1つのアセンブリを生き残ることができます。



ラージオブジェクトヒープには85 kbを超えるオブジェクトが含まれていますが、これはグラフ(含まれているオブジェクト)ではなく1つのオブジェクトを参照しています。 世代「2」に関連付けられて、一緒になります。





ソース:

1)John Robbins「Microsoft .NETおよびMicrosoft Windows用のアプリケーションのデバッグ」。

2)ジョン・スキート「C#forProfessionals。TheSubtleties of Programming」、第3版。

3)Sasha Goldstein、Dima Zurbalev、Ido Flatov「.Netプラットフォームでのアプリケーション最適化」。



多くの手紙が判明しましたが、JITコンパイラーは後で使用できます。

ご清聴ありがとうございました。



All Articles