texto - ¿Cómo escribir código de transmisión de archivos súper rápido en C#?
leer archivo txt asp net c# (9)
Tengo que dividir un archivo enorme en muchos archivos más pequeños. Cada uno de los archivos de destino está definido por un desplazamiento y una longitud como el número de bytes. Estoy usando el siguiente código:
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 lenta.
- ¿Hay alguna manera de hacer que el escritor se conecte directamente al lector? (Es decir, sin cargar realmente los contenidos en el Buffer en la memoria).
¿Qué tan grande es la length
? Puede hacer un mejor uso de un búfer de tamaño fijo (moderadamente grande, pero no obsceno), y olvidarse de BinaryReader
... solo 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;
}
}
}
(Para futura referencia.)
Probablemente, la forma más rápida de hacerlo sería utilizar archivos asignados en la memoria (por lo tanto, copiar principalmente la memoria, y el sistema operativo que maneja el archivo lee / escribe a través de su gestión de memoria / paginación).
Los archivos asignados en memoria son compatibles con el código administrado en .NET 4.0.
Pero como se indicó, debe crear un perfil y esperar cambiar al código nativo para obtener el máximo rendimiento.
La forma más rápida de hacer I / O de archivos desde C # es usar las funciones ReadFile y WriteFile de Windows. He escrito una clase de C # que encapsula esta capacidad, así como un programa de evaluación comparativa que analiza diferentes métodos de E / S de red, incluidos BinaryReader y BinaryWriter. Ver mi blog en:
http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/
Lo primero que recomendaría es tomar medidas. ¿Dónde estás perdiendo tu tiempo? ¿Está en la lectura, o la escritura?
Más de 100,000 accesos (sume los tiempos): ¿Cuánto tiempo se invierte en asignar la matriz del búfer? ¿Cuánto tiempo se invierte en abrir el archivo para leer (es el mismo archivo cada vez?) ¿Cuánto tiempo se invierte en las operaciones de lectura y escritura?
Si no está realizando ningún tipo de transformación en el archivo, ¿necesita un BinaryWriter o puede usar un flujo de archivos para las escrituras? (inténtalo, ¿obtienes un resultado idéntico? ¿Te ahorra tiempo?)
Nadie sugiere enhebrar? Escribir los archivos más pequeños parece un ejemplo de libro de texto donde los hilos son útiles. Configurar un montón de hilos para crear los archivos más pequeños. De esta manera, puede crearlos todos en paralelo y no necesita esperar a que finalice 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.
No creo que haya nada dentro de .NET que permita copiar una sección de un archivo sin almacenarlo en la memoria. Sin embargo, me parece que esto es ineficiente de todos modos, ya que necesita abrir el archivo de entrada y buscarlo muchas veces. Si solo está dividiendo el archivo, ¿por qué no abrir el archivo de entrada una vez y luego 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;
}
}
}
Esto tiene una pequeña ineficiencia en la creación de un búfer en cada invocación; es posible que desee crear el búfer una vez y pasarlo también al método:
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 la secuencia de salida (debido a la declaración de uso) que su código original no lo hizo.
El punto importante es que esto utilizará el almacenamiento en búfer del sistema operativo de manera más eficiente, ya que reutiliza la misma secuencia de entrada, en lugar de volver a abrir el archivo al principio y luego buscar.
Creo que será mucho más rápido, pero obviamente tendrás que probarlo para ver ...
Esto supone pedazos 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 desee optimizar también para esa situación: la forma más fácil de hacerlo sería introducir un BufferedStream
envuelva la secuencia de entrada.
No debe volver a abrir el archivo de origen cada vez que haga una copia, mejor abrirlo una vez y pasar el BinaryReader resultante a la función de copia. Además, puede ayudar si ordena sus búsquedas, de modo que no haga grandes saltos dentro del archivo.
Si las longitudes no son demasiado grandes, también puede intentar agrupar varias llamadas de copia agrupando las compensaciones cercanas y leer todo el bloque que necesita para ellas, por ejemplo:
offset = 1234, length = 34
offset = 1300, length = 40
offset = 1350, length = 1000
se pueden agrupar en una sola lectura:
offset = 1234, length = 1074
Entonces, solo tiene que "buscar" en su búfer y puede escribir los tres nuevos archivos desde allí sin tener que volver a leer.
Si ha considerado usar el CCR, ya que está escribiendo en archivos separados, puede hacer todo en paralelo (lectura y escritura) y el CCR lo hace muy fácil de hacer.
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 publica compensaciones en un puerto CCR que hace que se cree un subproceso para ejecutar el código en el método de división. Esto hace que abra el archivo varias veces, pero se deshace de la necesidad de sincronización. Puedes hacer que la memoria sea más eficiente, pero tendrás que sacrificar la velocidad.
Utilizando FileStream + StreamWriter, sé que es posible crear archivos masivos en poco tiempo (menos de 1 minuto y 30 segundos). Genero tres archivos con un total de más de 700 megabytes de un archivo utilizando esa técnica.
Su principal problema con el código que está utilizando es que está abriendo un archivo cada vez. Eso es crear la sobrecarga de E / S de archivos.
Si conociera los nombres de los archivos que generaría antes de tiempo, podría extraer 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.