お久しぶりです、エンジニアのMasashiです。
今回は、ファイルの書き込み速度について比較を行いたいと思います。
ファイル書き込みに関しての要望は多く、テキスト形式、csv形式の違いはあれど
多く実装されているのではないかと思います。
今回はファイルの書き込みを行う関数の速度を4つの方法から検証してみたいと思います。
測定までの流れ
今回測定を行うのは、【0123456789】という文字列を500,000回、
txt形式で書き込むまでに要した時間になります。
比較対象としてStreamWriterをtry~finallyの形で記載したもの、StreamWriterをusingを使用して記載したもの、AppendAllTextを使用、WriteAllLinesを使用の4つの場合について検証しています。
AppendAllTextについては、毎回ファイルのオープン・クローズが走ることで速度が
どの程度遅くなってしまうのかを計測するために、Loop内で使用するという間違った使い方で計測しています。
測定環境
- CPU:Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz (4 CPUs), ~2.9GHz
- メモリ:16GB
- Visual Studio 2015
対象データ
- 書き込む文字列:0123456789
- 書き込む回数:500,000
- ファイル形式:txt形式
測定対象
- StreamWriterをtry~finallyで記載
- StreamWriterをusingを使用して記載
- AppendAllTextを使用
- WriteAllLinesを使用
テストコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; namespace FileWriteSpeedTest { class Program { // テストデータ数(書き込み回数) private const int WriteDataCount = 500000; static void Main(string[] args) { var streamWriterTimes = new List<double>(); var usingStreamWriterTimes = new List<double>(); var writeAllTextTimes = new List<double>(); var appendAllTextTimes = new List<double>(); var writeAllLinesTimes = new List<double>(); var arr = MakeArray(); for (var i = 0; i < 10; i++) { streamWriterTimes.Add(StreamWriter()); usingStreamWriterTimes.Add(UsingStreamWriter()); //appendAllTextTimes.Add(AppendAllText()); writeAllLinesTimes.Add(WriteAllLines(arr)); } Console.WriteLine("StreamWriter平均値:" + streamWriterTimes.Average()); Console.WriteLine("UsingStreamWriter平均値:" + usingStreamWriterTimes.Average()); //Console.WriteLine("AppendAllText平均値:" + appendAllTextTimes.Average()); Console.WriteLine("WriteAllLines平均値:" + writeAllLinesTimes.Average()); Console.ReadLine(); } // StreamWriterをtry~finallyで記載 private static double StreamWriter() { var stopwatch = new Stopwatch(); stopwatch.Start(); var sw = new StreamWriter(@"C:\test\test1.txt", true, Encoding.GetEncoding("shift_jis")); try { for(var i = 0; i < WriteDataCount; i++) { sw.Write("0123456789"); } } finally { sw.Close(); } stopwatch.Stop(); Console.WriteLine("計測時間【StreamWriter】: " + stopwatch.Elapsed.TotalMilliseconds + "ms"); File.Delete(@"C:\test\test1.txt"); return stopwatch.Elapsed.TotalMilliseconds; } // StreamWriterをusingを使用して記載 private static double UsingStreamWriter() { var stopwatch = new Stopwatch(); stopwatch.Start(); using(var sw = new StreamWriter(@"C:\test\test2.txt", true, Encoding.GetEncoding("shift_jis"))) { for(var i = 0; i < WriteDataCount; i++) { sw.Write("0123456789"); } } stopwatch.Stop(); Console.WriteLine("計測時間【UsingStreamWriter】: " + stopwatch.Elapsed.TotalMilliseconds + "ms"); File.Delete(@"C:\test\test2.txt"); return stopwatch.Elapsed.TotalMilliseconds; } // AppendAllTextを使用 // 毎回オープン、クローズ処理が走るので、速度が遅く計測を諦めた // データをまとめて書き込めば何も問題なし private static double AppendAllText() { var stopwatch = new Stopwatch(); stopwatch.Start(); for (var i = 0; i < WriteDataCount; i++) { File.AppendAllText(@"C:\test\test3.txt", "0123456789", Encoding.GetEncoding("shift_jis")); } stopwatch.Stop(); Console.WriteLine("計測時間【AppendAllText】: " + stopwatch.Elapsed.TotalMilliseconds + "ms"); File.Delete(@"C:\test\test3.txt"); return stopwatch.Elapsed.TotalMilliseconds; } // WriteAllLinesを使用 private static double WriteAllLines(string[] arr) { var stopwatch = new Stopwatch(); stopwatch.Start(); File.WriteAllLines(@"C:\test\test4.txt", arr, Encoding.GetEncoding("shift_jis")); stopwatch.Stop(); Console.WriteLine("計測時間【WriteAllLines】: " + stopwatch.Elapsed.TotalMilliseconds + "ms"); File.Delete(@"C:\test\test4.txt"); return stopwatch.Elapsed.TotalMilliseconds; } // テストデータを配列形式で作成 private static string[] MakeArray() { var r = new Random(); var arr = new string[WriteDataCount]; for (var i = 0; i < WriteDataCount; i++) { arr[i] = "0123456789"; } return arr; } } } |
ファイル書き込み速度の測定結果
10回試行した結果の平均値が下記になっています。小数点第1位で四捨五入しています。
計測項目 | 計測時間(ms) |
---|---|
StreamWriterをtry~finallyで記載 | 81 |
StreamWriterをusingを使用して記載 | 78 |
AppendAllTextを使用 | 測定不能 |
WriteAllLinesを使用 | 100 |
結果の比較の前にAppendAllTextを使用したパターンですが、毎回ファイルのオープン・クローズを行うため、
結果の取得に非常に時間がかかり、計測することを諦めたため、計測不能という形で記載させていただきました。
上記関数は、オープン・クローズをユーザーが意識しなくていい形で使用できる関数であるため、
文字列形式を素直に1度書き込ませるだけであれば速度面の問題はありません。
今回の計測には入れていませんが、配列形式のデータを文字列形式に直した形のものをAppendAllTextを使用して
書き込ませてみました。
配列形式のデータを文字列に加工する段階から速度を計測した場合、StreamWriterを使用したパターンと速度に違いはありませんでしたので、正しい使い方をする分には問題ありません。
StreamWriterを使用したパターンは若干ですが、usingを使用したほうが速度が速くなりました。
測定誤差かと考え何度か計測を行ってみましたが、必ずusingを使用したほうが速い結果となるためそのあたりも今後調査してみたいと思います。
WriteAllLinesはデータを書き込むたびに改行を加えてくれるため、その分が速度差として現れただけだと考えられます。
ファイル書き込み速度のまとめ
ファイル書き込みの処理を行う際は、基本的にはどの関数を使用していただいても速度面で問題になることはないかと考えられます。
AppendAllTextを使用する場合は、データを加工してから使用することで、ファイルのオープン・クローズ処理を意識する必要がないため非常に便利です。
StreamWriterを使用する場合は、非常に小さい値ですが速度差が生まれるため、usingを使用して実装するのがいいかと思います。
またusingを使用することでClose処理を意識しなくてすむため、finallyでClose処理を入れることを忘れていたようなバグを防ぐことができます。
WriteAllLinesを使用する場合は、改行を入れる処理を意識しなくていいため、取得したデータを改行して書き込んでほしいという要望がある場合は使用することになると思います。
今回の計測により関数単位では、大きな速度差を見つけることはできませんでしたが、状況に応じて使用する関数を決めていいことがわかる結果になったかと思います。
この記事が少しでも皆さんのお役に立てば幸いです。