すぐに高速:重要なデータ型のパッキングおよびアンパッキング時間を測定します

こんにちは、Habr!





多くの経験の浅い開発者は、コードの背後で何が起こっているのかを常に把握して理解しているわけではありません。 ここで、重要なデータ型のパックとアンパックについて説明します(ロシア語では、これはひどく聞こえるので、「値型のボックス化とボックス化解除」)。

猫の下には、ランタイムの小さな例と測定値があります。



ボクシングとは何ですか?

まもなく。 重要なデータ型(値型)と参照(参照型)があります。 重要なデータ型の変数は値自体を保存します(ありがとう、cap!)、参照データ型の変数-この値が保存されるメモリ内のセクションへのリンク。



int valType = 15;
      
      





これは重要なデータ型です。 変数valTypeの値はスタックに保存されます。 多くの標準データ型は重要です(int、byte、long、boolなど)。

さらに、これを行おうとすると:



 int valType = 15; Object refType = valType;
      
      





その結果、参照型の変数( refType )を取得します。 ここでは、次のことが起こります:最初に、変数valType (重要な型)の値がスタックに表示され、次にこの変数の値を格納するためのコンテナーがメモリに作成されます(この場合、 int型の変数のコンテナー、つまり、値+同期ブロックインデックスの4バイト(さらに4つ)バイト))、およびこのコンテナへのポインタがあり、参照型の変数( refType )に格納されます。 このプロセスはボクシングと呼ばれます。

詳細についてはこちらをご覧ください 。ただし、 J。Richterの著書「CLR via C#」 (第5章)をお読みください。



これらの操作で最も不快なのは、暗黙的に発生することです。

たとえば、コンソールに数値を出力したいとします。 だから:



 Console.WriteLine(20);
      
      





またはこのように:



 Console.WriteLine("{0}", 20);
      
      





違いは何ですか? MSILでのコンパイル結果を見てみましょう(これはILdasm.exeユーティリティで実行できます)。



 //    Console.WriteLine(20); IL_0000: ldc.i4.s 20 IL_0002: call void [mscorlib]System.Console::WriteLine(int32) //     Console.WriteLine("{0}", 20); IL_0007: ldstr "{0}" IL_000c: ldc.i4.s 20 IL_000e: box [mscorlib]System.Int32 IL_0013: call void [mscorlib]System.Console::WriteLine(string, object)
      
      





2番目のケースでは、パッケージ化を実行するboxコマンドが表示されます

どこから来たのかを理解するには、 Console.WriteLineメソッドのシグネチャを調べて、すでに18個あることに注意してください。

最初の呼び出しでは、次の署名が使用されます。



 void WriteLine(int value);
      
      





ここではすべてが簡単です-このメソッドは重要なintデータ型を受け入れ、 int値を渡し、パラメーターは値で渡されます。 ここではパッケージングは​​必要ありません。

2番目の場合、別の署名が使用されます。



 void WriteLine(string format, object arg0);
      
      





フォーマット文字列の送信では、文字列が必要です-文字列を渡します。 引数arg0では、もう少し複雑です。メソッドは、参照データ型objectのオブジェクトを要求し、 int型の値をメソッドに渡します。 これは、パッケージングが必要な場所です。 その結果、 int型のコンテナがメモリに作成され、値20がメモリにコピーされ、このコンテナへのポインタが引数arg0に分類されます。



ここで、操作によってこれらの操作が大幅に遅くなるかどうかを計算してみましょう。

これを行うために、私は小さなコードを書きました:



コードシート
 static void Main() { //   ,  var val = 15; //   ,   Object obj = val; //   -  =) const int cycles = 1000000; var str = ""; //       var results = new List<TimeSpan>(); //   20 ,       for (var j = 0; j < 20; j++) { //   var start = DateTime.Now; for (var i = 0; i < cycles; i++) { //    10   //      str = String.Format("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}", obj, obj, obj, obj, obj, obj, obj, obj, obj, obj); } //   var end = DateTime.Now; //         (box) var objResult = end.Subtract(start); //    start = DateTime.Now; for (var i = 0; i < cycles; i++) { //      //      10   (box) str = String.Format("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}", val, val, val, val, val, val, val, val, val, val); } //   end = DateTime.Now; //     ,    10    var valResult = end.Subtract(start); //           results.Add(valResult.Subtract(objResult)); } //    foreach (var timeDif in results) { Console.WriteLine(timeDif); } //     Console.WriteLine(); Console.Write("Milliseconds need for 10KK boxing operations: "); Console.WriteLine(results.Aggregate(TimeSpan.Zero, (sum, current) => sum.Add(current)).TotalMilliseconds / results.Count); }
      
      







次の結果が得られました(Intel Core i5 750 2.67GHzプロセッサ、4コア、1つで実行):



 00:00:00.0600060 00:00:00.0770077 00:00:00.0570057 00:00:00.0710071 00:00:00.0680068 00:00:00.0650065 00:00:00.0530053 00:00:00.0740074 00:00:00.0570057 00:00:00.0580058 00:00:00.0590059 00:00:00.0500050 00:00:00.0550055 00:00:00.0720072 00:00:00.0800080 00:00:00.0640064 00:00:00.0640064 00:00:00.0670067 00:00:00.0660066 00:00:00.0590059 Milliseconds need for 10KK boxing operations: 63,80638
      
      





1,000万人あたり約64ミリ秒の平均 パッキング操作。



おわりに



結論として、上記のすべてが、コード内で逆アセンブラーを使用してボクシングを妄想的に検索し、時速100キロメートルの速度で毎秒余分なミリメートルを達成する理由ではないと言いたいと思います。 いいえ、もちろん、これは完全なナンセンスです。 ただし、コードで実際に何が行われているかを理解することは重要です。 そして、ある時点で、数十億回実行されるサイクルでの余分な操作が重要になる場合があります。



UPD! コメントの中で、 exmachineユーザーは、私が正しく測定を行っていないと言っています。 調整後の結果は次のとおりです。

結果
 Cache warming results: 00:00:00.0505219 00:00:00.0491484 00:00:00.0527804 00:00:00.0586028 00:00:00.0595744 00:00:00.0573599 00:00:00.0678498 00:00:00.0560197 00:00:00.0591139 00:00:00.0382205 00:00:00.0602378 00:00:00.0862110 00:00:00.0632895 00:00:00.0584091 00:00:00.0556713 00:00:00.0572194 00:00:00.0544349 00:00:00.0750407 00:00:00.0579586 00:00:00.0561487 Test results: 00:00:00.0640218 00:00:00.0558972 00:00:00.0612732 00:00:00.0560300 00:00:00.0547193 00:00:00.0556158 00:00:00.0558210 00:00:00.0554421 00:00:00.0632168 00:00:00.0611355 00:00:00.0539173 00:00:00.0594863 00:00:00.0549896 00:00:00.0585462 00:00:00.0598485 00:00:00.0586522 00:00:00.0560457 00:00:00.0568806 00:00:00.0784523 00:00:00.0521756 Milliseconds need for 10KK boxing operations: 58,60835
      
      









UPD2! ユーザーmstyuraは、以前にHabréで同様の質問がすでに取り上げられていたことを思い出しました。 詳細を確認することをお勧めします。



All Articles