2009-11-04 12 views
7

Tengo un enorme directorio de aproximadamente 500k jpg, y me gustaría archivar todos los archivos que son anteriores a cierta fecha. Actualmente, el script tarda horas en ejecutarse.Copia de archivo de rendimiento en C#?

Esto tiene mucho que ver con el funcionamiento muy pobre de los servidores de almacenamiento de GoGrid, pero al mismo tiempo, estoy seguro de que hay una forma más eficiente de acertar con la Cep/Cpu para lograr lo que estoy haciendo.

Aquí está el código que tengo:.

var dirInfo = new DirectoryInfo(PathToSource); 
var fileInfo = dirInfo.GetFiles("*.*"); 
var filesToArchive = fileInfo.Where(f => 
    f.LastWriteTime.Date < StartThresholdInDays.Days().Ago().Date 
     && f.LastWriteTime.Date >= StopThresholdInDays.Days().Ago().Date 
); 

foreach (var file in filesToArchive) 
{ 
    file.CopyTo(PathToTarget+file.Name); 
} 

Los Días() Ago() cosas es sólo azúcar sintáctico.

+0

que se basa en el sistema operativo host, que debería ser de primera categoría. –

+0

Ya, la verdad es que podría haber millones de archivos allí, ni siquiera puedo obtener un recuento del directorio a través de Windows Explorer debido a problemas de rendimiento similares. – Scott

+2

La gramática Nazi dice: "Performant" no es una palabra :) –

Respuesta

3
+0

Gracias Mauricio ... esto funciona para el problema de RAM, pero no para la CPU. Aún me lleva horas lograrlo, pero al menos la RAM no se dispara hacia mí. – Scott

+0

Eso funciona lo suficientemente bien como para resolver mi problema. Toma alrededor de 2 horas, pero ahora puede ejecutarse en el fondo con un máximo de 4 megas de RAM, mientras que antes, usaría cientos de megas. – Scott

1

Puede experimentar con el uso de (un número limitado de) Subprocesos para realizar CopyTo(). En este momento, toda la operación está limitada a 1 núcleo.

Esto solo mejorará el rendimiento si ahora está vinculado a la CPU. Pero si esto se ejecuta en un RAID, puede funcionar.

+0

Creo que GoGrid está "en la nube". Puede haber limitaciones en las conexiones activas. De todos modos, buen consejo. – user7116

2

Me gustaría tener en cuenta la regla 80/20 y tenga en cuenta que si la mayor parte de la desaceleración es file.CopyTo, y esta desaceleración supera con creces el rendimiento de la consulta LINQ, entonces no me preocupe. Puede probar esto eliminando la línea file.CopyTo y reemplazándola con una operación Console.WriteLine. Tiempo que versus la copia real. Encontrará la sobrecarga de GoGrid frente al resto de la operación. Mi corazonada es que no habrá grandes ganancias realistas en su extremo .

EDIT: Ok, entonces el 80% es la operación GetFiles, lo que no es sorprendente si de hecho hay un millón de archivos en el directorio. Su mejor opción puede ser la de comenzar a utilizar la API win32 (como FindFirstFile y family) y P/Invoke:

[DllImport("kernel32.dll", CharSet=CharSet.Auto)] 
static extern IntPtr FindFirstFile(string lpFileName, 
    out WIN32_FIND_DATA lpFindFileData); 

También me gustaría sugerir, si es posible, la alteración de la estructura de directorios para disminuir el número de archivos por directorio. Esto mejorará la situación inmensamente.

EDIT2: También consideraría cambiar de GetFiles("*.*") a solo GetFiles(). Ya que está pidiendo todo, no tiene sentido que aplique reglas globbing en cada paso.

+0

El grueso de la operación es la instrucción dirInfo.GetFiles ("*. *"). Estoy haciendo una prueba con solo 5 días de archivos, y me quedo sin RAM/Paciencia antes de que pueda obtener un recuento de los archivos en el directorio desde el que se realiza la consulta de linq. ¿Hay una forma mejor de GetFiles [], como simplemente que GetFiles [] devuelva archivos que están dentro de un rango, en lugar de tener que devolverlos todos? Al menos de esta forma, puedo dividir esta operación en fragmentos del 10% esta primera vez, y luego hacer que el archivador se ejecute todas las noches. Tal como está ahora, no puedo llegar a ninguna parte. – Scott

+0

Sí, alterar la estructura del directorio es lo que trato de hacer, pero primero necesito acceder a los archivos sin esperar todo el día y agotar el tiempo del servidor :) – Scott

10

La única parte que creo que podría mejorar es dirInfo.GetFiles("*.*"). En .NET 3.5 y versiones anteriores, devuelve una matriz con todos los nombres de archivo, lo que lleva tiempo construir y usa mucha RAM. En .NET 4.0, hay un nuevo método Directory.EnumerateFiles que devuelve un IEnumerable<string> y obtiene resultados inmediatamente a medida que se leen desde el disco. Esto podría mejorar un poco el rendimiento, pero no esperes milagros ...

+0

En realidad, eso es todo lo que se necesita hacer, EnumerateFiles devuelve Enumerator no el lista completa Guarda toda la memoria necesaria para la matriz. Digamos que son 500k archivos * 100bytes = 50MBs de RAM. Al usar Enumerate, solo usarás hasta 100bytes, porque obtienes 1 archivo a la vez. – Kugel

+0

+1, .Net 4.0 tiene muchas características realmente agradables en System.IO. No estoy seguro de si mejorará la situación con un millón de archivos en un directorio :-D – user7116

2

Deberías considerar utilizar una utilidad de terceros para realizar la copia por ti. Algo como robocopy puede acelerar su procesamiento significativamente. Consulte también https://serverfault.com/questions/54881/quickest-way-of-moving-a-large-number-of-files

+0

+1, robocopy/minage = X/maxage = Y – user7116

+2

¡Y robocopy está incluido en Win7 y Server 2008 de forma predeterminada! – joshperry

+0

sí, no es exactamente lo que yo llamaría "tercero";) –

0

Escuche esto Hanselminutes podcast. Scott habla con Aaron Bockover, el autor del reproductor de medios Banshee, se encontraron con este tema exacto y hablaron sobre él a las 8:20 en el podcast.

Si puede utilizar .Net 4.0, utilice su Directory.EnumerateFiles como lo menciona Thomas Levesque. De lo contrario, es posible que deba escribir su propio código de acceso al directorio como lo hicieron en Mono.Posix utilizando las API Win32 nativas.