c# performance windows-phone-8 windows-phone isolatedstoragefile

c# - StorageFile 50 veces más lento que IsolatedStorageFile



performance windows-phone-8 (1)

Estaba evaluando varios algoritmos para encontrar la forma más rápida de cargar todos los datos en mi aplicación cuando descubrí que la versión WP7 de mi aplicación que se ejecuta en mi Lumia 920 carga los datos 2 veces más rápido que la versión WP8 que se ejecuta en el mismo dispositivo.

Escribí el siguiente código independiente para probar el rendimiento del StorageFile de WP8 y IsolatedStorageFile de WP7.

Para aclarar el título, aquí mis resultados preliminares de referencia lo hice, leyendo 50 archivos de 20 kb y 100 kb:

Para el código, ver a continuación

Actualizar

Después de hacer benchmarks durante unas pocas horas hoy y algunos resultados interesantes, permítanme reformular mis preguntas:

  1. ¿Por qué await StreamReader.ReadToEndAsync() constantemente más lento en cada punto de referencia que el método no asincrónico StreamReader.ReadToEnd() ? (Esto ya podría ser respondido en un comentario de Neil Turner)

  2. Parece que hay una gran sobrecarga al abrir un archivo con StorageFile, pero solo cuando se abre en el subproceso de la interfaz de usuario. (Vea la diferencia en los tiempos de carga entre los métodos 1 y 3 o entre 5 y 6, donde 3 y 6 son aproximadamente 10 veces más rápidos que el método de la rosca de IU equivalente)

  3. ¿Hay alguna otra forma de leer los archivos que podrían ser más rápidos?

Actualización 3

Bueno, ahora con esta actualización agregué 10 algoritmos más, reinterrumpí cada algoritmo con cada tamaño de archivo utilizado anteriormente y la cantidad de archivos utilizados. Esta vez, cada algoritmo se ejecutó 10 veces. Entonces, los datos brutos en el archivo Excel son un promedio de estas ejecuciones. Como ahora hay 18 algoritmos, cada uno probado con 4 tamaños de archivo (1kb, 20kb, 100kb, 1mb) para 50, 100 y 200 archivos cada uno (18 * 4 * 3 = 216), hubo un total de 2160 ejecuciones de referencia, tomando un tiempo total de 95 minutos (tiempo de ejecución crudo).

Actualización 5

ReadStorageFile referencia ReadStorageFile 25, 26, 27 y método ReadStorageFile . Tuve que eliminar un texto porque la publicación tenía más de 30000 caracteres, que aparentemente es el máximo. Se actualizó el archivo de Excel con nuevos datos, nueva estructura, comparaciones y nuevos gráficos.

El código:

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

Cómo se ejecutan estos puntos de referencia:

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

Resultados de referencia

Aquí un enlace a los datos de referencia sin procesar: http://www.dehodev.com/windowsphonebenchmarks.xlsx

Ahora los gráficos (cada gráfico muestra los datos para cargar 50 a través de cada método, los resultados son todos en milisegundos)

Los próximos benchmarks con 1mb no son realmente representativos para las aplicaciones. Los incluyo aquí para ofrecer una mejor visión general de cómo se escalan estos métodos.

Entonces, para resumir todo: el método estándar usado para leer archivos (1.) es siempre el peor (excepto en el caso de que desee leer 50 archivos de 10mb, pero incluso entonces hay mejores métodos).

También estoy vinculando esto: aguarde AsyncMethod () versus espera aguarde Task.Factory.StartNew <TResult> (AsyncMethod) , donde se argumenta que normalmente no es útil agregar una nueva tarea. Sin embargo, los resultados que estoy viendo aquí son que simplemente no puede suponer eso y siempre debe verificar si agregar una tarea mejora el rendimiento.

Y por último: quería publicar esto en el foro oficial de desarrolladores de Windows Phone, pero cada vez que lo intento obtengo el mensaje "Error inesperado" ...

Actualización 2

Conclusiones

Después de revisar los datos, puede ver claramente que no importa el tamaño del archivo, cada algoritmo se ajusta linealmente a la cantidad de archivos. Entonces, para simplificar todo, podemos ignorar la cantidad de archivos (solo utilizaremos los datos de 50 archivos en futuras comparaciones).

Ahora en el tamaño del archivo: el tamaño del archivo es importante. Podemos ver que cuando aumentamos el tamaño del archivo, los algoritmos comienzan a converger. Con un tamaño de archivo de 10MB, el algoritmo anterior más lento tiene lugar en 4 de 8. Sin embargo, debido a que esta pregunta trata principalmente de teléfonos, es increíblemente raro que las aplicaciones lean varios archivos con esta cantidad de datos, incluso los archivos de 1MB serán raros para la mayoría de las aplicaciones. Mi suposición es que incluso leer 50 archivos de 20kb es poco común. La mayoría de las aplicaciones probablemente lean datos en el rango de 10 a 30 archivos, cada uno del tamaño de 0.5kb a 3kb. (Esto es solo una suposición, pero creo que podría ser exacto)


