マイクロソフト系技術情報 Wiki」は、「Open棟梁Project」,「OSSコンソーシアム .NET開発基盤部会」によって運営されています。

目次

概要

こちらでは、メモリに保持可能なデータ量の場合の設計ディシジョンについて説明します。
この場合、メモリの大量消費による同時実行性の低下やCPU時間が問題となります。

ステートメントレベルの性能

メモリ上のデータへのアクセス時間を1億回実行して測定してみました。

  • データの保持方式
    • データはグローバル変数に保持。
    • このようにすればスタックに積まれることもないので高速。

以下結果

 100000000回
 
 Only for :ExT:277[msec],   CT:281[msec],   KT:0[msec], UT:281[msec]
 → forループのオーバーヘッド
 if       :ExT:291[msec],   CT:281[msec],   KT:0[msec], UT:281[msec]
 → ifステートメント
 intRef   :ExT:217[msec],   CT:218[msec],   KT:0[msec], UT:218[msec]
 → int変数の参照+代入
 strRef   :ExT:216[msec],   CT:218[msec],   KT:0[msec], UT:218[msec]
 → string変数の参照+代入
 crRef    :ExT:281[msec],   CT:281[msec],   KT:0[msec], UT:281[msec]
 → オブジェクト・メンバ変数の参照+代入
 arryRef  :ExT:234[msec],   CT:234[msec],   KT:0[msec], UT:234[msec]
 → 配列の参照+代入
 arryRef4 :ExT:715[msec],   CT:718[msec],   KT:0[msec], UT:718[msec]
 → 4次元配列の参照+代入
 lstRef   :ExT:498[msec],   CT:499[msec],   KT:0[msec], UT:499[msec]
 → リストの参照+代入
 lstRef4  :ExT:1570[msec],  CT:1560[msec],  KT:0[msec], UT:1560[msec]
 → 4階層リストの参照+代入
 dicRef   :ExT:2996[msec],  CT:3011[msec],  KT:0[msec], UT:3011[msec]
 → Dictionaryの参照+代入
 dicRef4  :ExT:11695[msec], CT:11684[msec], KT:0[msec], UT:11684[msec]
 → 4階層Dictionaryの参照+代入

