StorageFile 比 IndependentStorageFile 慢 50 倍 [英] StorageFile 50 times slower than IsolatedStorageFile

查看:30
本文介绍了StorageFile 比 IndependentStorageFile 慢 50 倍的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我发现在 Lumia 920 上运行的应用程序的 WP7 版本加载数据的速度是在 Lumia 920 上运行的 WP8 版本时的 2 倍相同的设备.

我编写了以下独立代码来测试 WP8 的 StorageFile 和 WP7 的 IndependentStorageFile 的性能.

为了澄清标题,这里是我做的初步基准测试结果,读取了 20kb 和 100kb 的 50 个文件:

代码见下

更新

在今天做了几个小时的基准测试和一些有趣的结果之后,让我重新表述我的问题:

  1. 为什么 await StreamReader.ReadToEndAsync() 在每个基准测试中总是比非异步方法 StreamReader.ReadToEnd() 慢?(这可能已经在 Neil Turner 的评论中得到了回答)

  2. 使用 StorageFile 打开文件时似乎有很大的开销,但仅当它在 UI 线程中打开时.(参见方法 1 和 3 或方法 5 和 6 之间的加载时间差异,其中 3 和 6 比等效的 UI 线程方法快约 10 倍)

  3. 有没有其他方法可以更快地读取文件?

更新 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 文件.

代码:

