¿Cómo escribir código de transmisión de archivos súper rápido en C #?

Tengo que dividir un gran archivo en muchos archivos más pequeños. Cada uno de los archivos de destino está definido por un desplazamiento y longitud como la cantidad de bytes. Estoy usando el siguiente código:

private void copy(string srcFile, string dstFile, int offset, int length) { BinaryReader reader = new BinaryReader(File.OpenRead(srcFile)); reader.BaseStream.Seek(offset, SeekOrigin.Begin); byte[] buffer = reader.ReadBytes(length); BinaryWriter writer = new BinaryWriter(File.OpenWrite(dstFile)); writer.Write(buffer); } 

Teniendo en cuenta que tengo que llamar a esta función unas 100.000 veces, es notablemente lenta.

  1. ¿Hay alguna manera de hacer que el escritor esté conectado directamente al lector? (Es decir, sin cargar los contenidos en el Buffer en la memoria).

No creo que haya nada dentro de .NET que permita copiar una sección de un archivo sin almacenarlo en la memoria. Sin embargo, me parece que esto es ineficiente de todos modos, ya que necesita abrir el archivo de entrada y buscar muchas veces. Si solo está dividiendo el archivo, ¿por qué no abre el archivo de entrada una vez y luego escribe algo como:

 public static void CopySection(Stream input, string targetFile, int length) { byte[] buffer = new byte[8192]; using (Stream output = File.OpenWrite(targetFile)) { int bytesRead = 1; // This will finish silently if we couldn't read "length" bytes. // An alternative would be to throw an exception while (length > 0 && bytesRead > 0) { bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length)); output.Write(buffer, 0, bytesRead); length -= bytesRead; } } } 

Esto tiene una ineficiencia menor al crear un búfer en cada invocación; es posible que desee crear el búfer una vez y pasarlo al método también:

 public static void CopySection(Stream input, string targetFile, int length, byte[] buffer) { using (Stream output = File.OpenWrite(targetFile)) { int bytesRead = 1; // This will finish silently if we couldn't read "length" bytes. // An alternative would be to throw an exception while (length > 0 && bytesRead > 0) { bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length)); output.Write(buffer, 0, bytesRead); length -= bytesRead; } } } 

Tenga en cuenta que esto también cierra la secuencia de salida (debido a la instrucción de uso) que su código original no.

El punto importante es que esto utilizará el almacenamiento en memoria intermedia del archivo del sistema operativo de manera más eficiente, ya que reutilizará el mismo flujo de entrada, en lugar de volver a abrir el archivo al principio y luego buscar.

Creo que será significativamente más rápido, pero obviamente tendrás que probarlo para ver …

Esto supone fragmentos contiguos, por supuesto. Si necesita omitir partes del archivo, puede hacerlo desde fuera del método. Además, si está escribiendo archivos muy pequeños, es posible que también desee optimizarlos para esa situación: la manera más fácil de hacerlo sería introducir un BufferedStream envuelva el flujo de entrada.

La manera más rápida de hacer E / S de archivos desde C # es usar las funciones de Windows ReadFile y WriteFile. He escrito una clase C # que encapsula esta capacidad, así como un progtwig de evaluación comparativa que analiza los métodos de E / S de differnet, incluidos BinaryReader y BinaryWriter. Ver mi publicación en el blog en:

http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/

¿Qué tan grande es la length ? Puede que sea mejor reutilizar un búfer de tamaño fijo (moderadamente grande, pero no obsceno), y olvidarse de BinaryReader … simplemente use Stream.Read y Stream.Write .

(editar) algo así como:

 private static void copy(string srcFile, string dstFile, int offset, int length, byte[] buffer) { using(Stream inStream = File.OpenRead(srcFile)) using (Stream outStream = File.OpenWrite(dstFile)) { inStream.Seek(offset, SeekOrigin.Begin); int bufferLength = buffer.Length, bytesRead; while (length > bufferLength && (bytesRead = inStream.Read(buffer, 0, bufferLength)) > 0) { outStream.Write(buffer, 0, bytesRead); length -= bytesRead; } while (length > 0 && (bytesRead = inStream.Read(buffer, 0, length)) > 0) { outStream.Write(buffer, 0, bytesRead); length -= bytesRead; } } } 

No debe volver a abrir el archivo fuente cada vez que hace una copia, mejor abrirlo una vez y pasar el BinaryReader resultante a la función de copia. Además, podría ser útil si solicita sus búsquedas, por lo que no realiza grandes saltos dentro del archivo.

Si las longitudes no son demasiado grandes, también puede intentar agrupar varias llamadas de copia agrupando compensaciones que están cercanas entre sí y leyendo todo el bloque que necesita para ellas, por ejemplo:

 offset = 1234, length = 34 offset = 1300, length = 40 offset = 1350, length = 1000 

se puede agrupar en una sola lectura:

 offset = 1234, length = 1074 

Entonces solo tiene que “buscar” en su búfer y puede escribir los tres archivos nuevos desde allí sin tener que volver a leer.

¿Ha considerado usar el CCR ya que está escribiendo en archivos separados, puede hacer todo en paralelo (leer y escribir) y el CCR hace que sea muy fácil hacerlo?

 static void Main(string[] args) { Dispatcher dp = new Dispatcher(); DispatcherQueue dq = new DispatcherQueue("DQ", dp); Port offsetPort = new Port(); Arbiter.Activate(dq, Arbiter.Receive(true, offsetPort, new Handler(Split))); FileStream fs = File.Open(file_path, FileMode.Open); long size = fs.Length; fs.Dispose(); for (long i = 0; i < size; i += split_size) { offsetPort.Post(i); } } private static void Split(long offset) { FileStream reader = new FileStream(file_path, FileMode.Open, FileAccess.Read); reader.Seek(offset, SeekOrigin.Begin); long toRead = 0; if (offset + split_size <= reader.Length) toRead = split_size; else toRead = reader.Length - offset; byte[] buff = new byte[toRead]; reader.Read(buff, 0, (int)toRead); reader.Dispose(); File.WriteAllBytes("c:\\out" + offset + ".txt", buff); } 

Este código publica compensaciones en un puerto CCR que hace que se cree un subproceso para ejecutar el código en el método Split. Esto hace que abra el archivo varias veces, pero elimina la necesidad de sincronización. Puede hacer que sea más eficiente desde el punto de vista de la memoria, pero deberá sacrificar la velocidad.

Lo primero que recomendaría es tomar medidas. ¿Dónde estás perdiendo tu tiempo? ¿Está en la lectura o en la escritura?

Más de 100.000 accesos (sum los tiempos): ¿cuánto tiempo se dedica a asignar la matriz de almacenamiento intermedio? ¿Cuánto tiempo se dedica a abrir el archivo para lectura (es el mismo archivo cada vez?) ¿Cuánto tiempo se dedica a las operaciones de lectura y escritura?

Si no está haciendo ningún tipo de transformación en el archivo, ¿necesita un BinaryWriter, o puede utilizar una extensión de archivos para las escrituras? (pruébalo, ¿obtienes resultados idénticos? ¿ahorra tiempo?)

Usando FileStream + StreamWriter sé que es posible crear archivos masivos en poco tiempo (menos de 1 min 30 segundos). Genero tres archivos que sumn más de 700 megabytes de un archivo usando esa técnica.

Su principal problema con el código que está utilizando es que está abriendo un archivo cada vez. Eso es crear sobrecarga de E / S de archivos.

Si supiera los nombres de los archivos que generaría con anticipación, podría extraer File.OpenWrite en un método separado; boostá la velocidad. Sin ver el código que determina cómo está dividiendo los archivos, no creo que pueda obtener mucho más rápido.

Nadie sugiere enhebrar? Escribir los archivos más pequeños parece un ejemplo de libro de texto donde los hilos son útiles. Configure un grupo de hilos para crear los archivos más pequeños. de esta manera, puedes crearlos todos en paralelo y no necesitas esperar a que termine cada uno. Mi suposición es que crear los archivos (operación del disco) tomará mucho más tiempo que dividir los datos. y, por supuesto, primero debe verificar que un enfoque secuencial no es adecuado.

(Para futura referencia.)

Es muy posible que la forma más rápida de hacer esto sea utilizar archivos mapeados en memoria (por lo tanto, copiar principalmente la memoria y el sistema operativo que maneja las lecturas / escrituras del archivo a través de la administración de la paginación / memoria).

Los archivos asignados a la memoria son compatibles con el código administrado en .NET 4.0.

Pero como se señaló, debe crear un perfil y esperar cambiar al código nativo para obtener el máximo rendimiento.