c# optimization filestream

c# - La API de Windows parece mucho más rápida que BinaryWriter: ¿es correcta mi prueba?



optimization filestream (1)

[EDITAR]

Gracias a @VilleKrumlinde he corregido un error que accidentalmente presenté al intentar evitar una advertencia de análisis de código. Accidentalmente estaba activando el manejo de archivos "superpuestos", que seguía restableciendo la longitud del archivo. Ahora está solucionado, y puede llamar a FastWrite() varias veces para la misma FastWrite() sin problemas.

[Fin Editar]

Visión general

Estoy haciendo algunas pruebas de tiempo para comparar dos formas diferentes de escribir matrices de estructuras en el disco. Creo que lo sensato que se percibe es que los costos de E / S son tan altos en comparación con otras cosas que no vale la pena perder demasiado tiempo optimizando las otras.

Sin embargo, mis pruebas de tiempo parecen indicar lo contrario. O estoy cometiendo un error (lo cual es completamente posible), o mi optimización realmente es bastante significativa.

Historia

Primero un poco de historia: este método FastWrite() se escribió originalmente hace años para admitir la escritura de estructuras en un archivo que fue consumido por un programa de C ++ heredado, y todavía lo estamos utilizando para este propósito. (También hay un método FastRead() correspondiente FastRead() . Fue escrito principalmente para facilitar la escritura de matrices de estructuras de blittable en un archivo, y su velocidad era una preocupación secundaria.

Más de una persona me ha dicho que las optimizaciones como esta no son realmente mucho más rápidas que solo con un BinaryWriter , por lo que finalmente he mordido la bala y he realizado algunas pruebas de sincronización. Los resultados me han sorprendido ...

Parece que mi método FastWrite() es de 30 a 50 veces más rápido que el equivalente usando BinaryWriter . Eso parece ridículo, así que estoy publicando mi código aquí para ver si alguien puede encontrar los errores.

Especificación del sistema

  • Probó una versión x86 RELEASE, ejecutada desde FUERA del depurador.
  • Funcionando en Windows 8, x64, 16GB de memoria.
  • Ejecutar en un disco duro normal (no un SSD).
  • Usando .Net 4 con Visual Studio 2012 (entonces .Net 4.5 está instalado)

Resultados

Mis resultados son:

SlowWrite() took 00:00:02.0747141 FastWrite() took 00:00:00.0318139 SlowWrite() took 00:00:01.9205158 FastWrite() took 00:00:00.0327242 SlowWrite() took 00:00:01.9289878 FastWrite() took 00:00:00.0321100 SlowWrite() took 00:00:01.9374454 FastWrite() took 00:00:00.0316074

Como puede ver, eso parece mostrar que FastWrite() es 50 veces más rápido en esa ejecución.

Aquí está mi código de prueba. Después de ejecutar la prueba, hice una comparación binaria de los dos archivos para verificar que realmente eran idénticos (es decir, FastWrite() y SlowWrite() produjeron archivos idénticos).

Vea lo que puede hacer de ella. :)