以下プログラム

   class Program
   {
       static void Main(string[] args)
       {
           int i = 0;
           const int num = 100000000;
           Console.WriteLine(num.ToString() + "回");
           
           PerformanceRecorder pr = new PerformanceRecorder();
           //--------------------------------------------------
           // ウォームアップ
           //--------------------------------------------------
           for (i = 0; i < num; i++)
           {
           }
           for (i = 0; i < num; i++)
           {
           }
           
           //--------------------------------------------------
           // Only for
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
           }
           Console.WriteLine("Only for:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // ifステートメント
           //--------------------------------------------------
           bool bln = true;
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               if (bln)
               { }
           }
           Console.WriteLine("if:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // intの参照と代入
           //--------------------------------------------------
           int intVal = 0;
           int intRef = 100;
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               intVal = intRef;
           }
           Console.WriteLine("intRef:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // stringの参照と代入
           //--------------------------------------------------
           string strVal = "";
           string strRef = "aaa";
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = strRef;
           }
           Console.WriteLine("strRef:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // クラスメンバの参照と代入
           //--------------------------------------------------
           ClassRecord crRef = new ClassRecord();
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = crRef.G;
           }
           Console.WriteLine("crRef:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // 配列添字での参照と代入
           //--------------------------------------------------
           string[] arryRef = new string[] { strRef, strRef, strRef, strRef, strRef, strRef, strRef };
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = arryRef[6];
           }
           Console.WriteLine("arryRef:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // 4次元配列添字での参照と代入
           //--------------------------------------------------
           string[, , ,] arryRef4 = new string[1, 1, 1, 1] { { { {""} } } };
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = arryRef4[0, 0, 0, 0];
           }
           Console.WriteLine("arryRef4:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // Generic List 添字での参照と代入
           //--------------------------------------------------
           List<string> lstRef = new List<string>();
           lstRef.Add(strRef);
           lstRef.Add(strRef);
           lstRef.Add(strRef);
           lstRef.Add(strRef);
           lstRef.Add(strRef);
           lstRef.Add(strRef);
           lstRef.Add(strRef);
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = lstRef[6];
           }
           Console.WriteLine("lstRef:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // 4階層Generic List 添字での参照と代入
           //--------------------------------------------------
           List<List<string>> lstRef2 = new List<List<string>>();
           List<List<List<string>>> lstRef3 = new List<List<List<string>>>();
           List<List<List<List<string>>>> lstRef4 = new List<List<List<List<string>>>>();
           lstRef.Add(strRef);
           lstRef2.Add(lstRef);
           lstRef3.Add(lstRef2);
           lstRef4.Add(lstRef3);
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = lstRef4[0][0][0][0];
           }
           Console.WriteLine("lstRef4:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // Generic Dictionary 添字での参照と代入
           //--------------------------------------------------
           Dictionary<string, string> dicRef = new Dictionary<string, string>();
           dicRef["A"] = strRef;
           dicRef["B"] = strRef;
           dicRef["C"] = strRef;
           dicRef["D"] = strRef;
           dicRef["E"] = strRef;
           dicRef["F"] = strRef;
           dicRef["G"] = strRef;
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = dicRef["G"];
           }
           Console.WriteLine("dicRef:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // 4階層Generic Dictionary 添字での参照と代入
           //--------------------------------------------------
           Dictionary<string, Dictionary<string, string>> dicRef2 = new Dictionary<string, Dictionary<string, string>>();
           Dictionary<string, Dictionary<string, Dictionary<string, string>>> dicRef3 = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();
           Dictionary<string, Dictionary<string, Dictionary<string, Dictionary<string, string>>>> dicRef4 = new Dictionary<string, Dictionary<string, Dictionary<string, Dictionary<string, string>>>>();
           dicRef["G"] = strRef;
           dicRef2["G"] = dicRef;
           dicRef3["G"] = dicRef2;
           dicRef4["G"] = dicRef3;
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = dicRef4["G"]["G"]["G"]["G"];
           }
           Console.WriteLine("dicRef4:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // 4階層Generic Dictionary 添字での参照と代入(With句的対策)
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           
           dicRef = dicRef4["G"]["G"]["G"]; 
           for (i = 0; i < num; i++)
           {
               strVal = dicRef["G"];
           }
           Console.WriteLine("dicRef4性能対策実施版:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // 4階層Generic Dictionary もしハッシュ・キーがintだったら
           //--------------------------------------------------
           Dictionary<int, string> dicRefInt = new Dictionary<int, string>();
           dicRefInt[0] = strRef;
           dicRefInt[1] = strRef;
           dicRefInt[2] = strRef;
           dicRefInt[3] = strRef;
           dicRefInt[4] = strRef;
           dicRefInt[5] = strRef;
           dicRefInt[6] = strRef;
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = dicRefInt[6];
           }
           Console.WriteLine("dicRefハッシュ・キーがint:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           //--------------------------------------------------
           // Generic Dictionary 添字での参照と代入(大量データ)
           //--------------------------------------------------
           Dictionary<string, string> dicRefBigdata = new Dictionary<string, string>();
           int j = 0;
           for(j = 0; j< 1000000; j++)
           {
               dicRefBigdata[j.ToString()] = strRef;
           }
           
           //--------------------------------------------------
           pr.StartsPerformanceRecord();
           for (i = 0; i < num; i++)
           {
               strVal = dicRefBigdata["999999"];
           }
           Console.WriteLine("dicRefBigdata:" + pr.EndsPerformanceRecord());
           //--------------------------------------------------
           
           Console.ReadKey();
       }
   }

以下結論

メモリ上のアクセス時間は、配列や、Genericの4階層を使用し始めると

  • 辿るポインタが増え、時間が増加し始めます。
  • 配列よりGenericのlist→Dictionaryの方が遅い。
  • Dictionaryが特に遅いのは
    • 内部でハッシュの計算とアドレスの解決をしているためと思われる。
    • ハッシュキーをstringからintに変更した場合、性能改善する(≒list)。

(余談:ifのオーバーヘッドはあまり無いようです。)

従って、データ保持は、

  1. レコード(フィールド)は→Class(メンバ)に保持
  2. キー階層は→配列、Genericのlist→Dictionary

を使用することになると思います。

  • 配列は、キーが数字の場合に利用する。
    「可変長か?固定長か?」で「配列か?Genericのlistか?」の使い分けがあるかもしれません。
  • Dictionaryはハッシュでのアクセスが遅いので、キーが数字以外の場合に利用可能する。

ということだと思います。

チューニング

階層が増える場合、チューニング手段としてよくやる
VBのWithステートメントを使用する要領で、辿るポインタ数を減らします。

 //--------------------------------------------------
 pr.StartsPerformanceRecord();
 for (i = 0; i < num; i++)
 {
   strVal = dicRef4["G"]["G"]["G"]["G"];
 }
 Console.WriteLine("dicRef4:" + pr.EndsPerformanceRecord());
 //--------------------------------------------------

↓↓↓

 //--------------------------------------------------
 pr.StartsPerformanceRecord();
           
 dicRef = dicRef4["G"]["G"]["G"]; 
 for (i = 0; i < num; i++)
 {
   strVal = dicRef["G"];
 }
 Console.WriteLine("dicRef4性能対策実施版:" + pr.EndsPerformanceRecord());
 //--------------------------------------------------

以下のように、性能が改善します。

 dicRef:
   ExT:2996[msec],  CT:3011[msec],  KT:0[msec], UT:3011[msec]
 dicRef4:
   ExT:11754[msec], CT:11747[msec], KT:0[msec], UT:11747[msec]
 dicRef4性能対策実施版:
   ExT:3137[msec],  CT:3089[msec],  KT:0[msec], UT:3089[msec]

データ容量の影響

要素数が増えた場合、若干遅延します。

 dicRef:要素数7
   ExT:3003[msec], CT:2948[msec], KT:0[msec], UT:2948[msec]
 dicRefBigdata:要素数100万
   ExT:3979[msec], CT:3994[msec], KT:0[msec], UT:3994[msec]

データのロード

  • ロードするデータがメモリに収まるかどうかがポイントになります。
  • 処理時間が問題となる場合は、ロード処理の時間も問題になる可能性があります。
  • .NETでは、オブジェクトとしてロードされるのでファイルサイズと異なる可能性があります。
  • また、どういうオブジェクト・モデルにロードするかによってサイズが変わってきます。
  • 使用可能なメモリ・サイズを超えるとページングが発生し、処理時間内に完了しません。

CSVデータのロード

CSVデータをオブジェクト表現した際にサイズはどうなるか?

  • 以下のCSVデータ10MBをUnicodeでファイルに保存、
    オブジェクト・モデルにロードしてデータサイズの差分を確認する。
 xxxxxxxxxx,xxxxxxxxxx,・・・,xxxxxxxxxx
 xxxxxxxxxx,xxxxxxxxxx,・・・,xxxxxxxxxx
 xxxxxxxxxx,xxxxxxxxxx,・・・,xxxxxxxxxx
   ・・・
 xxxxxxxxxx,xxxxxxxxxx,・・・,xxxxxxxxxx
  • Unicodeで保存するのは、文字列のプログラム表現がUnicodeのため、
    オブジェクト・モデル化によるデータサイズ増加を把握し易くするため。
  • データのロードには、Microsoft.VisualBasic?.FileIO.TextFieldParser?を使用した。
  • データの保持方式
    ステートメントレベルの性能で高速であった方式を選択した。
    1. スタックに積まれないようにグローバル変数とする。
    2. レコード(フィールド)は→Class(メンバ)に保持する。
    3. 上記のClassをGenericのlistに保持する。

以下結果

  • ファイルサイズ10MBに対して、プロセスメモリサイズは22MB
  • 試しにファイルサイズ20MBにした所、プロセスメモリサイズは41MB

となりました。

  • その他
  • キー部分が共通なものを配列、Genericの階層にまとめるとデータの削減になる。
  • 以下、オブジェクト・モデルにロードするまでの性能情報
 45318record(10MB)
   End:ExT:1002[msec], CT:983[msec], KT:31[msec], UT:952[msec]
 90636record(20MB)
   End:ExT:2095[msec], CT:2044[msec], KT:172[msec], UT:1872[msec]

以下プログラム

   class Program
   {
       static List<ClassRecord> ListClassRecord = new List<ClassRecord>();
       
       static void Main(string[] args)
       {
           // CSVファイルを読み込むには?[2.0のみ、C#、VB] - @IT
           // http://www.atmarkit.co.jp/fdotnet/dotnettips/487csvparser/csvparser.html
           
           TextFieldParser parser = new TextFieldParser("CSV.csv", System.Text.Encoding.GetEncoding("utf-16"));
           
           int i = 0;
           
           PerformanceRecorder pr = new PerformanceRecorder();
           pr.StartsPerformanceRecord();
           using (parser)
           {
               parser.TextFieldType = FieldType.Delimited;
               parser.SetDelimiters(","); // 区切り文字はコンマ
               
               // parser.HasFieldsEnclosedInQuotes = false;
               // parser.TrimWhiteSpace = false;
               
               
               while (!parser.EndOfData)
               {
                   i++;
                   //// コンソールに出力
                   //Console.WriteLine(i.ToString());
                   
                   // 1行読み込み
                   string[] row = parser.ReadFields();
                   // クラスにロード
                   Program.ListClassRecord.Add(new ClassRecord(row));
               }
           }
           
           Console.WriteLine(i.ToString() +"record");
           Console.WriteLine("End:" + pr.EndsPerformanceRecord());
           Console.ReadKey();
       }
   }

xxxデータのロード

以下結論

データサイズは単純に2倍になっているが、何処が肥大しているか?

  • レコード数なのか?
  • カラム数なのか?

が不明確であるので測定してみないと解らない。

また、フィールドのサイズ次第で増大する比率が変わってくる。

マシンスペック

以下性能情報の取得に使用したマシンのスペック

 ------------------
 System Information
 ------------------
 Time of this report: 9/5/2014, 21:04:22
        Machine name: ************
    Operating System: Windows 7 Professional 32-bit (6.1, Build 7601) Service Pack 1 (7601.win7sp1_gdr.140303-2144)
            Language: Japanese (Regional Setting: Japanese)
 System Manufacturer: Hewlett-Packard
        System Model: HP ProBook 6570b
                BIOS: Default System BIOS
           Processor: Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz (4 CPUs), ~2.5GHz
              Memory: 4096MB RAM
 Available OS Memory: 2954MB RAM
           Page File: 4026MB used, 1969MB available
         Windows Dir: C:\Windows
     DirectX Version: DirectX 11
 DX Setup Parameters: Not found
    User DPI Setting: 120 DPI (125 percent)
  System DPI Setting: 120 DPI (125 percent)
     DWM DPI Scaling: Disabled
      DxDiag Version: 6.01.7601.17514 32bit Unicode

Tags: :データアクセス


添付ファイル: fileConsoleApplication.zip 174件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-02-02 (金) 16:58:48 (501d)