公共异步任务 b1LoadDataStorageFileAsync(){StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");data = await data.GetFolderAsync("samplefiles");//b1for (int i = 0; i < filepaths.Count; i++){StorageFile f = await data.GetFileAsync(filepaths[i]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){文件内容 = 等待 r.ReadToEndAsync();}}}}公共异步任务 b2LoadDataIsolatedStorage(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){文件内容 = r.ReadToEnd();}}}}等待 TaskEx.Delay(0);}公共异步任务 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]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){文件内容 = 等待 r.ReadToEndAsync();}}}});}公共异步任务 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]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){文件内容 = r.ReadToEnd();}}}});}公共异步任务 b5LoadDataStorageFile(){StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");data = await data.GetFolderAsync("samplefiles");//b5for (int i = 0; i < filepaths.Count; i++){StorageFile f = await data.GetFileAsync(filepaths[i]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){文件内容 = r.ReadToEnd();}}}}公共异步任务 b6LoadDataIsolatedStorageThread(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){await Task.Factory.StartNew(() =>{for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){文件内容 = r.ReadToEnd();}}}});}}公共异步任务 b7LoadDataIsolatedStorageAsync(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){文件内容 = 等待 r.ReadToEndAsync();}}}}}公共异步任务 b8LoadDataIsolatedStorageAsyncThread(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){await await Task.Factory.StartNew(async () =>{for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){文件内容 = 等待 r.ReadToEndAsync();}}}});}}公共异步任务 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]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); });}}}}公共异步任务 b10LoadDataIsolatedStorageAsyncMy10(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){//b10for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); });}}}}}公共异步任务 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]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){文件内容 = r.ReadToEnd();}}});}}公共异步任务 b12LoadDataIsolatedStorageMy12(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){for (int i = 0; i < filepaths.Count; i++){await Task.Factory.StartNew(() =>{使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){文件内容 = r.ReadToEnd();}}});}}}公共异步任务 b13LoadDataStorageFileParallel13(){StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");data = await data.GetFolderAsync("samplefiles");列表<任务>任务 = 新列表<任务>();for (int i = 0; i < filepaths.Count; i++){整数索引 = i;var task = await Task.Factory.StartNew(async() =>{StorageFile f = await data.GetFileAsync(filepaths[index]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){字符串内容 = r.ReadToEnd();如果(内容.长度== 0){//只是一些代码,以确保这不会被编译器的优化删除//因为内容"不使用其他方式//永远不应该被调用ShowNotificationText(内容);}}}});任务.添加(任务);}等待 TaskEx.WhenAll(tasks);}公共异步任务 b14LoadDataIsolatedStorageParallel14(){列表<任务>任务 = 新列表<任务>();使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){for (int i = 0; i < filepaths.Count; i++){整数索引 = i;var t = Task.Factory.StartNew(() =>{使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[index], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){字符串内容 = r.ReadToEnd();如果(内容.长度== 0){//只是一些代码,以确保这不会被编译器的优化删除//因为内容"不使用其他方式//永远不应该被调用ShowNotificationText(内容);}}}});任务.添加(t);}等待 TaskEx.WhenAll(tasks);}}公共异步任务 b15LoadDataStorageFileParallelThread15(){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++){整数索引 = i;var task = await Task.Factory.StartNew(async() =>{StorageFile f = await data.GetFileAsync(filepaths[index]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){字符串内容 = r.ReadToEnd();如果(内容.长度== 0){//只是一些代码,以确保这不会被编译器的优化删除//因为内容"不使用其他方式//永远不应该被调用ShowNotificationText(内容);}}}});任务.添加(任务);}等待 TaskEx.WhenAll(tasks);});}公共异步任务 b16LoadDataIsolatedStorageParallelThread16(){await await Task.Factory.StartNew(async () =>{列表<任务>任务 = 新列表<任务>();使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){for (int i = 0; i < filepaths.Count; i++){整数索引 = i;var t = Task.Factory.StartNew(() =>{使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[index], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){字符串内容 = r.ReadToEnd();如果(内容.长度== 0){//只是一些代码,以确保这不会被编译器的优化删除//因为内容"不使用其他方式//永远不应该被调用ShowNotificationText(内容);}}}});任务.添加(t);}等待 TaskEx.WhenAll(tasks);}});}公共异步任务 b17LoadDataStorageFileParallel17(){StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");data = await data.GetFolderAsync("samplefiles");列表<任务<任务>>tasks = new List>();for (int i = 0; i < filepaths.Count; i++){整数索引 = i;var task = Task.Factory.StartNew(async() =>{StorageFile f = await data.GetFileAsync(filepaths[index]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){字符串内容 = r.ReadToEnd();如果(内容.长度== 0){//只是一些代码,以确保这不会被编译器的优化删除//因为内容"不使用其他方式//永远不应该被调用ShowNotificationText(内容);}}}});任务.添加(任务);}等待 TaskEx.WhenAll(tasks);列表<任务>tasks2 = new List();foreach(任务中的变量项目){tasks2.Add(item.Result);}等待 TaskEx.WhenAll(tasks2);}公共异步任务 b18LoadDataStorageFileParallelThread18(){StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");data = await data.GetFolderAsync("samplefiles");await await Task.Factory.StartNew(async () =>{列表<任务<任务>>tasks = new List>();for (int i = 0; i < filepaths.Count; i++){整数索引 = i;var task = Task.Factory.StartNew(async() =>{StorageFile f = await data.GetFileAsync(filepaths[index]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){字符串内容 = r.ReadToEnd();如果(内容.长度== 0){//只是一些代码,以确保这不会被编译器的优化删除//因为内容"不使用其他方式//永远不应该被调用ShowNotificationText(内容);}}}});任务.添加(任务);}等待 TaskEx.WhenAll(tasks);列表<任务>tasks2 = new List();foreach(任务中的变量项目){tasks2.Add(item.Result);}等待 TaskEx.WhenAll(tasks2);});}公共异步任务 b19LoadDataIsolatedStorageAsyncMyThread(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){//b19await await Task.Factory.StartNew(async () =>{for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); });}}}});}}公共异步任务 b20LoadDataIsolatedStorageAsyncMyConfigure(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); }).ConfigureAwait(false);}}}}}公共异步任务 b21LoadDataIsolatedStorageAsyncMyThreadConfigure(){使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){await await Task.Factory.StartNew(async () =>{for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); }).ConfigureAwait(false);}}}});}}公共异步任务 b22LoadDataOwnReadFileMethod(){await await Task.Factory.StartNew(async () =>{for (int i = 0; i < filepaths.Count; i++){filecontent = await ReadFile("/benchmarks/samplefiles/" + filepaths[i]);}});}公共异步任务 b23LoadDataOwnReadFileMethodParallel(){列表<任务>任务 = 新列表<任务>();for (int i = 0; i < filepaths.Count; i++){整数索引 = i;var t = ReadFile("/benchmarks/samplefiles/" + filepaths[i]);任务.添加(t);}等待 TaskEx.WhenAll(tasks);}公共异步任务 b24LoadDataOwnReadFileMethodParallelThread(){await await Task.Factory.StartNew(async () =>{列表<任务>任务 = 新列表<任务>();for (int i = 0; i < filepaths.Count; i++){整数索引 = i;var t = ReadFile("/benchmarks/samplefiles/" + filepaths[i]);任务.添加(t);}等待 TaskEx.WhenAll(tasks);});}公共异步任务 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]);}});}公共异步任务 b26LoadDataOwnReadFileMethodParallelStorageFile(){StorageFolder data = await ApplicationData.Current.LocalFolder.GetFolderAsync("benchmarks");data = await data.GetFolderAsync("samplefiles");列表<任务>任务 = 新列表<任务>();for (int i = 0; i < filepaths.Count; i++){整数索引 = i;var t = ReadStorageFile(data, filepaths[i]);任务.添加(t);}等待 TaskEx.WhenAll(tasks);}公共异步任务 b27LoadDataOwnReadFileMethodParallelThreadStorageFile(){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++){整数索引 = i;var t = ReadStorageFile(data, filepaths[i]);任务.添加(t);}等待 TaskEx.WhenAll(tasks);});}公共异步任务 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, @"benchmarkssamplefiles" + filepaths[i]);}});}公共异步任务ReadStorageFile(StorageFolder 文件夹,字符串文件名){return await await Task.Factory.StartNew>(async() =>{字符串 filec = "";StorageFile f = await folder.GetFileAsync(filename);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){filec = 等待 r.ReadToEndAsyncThread();}}返回文件c;});}公共异步任务读取文件(字符串文件路径){return await await Task.Factory.StartNew>(async() =>{字符串 filec = "";使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){使用 (var stream = new IndependentStorageFileStream(filepath, FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){filec = 等待 r.ReadToEndAsyncThread();}}}返回文件c;});}

