Lumia 920 で実行されているアプリの WP7 バージョンが、同じデバイスで実行されている WP8 バージョンの 2 倍の速さでデータをロードすることを発見したとき、アプリにすべてのデータをロードする最速の方法を見つけるために、複数のアルゴリズムのベンチマークを行っていました。
次に、WP8 の StorageFile と WP7 の IsolatedStorageFile のパフォーマンスをテストするために、次の独立したコードを作成しました。
タイトルを明確にするために、20kb と 100kb の 50 個のファイルを読み取って行った予備的なベンチマーク結果をここに示します。
コードについては、以下を参照してください
アップデート
今日数時間ベンチマークを行い、いくつかの興味深い結果を出した後、私の質問を言い換えさせてください。
await StreamReader.ReadToEndAsync()
非非同期メソッドよりもすべてのベンチマークで一貫して遅いのはなぜStreamReader.ReadToEnd()
ですか? (これは Neil Turner からのコメントで既に回答されている可能性があります)StorageFile でファイルを開くときに大きなオーバーヘッドがあるようですが、それは UI スレッドで開いた場合のみです。(メソッド 1 と 3 または 5 と 6 の間の読み込み時間の違いを参照してください。3 と 6 は、同等の UI スレッド メソッドよりも約 10 倍高速です)
より高速な可能性があるファイルを読み取る他の方法はありますか?
アップデート 3
さて、今回の更新でさらに 10 個のアルゴリズムを追加し、以前に使用したすべてのファイル サイズと使用したファイル数ですべてのアルゴリズムを再実行しました。今回は、各アルゴリズムを 10 回実行しました。したがって、Excel ファイルの生データは、これらの実行の平均です。現在 18 個のアルゴリズムがあり、それぞれが 4 つのファイル サイズ (1kb、20kb、100kb、1mb) でそれぞれ 50、100、および 200 ファイル (18*4*3 = 216) でテストされているため、合計 2160 回のベンチマーク実行がありました。合計で 95 分かかります (生の実行時間)。
更新 5
ベンチマーク 25、26、27 およびReadStorageFile
メソッドを追加。投稿が明らかに最大である 30000 文字を超えていたため、一部のテキストを削除する必要がありました。新しいデータ、新しい構造、比較、および新しいグラフで Excel ファイルを更新しました。
コード:
public async Task b1LoadDataStorageFileAsync()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
//b1
for (int i = 0; i < filepaths.Count; i++)
{
StorageFile f = await data.GetFileAsync(filepaths[i]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await r.ReadToEndAsync();
}
}
}
}
public async Task b2LoadDataIsolatedStorage()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
for (int i = 0; i < filepaths.Count; i++)
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = r.ReadToEnd();
}
}
}
}
await TaskEx.Delay(0);
}
public async Task b3LoadDataStorageFileAsyncThread()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
await await Task.Factory.StartNew(async () =>
{
for (int i = 0; i < filepaths.Count; i++)
{
StorageFile f = await data.GetFileAsync(filepaths[i]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await r.ReadToEndAsync();
}
}
}
});
}
public async Task b4LoadDataStorageFileThread()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
await await Task.Factory.StartNew(async () =>
{
for (int i = 0; i < filepaths.Count; i++)
{
StorageFile f = await data.GetFileAsync(filepaths[i]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = r.ReadToEnd();
}
}
}
});
}
public async Task b5LoadDataStorageFile()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
//b5
for (int i = 0; i < filepaths.Count; i++)
{
StorageFile f = await data.GetFileAsync(filepaths[i]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = r.ReadToEnd();
}
}
}
}
public async Task b6LoadDataIsolatedStorageThread()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
await Task.Factory.StartNew(() =>
{
for (int i = 0; i < filepaths.Count; i++)
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = r.ReadToEnd();
}
}
}
});
}
}
public async Task b7LoadDataIsolatedStorageAsync()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
for (int i = 0; i < filepaths.Count; i++)
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await r.ReadToEndAsync();
}
}
}
}
}
public async Task b8LoadDataIsolatedStorageAsyncThread()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
await await Task.Factory.StartNew(async () =>
{
for (int i = 0; i < filepaths.Count; i++)
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await r.ReadToEndAsync();
}
}
}
});
}
}
public async Task b9LoadDataStorageFileAsyncMy9()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
for (int i = 0; i < filepaths.Count; i++)
{
StorageFile f = await data.GetFileAsync(filepaths[i]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); });
}
}
}
}
public async Task b10LoadDataIsolatedStorageAsyncMy10()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
//b10
for (int i = 0; i < filepaths.Count; i++)
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); });
}
}
}
}
}
public async Task b11LoadDataStorageFileAsyncMy11()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
for (int i = 0; i < filepaths.Count; i++)
{
await await Task.Factory.StartNew(async () =>
{
StorageFile f = await data.GetFileAsync(filepaths[i]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = r.ReadToEnd();
}
}
});
}
}
public async Task b12LoadDataIsolatedStorageMy12()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
for (int i = 0; i < filepaths.Count; i++)
{
await Task.Factory.StartNew(() =>
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = r.ReadToEnd();
}
}
});
}
}
}
public async Task b13LoadDataStorageFileParallel13()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
List<Task> tasks = new List<Task>();
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var task = await Task.Factory.StartNew(async () =>
{
StorageFile f = await data.GetFileAsync(filepaths[index]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
String content = r.ReadToEnd();
if (content.Length == 0)
{
//just some code to ensure this is not removed by optimization from the compiler
//because "content" is not used otherwise
//should never be called
ShowNotificationText(content);
}
}
}
});
tasks.Add(task);
}
await TaskEx.WhenAll(tasks);
}
public async Task b14LoadDataIsolatedStorageParallel14()
{
List<Task> tasks = new List<Task>();
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var t = Task.Factory.StartNew(() =>
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[index], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
String content = r.ReadToEnd();
if (content.Length == 0)
{
//just some code to ensure this is not removed by optimization from the compiler
//because "content" is not used otherwise
//should never be called
ShowNotificationText(content);
}
}
}
});
tasks.Add(t);
}
await TaskEx.WhenAll(tasks);
}
}
public async Task b15LoadDataStorageFileParallelThread15()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
await await Task.Factory.StartNew(async () =>
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var task = await Task.Factory.StartNew(async () =>
{
StorageFile f = await data.GetFileAsync(filepaths[index]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
String content = r.ReadToEnd();
if (content.Length == 0)
{
//just some code to ensure this is not removed by optimization from the compiler
//because "content" is not used otherwise
//should never be called
ShowNotificationText(content);
}
}
}
});
tasks.Add(task);
}
await TaskEx.WhenAll(tasks);
});
}
public async Task b16LoadDataIsolatedStorageParallelThread16()
{
await await Task.Factory.StartNew(async () =>
{
List<Task> tasks = new List<Task>();
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var t = Task.Factory.StartNew(() =>
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[index], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
String content = r.ReadToEnd();
if (content.Length == 0)
{
//just some code to ensure this is not removed by optimization from the compiler
//because "content" is not used otherwise
//should never be called
ShowNotificationText(content);
}
}
}
});
tasks.Add(t);
}
await TaskEx.WhenAll(tasks);
}
});
}
public async Task b17LoadDataStorageFileParallel17()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
List<Task<Task>> tasks = new List<Task<Task>>();
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var task = Task.Factory.StartNew<Task>(async () =>
{
StorageFile f = await data.GetFileAsync(filepaths[index]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
String content = r.ReadToEnd();
if (content.Length == 0)
{
//just some code to ensure this is not removed by optimization from the compiler
//because "content" is not used otherwise
//should never be called
ShowNotificationText(content);
}
}
}
});
tasks.Add(task);
}
await TaskEx.WhenAll(tasks);
List<Task> tasks2 = new List<Task>();
foreach (var item in tasks)
{
tasks2.Add(item.Result);
}
await TaskEx.WhenAll(tasks2);
}
public async Task b18LoadDataStorageFileParallelThread18()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
await await Task.Factory.StartNew(async () =>
{
List<Task<Task>> tasks = new List<Task<Task>>();
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var task = Task.Factory.StartNew<Task>(async () =>
{
StorageFile f = await data.GetFileAsync(filepaths[index]);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
String content = r.ReadToEnd();
if (content.Length == 0)
{
//just some code to ensure this is not removed by optimization from the compiler
//because "content" is not used otherwise
//should never be called
ShowNotificationText(content);
}
}
}
});
tasks.Add(task);
}
await TaskEx.WhenAll(tasks);
List<Task> tasks2 = new List<Task>();
foreach (var item in tasks)
{
tasks2.Add(item.Result);
}
await TaskEx.WhenAll(tasks2);
});
}
public async Task b19LoadDataIsolatedStorageAsyncMyThread()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
//b19
await await Task.Factory.StartNew(async () =>
{
for (int i = 0; i < filepaths.Count; i++)
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); });
}
}
}
});
}
}
public async Task b20LoadDataIsolatedStorageAsyncMyConfigure()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
for (int i = 0; i < filepaths.Count; i++)
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); }).ConfigureAwait(false);
}
}
}
}
}
public async Task b21LoadDataIsolatedStorageAsyncMyThreadConfigure()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
await await Task.Factory.StartNew(async () =>
{
for (int i = 0; i < filepaths.Count; i++)
{
using (var stream = new IsolatedStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); }).ConfigureAwait(false);
}
}
}
});
}
}
public async Task b22LoadDataOwnReadFileMethod()
{
await await Task.Factory.StartNew(async () =>
{
for (int i = 0; i < filepaths.Count; i++)
{
filecontent = await ReadFile("/benchmarks/samplefiles/" + filepaths[i]);
}
});
}
public async Task b23LoadDataOwnReadFileMethodParallel()
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var t = ReadFile("/benchmarks/samplefiles/" + filepaths[i]);
tasks.Add(t);
}
await TaskEx.WhenAll(tasks);
}
public async Task b24LoadDataOwnReadFileMethodParallelThread()
{
await await Task.Factory.StartNew(async () =>
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var t = ReadFile("/benchmarks/samplefiles/" + filepaths[i]);
tasks.Add(t);
}
await TaskEx.WhenAll(tasks);
});
}
public async Task b25LoadDataOwnReadFileMethodStorageFile()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
await await Task.Factory.StartNew(async () =>
{
for (int i = 0; i < filepaths.Count; i++)
{
filecontent = await ReadStorageFile(data, filepaths[i]);
}
});
}
public async Task b26LoadDataOwnReadFileMethodParallelStorageFile()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
List<Task> tasks = new List<Task>();
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var t = ReadStorageFile(data, filepaths[i]);
tasks.Add(t);
}
await TaskEx.WhenAll(tasks);
}
public async Task b27LoadDataOwnReadFileMethodParallelThreadStorageFile()
{
StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
data = await data.GetFolderAsync("samplefiles");
await await Task.Factory.StartNew(async () =>
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < filepaths.Count; i++)
{
int index = i;
var t = ReadStorageFile(data, filepaths[i]);
tasks.Add(t);
}
await TaskEx.WhenAll(tasks);
});
}
public async Task b28LoadDataOwnReadFileMethodStorageFile()
{
//StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");
//data = await data.GetFolderAsync("samplefiles");
await await Task.Factory.StartNew(async () =>
{
for (int i = 0; i < filepaths.Count; i++)
{
filecontent = await ReadStorageFile(ApplicationData.Current.LocalFolder, @"benchmarks\samplefiles\" + filepaths[i]);
}
});
}
public async Task<String> ReadStorageFile(StorageFolder folder, String filename)
{
return await await Task.Factory.StartNew<Task<String>>(async () =>
{
String filec = "";
StorageFile f = await folder.GetFileAsync(filename);
using (var stream = await f.OpenStreamForReadAsync())
{
using (StreamReader r = new StreamReader(stream))
{
filec = await r.ReadToEndAsyncThread();
}
}
return filec;
});
}
public async Task<String> ReadFile(String filepath)
{
return await await Task.Factory.StartNew<Task<String>>(async () =>
{
String filec = "";
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
using (var stream = new IsolatedStorageFileStream(filepath, FileMode.Open, store))
{
using (StreamReader r = new StreamReader(stream))
{
filec = await r.ReadToEndAsyncThread();
}
}
}
return filec;
});
}
これらのベンチマークの実行方法:
public async Task RunBenchmark(String message, Func<Task> benchmarkmethod)
{
SystemTray.ProgressIndicator.IsVisible = true;
SystemTray.ProgressIndicator.Text = message;
SystemTray.ProgressIndicator.Value = 0;
long milliseconds = 0;
Stopwatch w = new Stopwatch();
List<long> results = new List<long>(benchmarkruns);
for (int i = 0; i < benchmarkruns; i++)
{
w.Reset();
w.Start();
await benchmarkmethod();
w.Stop();
milliseconds += w.ElapsedMilliseconds;
results.Add(w.ElapsedMilliseconds);
SystemTray.ProgressIndicator.Value += (double)1 / (double)benchmarkruns;
}
Log.Write("Fastest: " + results.Min(), "Slowest: " + results.Max(), "Average: " + results.Average(), "Median: " + results[results.Count / 2], "Maxdifference: " + (results.Max() - results.Min()),
"All results: " + results);
ShowNotificationText((message + ":").PadRight(24) + (milliseconds / ((double)benchmarkruns)).ToString());
SystemTray.ProgressIndicator.IsVisible = false;
}
ベンチマーク結果
生のベンチマーク データへのリンク: http://www.dehodev.com/windowsphonebenchmarks.xlsx
グラフ (すべてのグラフは、各メソッドを介して 50 をロードするためのデータを示します。結果はすべてミリ秒単位です)
次の 1 MB のベンチマークは、アプリを代表するものではありません。これらの方法がどのようにスケーリングするかについてより良い概要を示すために、ここにそれらを含めます。
要約すると、ファイルの読み取りに使用される標準的な方法 (1.) は常に最悪です (ただし、10 MB のファイルを 50 個読み取りたい場合を除きますが、それでもより良い方法があります)。
これもリンクしています: await AsyncMethod() 対 await await Task.Factory.StartNew<TResult>(AsyncMethod)、通常は新しいタスクを追加するのは役に立たないと主張されています。ただし、ここで見た結果は、それを想定することはできず、タスクを追加するとパフォーマンスが向上するかどうかを常に確認する必要があるということです。
そして最後に、これを公式の Windows Phone 開発者フォーラムに投稿したかったのですが、試行するたびに「予期しないエラー」メッセージが表示されます...
更新 2
結論:
データを確認すると、ファイル サイズに関係なく、すべてのアルゴリズムがファイル数に比例してスケーリングすることが明確にわかります。したがって、すべてを単純化するために、ファイルの数を無視できます (今後の比較では、50 ファイルのデータのみを使用します)。
次にファイル サイズについて: ファイル サイズは重要です。ファイル サイズを大きくすると、アルゴリズムが収束し始めることがわかります。10MB のファイル サイズでは、前の最も遅いアルゴリズムは 4/8 で実行されます。ただし、この質問は主に携帯電話を対象としているため、アプリがこれほど多くのデータを含む複数のファイルを読み取ることは非常にまれであり、ほとんどのアプリでは 1MB のファイルであってもまれです。私の推測では、50 個の 20kb ファイルを読み取ることさえ珍しいことです。ほとんどのアプリは、それぞれ 0.5kb から 3kb のサイズの 10 から 30 個のファイルの範囲でおそらくデータを読み取っています。(あくまで推測ですが、多分正しいと思います)