当我发现在 Lumia 920 上运行的应用程序的 WP7 版本加载数据的速度是在同一设备上运行的 WP8 版本的 2 倍时,我只是在对多种算法进行基准测试以找到在我的应用程序中加载所有数据的最快方法。
我编写了以下独立代码来测试 WP8 中的 StorageFile 和 WP7 中的 IndependentStorageFile 的性能。
为了澄清标题,这里是我做的初步基准测试结果,读取了 20kb 和 100kb 的 50 个文件:
代码见下
更新
在今天做了几个小时的基准测试和一些有趣的结果之后,让我重新表述我的问题:
await StreamReader.ReadToEndAsync()
在每个基准测试中始终比非异步方法慢 StreamReader.ReadToEnd()
? (这可能已经在 Neil Turner 的评论中得到了回答)更新 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 的数据,结果均以毫秒为单位)
下一个 1mb 的基准测试并不能真正代表应用程序。我将它们包括在这里是为了更好地概述这些方法的扩展方式。
所以总结一下:用于读取文件的标准方法 (1.) 总是最糟糕的(除非您想读取 50 个 10mb 文件,但即使如此,也有更好的方法)。
我也在链接这个:await AsyncMethod() versus await await Task.Factory.StartNew<TResult>(AsyncMethod) ,其中有人认为添加新任务通常没有用。然而,我在这里看到的结果是你不能假设,并且应该始终检查添加任务是否可以提高性能。
最后:我想在官方 Windows Phone 开发者论坛上发布此消息,但每次尝试时,我都会收到“意外错误”消息...
更新 2
结论:
查看数据后,您可以清楚地看到,无论文件大小如何,每个算法都与文件数量成线性关系。所以为了简化一切,我们可以忽略文件的数量(我们将在 future 的比较中只使用 50 个文件的数据)。
现在是文件大小:文件大小很重要。我们可以看到,当我们增加文件大小时,算法开始收敛。文件大小为 10MB 时,之前最慢的算法出现 4 个(共 8 个)。 然而,由于这个问题主要涉及手机,因此应用程序读取具有如此多数据的多个文件的情况非常罕见,对于大多数应用程序来说,即使是 1MB 的文件也很少见。我的猜测是,即使读取 50 个 20kb 文件也不常见。大多数应用程序可能正在读取 10 到 30 个文件范围内的数据,每个文件的大小为 0.5kb 到 3kb。 (这只是一个猜测,但我认为它可能是准确的)
最佳答案
这将是一个很长的答案,其中包括我所有问题的答案,以及关于使用什么方法的建议。
这个答案还没有完成,但是在已经写了 5 页之后,我想我现在就发布第一部分。
在运行超过 2160 个基准测试、比较和分析收集的数据后,我很确定我可以回答我自己的问题并提供有关如何获得 StorageFile(和 IndependentStorageFile)最佳性能的更多见解
(对于原始结果和所有基准方法,请参阅问题)
先来看第一个问题:
Why is
await StreamReader.ReadToEndAsync()
consistently slower in every benchmark than the non async methodStreamReader.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”
我预计性能会受到轻微影响,但我们都认为这不会导致等待的每个基准测试出现如此大的下降。
让我们分析一下循环中等待的性能影响。
为此,我们首先比较基准 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 对比基准测试结果: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 个问题
There seems to be a big overhead when opening a file with StorageFile, but only when it is opened in the UI thread. (Why?)
让我们看看基准 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<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(); });
}
}
}
});
以毫秒为单位的基准(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<String>(() => { return r.ReadToEnd(); }).ConfigureAwait(false);
20: (36, 55, 168, 1435)
这似乎不仅适用于新任务,而且适用于每个异步方法(至少对于我测试的所有方法)
所以这个问题的答案是答案一和我们刚刚发现的组合:
大的开销是因为上下文切换,但在不同的线程中,要么没有上下文切换发生,要么没有由它们引起的开销。 (当然,这不仅适用于问题中要求的打开文件,而且适用于每个异步方法)
问题 3
问题 3 不能真正得到完整回答,在特定条件下总有可能会更快一点的方法,但我们至少可以告诉我们,某些方法永远不应该使用,并从数据中找到最常见情况的最佳解决方案我收集:
我们先来看看
StreamReader.ReadToEndAsync
和替代品。为此,我们可以比较基准 7 和基准 10它们只有一行不同:
乙7:
filecontent = await r.ReadToEndAsync();
乙10:
filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); });
您可能认为它们的表现会相似或好或坏,而您错了(至少在某些情况下)。
当我第一次想到做这个测试时,我以为
ReadToEndAsync()
会这样实现。基准:
b7: (848, 853, 899, 3386)
b10: (846, 865, 916, 1564)
我们可以清楚地看到,在大部分时间都花在读取文件上的情况下,第二种方法要快得多。
我的建议:
请勿使用
ReadToEndAsync()
但是给自己写一个这样的扩展方法:public static async Task<String> ReadToEndAsyncThread(this StreamReader reader)
{
return await Task.Factory.StartNew<String>(() => { 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 的普通循环和具有并行加载的循环。这应该提供非常好的性能并导致易于重用和可维护的代码: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;
});
}
以下是一些基准测试(与基准测试 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 方法。 (虽然为此我可能不会写分析)
关于c# - StorageFile 比 IsolatedStorageFile 慢 50 倍,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17935624/