这些基准测试的运行方式:

public async Task RunBenchmark(String message, Func benchmarkmethod){SystemTray.ProgressIndicator.IsVisible = true;SystemTray.ProgressIndicator.Text = 消息;SystemTray.ProgressIndicator.Value = 0;长毫秒 = 0;秒表 w = new Stopwatch();列表<长>结果 = new List(benchmarkruns);for (int i = 0; i < benchmarkruns; i++){w.重置();w.Start();等待基准方法();w.停止();毫秒 += w.ElapsedMilliseconds;结果.添加(w.ElapsedMilliseconds);SystemTray.ProgressIndicator.Value += (double)1/(double)b​​enchmarkruns;}Log.Write("最快:" + results.Min(), "最慢:" + results.Max(), "Average:" + results.Average(), "Median:" + results[results.Count/2], "Maxdifference:" + (results.Max() - results.Min()),"所有结果:" + 结果);ShowNotificationText((message + ":").PadRight(24) + (milliseconds/((double)b​​enchmarkruns)).ToString());SystemTray.ProgressIndicator.IsVisible = false;}

基准测试结果

这里是原始基准数据的链接:,其中有人认为通常添加新任务是没有用的.然而,我在这里看到的结果是你不能假设,并且应该始终检查添加任务是否可以提高性能.

最后:我想在官方 Windows Phone 开发者论坛上发帖,但每次尝试时,我都会收到意外错误"消息...

更新 2

结论:

查看数据后,您可以清楚地看到,无论文件大小如何,每个算法都与文件数量成线性关系.所以为了简化一切,我们可以忽略文件的数量(我们将在以后的比较中只使用 50 个文件的数据).

现在是文件大小:文件大小很重要.我们可以看到,当我们增加文件大小时,算法开始收敛.在 10MB 文件大小时,之前最慢的算法出现 8 个中的 4 个. 然而,由于这个问题主要涉及手机,因此应用程序读取具有如此多数据的多个文件的情况非常罕见,对于大多数应用程序来说,即使是 1MB 文件也很少见.我的猜测是,即使读取 50 个 20kb 文件也不常见.大多数应用程序可能正在读取 10 到 30 个文件范围内的数据,每个文件的大小为 0.5kb 到 3kb.(这只是一个猜测,但我认为它可能是准确的)