Esta será una respuesta larga que incluye respuestas a todas mis preguntas y recomendaciones sobre qué métodos usar.

Esta respuesta aún no ha terminado, pero después de tener 5 páginas en una palabra, pensé que voy a publicar la primera parte ahora.

Después de ejecutar más de 2160 puntos de referencia, comparar y analizar los datos recopilados, estoy bastante seguro de que puedo responder mis propias preguntas y proporcionar información adicional sobre cómo obtener el mejor rendimiento posible para StorageFile (y IsolatedStorageFile)

(para resultados brutos y todos los métodos de referencia, vea la pregunta)

Veamos la primera pregunta:

¿Por qué await StreamReader.ReadToEndAsync() constantemente más lento en cada punto de referencia que el método no asincrónico StreamReader.ReadToEnd() ?

Neil Turner escribió en comentarios: "esperar en un bucle causará una ligera perforación". golpeado debido al constante cambio de contexto de ida y vuelta "

Esperaba un pequeño golpe de rendimiento, pero los dos no pensamos que causaría una caída tan grande en cada punto de referencia con la espera. Analicemos el rendimiento de hit en espera en un bucle.

Para esto, primero comparamos los resultados de los puntos de referencia b1 y b5 (y b2 como una comparación de mejor caso no relacionada) aquí las partes importantes de los dos métodos:

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

Resultados de referencia:

50 archivos, 100kb:

B1: 2651ms

B5: 1553ms

B2: 147

200 archivos, 1kb

B1: 9984ms

B5: 6572

B2: 87

En ambos escenarios, B5 toma aproximadamente 2/3 del tiempo que toma B1, con solo 2 espera en un ciclo frente a 3 espera en B1. Parece que la carga real tanto de b1 como de b5 podría ser aproximadamente la misma que en b2 y solo la espera causa la enorme caída en el rendimiento (probablemente debido al cambio de contexto) (suposición 1).

Tratemos de calcular cuánto tiempo tarda un cambio de contexto (con b1) y luego verificamos si la suposición 1 era correcta.

Con 50 archivos y 3 en espera, tenemos 150 conmutadores de contexto: (2651ms-147ms) / 150 = 16.7ms para un cambio de contexto. ¿Podemos confirmar esto? :

B5, 50 archivos: 16.7ms * 50 * 2 = 1670ms + 147ms = 1817ms vs resultados de los benchmarks: 1553ms

B1, 200 archivos: 16.7ms * 200 * 3 = 10020ms + 87ms = 10107ms vs 9984ms

B5, 200 archivos: 16.7ms * 200 * 2 = 6680ms + 87ms = 6767ms vs 6572ms

Parece bastante prometedor con solo pequeñas diferencias relativas que podrían atribuirse a un margen de error en los resultados del índice de referencia.

Benchmark (aguarda, archivos): Cálculo vs resultados de Benchmark

B7 (1 espera, 50 archivos): 16.7ms * 50 + 147 = 982ms vs 899ms

B7 (1 espera, 200 archivos): 16.7 * 200 + 87 = 3427ms vs 3354ms

B12 (1 espera, 50 archivos): 982ms vs 897ms

B12 (1 espera, 200 archivos): 3427ms vs 3348ms

B9 (3 espera, 50 archivos): 2652ms vs 2526ms

B9 (3 espera, 200 archivos): 10107ms vs 10014ms

Creo que con estos resultados es seguro decir que un cambio de contexto toma alrededor de 16.7ms (al menos en un bucle).

Con esto aclarado, algunos de los resultados de referencia tienen mucho más sentido. En benchmarks con 3 espera, la mayoría de las veces solo vemos una diferencia del 0.1% en los resultados de diferentes tamaños de archivos (1, 20, 100). Que es la diferencia absoluta que podemos observar en nuestro punto de referencia de referencia b2.

Conclusión: los bucles de espera en realidad son realmente malos (si el ciclo se ejecuta en el hilo de la interfaz de usuario, pero lo abordaré más adelante)

A la pregunta número 2

Parece que hay una gran sobrecarga al abrir un archivo con StorageFile, pero solo cuando se abre en el subproceso de la interfaz de usuario. (¿Por qué?)

Veamos el punto de referencia 10 y 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(); }); } } } });

Parámetros (1kb, 20kb, 100kb, 1mb) en ms:

10: (846, 865, 916, 1564)

19: (35, 57, 166, 1438)

En el punto de referencia 10, nuevamente vemos un gran golpe de rendimiento con el cambio de contexto. Sin embargo, cuando ejecutamos el ciclo for en un hilo diferente (b19), obtenemos casi el mismo rendimiento que con nuestro benchmark 2 de referencia (Ui blocking IsolatedStorageFile). En teoría, debería haber interruptores de contexto (al menos que yo sepa). Sospecho que el compilador optimiza el código en esta situación que no hay interruptores de contexto.

Como cuestión de hecho, obtenemos casi el mismo rendimiento, como en el punto de referencia 20, que es básicamente el mismo que el punto de referencia 10, pero con un ConfigureAwait (falso):

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

