2009-06-05 11 views
39

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:¿Cómo escribir código súper rápido de transmisión de archivos en C#?

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 muy lento.

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

File.OpenRead y 100.000 File.OpenWrite estará bien lento ... –

+0

¿Está dividir el archivo a la perfección, es decir, se puede reconstruir el archivo de gran tamaño con sólo unirse a todos los pequeños archivos juntos? Si es así, hay ahorros para tener allí. Si no, ¿se superponen los rangos de los archivos pequeños? ¿Están ordenados por orden de compensación? – jamie

Respuesta

45

No creo que haya nada dentro de .NET para permitir la copia de una sección de un archivo sin búfer 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 usted es simplemente dividir el archivo, por qué no abrir el archivo de entrada una vez, y luego simplemente escribir 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; 
     } 
    } 
} 

Esta ineficiencia tiene una menor importancia en la creación de un búfer en cada invocación - es posible que desee para crear el buffer de una vez sucedió que en el método, así:

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 el flujo de salida (debido a la instrucción using), que a su código original no lo hizo.

Lo 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.

Yo creo que va a ser significativamente más rápido, pero es obvio que necesita para probarlo a ver ...

Esto supone trozos 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 forma más sencilla de hacerlo sería introducir un BufferedStream que envuelva el flujo de entrada.

+0

Sé que esta es una publicación de hace dos años, me pregunto ... ¿sigue siendo esta la manera más rápida? (es decir, ¿hay algo nuevo en .Net que tener en cuenta?). Además, ¿sería más rápido realizar el 'Math.Min' antes de ingresar al ciclo? O mejor aún, para eliminar el parámetro de longitud, ya que se puede calcular mediante el búfer? Lamento ser quisquilloso y necro esto! Gracias por adelantado. – Smudge202

+2

@ Smudge202: Dado que esto está realizando IO, la llamada a Math.Min ciertamente * no * será relevante en términos de rendimiento. El punto de tener tanto el parámetro de longitud como la longitud del búfer es permitirle reutilizar un búfer posiblemente sobredimensionado. –

+0

Gotcha, y gracias por responderme. Odiaría comenzar una nueva pregunta cuando probablemente haya una respuesta lo suficientemente buena aquí, pero ¿diría usted que si quisiera leer los primeros * x * bytes de una gran cantidad de archivos (con el fin de captar el Metadatos XMP de una gran cantidad de archivos), el enfoque anterior (con algunos ajustes) aún sería recomendable. – Smudge202

6

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

(editar) algo 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; 
     } 
    }   
} 
+1

¿Alguna razón para el lavado en el extremo? Cerrarlo debería hacer eso. Además, creo que quieres restar de longitud en el primer ciclo :) –

+0

¡Buenos ojos, Jon! El color era fuerza de hábito; de una gran cantidad de código cuando paso las transmisiones en lugar de abrirlas/cerrarlas en el método; es conveniente (si se escribe una cantidad no trivial de datos) vaciarla antes de regresar. –

3

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 se puede tratar de agrupar varias llamadas de copia mediante la agrupación de las compensaciones que están cerca el uno al otro y la lectura de todo el bloque necesita para ellos, por ejemplo:

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

se pueden agrupar a una lectura:

offset = 1234, length = 1074 

Entonces sólo tiene que "buscar" en su memoria intermedia y puede escribir los tres nuevos archivos desde allí sin tener que leer de nuevo.

1

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 (sume los tiempos): ¿Cuánto tiempo se dedica a asignar la matriz de almacenamiento intermedio? ¿Cuánto tiempo se pasa abriendo el archivo para lectura? (¿Es el mismo archivo cada vez?) ¿Cuánto tiempo se dedica a las operaciones de lectura y escritura?

Si usted no está haciendo ningún tipo de transformación en el archivo, qué necesita un BinaryWriter, o se puede utilizar un filestream para las escrituras? (Probarlo, se llega salida idéntica? ¿Se ahorra tiempo?)

-1

(para referencia futura.)

Es muy posible que la manera más rápida de hacer esto sería el uso de archivos de memoria asignada (para copiar en primer lugar de memoria , y el sistema operativo que maneja el archivo lee/escribe a través de su administración de paginación/memoria).

Memoria Los archivos asignados son compatibles con el código administrado en .NET 4.0.

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

+1

Los archivos mapeados en memoria están alineados con la página para que salgan. El problema aquí es más probable es que el tiempo de acceso al disco, y los archivos mapeados en memoria no ayuden con eso de todos modos. El sistema operativo administrará los archivos de almacenamiento en caché, ya sea que estén mapeados en la memoria o no. – jamie

0

¿Nadie sugiere el enhebrado? 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.

+0

Enhebrar puede ser útil, pero su cuello de botella seguramente está en la E/S: la CPU probablemente esté pasando mucho tiempo esperando en el disco. Eso no quiere decir que el enhebrado no suponga ninguna diferencia (por ejemplo, si las escrituras son para husos diferentes, entonces podría obtener un aumento de rendimiento mejor que si estuviese en un solo disco) – JMarsch

3

Ha considerado el uso del CCR, ya que está escribiendo para separar los archivos que usted puede hacer todo en paralelo (lectura y escritura) y el CCR hace que sea muy fácil de hacer esto.

static void Main(string[] args) 
    { 
     Dispatcher dp = new Dispatcher(); 
     DispatcherQueue dq = new DispatcherQueue("DQ", dp); 

     Port<long> offsetPort = new Port<long>(); 

     Arbiter.Activate(dq, Arbiter.Receive<long>(true, offsetPort, 
      new Handler<long>(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 de mensajes compensaciones a un puerto CCR que provoca un Tema a crearse para ejecutar el código en el método de 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.

+1

Recuerde con esto (o cualquier solución de enhebrado) puede acceder a un escenario donde maximizará su IO: habrá alcanzado su mejor rendimiento (es decir, si intenta escribir cientos/miles de archivos pequeños al mismo tiempo, varios archivos de gran tamaño, etc.).Siempre he descubierto que si puedo hacer que un archivo sea de lectura/escritura de manera eficiente, poco puedo hacer para mejorarlo mediante la paralelización (el ensamblaje puede ayudar mucho, hacer lecturas/escrituras en el ensamblador y puede ser espectacular, hasta el IO límites, sin embargo puede ser un dolor escribir, y usted necesita estar seguro de que quiere hardware directo o acceso de nivel de BIOS a sus dispositivos – GMasucci

1

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

Su problema principal 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 sabía los nombres de los archivos que estaría generando antes de tiempo, se podría extraer el File.OpenWrite en un método separado; aumentará 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.

21

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

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

+0

Gracias por la información detallada del blog. ¡Tenga una insignia de "Buena respuesta"! – ouflak

Cuestiones relacionadas