解决方案

这将是一个很长的答案,其中包括我所有问题的答案,以及使用方法的建议.

这个答案还没有完成,但是在word已经写了5页之后,我想我现在就发布第一部分.

<小时>

在运行 2160 多个基准测试、比较和分析收集到的数据之后,我很确定我可以回答自己的问题并提供有关如何获得最佳 StorageFile(和 IndependentStorageFile)性能的更多见解

(对于原始结果和所有基准方法,请参阅问题)

我们来看第一个问题:

<块引用>

为什么 await StreamReader.ReadToEndAsync() 在每个基准比非异步方法 StreamReader.ReadToEnd()?

Neil Turner 在评论中写道:在循环中等待会导致轻微的性能由于不断来回切换上下文而命中"

我预计性能会受到轻微影响,但我们都认为这不会导致等待的每个基准测试出现如此大的下降.让我们分析一下循环中等待的性能损失.

为此,我们首先比较基准 b1 和 b5(和 b2 作为不相关的最佳情况比较)的结果,这里是两种方法的重要部分:

//b1for (int i = 0; i < filepaths.Count; i++){StorageFile f = await data.GetFileAsync(filepaths[i]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){文件内容 = 等待 r.ReadToEndAsync();}}}//b5for (int i = 0; i < filepaths.Count; i++){StorageFile f = await data.GetFileAsync(filepaths[i]);使用 (var stream = await f.OpenStreamForReadAsync()){使用 (StreamReader r = new StreamReader(stream)){文件内容 = r.ReadToEnd();}}}

基准测试结果:

50 个文件,100kb:

B1:2651 毫秒

B5:1553 毫秒

B2:147

200 个文件,1kb

B1:9984 毫秒

B5:6572

B2:87

在这两种情况下,B5 大约占用 B1 时间的 2/3,循环中只有 2 次等待,而 B1 中只有 3 次等待.似乎 b1 和 b5 的实际加载可能与 b2 中的大致相同,只有等待导致性能大幅下降(可能是因为上下文切换)(假设 1).

让我们试着计算一次上下文切换需要多长时间(使用 b1),然后检查假设 1 是否正确.

有 50 个文件和 3 个等待,我们有 150 个上下文切换:(2651ms-147ms)/150 = 16.7ms 一个上下文切换.我们可以证实这一点吗?:

B5,50 个文件:16.7ms * 50 * 2 = 1670ms + 147ms = 1817ms 对比基准测试结果:1553ms

B1,200 个文件:16.7ms * 200 * 3 = 10020ms + 87ms = 10107ms vs 9984ms

B5,200 个文件:16.7ms * 200 * 2 = 6680ms + 87ms = 6767ms vs 6572ms

看起来很有希望,只有相对较小的差异可能归因于基准结果中的误差幅度.

基准(等待、文件):计算与基准结果

B7(1 个等待,50 个文件):16.7ms*50 + 147= 982ms vs 899ms

B7(1 次等待,200 个文件):16.7*200+87 = 3427ms vs 3354ms

B12(1 个等待,50 个文件):982 毫秒与 897 毫秒

B12(1 个等待,200 个文件):3427 毫秒与 3348 毫秒

B9(3 个等待,50 个文件):2652 毫秒与 2526 毫秒

B9(3 个等待,200 个文件):10107 毫秒与 10014 毫秒

我认为有了这个结果,可以肯定地说,一次上下文切换需要大约 16.7 毫秒(至少在一个循环中).

清楚这一点后,一些基准测试结果就更有意义了.在具有 3 个等待的基准测试中,我们通常看到不同文件大小(1、20、100)的结果只有 0.1% 的差异.这大约是我们在参考基准 b2 中可以观察到的绝对差异.

结论:循环中的等待真的很糟糕(如果循环在 ui 线程中执行,但我稍后会谈到)

关于第 2 个问题

<块引用>

使用 StorageFile 打开文件时似乎有很大的开销,但仅当它在 UI 线程中打开时.(为什么?)

让我们看看基准 10 和 19:

//b10for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); });}}}//b19await await Task.Factory.StartNew(async () =>{for (int i = 0; i < filepaths.Count; i++){使用 (var stream = new IndependentStorageFileStream("/benchmarks/samplefiles/" + filepaths[i], FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); });}}}});

以毫秒为单位的基准(1kb、20kb、100kb、1mb):

10: (846, 865, 916, 1564)

19: (35, 57, 166, 1438)

在基准测试 10 中,我们再次看到上下文切换对性能造成了巨大影响.但是,当我们在不同的线程 (b19) 中执行 for 循环时,我们获得的性能与参考基准测试 2(Ui 阻塞隔离存储文件)几乎相同.从理论上讲,仍然应该有上下文切换(至少据我所知).我怀疑编译器在没有上下文切换的情况下优化了代码.

事实上,我们获得了与基准 20 几乎相同的性能,它与基准 10 基本相同,但具有 ConfigureAwait(false):

filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); }).ConfigureAwait(false);

20: (36, 55, 168, 1435)

这似乎不仅适用于新任务,而且适用于每个异步方法(至少对于我测试的所有方法)

所以这个问题的答案是答案一和我们刚刚发现的组合:

大开销是因为上下文切换,但在不同的线程中,要么没有上下文切换发生,要么没有由它们引起的开销.(当然,这不仅适用于问题中询问的打开文件,而且适用于每个异步方法)

问题 3

问题 3 无法真正得到完整回答,总有一些方法在特定条件下可能会更快一点,但我们至少可以说某些方法永远不应该使用,并为最常见的情况找到最佳解决方案来自我收集的数据:

我们先来看看StreamReader.ReadToEndAsync 和替代方案.为此,我们可以比较基准 7 和基准 10

它们只有一行不同:

b7:

filecontent = await r.ReadToEndAsync();

b10:

filecontent = await Task.Factory.StartNew(() => { return r.ReadToEnd(); });

您可能认为他们的表现会类似好或坏,而您错了(至少在某些情况下).

当我第一次想到做这个测试时,我认为 ReadToEndAsync() 会这样实现.

基准:

b7: (848, 853, 899, 3386)

b10: (846, 865, 916, 1564)

我们可以清楚地看到,在大部分时间都花在读取文件上的情况下,第二种方法要快得多.

我的建议:

不要使用 ReadToEndAsync() 而是自己写一个像这样的扩展方法:

public static async TaskReadToEndAsyncThread(此 StreamReader 阅读器){return await Task.Factory.StartNew(() => { return reader.ReadToEnd(); });}

始终使用它而不是 ReadToEndAsync().

在比较基准 8 和 19(基准 7 和 10,for 循环在不同线程中执行时)时,您可以更清楚地看到这一点:

b8: (55, 103, 360, 3252)

b19: (35, 57, 166, 1438)

b6: (35, 55, 163, 1374)

在这两种情况下,上下文切换都没有开销,您可以清楚地看到,ReadToEndAsync() 的性能绝对糟糕.(基准测试 6 也几乎与 8 和 19 相同,但使用 filecontent = r.ReadToEnd();.同时扩展到 10 个文件,大小为 10mb)

如果我们将其与我们的参考 ui 阻塞方法进行比较:

b2: (21, 44, 147, 1365)

我们可以看到,基准 6 和 19 在不阻塞 ui 线程的情况下非常接近相同的性能.我们可以进一步提高性能吗?是的,但在并行加载时只是略微增加:

b14: (36, 45, 133, 1074)

b16: (31, 52, 141, 1086)

但是,如果您查看这些方法,它们并不是很漂亮,并且在任何地方都必须加载某些东西,这将是糟糕的设计.为此,我编写了 ReadFile(string filepath) 方法,该方法可用于单个文件、1 个 await 的普通循环和并行加载的循环.这应该会提供非常好的性能并产生易于重用和可维护的代码:

公共异步任务<字符串>读取文件(字符串文件路径){return await await Task.Factory.StartNew>(async() =>{字符串 filec = "";使用 (var store = IndependentStorageFile.GetUserStoreForApplication()){使用 (var stream = new IndependentStorageFileStream(filepath, FileMode.Open, store)){使用 (StreamReader r = new StreamReader(stream)){filec = 等待 r.ReadToEndAsyncThread();}}}返回文件c;});}