20: (36, 55, 168, 1435)

Este parece ser el caso no solo para las nuevas Tareas, sino también para cada método asíncrono (bueno al menos para todo lo que probé)

Entonces, la respuesta a esta pregunta es la combinación de respuesta uno y lo que acabamos de descubrir:

La gran sobrecarga se debe a los conmutadores de contexto, pero en un subproceso diferente, ya sea que no se produzcan cambios de contexto o que no se generen gastos indirectos. (Por supuesto, esto no solo es cierto para abrir un archivo como se solicitó en la pregunta, sino para cada método asíncrono)

Pregunta 3

La pregunta 3 no puede responderse del todo; siempre hay formas que pueden ser un poco más rápidas en condiciones específicas, pero al menos podemos decir que algunos métodos nunca deberían usarse y encontrar la mejor solución para los casos más comunes a partir de los datos. Reuní:

Primero echemos un vistazo a StreamReader.ReadToEndAsync y alternativas. Para eso, podemos comparar el punto de referencia 7 y el punto de referencia 10

Solo difieren en una línea:

b7:

filecontent = await r.ReadToEndAsync();

b10:

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

Podría pensar que funcionarían de forma similar, buena o mala, y se equivocaría (al menos en algunos casos).

Cuando pensé en hacer esta prueba por primera vez, pensé que ReadToEndAsync() se implementaría de esa manera.

Puntos de referencia:

b7: (848, 853, 899, 3386)

b10: (846, 865, 916, 1564)

Podemos ver claramente que en el caso donde la mayor parte del tiempo se usa leyendo el archivo, el segundo método es mucho más rápido.

Mi recomendación:

No use ReadToEndAsync() pero escríbase un método de extensión como este:

public static async Task<String> ReadToEndAsyncThread(this StreamReader reader) { return await Task.Factory.StartNew<String>(() => { return reader.ReadToEnd(); }); }

Siempre use esto en lugar de ReadToEndAsync() .

Esto se puede ver aún más cuando se comparan los puntos de referencia 8 y 19 (que son los puntos de referencia 7 y 10, y el bucle for se ejecuta en un hilo diferente:

b8: (55, 103, 360, 3252)

b19: (35, 57, 166, 1438)

b6: (35, 55, 163, 1374)

En ambos casos, no hay sobrecarga desde el cambio de contexto y se puede ver claramente, que el rendimiento de ReadToEndAsync() es absolutamente terrible. (El punto de referencia 6 también es casi idéntico al 8 y 19, pero con filecontent = r.ReadToEnd(); También escala a 10 archivos con 10mb)

Si comparamos esto con nuestro método de bloqueo de ui de referencia:

b2: (21, 44, 147, 1365)

Podemos ver que tanto el punto de referencia 6 como el 19 están muy cerca del mismo rendimiento sin bloquear el hilo de la interfaz de usuario. ¿Podemos mejorar el rendimiento aún más? Sí, pero solo marginalmente con carga paralela:

b14: ​​(36, 45, 133, 1074)

b16: (31, 52, 141, 1086)

Sin embargo, si observas estos métodos, no son muy bonitos y escribir que donde sea que tengas que cargar algo sería un mal diseño. Para eso escribí el método ReadFile(string filepath) que se puede usar para archivos individuales, en loops normales con 1 espera y en loops con carga paralela. Esto debería proporcionar un rendimiento realmente bueno y dar como resultado un código fácil de reutilizar y mantener:

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

Aquí hay algunos puntos de referencia (en comparación con el punto de referencia 16) (para este punto de referencia tuve una ejecución de referencia separada, donde tomé el tiempo MEDIANO (no el promedio) de 100 ejecuciones de cada método):

b16: (16, 32, 122, 1197)

b22: (59, 81, 219, 1516)

b23: (50, 48, 160, 1015)

b24: (34, 50, 87, 1002)

(La mediana en todos estos métodos es muy cercana a la media, con el promedio algunas veces siendo un poco más lento, a veces más rápido. Los datos deberían ser comparables)

(Tenga en cuenta que, aunque los valores son la mediana de 100 ejecuciones, los datos en el rango de 0-100 ms no son realmente comparables. Por ejemplo, en las primeras 100 ejecuciones, el punto de referencia 24 tenía una mediana de 1002 ms, en el segundo 100 ejecuciones , 899ms.)

El índice de referencia 22 es comparable con el punto de referencia 19. Los parámetros de referencia 23 y 24 son comparables con el punto de referencia 14 y 16.

Bien, ahora esta debería ser una de las mejores maneras de leer los archivos, cuando IsolatedStorageFile está disponible.

Añadiré un análisis similar para StorageFile para situaciones en las que solo tiene StorageFile disponible (código compartido con las aplicaciones de Windows 8).

Y debido a que estoy interesado en cómo funciona StorageFile en Windows 8, probablemente también probaré todos los métodos de StorageFile en mi máquina con Windows 8. (aunque para eso probablemente no voy a escribir un análisis)