28

Lumia 920 で実行されているアプリの WP7 バージョンが、同じデバイスで実行されている WP8 バージョンの 2 倍の速さでデータをロードすることを発見したとき、アプリにすべてのデータをロードする最速の方法を見つけるために、複数のアルゴリズムのベンチマークを行っていました。

次に、WP8 の StorageFile と WP7 の IsolatedStorageFile のパフォーマンスをテストするために、次の独立したコードを作成しました。

タイトルを明確にするために、20kb と 100kb の 50 個のファイルを読み取って行った予備的なベンチマーク結果をここに示します。

ここに画像の説明を入力

コードについては、以下を参照してください

アップデート

今日数時間ベンチマークを行い、いくつかの興味深い結果を出した後、私の質問を言い換えさせてください。

  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 ファイルサイズのベンチマーク

次の 1 MB のベンチマークは、アプリを代表するものではありません。これらの方法がどのようにスケーリングするかについてより良い概要を示すために、ここにそれらを含めます。

ここに画像の説明を入力

要約すると、ファイルの読み取りに使用される標準的な方法 (1.) は常に最悪です (ただし、10 MB のファイルを 50 個読み取りたい場合を除きますが、それでもより良い方法があります)。


これもリンクしています: await AsyncMethod() 対 await await Task.Factory.StartNew<TResult>(AsyncMethod)、通常は新しいタスクを追加するのは役に立たないと主張されています。ただし、ここで見た結果は、それを想定することはできず、タスクを追加するとパフォーマンスが向上するかどうかを常に確認する必要があるということです。

そして最後に、これを公式の Windows Phone 開発者フォーラムに投稿したかったのですが、試行するたびに「予期しないエラー」メッセージが表示されます...

更新 2

結論:

データを確認すると、ファイル サイズに関係なく、すべてのアルゴリズムがファイル数に比例してスケーリングすることが明確にわかります。したがって、すべてを単純化するために、ファイルの数を無視できます (今後の比較では、50 ファイルのデータのみを使用します)。

次にファイル サイズについて: ファイル サイズは重要です。ファイル サイズを大きくすると、アルゴリズムが収束し始めることがわかります。10MB のファイル サイズでは、前の最も遅いアルゴリズムは 4/8 で実行されます。ただし、この質問は主に携帯電話を対象としているため、アプリがこれほど多くのデータを含む複数のファイルを読み取ることは非常にまれであり、ほとんどのアプリでは 1MB のファイルであってもまれです。私の推測では、50 個の 20kb ファイルを読み取ることさえ珍しいことです。ほとんどのアプリは、それぞれ 0.5kb から 3kb のサイズの 10 から 30 個のファイルの範囲でおそらくデータを読み取っています。(あくまで推測ですが、多分正しいと思います)

4

1 に答える 1

15

これは、私のすべての質問への回答と、使用する方法に関する推奨事項を含む長い回答になります。

この回答もまだ完成していませんが、すでに 5 ページの単語ができたので、最初の部分を投稿しようと思いました。


2160 以上のベンチマークを実行し、収集したデータを比較および分析した後、私は自分自身の質問に答え、StorageFile (および IsolatedStorageFile) で可能な限り最高のパフォーマンスを得る方法について追加の洞察を提供できると確信しています。

(生の結果とすべてのベンチマーク方法については、質問を参照してください)

最初の質問を見てみましょう。

await StreamReader.ReadToEndAsync()非非同期メソッドよりもすべてのベンチマークで一貫して遅いのはなぜStreamReader.ReadToEnd()ですか?

Neil Turner はコメントで次のように書いています。常にコンテキストが前後に切り替わるためヒットします」</p>

わずかなパフォーマンスの低下を予想していましたが、awaits を使用したすべてのベンチマークでこれほど大きな低下が生じるとは考えていませんでした。ループ内の awaits のパフォーマンス ヒットを分析してみましょう。

このために、最初にベンチマーク b1 と b5 (および b2 は無関係の最良のケースの比較) の結果を比較します。ここでは、2 つの方法の重要な部分を示します。