以下是一些基准测试(与基准测试 16 相比)(对于这个基准测试,我有一个单独的基准测试运行,我从每种方法的 100 次运行中取中值(不是平均)时间):

b16: (16, 32, 122, 1197)

b22: (59, 81, 219, 1516)

b23: (50, 48, 160, 1015)

b24: (34, 50, 87, 1002)

(所有这些方法的中位数都非常接近平均值,平均值有时慢一点,有时快一点.数据应该是可比的)

(请注意,即使这些值是 100 次运行的中位数,0-100 毫秒范围内的数据也没有真正的可比性.例如,在前 100 次运行中,基准 24 的中位数为 1002 毫秒,在第二个 100 次运行,899 毫秒.)

基准 22 与基准 19 具有可比性.基准 23 和 24 与基准 14 和 16 具有可比性.

好的,现在这应该是读取文件的最佳方式之一,当IsolatedStorageFile 可用时.

对于只有 StorageFile 可用的情况(与 Windows 8 应用程序共享代码),我将为 StorageFile 添加一个类似的分析.

因为我对 StorageFile 在 Windows 8 上的表现很感兴趣,所以我可能也会在我的 Windows 8 机器上测试所有 StorageFile 方法.(though for that I’m probably not going to write an analysis)

I was just benchmarking multiple algorithms to find the fastest way to load all data in my app when I discovered that the WP7 version of my app running on my Lumia 920 loads the data 2 times as fast as the WP8 version running on the same device.

I than wrote the following independent code to test performance of the StorageFile from WP8 and the IsolatedStorageFile from WP7.

To clarify the title, here my preliminary benchmark results I did, reading 50 files of 20kb and 100kb:

For the code, see below

Update

After doing benchmarks for a few hours today and some interesting results, let me rephrase my questions:

  1. Why is await StreamReader.ReadToEndAsync() consistently slower in every benchmark than the non async method StreamReader.ReadToEnd()? (This might already be answered in a comment from Neil Turner)

  2. There seems to be a big overhead when opening a file with StorageFile, but only when it is opened in the UI thread. (See difference in loading times between method 1 and 3 or between 5 and 6, where 3 and 6 are about 10 times faster than the equivalent UI thread method)

  3. Are there any other ways to read the files that might be faster?

Update 3

Well, now with this Update I added 10 more algorithms, reran every algorithm with every previously used file size and number of files used. This time each algorithm was run 10 times. So the raw data in the excel file is an average of these runs. As there are now 18 algorithms, each tested with 4 file sizes (1kb, 20kb, 100kb, 1mb) for 50, 100, and 200 files each (18*4*3 = 216), there were a total of 2160 benchmark runs, taking a total time of 95 minutes (raw running time).

Update 5

Added benchmarks 25, 26, 27 and ReadStorageFile method. Had to remove some text because the post had over 30000 characters which is apparently the maximum. Updated the Excel file with new data, new structure, comparisons and new graphs.

The code:

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, @"benchmarkssamplefiles" + 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;
        });
}

How these benchmarks are run:

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;
    }

Benchmark results

Here a link to the raw benchmark data: http://www.dehodev.com/windowsphonebenchmarks.xlsx

Now the graphs (every graph shows the data for loading 50 via each method, results are all in milliseconds)

The next benchmarks with 1mb are not really representative for apps. I include them here to give a better overview on how these methods scale.

So to sum it all up: The standard method used to read files (1.) is always the worst (except in the case you want to read 50 10mb files, but even then there are better methods).


I'm also linking this: await AsyncMethod() versus await await Task.Factory.StartNew<TResult>(AsyncMethod), where it is argued that normally it is not useful to add a new task. However the results I'm seeing here are that you just can't asume that and should always check if adding a task improves performance.

And last: I wanted to post this in the official Windows Phone developer forum but everytime I try, I get an "Unexpected Error" message...

Update 2

Conclusions:

After reviewing the data you can clearly see that no matter the file size every algorithm scales linear to the number of files. So to simplify everything we can ignore the number of files (we will just use the data for 50 files in future comparisons).