using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading; using Microsoft.Win32.SafeHandles; namespace ConsoleApplication1 { internal class Program { [StructLayout(LayoutKind.Sequential, Pack = 1)] struct TestStruct { public byte ByteValue; public short ShortValue; public int IntValue; public long LongValue; public float FloatValue; public double DoubleValue; } static void Main() { Directory.CreateDirectory("C://TEST"); string filename1 = "C://TEST//TEST1.BIN"; string filename2 = "C://TEST//TEST2.BIN"; int count = 1000; var array = new TestStruct[10000]; for (int i = 0; i < array.Length; ++i) array[i].IntValue = i; var sw = new Stopwatch(); for (int trial = 0; trial < 4; ++trial) { sw.Restart(); using (var output = new FileStream(filename1, FileMode.Create)) using (var writer = new BinaryWriter(output, Encoding.Default, true)) { for (int i = 0; i < count; ++i) { output.Position = 0; SlowWrite(writer, array, 0, array.Length); } } Console.WriteLine("SlowWrite() took " + sw.Elapsed); sw.Restart(); using (var output = new FileStream(filename2, FileMode.Create)) { for (int i = 0; i < count; ++i) { output.Position = 0; FastWrite(output, array, 0, array.Length); } } Console.WriteLine("FastWrite() took " + sw.Elapsed); } } static void SlowWrite(BinaryWriter writer, TestStruct[] array, int offset, int count) { for (int i = offset; i < offset + count; ++i) { var item = array[i]; // I also tried just writing from array[i] directly with similar results. writer.Write(item.ByteValue); writer.Write(item.ShortValue); writer.Write(item.IntValue); writer.Write(item.LongValue); writer.Write(item.FloatValue); writer.Write(item.DoubleValue); } } static void FastWrite<T>(FileStream fs, T[] array, int offset, int count) where T: struct { int sizeOfT = Marshal.SizeOf(typeof(T)); GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); try { uint bytesWritten; uint bytesToWrite = (uint)(count * sizeOfT); if ( !WriteFile ( fs.SafeFileHandle, new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64() + (offset*sizeOfT)), bytesToWrite, out bytesWritten, IntPtr.Zero ) ) { throw new IOException("Unable to write file.", new Win32Exception(Marshal.GetLastWin32Error())); } Debug.Assert(bytesWritten == bytesToWrite); } finally { gcHandle.Free(); } } [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool WriteFile ( SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped ); } }

Seguir

También he probado el código propuesto por @ ErenErsönmez, como sigue (y verifiqué que los tres archivos son idénticos al final de la prueba):

static void ErenWrite<T>(FileStream fs, T[] array, int offset, int count) where T : struct { // Note: This doesn''t use ''offset'' or ''count'', but it could easily be changed to do so, // and it doesn''t change the results of this particular test program. int size = Marshal.SizeOf(typeof(TestStruct)) * array.Length; var bytes = new byte[size]; GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); try { var ptr = new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64()); Marshal.Copy(ptr, bytes, 0, size); fs.Write(bytes, 0, size); } finally { gcHandle.Free(); } }

output.Position = 0; una prueba para ese código, y al mismo tiempo output.Position = 0; las líneas de output.Position = 0; para que los archivos ahora crezcan a 263K (que es un tamaño razonable).

Con esos cambios, los resultados son:

NOTA ¡Mire cuánto más lento son los tiempos de FastWrite() cuando no se reinicia el puntero del archivo a cero!

SlowWrite() took 00:00:01.9929327 FastWrite() took 00:00:00.1152534 ErenWrite() took 00:00:00.2185131 SlowWrite() took 00:00:01.8877979 FastWrite() took 00:00:00.2087977 ErenWrite() took 00:00:00.2191266 SlowWrite() took 00:00:01.9279477 FastWrite() took 00:00:00.2096208 ErenWrite() took 00:00:00.2102270 SlowWrite() took 00:00:01.7823760 FastWrite() took 00:00:00.1137891 ErenWrite() took 00:00:00.3028128

Así que parece que puedes alcanzar casi la misma velocidad usando Marshaling sin tener que usar la API de Windows. El único inconveniente es que el método de Eren tiene que hacer una copia de todo el conjunto de estructuras, lo que podría ser un problema si la memoria es limitada.


No creo que la diferencia tenga que ver con BinaryWriter . Creo que se debe al hecho de que está realizando múltiples SlowWrite archivos en SlowWrite (10000 * 6) frente a un solo IO en FastWrite . Su FastWrite tiene la ventaja de tener un solo blob de bytes listo para escribir en el archivo. Por otro lado, está tomando el éxito de convertir las estructuras en arreglos de bytes uno por uno en SlowWrite .

Para probar esta teoría, escribí un pequeño método que precompila una matriz de bytes grandes de todas las estructuras, y luego usé esta matriz de bytes en SlowWrite :

static byte[] bytes; static void Prep(TestStruct[] array) { int size = Marshal.SizeOf(typeof(TestStruct)) * array.Length; bytes = new byte[size]; GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); var ptr = gcHandle.AddrOfPinnedObject(); Marshal.Copy(ptr, bytes, 0, size); gcHandle.Free(); } static void SlowWrite(BinaryWriter writer) { writer.Write(bytes); }

Resultados:

SlowWrite() took 00:00:00.0360392 FastWrite() took 00:00:00.0385015 SlowWrite() took 00:00:00.0358703 FastWrite() took 00:00:00.0381371 SlowWrite() took 00:00:00.0373875 FastWrite() took 00:00:00.0367692 SlowWrite() took 00:00:00.0348295 FastWrite() took 00:00:00.0373931

Tenga en cuenta que SlowWrite ahora tiene un FastWrite muy comparable al de FastWrite , y creo que esto demuestra que la diferencia de rendimiento no se debe al rendimiento real de E / S, sino que está más relacionada con el proceso de conversión binario.