2009-08-07 35 views
32

Mi problema está en lo que respecta al rendimiento de copia de archivos. Tenemos un sistema de administración de medios que requiere mover muchos archivos en el sistema de archivos a diferentes ubicaciones, incluidos los recursos compartidos de Windows en la misma red, sitios FTP, AmazonS3, etc. Cuando todos estábamos en una red de Windows, podíamos salirnos usando System.IO.File.Copy (origen, destino) para copiar un archivo. Dado que muchas veces todo lo que tenemos es un flujo de entrada (como un MemoryStream), tratamos de abstraer la operación de copia para tomar un flujo de entrada y un flujo de salida, pero estamos viendo una disminución masiva de rendimiento. A continuación se muestra un código para copiar un archivo para usar como punto de discusión.File.Copy vs. FileStream.Write manual para copiar el archivo

public void Copy(System.IO.Stream inStream, string outputFilePath) 
{ 
    int bufferSize = 1024 * 64; 

    using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write)) 
    { 

     int bytesRead = -1; 
     byte[] bytes = new byte[bufferSize]; 

     while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0) 
     { 
      fileStream.Write(bytes, 0, bytesRead); 
      fileStream.Flush(); 
     } 
    } 
} 

¿Alguien sabe por qué esto funciona mucho más lento que File.Copy? ¿Hay algo que pueda hacer para mejorar el rendimiento? ¿Voy a tener que poner una lógica especial para ver si estoy copiando de una ubicación de Windows a otra, en cuyo caso solo usaría File.Copy y en los otros casos usaré las transmisiones?

Háganme saber lo que piensa y si necesita información adicional. He probado diferentes tamaños de buffer y parece que un tamaño de buffer de 64k es óptimo para nuestros archivos "pequeños" y 256k + es un mejor tamaño de buffer para nuestros archivos "grandes", pero en cualquier caso funciona mucho peor que File.Copy () ¡Gracias por adelantado!

+3

Esto podría tener algo que ver con interoperabilidad nativa. Mi sospecha es que File.Copy() y las operaciones de IO de flujo se construyen sobre la API de Windows, y que la secuencia de lectura de lectura/escritura repetidamente en un bucle es más costosa que una llamada de archivo de copia nativa que File.Copy() hará. –

+0

@Steve: Tiene razón, vea mi respuesta. –

Respuesta

23

File.Copy fue construido alrededor de CopyFile función de Win32 y esta función tarda mucho la atención de la tripulación EM (recuerde esto Vista hilos relacionados con el rendimiento de copia lenta).

varias pistas para mejorar el rendimiento de su método:

  1. Al igual que muchos dijeron anteriormente eliminar método Flush de su ciclo. No lo necesitas en absoluto.
  2. El aumento del búfer puede ayudar, pero solo en las operaciones de archivo a archivo, para recursos compartidos de red o servidores ftp esto se ralentizará. 60 * 1024 es ideal para recursos compartidos de red, al menos antes de vista. para ftp 32k será suficiente en la mayoría de los casos.
  3. Ayuda al proporcionar su estrategia de almacenamiento en caché (en su caso, lectura y escritura secuencial), use la anulación del constructor de FileStream con el parámetro FileOptions (SequentalScan).
  4. Puede acelerar la copia mediante el uso de un patrón asincrónico (especialmente útil para casos de red a archivo), pero no use hilos para esto, en su lugar utilice io superpuesto (BeginRead, EndRead, BeginWrite, EndWrite en .net) y no se olvide de conjunto de opciones asíncronas en FileStream constructor (ver FileOptions)

Ejemplo de patrón de copia asíncrona:

int Readed = 0; 
IAsyncResult ReadResult; 
IAsyncResult WriteResult; 

ReadResult = sourceStream.BeginRead(ActiveBuffer, 0, ActiveBuffer.Length, null, null); 
do 
{ 
    Readed = sourceStream.EndRead(ReadResult); 

    WriteResult = destStream.BeginWrite(ActiveBuffer, 0, Readed, null, null); 
    WriteBuffer = ActiveBuffer; 

    if (Readed > 0) 
    { 
     ReadResult = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null); 
     BackBuffer = Interlocked.Exchange(ref ActiveBuffer, BackBuffer); 
    } 

    destStream.EndWrite(WriteResult); 
    } 
    while (Readed > 0); 
1