Now on to file size: File size is important. We can see that when we increase the file size the algorithms begin to converge. At 10MB file size the previous slowest algorithm takes place 4 of 8. However because this question primarily deals with phones it’s incredibly rare that apps will read multiple files with this much data, even 1MB files will be rare for most apps. My guess is, that even reading 50 20kb files is uncommon. Most apps are probably reading data in the range of 10 to 30 files, each the size of 0.5kb to 3kb. (This is only a guess, but I think it might be accurate)

解决方案

This will be a long answer that includes answers to all my questions, and recommendations on what methods to use.

This answer is also not yet finished, but after having 5 pages in word already, I thought I'll post the first part now.


After running over 2160 benchmarks, comparing and analyzing the gathered data, I’m pretty sure I can answer my own questions and provide additional insights on how to get the best possible performance for StorageFile (and IsolatedStorageFile)

(for raw results and all benchmark methods, see question)

Let’s see the first question:

Why is await StreamReader.ReadToEndAsync() consistently slower in every benchmark than the non async method StreamReader.ReadToEnd()?

Neil Turner wrote in comments: "awaiting in a loop will cause a slight perf . hit due to the constant context switching back and forth"

I expected a slight performance hit but we both didn’t think it would cause such a big drop in every benchmark with awaits. Let’s analyze the performance hit of awaits in a loop.

For this we first compare the results of the benchmarks b1 and b5 (and b2 as an unrelated best case comparison) here the important parts of the two methods:

//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();
        }
    }
}
//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();
        }
    }
}

Benchmark results:

50 files, 100kb:

B1: 2651ms

B5: 1553ms

B2: 147

200 files, 1kb

B1: 9984ms

B5: 6572

B2: 87

In both scenarios B5 takes roughly about 2/3 of the time B1 takes, with only 2 awaits in a loop vs 3 awaits in B1. It seems that the actual loading of both b1 and b5 might be about the same as in b2 and only the awaits cause the huge drop in performance (probably because of context switching) (assumption 1).

Let’s try to calculate how long one context switch takes (with b1) and then check if assumption 1 was correct.

With 50 files and 3 awaits, we have 150 context switches: (2651ms-147ms)/150 = 16.7ms for one context switch. Can we confirm this? :

B5, 50 files: 16.7ms * 50 * 2 = 1670ms + 147ms = 1817ms vs benchmarks results: 1553ms

B1, 200 files: 16.7ms * 200 * 3 = 10020ms + 87ms = 10107ms vs 9984ms

B5, 200 files: 16.7ms * 200 * 2 = 6680ms + 87ms = 6767ms vs 6572ms

Seems pretty promising with only relative small differences that could be attributed to a margin of error in the benchmark results.

Benchmark (awaits, files): Calculation vs Benchmark results

B7 (1 await, 50 files): 16.7ms*50 + 147= 982ms vs 899ms

B7 (1 await, 200 files): 16.7*200+87 = 3427ms vs 3354ms

B12 (1 await, 50 files): 982ms vs 897ms

B12 (1 await, 200 files): 3427ms vs 3348ms

B9 (3 awaits, 50 files): 2652ms vs 2526ms

B9 (3 awaits, 200 files): 10107ms vs 10014ms

I think with this results it is safe to say, one context switch takes about 16.7ms (at least in a loop).

With this cleared up, some of the benchmark results make much more sense. In benchmarks with 3 awaits, we mostly see only a 0.1% difference in results of different file sizes (1, 20, 100). Which is about the absolute difference we can observe in our reference benchmark b2.

Conclusion: awaits in loops are really really bad (if the loop is executed in the ui thread, but I will come to that later)

On to question number 2

There seems to be a big overhead when opening a file with StorageFile, but only when it is opened in the UI thread. (Why?)

Let’s look at benchmark 10 and 19:

//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(); });
        }
    }
}
//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(); });
            }
        }
    }
});

Benchmarks (1kb, 20kb, 100kb, 1mb) in ms:

10: (846, 865, 916, 1564)

19: (35, 57, 166, 1438)

In benchmark 10, we again see a huge performance hit with the context switching. However, when we execute the for loop in a different thread (b19), we get almost the same performance as with our reference benchmark 2 (Ui blocking IsolatedStorageFile). Theoretically there should still be context switches (at least to my knowledge). I suspect that the compiler optimizes the code in this situation that there are no context switches.

