Подтвердить что ты не робот

StorageFile в 50 раз медленнее, чем IsolatedStorageFile

Я просто сравнивал несколько алгоритмов, чтобы найти самый быстрый способ загрузить все данные в моем приложении, когда обнаружил, что версия моего приложения WP7, работающего на моем Lumia 920, загружает данные в 2 раза быстрее, чем версия WP8, запущенная на то же устройство.

I, чем написал следующий независимый код для проверки производительности StorageFile из WP8 и IsolStorageFile из WP7.

Чтобы прояснить заголовок, вот мои предварительные результаты тестов, которые я сделал, прочитав 50 файлов размером 20kb и 100kb:

enter image description here

Код приведен ниже

Update

После выполнения тестов на несколько часов сегодня и некоторых интересных результатов, позвольте мне перефразировать мои вопросы:

  • Почему await StreamReader.ReadToEndAsync() последовательно медленнее в каждом тестировании, чем метод non async StreamReader.ReadToEnd()? (Это может уже быть отвечено в комментарии Нила Тернера)

  • При открытии файла с StorageFile возникают большие накладные расходы, но только тогда, когда он открывается в потоке пользовательского интерфейса. (См. Разницу во времени загрузки между методом 1 и 3 или между 5 и 6, где 3 и 6 примерно в 10 раз быстрее, чем эквивалентный метод потока пользовательских интерфейсов).

  • Есть ли другие способы чтения файлов, которые могут быть быстрее?

Обновление 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 file size benchmarks

Следующие тесты с 1mb на самом деле не являются репрезентативными для приложений. Я включаю их здесь, чтобы дать лучший обзор того, как эти методы масштабируются.

enter image description here

Итак, суммируем все: стандартный метод, используемый для чтения файлов (1.), всегда худший (за исключением случая, когда вы хотите прочитать 50 файлов 10 Мбайт, но даже тогда есть лучшие методы).


Я также связываю это: ждут AsyncMethod() и ждут ждут Task.Factory.StartNew <TResult> (AsyncMethod), где утверждается, что обычно это не является полезным для добавления новой задачи. Однако результаты, которые я вижу здесь, это то, что вы просто не можете этого использовать, и всегда должны проверять, улучшает ли производительность задачу.

И последнее: я хотел опубликовать это на официальном форуме разработчиков Windows Phone, но каждый раз, когда я пытаюсь, я получаю сообщение "Неожиданная ошибка"...

Обновление 2

Выводы:

После просмотра данных вы можете ясно видеть, что независимо от размера файла каждый алгоритм масштабируется линейно по количеству файлов. Поэтому, чтобы упростить все, мы можем игнорировать количество файлов (мы будем использовать данные для 50 файлов в будущих сравнениях).

Теперь о размере файла: размер файла важен. Мы видим, что когда мы увеличиваем размер файла, алгоритмы начинают сходиться. При размере файла 10 Мбайт предыдущий самый медленный алгоритм имеет место 4 из 8. Однако, поскольку этот вопрос в первую очередь касается телефонов, невероятно редко, что приложения будут считывать несколько файлов с таким большим количеством данных, даже 1 МБ файлы будут редкость для большинства приложений. Я предполагаю, что даже чтение 50 файлов 20 КБ необычно. Большинство приложений, вероятно, считывают данные в диапазоне от 10 до 30 файлов, каждый размером от 0,5 до 3 килобайт. (Это только предположение, но я думаю, что это может быть точным)

4b9b3361

Ответ 1

Это будет длинный ответ, который включает ответы на все мои вопросы и рекомендации о том, какие методы использовать.

Этот ответ еще не завершен, но после того, как у меня уже есть 5 страниц, я подумал, что сейчас отправлю первую часть.


После выполнения тестов на 2160, сравнивая и анализируя собранные данные, я уверен, что могу ответить на мои собственные вопросы и дать дополнительную информацию о том, как получить наилучшую производительность для StorageFile (и IsolStorageFile)

(для исходных результатов и всех эталонных методов см. вопрос)

Рассмотрим первый вопрос:

Почему await StreamReader.ReadToEndAsync() последовательно медленнее в каждый тест, чем метод non async 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();
        }
    }
}

Результаты тестов:

50 файлов, 100kb:

B1: 2651ms

B5: 1553 мс

B2: 147

200 файлов, 1kb

B1: 9984 мс

B5: 6572

B2: 87

В обоих сценариях B5 занимает примерно 2/3 времени, когда принимает B1, и только 2 ожидания в цикле против 3 ожидаются в B1. Похоже, что фактическая загрузка обоих b1 и b5 может быть примерно такой же, как и в b2, и только ожидаемые вызовы приводят к огромному снижению производительности (вероятно, из-за переключения контекста) (предположение 1).

