28

当我发现在 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方法。不得不删除一些文本,因为该帖子有超过 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 的数据,结果均以毫秒为单位)

1kb 文件大小基准

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

在此处输入图像描述

综上所述:用于读取文件的标准方法 (1.) 始终是最差的(除非您想读取 50 个 10mb 文件,但即便如此,也有更好的方法)。


我还链接了这个:await AsyncMethod() 与 await await Task.Factory.StartNew<TResult>(AsyncMethod),其中有人认为通常添加新任务是没有用的。但是,我在这里看到的结果是您无法假设,并且应该始终检查添加任务是否可以提高性能。

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

更新 2

结论:

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

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

4

1 回答 1

15

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

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


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

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

我们来看第一个问题:

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

Neil Turner 在评论中写道:“在循环中等待会导致轻微的性能下降。由于不断来回切换上下文而命中”</p>

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

为此,我们首先比较基准 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: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 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 个文件):982 毫秒 vs 897 毫秒

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

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

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

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

弄清楚这一点后,一些基准测试结果就更有意义了。在具有 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<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

它们仅在一行中有所不同:

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

始终使用 this 而不是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 个等待的正常循环和具有并行加载的循环中。这应该提供非常好的性能并导致易于重用和可维护的代码:

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-100 毫秒范围内的数据也不是真正可比较的。例如,在前 100 次运行中,基准 24 的中位数为 1002 毫秒,在后 100 次运行中, 899 毫秒。)

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

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

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

因为我对 StorageFile 在 Windows 8 上的执行方式感兴趣,所以我可能也会在我的 Windows 8 机器上测试所有 StorageFile 方法。(尽管为此我可能不会写分析)

于 2013-08-10T22:12:38.417 回答