StorageFile 50 times slower than IsolatedStorageFile

Ste*_*xel 28 c# performance isolatedstoragefile windows-phone windows-phone-8

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

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

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

enter image description here

For the code, see below

Update

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

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

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

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

Update 3

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

Update 5

增加了基准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;
        });
}
Run Code Online (Sandbox Code Playgroud)

如何运行这些基准测试:

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;
    }
Run Code Online (Sandbox Code Playgroud)

基准测试结果

这里是原始基准数据的链接:http://www.dehodev.com/windowsphonebenchmarks.xlsx

现在图表(每个图表显示通过每种方法加载50的数据,结果都是以毫秒为单位)

1kb file size benchmarks

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

enter image description here

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


我也链接这个:await AsyncMethod()与等待等待等待Task.Factory.StartNew <TResult>(AsyncMethod),其中认为通常添加新任务没有用.然而,我在这里看到的结果是你不能认为它应该总是检查添加任务是否会提高性能.

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

更新2

结论:

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

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

Ste*_*xel 15

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

这个答案还没有完成,但是在已经有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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

基准测试结果:

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<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(); });
            }
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

以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<String>(() => { return r.ReadToEnd(); }).ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)

20:(36,55,168,1435)

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

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

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

问题3

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

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

它们只有一行不同:

B7:

filecontent = await r.ReadToEndAsync();
Run Code Online (Sandbox Code Playgroud)

B10:

filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); });
Run Code Online (Sandbox Code Playgroud)

你可能会认为他们的表现同样好或坏,你就错了(至少在某些情况下).

当我第一次想到做这个测试时,我认为这样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(); });
}
Run Code Online (Sandbox Code Playgroud)

始终使用此而不是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;
        });
}
Run Code Online (Sandbox Code Playgroud)

以下是一些基准测试(与基准测试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方法.(虽然为此我可能不会写分析)