StorageFile比IsolatedStorageFile慢50倍

当我发现在我的Lumia 920上运行的应用程序的WP7版本加载数据的速度是在同一设备上运行的WP8版本的2倍时,我只是对多个算法进行基准测试以找到加载我应用程序中所有数据的最快方法。

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

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

在此处输入图像描述

有关代码,请参阅下文

更新

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

  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方法。 不得不删除一些文本,因为post有超过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  { for (int i = 0; i  { 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  { 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  { 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(() => { 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(() => { 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  { 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  { 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 tasks = new List(); for (int i = 0; i  { 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 tasks = new List(); using (var store = IsolatedStorageFile.GetUserStoreForApplication()) { for (int i = 0; i  { 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 tasks = new List(); for (int i = 0; i  { 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 tasks = new List(); using (var store = IsolatedStorageFile.GetUserStoreForApplication()) { for (int i = 0; i  { 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> tasks = new List<Task>(); for (int i = 0; i < filepaths.Count; i++) { int index = i; var task = 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); List tasks2 = new List(); 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> tasks = new List<Task>(); for (int i = 0; i < filepaths.Count; i++) { int index = i; var task = 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); List tasks2 = new List(); 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(() => { 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(() => { 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(() => { 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 tasks = new List(); for (int i = 0; i  { List tasks = new List(); for (int i = 0; i  { 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 tasks = new List(); for (int i = 0; i  { List tasks = new List(); for (int i = 0; i  { for (int i = 0; i < filepaths.Count; i++) { filecontent = await ReadStorageFile(ApplicationData.Current.LocalFolder, @"benchmarks\samplefiles\" + filepaths[i]); } }); } public async Task ReadStorageFile(StorageFolder folder, String filename) { return await await Task.Factory.StartNew<Task>(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 ReadFile(String filepath) { return await await Task.Factory.StartNew<Task>(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 benchmarkmethod) { SystemTray.ProgressIndicator.IsVisible = true; SystemTray.ProgressIndicator.Text = message; SystemTray.ProgressIndicator.Value = 0; long milliseconds = 0; Stopwatch w = new Stopwatch(); List results = new List(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的数据,结果都是以毫秒为单位)

1kb文件大小基准

使用1mb的下一个基准测试并不能真正代表应用程序。 我将它们包含在这里,以便更好地概述这些方法如何扩展。

在此处输入图像描述

总而言之:用于读取文件的标准方法(1.)总是最差的(除非你想要读取50个10mb文件,但即使这样,也有更好的方法)。


我也链接这个: 等待AsyncMethod()与等待等待等待Task.Factory.StartNew (AsyncMethod) ,其中认为通常添加新任务没有用。 然而,我在这里看到的结果是你无法假设,应该始终检查添加任务是否可以提高性能。

最后:我想在官方Windows Phone开发者论坛上发布这个,但每次尝试时,都会收到“意外错误”消息…

更新2

结论:

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

现在文件大小:文件大小很重要。 我们可以看到,当我们增加文件大小时,算法开始收敛。 在10MB文件大小时,之前最慢的算法发生在4个中。但是因为这个问题主要涉及手机,所以应用程序将使用这么多数据读取多个文件是非常罕见的,对于大多数应用来说,即使是1MB文件也是罕见的。 我的猜测是,即使读取50个20kb的文件也是不常见的。 大多数应用程序可能正在读取10到30个文件的数据,每个文件的大小为0.5kb到3kb。 (这只是猜测,但我认为这可能是准确的)

这将是一个很长的答案,包括我所有问题的答案,以及有关使用哪些方法的建议。

这个答案还没有完成,但在已经有5个单词之后,我想我现在将发布第一部分。


运行2160个基准测试,比较和分析收集的数据后,我非常确定我可以回答自己的问题并提供有关如何为StorageFile(和IsolatedStorageFile)获得最佳性能的其他见解

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

让我们看看第一个问题:

为什么await StreamReader.ReadToEndAsync()在每个基准测试中都比非异步方法StreamReader.ReadToEnd()更慢?

尼尔特纳在评论中写道:“等待一个循环将导致轻微的性能。 由于来回不断的上下文切换而命中“

我预计会有轻微的性能下降,但我们都不认为它会导致每个基准测试都出现如此大的下降。 让我们分析一下循环中等待的性能。

为此,我们首先将基准b1和b5(和b2作为无关的最佳案例比较)的结果进行比较,这两个方法的重要部分:

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

基准测试结果:

50个文件,100kb:

B1:2651ms

B5:1553ms

B2:147

200个文件,1kb

B1:9984ms

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 vs基准测试结果: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对比3354ms

B12(1等待,50个文件):982ms对比897ms

B12(1等待,200个文件):3427ms对3348ms

B9(3等待,50个文件):2652ms对比2526ms

B9(3等待,200个文件):10107ms对比10014ms

我认为通过这个结果可以肯定地说,一个上下文切换大约需要16.7ms(至少在一个循环中)。

随着这一点的澄清,一些基准测试结果更有意义。 在有3个等待的基准测试中,我们看到不同文件大小(1,20,100)的结果差异仅为0.1%。 这与我们在参考基准b2中可以观察到的绝对差异有关。

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

关于问题2

使用StorageFile打开文件时似乎有很大的开销,但只有在UI线程中打开它时才会出现。 (为什么?)

让我们看看基准10和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(() => { 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(() => { return r.ReadToEnd(); }); } } } }); 

以ms为单位的基准(1kb,20kb,100kb,1mb):

10:(846,865,916,1564)

19:(35,57,166,1438)

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

事实上,我们获得的表现几乎与基准测试20相同,基准测试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,3366)

b10:(846,865,916,1564)

我们可以清楚地看到,在大部分时间用于读取文件的情况下,第二种方法更快。

我的建议:

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

 public static async Task ReadToEndAsyncThread(this StreamReader reader) { 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();也可以使用10mb扩展到10个文件)

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

b2:(21,44,147,1365)

我们可以看到,基准测试6和19都非常接近相同的性能而不会阻塞ui线程。 我们能否进一步提高性能? 是的,但只是平行加载:

b14:(36,45,133,1074)

b16:(31,52,141,1086)

但是,如果你看一下这些方法,它们就不是很漂亮了,写下你必须装载东西的地方就是糟糕的设计。 为此,我编写了方法ReadFile(string filepath) ,它可以用于单个文件,在1 await的正常循环中和在并行加载的循环中。 这应该可以提供非常好的性能,并且可以轻松地重用和维护代码:

 public async Task ReadFile(String filepath) { return await await Task.Factory.StartNew>(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; }); } 

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

b16:(16,32,122,1197)

b22:(59,81,219,1516)

b23:(50,48,160,1015)

b24:(34,50,87,1002)

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

(请注意,即使值是100次运行的中位数,0-100ms范围内的数据也不具有可比性。例如,在前100次运行中,基准24的中位数为1002ms,在第二次100次运行中,899ms。)

基准22与基准19相当。基准23和24与基准14和16相当。

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

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

而且因为我对StorageFile在Windows 8上的表现感兴趣,我可能也会测试我的Windows 8机器上的所有StorageFile方法。 (虽然为此我可能不会写分析)