As a matter of fact, we get nearly the same performance, as in benchmark 20, which is basically the same as benchmark 10 but with a ConfigureAwait(false):

filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); }).ConfigureAwait(false);

20: (36, 55, 168, 1435)

This seems to be the case not only for new Tasks, but for every async method (well at least for all that I tested)

So the answer to this question is combination of answer one and what we just found out:

The big overhead is because of the context switches, but in a different thread either no context switches occur or there is no overhead caused by them. (Of course this is not only true for opening a file as was asked in the question but for every async method)

Question 3

Question 3 can’t really be fully answered there can always be ways that might be a little bit faster in specific conditions but we can at least tell that some methods should never be used and find the best solution for the most common cases from the data I gathered:

Let’s first take a look at StreamReader.ReadToEndAsync and alternatives. For that, we can compare benchmark 7 and benchmark 10

They only differ in one line:

b7:

filecontent = await r.ReadToEndAsync();

b10:

filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); });

You might think that they would perform similarly good or bad and you would be wrong (at least in some cases).

When I first thought of doing this test, I thought that ReadToEndAsync() would be implemented that way.

Benchmarks:

b7: (848, 853, 899, 3386)

b10: (846, 865, 916, 1564)

We can clearly see that in the case where most of the time is spent reading the file, the second method is way faster.

My recommendation:

Don’t use ReadToEndAsync() but write yourself an extension method like this:

public static async Task<String> ReadToEndAsyncThread(this StreamReader reader)
{
    return await Task.Factory.StartNew<String>(() => { return reader.ReadToEnd(); });
}

Always use this instead of ReadToEndAsync().

You can see this even more when comparing benchmark 8 and 19 (which are benchmark 7 and 10, with the for loop being executed in a different thread:

b8: (55, 103, 360, 3252)

b19: (35, 57, 166, 1438)

b6: (35, 55, 163, 1374)

In both cases there is no overhead from context switching and you can clearly see, that the performance from ReadToEndAsync() is absolutely terrible. (Benchmark 6 is also nearly identical to 8 and 19, but with filecontent = r.ReadToEnd();. Also scaling to 10 files with 10mb)

If we compare this to our reference ui blocking method:

b2: (21, 44, 147, 1365)

We can see, that both benchmark 6 and 19 come very close to the same performance without blocking the ui thread. Can we improve the performance even more? Yes, but only marginally with parallel loading:

b14: (36, 45, 133, 1074)

b16: (31, 52, 141, 1086)

However, if you look at these methods, they are not very pretty and writing that everywhere you have to load something would be bad design. For that I wrote the method ReadFile(string filepath) which can be used for single files, in normal loops with 1 await and in loops with parallel loading. This should give really good performance and result in easily reusable and maintainable code:

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;
        });
}

Here are some benchmarks (compared with benchmark 16) (for this benchmark I had a separate benchmark run, where I took the MEDIAN (not the average) time from 100 runs of each method):

b16: (16, 32, 122, 1197)

b22: (59, 81, 219, 1516)

b23: (50, 48, 160, 1015)

b24: (34, 50, 87, 1002)

(the median in all of these is methods is very close to the average, with the average sometimes being a little bit slower, sometimes faster. The data should be comparable)

(Please note, that even though the values are the median of 100 runs, the data in the range of 0-100ms is not really comparable. E.g. in the first 100 runs, benchmark 24 had a median of 1002ms, in the second 100 runs, 899ms. )

Benchmark 22 is comparable with benchmark 19. Benchmark 23 and 24 are comparable with benchmark 14 and 16.

Ok, now this should be about one the best ways to read the files, when IsolatedStorageFile is available.

I’ll add a similar analysis for StorageFile for situations where you only have StorageFile available (sharing code with Windows 8 Apps).

And because I’m interested on how StorageFile performs on Windows 8, I’ll probably test all StorageFile methods on my Windows 8 machine too. (though for that I’m probably not going to write an analysis)

这篇关于StorageFile 比 IndependentStorageFile 慢 50 倍的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