Давайте попробуем рассчитать, сколько времени занимает один коммутатор контекста (с b1), а затем проверить правильность предположения 1.

С 50 файлами и 3 ожиданиями мы имеем 150 контекстных переключателей: (2651ms-147ms)/150 = 16,7ms для одного контекстного коммутатора. Можем ли мы подтвердить это?

B5, 50 файлов: 16,7мс * 50 * 2 = 1670 мс + 147 мс = 1817 мс по сравнению с результатами тестов: 1553мс

B1, 200 файлов: 16.7ms * 200 * 3 = 10020ms + 87ms = 10107ms против 9984ms

B5, 200 файлов: 16.7ms * 200 * 2 = 6680ms + 87ms = 6767ms против 6572ms

Кажется довольно многообещающим только с относительными небольшими различиями, которые могут быть отнесены к пределу погрешности в результатах теста.

Бенчмарк (ожидает, файлы): Расчет и результаты тестов

B7 (1 ожидание, 50 файлов): 16,7 мс * 50 + 147 = 982мс против 899 мс

B7 (1 ожидание, 200 файлов): 16,7 * 200 + 87 = 3427мс против 3354 мс

B12 (1 ожидание, 50 файлов): 982ms против 897ms

B12 (1 ожидание, 200 файлов): 3427мс против 3348 мс

B9 (3 ждет, 50 файлов): 2652мс против 2526 мс

B9 (3 ждет, 200 файлов): 10107ms против 10014ms

Я думаю, что с этими результатами можно с уверенностью сказать, что один коммутатор контекста занимает около 16,7 мс (по крайней мере, в цикле).

С учетом этого, некоторые результаты тестов дают больше смысла. В тестах с 3 ожиданиями мы видим только 0,1% -ную разницу в результатах разных размеров файлов (1, 20, 100). Что касается абсолютной разницы, которую мы можем наблюдать в нашем эталонном бенчмарке b2.

Заключение: ожидания в циклах действительно очень плохие (если цикл выполняется в потоке ui, но я приду к нему позже)

На вопрос номер 2

Кажется, что при открытии файла с StorageFile возникают большие накладные расходы, но только тогда, когда он открывается в потоке пользовательского интерфейса. (Почему?)

Давайте посмотрим на тесты 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 мы снова видим огромный успех при переключении контекста. Однако, когда мы выполняем цикл for в другом потоке (b19), мы получаем почти ту же производительность, что и с нашим эталонным эталоном 2 (Ui, блокирующий IsolStorageFile). Теоретически по-прежнему должны быть переключатели контекста (по крайней мере, насколько мне известно). Я подозреваю, что компилятор оптимизирует код в этой ситуации, поскольку нет переключателей контекста.

Фактически, мы получаем почти ту же производительность, что и в тесте 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(); });
}

Всегда используйте это вместо ReadToEndAsync().

Это можно увидеть еще при сравнении эталонных тестов 8 и 19 (которые являются эталонами 7 и 10, причем цикл for выполняется в другом потоке:

b8: (55, 103, 360, 3252)

b19: (35, 57, 166, 1438)

b6: (35, 55, 163, 1374)

В обоих случаях нет накладных расходов при переключении контекста, и вы можете ясно видеть, что производительность от ReadToEndAsync() абсолютно ужасна. (Benchmark 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) (для этого теста у меня был отдельный контрольный пробег, где я взял среднее время MEDIAN (не среднее) из 100 прогонов каждого метода):

b16: (16, 32, 122, 1197)

b22: (59, 81, 219, 1516)

b23: (50, 48, 160, 1015)

b24: (34, 50, 87, 1002)

(медиана во всех этих методах очень близка к среднему, причем среднее иногда бывает немного медленнее, а иногда и быстрее. Данные должны быть сопоставимы)

(Обратите внимание, что хотя значения являются медианами из 100 прогонов, данные в диапазоне 0-100 мс действительно не сравнимы. Например, в первых 100 прогонах контрольный показатель 24 имел медиану 1002 мс, в второй 100 пробегов, 899 мс.)

Сравнительный показатель 22 сопоставим с эталоном 19. Контрольный показатель 23 и 24 сопоставим с эталоном 14 и 16.

Хорошо, теперь это должно быть примерно одним из лучших способов чтения файлов, когда доступен IsolStorageFile.

Добавьте аналогичный анализ для StorageFile для ситуаций, когда вы имеете доступ только к StorageFile (код обмена с приложениями Windows 8).

И поскольку я заинтересован в том, как StorageFile работает в Windows 8, я, вероятно, также проверил все методы StorageFile на моей машине с Windows 8. (хотя для этого я, вероятно, не собираюсь писать анализ)