当我发现在我的Lumia 920上运行的应用程序的WP7版本加载数据的速度是运行在WP8上的WP8版本的2倍时,我只是对多个算法进行基准测试以找到加载我应用中所有数据的最快方法相同的设备。
我写了以下独立代码来测试WP8的StorageFile和WP7的IsolatedStorageFile的性能。
为了澄清标题,这里是我做的初步基准测试结果,阅读50个20kb和100kb的文件:
有关代码,请参阅下文
在做了几个小时的基准测试和一些有趣的结果后,让我重新解释一下我的问题:
为什么await StreamReader.ReadToEndAsync()
在每个基准测试中都比非异步方法StreamReader.ReadToEnd()
慢一点? (这可能已在Neil Turner的评论中得到解答)
使用StorageFile打开文件时似乎有很大的开销,但只有在UI线程中打开它时才会出现。 (参见方法1和3之间或5到6之间的加载时间的差异,其中3和6比等效的UI线程方法快10倍)
还有其他方法可以更快地阅读文件吗?
好吧,现在有了这个更新,我又添加了10个算法,重新使用每个以前使用的文件大小和使用的文件数量的每个算法。这次每个算法运行10次。因此,excel文件中的原始数据是这些运行的平均值。由于现在有18种算法,每种算法都有4个文件大小(1kb,20kb,100kb,1mb),每个文件分别为50,100和200个文件(18 * 4 * 3 = 216),总共有2160个基准测试运行,总时间为95分钟(原始运行时间)。
添加了基准测试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开发者论坛上发帖,但每次尝试时,都会收到“意外错误”消息......
在查看数据后,您可以清楚地看到,无论文件大小如何,每个算法都会与文件数量成线性关系。因此,为了简化一切,我们可以忽略文件的数量(我们将在未来的比较中使用50个文件的数据)。
现在开始文件大小:文件大小很重要。我们可以看到,当我们增加文件大小时,算法开始收敛。在10MB文件大小时,之前最慢的算法发生在4个中的8个。但是因为这个问题主要涉及手机,所以应用程序将使用这么多数据读取多个文件是非常罕见的,对于大多数应用程序来说,即使是1MB文件也是罕见的。我的猜测是,即使读取50个20kb文件也是不常见的。大多数应用程序可能正在读取10到30个文件的数据,每个文件的大小为0.5kb到3kb。 (这只是猜测,但我认为这可能是准确的)
答案 0 :(得分:15)
这将是一个很长的答案,其中包括我所有问题的答案,以及有关使用哪种方法的建议。
这个答案还没有完成,但是在已经有5个单词之后,我想我现在会发布第一部分。
在运行超过2160个基准测试,比较和分析收集的数据后,我非常确定我可以回答我自己的问题并提供有关如何为StorageFile(和IsolatedStorageFile)获得最佳性能的其他见解
(对于原始结果和所有基准方法,请参阅问题)
为什么
await StreamReader.ReadToEndAsync()
始终较慢 每个基准测试都比非异步方法StreamReader.ReadToEnd()
?尼尔特纳在评论中写道:“等待一个循环会导致轻微的 perf。由于来回不断的上下文切换而命中“
我预计会有轻微的性能下降,但我们都不认为这会导致每个基准测试都出现如此大的下降。 让我们分析一下循环中等待的性能。
为此,我们首先将基准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 vs 3354ms
B12(1等待,50个文件):982ms vs 897ms
B12(1等待,200个文件):3427ms vs 3348ms
B9(3等待,50个文件):2652ms vs 2526ms
B9(3等待,200个档案):10107ms vs 10014ms
我认为通过这个结果可以肯定地说,一个上下文切换大约需要16.7ms(至少在一个循环中)。
随着这一点的澄清,一些基准测试结果更有意义。在有3个等待的基准测试中,我们看到不同文件大小(1,20,100)的结果差异仅为0.1%。这与我们在参考基准b2中可以观察到的绝对差异有关。
结论:等待in循环真的很糟糕(如果循环是在ui线程中执行的,但我稍后会讨论)
使用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<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(); });
}
}
}
});
以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(假):
filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); }).ConfigureAwait(false);
20:(36,55,168,1435)
这似乎不仅适用于新任务,而且适用于每种异步方法(至少对于我测试过的所有方法)
所以这个问题的答案是答案一和我们刚刚发现的结合:
很大的开销是因为上下文切换,但是在不同的线程中,不会发生上下文切换,也不会产生由它们引起的开销。 (当然,这不仅适用于打开问题,而是针对每个异步方法)
问题3无法真正得到完全回答,在特定条件下总会有一些方法可能会更快一点但我们至少可以告诉我们应该永远不要使用某些方法并找到最常见情况下的最佳解决方案根据我收集的数据:
让我们先来看看StreamReader.ReadToEndAsync
和其他选择。为此,我们可以比较基准7和基准10
他们只有一行不同:
B7:
filecontent = await r.ReadToEndAsync();
B10:
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();
。也可以用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<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次运行中获取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方法。 (虽然为此我可能不会写分析)