Una cosa que se destaca es que estás leyendo un trozo, escribiendo ese trozo, leyendo otro trozo, y así sucesivamente.

Las operaciones de transmisión son excelentes candidatos para el subprocesamiento múltiple. Supongo que File.Copy implementa multithreading.

Intenta leer en un hilo y escribir en otro hilo. Tendrá que coordinar los hilos para que el hilo de escritura no empiece a escribir un búfer hasta que el hilo de lectura termine de llenarlo. Puede resolver esto teniendo dos búferes, uno que se está leyendo mientras se está escribiendo el otro, y un indicador que indica qué búfer se está utilizando actualmente con ese fin.

+0

Actualmente estoy investigando multihebra. ¿Hay buenos proyectos de código abierto que hacen exactamente esto? Seguiré investigando. Gracias por la rápida respuesta. – jakejgordon

1

Intente eliminar la llamada de descarga y muévala fuera del ciclo.

A veces el sistema operativo sabe mejor cuándo vaciar el IO .. Le permite utilizar mejor sus búfers internos.

+0

Tampoco creo que la operación Copiar involucre el subprocesamiento múltiple y, personalmente, consideraría que es una mala idea. Significaría crear un hilo para cada operación de copia, que supuestamente es incluso más costoso que el simple uso de flujos. –

+0

@aviadbenov: Es cierto que crear nuestros propios hilos para manejar operaciones IO es excesivo. Sin embargo, .NET mantiene un conjunto de hilos expresamente para este propósito. El uso de llamadas IO asíncronas correctamente nos permite usar estos hilos sin tener que crearlos y destruirlos nosotros mismos. – AnthonyWJones

+0

@Anthony: Lo que dices es cierto pero también peligroso. Si muchos hilos estarían copiando archivos, ¡el grupo de hilos se convertiría en el cuello de botella de la operación de copia! –

4

Tres cambios mejorarán el rendimiento:

  1. Aumentar el tamaño de búfer, trate de 1 MB (experimento bien -sólo)
  2. Después de abrir su filestream, llame fileStream.SetLength (inStream.Length) para asignar todo el bloque en el disco frontal (solo funciona si inStream es buscable)
  3. Eliminar fileStream.Flush(): es redundante y probablemente tenga el mayor impacto en el rendimiento, ya que se bloqueará hasta que se complete la descarga. La corriente se vaciará de todos modos en el desecho.

esto parecía aproximadamente 3-4 veces más rápido en los experimentos que he intentado:

public static void Copy(System.IO.Stream inStream, string outputFilePath) 
    { 
     int bufferSize = 1024 * 1024; 

     using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write)) 
     { 
      fileStream.SetLength(inStream.Length); 
      int bytesRead = -1; 
      byte[] bytes = new byte[bufferSize]; 

      while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0) 
      { 
       fileStream.Write(bytes, 0, bytesRead); 
      } 
     } 
    } 
1

Mark Russinovich sería la autoridad en esto.

Escribió en su blog una entrada Inside Vista SP1 File Copy Improvements que resume el estado de la técnica de Windows a través de Vista SP1.

Mi conjetura semi-educada sería que File.Copy sería más robusto en la mayoría de las situaciones. Por supuesto, eso no significa que en algún caso específico de la esquina, su propio código podría vencerlo ...

7

Desempolvando reflector podemos ver que File.Copy realidad llama a la API de Win32:

if (!Win32Native.CopyFile(fullPathInternal, dst, !overwrite)) 

la que se resuelve

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)] 
internal static extern bool CopyFile(string src, string dst, bool failIfExists); 

And here is the documentation for CopyFile

6

usted nunca va a poder vencer al sistema operativo a hacer algo tan fundemental con su propio código, ni siquiera si lo preparó cuidadosamente en ensamblador.

Si necesita asegurarse de que sus operaciones se lleven a cabo con el mejor rendimiento Y si desea mezclar y combinar varias fuentes, deberá crear un tipo que describa las ubicaciones de los recursos. A continuación, crea una API que tiene funciones como Copy que toma dos de esos tipos y, después de examinar las descripciones de ambos, elige el mecanismo de copia de mejor rendimiento. Por ejemplo, habiendo determinado que ambas ubicaciones son ubicaciones de archivos de Windows, usted elegiría File.Copy O si la fuente es un archivo de Windows pero el destino es HTTP POST, utiliza una WebRequest.

Cuestiones relacionadas