//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 とほぼ同じで、await だけがパフォーマンスの大幅な低下を引き起こしているようです (おそらくコンテキストの切り替えのため) (仮定 1)。

1 回のコンテキスト切り替えにかかる時間を (b1 で) 計算して、仮定 1 が正しいかどうかを確認してみましょう。

50 個のファイルと 3 つの await を使用すると、150 個のコンテキスト スイッチがあります。1 つのコンテキスト スイッチに対して (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 対 9984ms

B5、200 ファイル: 16.7ms * 200 * 2 = 6680ms + 87ms = 6767ms 対 6572ms

ベンチマーク結果の誤差範囲に起因する可能性のある比較的小さな違いのみで、かなり有望に思えます。

ベンチマーク (待機、ファイル): 計算とベンチマークの結果

B7 (1 待機、50 ファイル): 16.7ms*50 + 147= 982ms 対 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

この結果から、1 回のコンテキスト切り替えに約 16.7 ミリ秒かかると言っても過言ではないと思います (少なくともループでは)。

これが明確になると、ベンチマーク結果の一部がより意味のあるものになります。3 つの await を使用したベンチマークでは、さまざまなファイル サイズ (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 が IsolatedStorageFile をブロックする) とほぼ同じパフォーマンスが得られます。理論的には、コンテキスト スイッチがまだ存在するはずです (少なくとも私の知る限り)。コンテキスト スイッチがないこの状況では、コンパイラがコードを最適化すると思われます。

実際のところ、ベンチマーク 20 とほぼ同じパフォーマンスが得られます。これは基本的にベンチマーク 10 と同じですが、ConfigureAwait(false) を使用しています。

filecontent = await Task.Factory.StartNew<String>(() => { return r.ReadToEnd(); }).ConfigureAwait(false);

20: (36, 55, 168, 1435)

これは、新しいタスクだけでなく、すべての非同期メソッドに当てはまるようです (少なくとも、私がテストしたすべての場合)。

したがって、この質問に対する答えは、答え 1 と今わかったことを組み合わせたものです。

大きなオーバーヘッドはコンテキスト スイッチによるものですが、別のスレッドでは、コンテキスト スイッチが発生しないか、コンテキスト スイッチによるオーバーヘッドはありません。(もちろん、これは質問で尋ねられたようにファイルを開くだけでなく、すべての非同期メソッドにも当てはまります)

質問 3

質問 3 に完全に回答することはできません。特定の条件で少し速くなる方法は常にありますが、少なくとも一部の方法は決して使用すべきではなく、データから最も一般的なケースに最適なソリューションを見つけることができます。私が集めました:

StreamReader.ReadToEndAsync最初にと の代替案を見てみましょう。そのために、ベンチマーク 7 とベンチマーク 10 を比較できます。

それらは 1 行だけ異なります。

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)

ほとんどの時間がファイルの読み取りに費やされる場合、2 番目の方法の方がはるかに高速であることがはっきりとわかります。

私の推奨事項:

使用しないで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 MB で 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 ~ 100 ミリ秒の範囲のデータは実際には比較できないことに注意してください。たとえば、最初の 100 回の実行では、ベンチマーク 24 の中央値は 1002 ミリ秒で、2 回目の 100 回の実行では中央値でした。 、899ミリ秒)

ベンチマーク 22 は、ベンチマーク 19 と同等です。ベンチマーク 23 および 24 は、ベンチマーク 14 および 16 と同等です。

さて、IsolatedStorageFile が利用可能な場合、これはファイルを読み取る最良の方法の 1 つです。

StorageFile しか利用できない状況 (Windows 8 アプリとコードを共有する) については、StorageFile について同様の分析を追加します。

また、Windows 8 で StorageFile がどのように機能するかに興味があるので、Windows 8 マシンでもすべての StorageFile メソッドをテストすることになるでしょう。(そのため、おそらく分析を書くつもりはありません)

于 2013-08-10T22:12:38.417 に答える