example - ziparchive c#
Agregar archivos a Zip existente: problema de rendimiento (5)
Está abriendo el archivo repetidamente, ¿por qué no agrega un bucle y los agrega a un archivo zip y luego lo guarda?
var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories)
.Where(s => s.EndsWith(".aes"))
.Select(f => new FileInfo(f));
using (var zip = ZipFile.Read(nameOfExistingZip))
{
foreach (var additionFile in listAes)
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
zip.AddFile(additionFile.FullName);
}
zip.Save();
}
Si los archivos no están todos disponibles de inmediato, al menos podría agruparlos. Entonces, si espera archivos de 200k, pero solo ha recibido 10 hasta ahora, no abra el archivo zip, agregue uno y luego ciérrelo. Espere unos cuantos más y agréguelos en lotes.
Tengo un servicio web WCF que guarda los archivos en una carpeta (aproximadamente 200,000 archivos pequeños). Después de eso, necesito moverlos a otro servidor.
La solución que encontré fue cerrarlas y luego moverlas.
Cuando adopté esta solución, hice la prueba con (20,000 archivos), comprimir 20,000 archivos tomó solo unos 2 minutos y mover el zip es realmente rápido. Pero en producción, comprimir 200,000 archivos lleva más de 2 horas.
Aquí está mi código para comprimir la carpeta:
using (ZipFile zipFile = new ZipFile())
{
zipFile.UseZip64WhenSaving = Zip64Option.Always;
zipFile.CompressionLevel = CompressionLevel.None;
zipFile.AddDirectory(this.SourceDirectory.FullName, string.Empty);
zipFile.Save(DestinationCurrentFileInfo.FullName);
}
Quiero modificar el servicio web WCF para que, en lugar de guardarlo en una carpeta, lo guarde en el zip.
Yo uso el siguiente código para probar:
var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".aes")).Select(f => new FileInfo(f));
foreach (var additionFile in listAes)
{
using (var zip = ZipFile.Read(nameOfExistingZip))
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
zip.AddFile(additionFile.FullName);
zip.Save();
}
file.WriteLine("Delay for adding a file : " + sw.Elapsed.TotalMilliseconds);
sw.Restart();
}
El primer archivo que se agrega al archivo zip solo toma 5 ms, mientras que el archivo número 10,000 que se agrega agrega 800 ms.
¿Hay alguna forma de optimizar esto? ¿O si tienes otras sugerencias?
EDITAR
El ejemplo que se muestra arriba es solo para prueba, en el servicio web WCF, tendré diferentes solicitudes de envío de archivos que necesito agregar al archivo Zip. Como WCF no tiene estadísticas, tendré una nueva instancia de mi clase con cada llamada, así que, ¿cómo puedo mantener abierto el archivo Zip para agregar más archivos?
He mirado su código e inmediatamente detecto problemas. El problema con muchos desarrolladores de software hoy en día es que hoy en día no entienden cómo funcionan las cosas, lo que hace que sea imposible razonar al respecto . En este caso particular, parece que no sabes cómo funcionan los archivos ZIP; por lo tanto, te sugiero que primero leas cómo funcionan e intentas descomponer lo que sucede debajo del capó.
Razonamiento
Ahora que estamos todos en la misma página sobre cómo funcionan, comencemos el razonamiento desglosando cómo funciona esto utilizando su código fuente; Continuaremos de allí en adelante.
var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".aes")).Select(f => new FileInfo(f));
foreach (var additionFile in listAes)
{
// (1)
using (var zip = ZipFile.Read(nameOfExistingZip))
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
// (2)
zip.AddFile(additionFile.FullName);
// (3)
zip.Save();
}
file.WriteLine("Delay for adding a file : " + sw.Elapsed.TotalMilliseconds);
sw.Restart();
}
- (1) abre un archivo ZIP. Estás haciendo esto por cada archivo que intentas agregar
- (2) Agrega un solo archivo al archivo ZIP
- (3) Guarda el archivo ZIP completo
En mi computadora esto toma aproximadamente una hora.
Ahora, no todos los detalles del formato de archivo son relevantes. Estamos buscando cosas que empeorarán cada vez más en su programa.
Al pasar por alto la especificación del formato de archivo, notará que la compresión se basa en Deflate, que no requiere información sobre los otros archivos comprimidos. Continuando, notaremos cómo se almacena la ''tabla de archivos'' en el archivo ZIP:
Notará que hay un ''directorio central'' que almacena los archivos en el archivo ZIP. Básicamente se almacena como una ''lista''. Por lo tanto, al usar esta información podemos razonar de qué manera trivial es actualizar eso al implementar los pasos (1-3) en este orden:
- Abra el archivo zip, lea el directorio central
- Agregue datos para el (nuevo) archivo comprimido, almacene el puntero junto con el nombre del archivo en el nuevo directorio central.
- Vuelva a escribir el directorio central.
Piénselo por un momento, para el archivo # 1 necesita 1 operación de escritura; para el archivo # 2, necesita leer (1 elemento), adjuntar (en la memoria) y escribir (2 elementos); para el archivo # 3, necesita leer (2 elementos), adjuntar (en la memoria) y escribir (3 elementos). Y así. Básicamente, esto significa que el rendimiento se agotará si agrega más archivos . Ya has observado esto, ahora sabes por qué.
Una posible solucion
En la solución anterior he añadido todos los archivos a la vez. Eso podría no funcionar en su caso de uso. Otra solución es implementar una combinación que básicamente combine 2 archivos cada vez. Esto es más conveniente si no tiene todos los archivos disponibles cuando inicia el proceso de compresión.
Básicamente el algoritmo se convierte en:
- Agrega unos cuantos (por ejemplo, 16, archivos). Puedes jugar con este número. Almacene esto en -say- ''file16.zip''.
- Añade más archivos. Cuando llega a 16 archivos, debe combinar los dos archivos de 16 elementos en un solo archivo de 32 elementos.
- Fusiona archivos hasta que no puedas fusionar más. Básicamente, cada vez que tiene dos archivos de N elementos, crea un nuevo archivo de 2 * N elementos.
- Goto (2).
Una vez más, podemos razonar al respecto. Los primeros 16 archivos no son un problema, ya lo hemos establecido.
También podemos razonar lo que sucederá en nuestro programa. Debido a que estamos fusionando 2 archivos en 1 archivo, no tenemos que hacer tantas lecturas y escrituras. De hecho, si lo razona, verá que tiene un archivo de 32 entradas en 2 fusiones, 64 en 4 fusiones, 128 en 8 fusiones, 256 en 16 fusiones ... hey, espere que conozcamos esta secuencia, es 2^N
Una vez más, al razonar sobre esto, encontraremos que necesitamos aproximadamente 500 fusiones, lo cual es mucho mejor que las 200.000 operaciones con las que comenzamos.
Hackeando en el archivo ZIP.
Otra solución que podría venir a la mente es ubicar el directorio central en general, creando un espacio de holgura para que se agreguen futuras entradas. Sin embargo, esto probablemente requiera que usted ingrese el código postal y cree su propio escritor de archivos ZIP. La idea es que, básicamente, asigne el directorio central a las entradas de 200 K antes de comenzar, para que pueda agregarlas en el lugar.
Nuevamente, podemos razonar al respecto: agregar archivo ahora significa: agregar un archivo y actualizar algunos encabezados. No será tan rápido como la solución original porque necesitará IO de disco aleatorio, pero probablemente funcionará lo suficientemente rápido.
No he resuelto esto, pero no me parece demasiado complicado.
La solución más fácil es la más práctica.
Lo que no hemos discutido hasta ahora es la solución más fácil posible: un enfoque que viene a la mente es simplemente agregar todos los archivos a la vez, lo cual, de nuevo, podemos razonar.
La implementación es bastante fácil, porque ahora no tenemos que hacer nada sofisticado; simplemente podemos usar el controlador ZIP (yo uso iónico) tal como está:
static void Main()
{
try { File.Delete(@"c:/tmp/test.zip"); }
catch { }
var sw = Stopwatch.StartNew();
using (var zip = new ZipFile(@"c:/tmp/test.zip"))
{
zip.UseZip64WhenSaving = Zip64Option.Always;
for (int i = 0; i < 200000; ++i)
{
string filename = "foo" + i.ToString() + ".txt";
byte[] contents = Encoding.UTF8.GetBytes("Hello world!");
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
zip.AddEntry(filename, contents);
}
zip.Save();
}
Console.WriteLine("Elapsed: {0:0.00}s", sw.Elapsed.TotalSeconds);
Console.ReadLine();
}
Whop Eso termina en 4,5 segundos. Mucho mejor.
Puede comprimir todos los archivos usando .Net TPL (biblioteca paralela de tareas) de la siguiente manera:
while(0 != (read = sourceStream.Read(bufferRead, 0, sliceBytes)))
{
tasks[taskCounter] = Task.Factory.StartNew(() =>
CompressStreamP(bufferRead, read, taskCounter, ref listOfMemStream, eventSignal)); // Line 1
eventSignal.WaitOne(-1); // Line 2
taskCounter++; // Line 3
bufferRead = new byte[sliceBytes]; // Line 4
}
Task.WaitAll(tasks); // Line 6
Aquí hay una biblioteca compilada y un código fuente:
http://www.codeproject.com/Articles/49264/Parallel-fast-compression-unleashing-the-power-of
Puedo ver que solo desea agrupar los 200,000 archivos en un solo archivo grande, sin compresión (como un archivo tar
). Dos ideas para explorar:
Experimente con otros formatos de archivo distintos de
Zip
, ya que puede que no sea el más rápido.Tar
(archivo de cinta) viene a la mente (con sus ventajas de velocidad natural debido a su simplicidad), incluso tiene un modo de apéndice que es exactamente lo que está buscando para garantizar las operaciones O (1). SharpCompress es una biblioteca que le permitirá trabajar con este formato (y otros).Si tiene control sobre su servidor remoto, podría implementar su propio formato de archivo, lo más simple que pueda pensar sería en comprimir cada nuevo archivo por separado (para almacenar los metadatos del archivo, como el nombre, la fecha, etc.) en el contenido del archivo. ), y luego adjuntar cada archivo comprimido en un solo archivo de bytes sin procesar. Solo necesitaría almacenar las compensaciones de bytes (separadas por columnas en otro archivo txt) para permitir que el servidor remoto divida el enorme archivo en los 200,000 archivos comprimidos y luego descomprima cada uno de ellos para obtener los metadatos. Supongo que esto también es aproximadamente lo que hace el alquitrán detrás de la escena :).
¿Ha intentado comprimir en un
MemoryStream
lugar de en un archivo, y solo se ha vaciado en un archivo cuando ha terminado el día? Por supuesto, para fines de copia de seguridad, su servicio WCF tendría que guardar una copia de los archivos individuales recibidos hasta que esté seguro de que se han "comprometido" con el servidor remoto.Si necesita compresión, vale la pena intentar 7-Zip (y jugar con las opciones).
Si está de acuerdo con el rendimiento de 100 * 20,000 archivos, ¿no puede simplemente particionar su ZIP grande en 100 archivos ZIP "pequeños"? Para simplificar, cree un nuevo archivo ZIP cada minuto y ponga una marca de tiempo en el nombre.