Pregunta File.Copy vs. FileStream.Write Manual para copiar el archivo


Mi problema está relacionado con el 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.

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 se realiza 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?

Por favor, hágame 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!


32
2017-08-07 20:42


origen


Respuestas:


File.Copy fue construido alrededor Copiar archivo La función Win32 y esta función llama mucho la atención de la tripulación de MS (recuerde estos hilos relacionados con Vista sobre el rendimiento de copia lenta).

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

  1. Como muchos dijeron antes, quite el método de lavado de su ciclo. No lo necesitas en absoluto.
  2. Aumentar el 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. Ayúdelo proporcionando su estrategia de almacenamiento en caché (en su caso, lectura y escritura secuencial), use la anulación del constructor FileStream con FileOptions parámetro (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 use io superpuesto (BeginRead, EndRead, BeginWrite, EndWrite en .net) y no lo olvide establecer la opción asincrónica en el constructor FileStream (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);

23
2017-08-07 21:11



Al quitar el reflector de polvo, podemos ver que File.Copy realmente llama a la API de Win32:

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

Lo cual resuelve

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

Y aquí está la documentación para CopyFile


7
2017-08-07 21:24



Nunca podrá vencer al sistema operativo para hacer algo tan fundamental con su propio código, ni siquiera si lo creó cuidadosamente en ensamblador.

Si necesita asegurarse de que sus operaciones se lleven a cabo con el mejor rendimiento Y quiera mezclar y combinar varias fuentes, deberá crear un tipo que describa las ubicaciones de los recursos. Luego creas una API que tiene funciones tales como Copyque 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.


6
2017-08-07 22:12



Tres cambios mejorarán dramáticamente el rendimiento:

  1. Aumenta el tamaño de tu búfer, prueba 1MB (bien, solo experimento)
  2. Después de abrir FileStream, llame a fileStream.SetLength (inStream.Length) para asignar todo el bloque en el disco por adelantado (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 pareció aproximadamente 3-4 veces más rápido en los experimentos que intenté:

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

4
2017-08-07 20:58



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

Las operaciones de transmisión son excelentes candidatos para multihilo. 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.


1
2017-08-07 20:48



Intente eliminar la llamada Flush y muévala fuera del bucle.

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


1
2017-08-07 20:54



Aquí hay una respuesta similar

¿Cómo copio los contenidos de una secuencia a otra?

Su principal problema es la llamada a Flush (), que vinculará su rendimiento a la velocidad de E / S.


1
2017-08-07 20:54



Mark Russinovich sería la autoridad en esto.

Él escribió en su Blog una entrada Mejoras en la copia de archivos Vista Vista SP1 que resume el estado del arte 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 cantidad de situaciones. Por supuesto, eso no significa que en algún caso de esquina específico, tu propio código podría superarlo ...


1
2017-08-07 21:23