Pergunta

Eu tenho que dividir um arquivo grande em vários arquivos menores. Cada um dos arquivos de destino é definida por uma e afastamento do comprimento como o número de bytes. Eu estou usando o seguinte 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);
}

Considerando que eu tenho que chamar essa função cerca de 100.000 vezes, é extremamente lento.

  1. Existe uma maneira de fazer o escritor conectado diretamente ao leitor? (Isto é, sem realmente carregar o conteúdo para o buffer na memória.)
Foi útil?

Solução

Eu não acredito que haja qualquer coisa dentro de .NET para permitir a cópia de uma seção de um arquivo sem buffer na memória. No entanto, parece-me que este é ineficiente de qualquer maneira, como ele precisa abrir o arquivo de entrada e buscar muitas vezes. Se você é apenas dividir o arquivo, por que não abrir o arquivo de entrada uma vez, e alguma coisa, então basta escrever 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;
        }
    }
}

Isto tem uma ineficiência menor na criação de um buffer em cada chamada - você pode querer criar o tampão uma vez e passar isso para o método assim:

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

Note que este também fecha o fluxo de saída (devido ao uso declaração) que o seu código original não.

O ponto importante é que isso vai usar o buffer de arquivo do sistema operacional de forma mais eficiente, porque você reutilizar o mesmo fluxo de entrada, em vez de reabrir o arquivo no início e, em seguida, procurando.

I pensar que vai ser significativamente mais rápido, mas, obviamente, você precisa experimentar para ver ...

Isso pressupõe blocos contíguos, é claro. Se você precisar pular bits do arquivo, você pode fazer isso de fora do método. Além disso, se você estiver escrevendo arquivos muito pequenos, você pode querer para otimizar essa situação também - a maneira mais fácil de fazer isso provavelmente seria a introdução de um BufferedStream envolvendo o fluxo de entrada.

Outras dicas

A maneira mais rápida de fazer arquivo de I / O de C # é usar as funções do Windows ReadFile e WriteFile. Eu escrevi uma classe C # que encapsula essa capacidade, bem como um programa de benchmarking que olha para differnet S métodos, incluindo BinaryReader e BinaryWriter I /. Ver meu blog em:

http://designingefficientsoftware.wordpress.com / 2011/03/03 / eficiente-file-io-de-csharp /

Como é grande length? Você pode fazer melhor para re-utilizar um tamanho fixo (moderadamente grande, mas não obsceno) tampão, e esquecer BinaryReader ... apenas Stream.Read uso e Stream.Write.

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

Você não deve voltar a abrir o arquivo de origem a cada vez que você faz uma cópia, melhor abri-lo uma vez e passar o BinaryReader resultante para a função de cópia. Além disso, pode ajudar se você pedir a sua busca, assim você não fazer grandes saltos dentro do arquivo.

Se os comprimentos não são muito grandes, você pode também tentar agrupar várias chamadas de cópia agrupando as compensações que estão perto uns dos outros e ler todo o bloco que você precisa para eles, por exemplo:

offset = 1234, length = 34
offset = 1300, length = 40
offset = 1350, length = 1000

podem ser agrupados para uma leitura:

offset = 1234, length = 1074

Em seguida, você só tem que "buscar" em seu buffer e pode escrever os três novos arquivos de lá sem ter que ler novamente.

Você já pensou em usar a CCR desde que você está escrevendo para arquivos separados que você pode fazer tudo em paralelo (ler e escrever) e CCR torna muito fácil de fazer isso.

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 posts Código compensações para uma porta CCR que faz com que um segmento a ser criado para executar o código no método Split. Este faz com que você abra o arquivo várias vezes, mas se livrar da necessidade de sincronização. Você pode torná-lo mais eficiente de memória, mas você vai ter que sacrificar a velocidade.

A primeira coisa que eu recomendo é fazer medições. Onde você está perdendo seu tempo? É na leitura ou a escrita?

Mais de 100.000 acessos (somatório dos tempos): Quanto tempo é gasto alocar a matriz buffer? Quanto tempo é gasto abrir o arquivo para leitura (é o mesmo arquivo de cada vez?) Quanto tempo é gasto em operações de leitura e gravação?

Se você não está fazendo qualquer tipo de transformação no arquivo, você precisa de um BinaryWriter, ou você pode usar um filestream para gravações? (Experimentá-lo, você recebe saída idêntica? O faz economizar tempo?)

Usando FileStream + StreamWriter Eu sei que é possível criar arquivos enormes em pouco tempo (menos de 1 min 30 segundos). Eu gerar três arquivos totalizando mais de 700 megabytes de um arquivo usando essa técnica.

Seu problema principal com o código que você está usando é que você está abrindo um arquivo de cada vez. Que está criando arquivo de I / O em cima.

Se você soubesse os nomes dos arquivos que você estaria gerando antes do tempo, você pode extrair o File.OpenWrite em um método separado; que irá aumentar a velocidade. Sem ver o código que determina como você está dividindo os arquivos, eu não acho que você pode obter muito mais rápido.

Ninguém sugere enfiar? Escrevendo os arquivos menores é semelhante ao exemplo do livro de texto de onde tópicos são úteis. Configurar um monte de tópicos para criar os arquivos menores. Desta forma, você pode criá-los todos em paralelo e você não precisa esperar por cada um ao fim. Minha suposição é que a criação dos arquivos (operação de disco) terá caminho mais longo do que dividir os dados. e, claro, você deve verificar primeiro que uma abordagem sequencial não é adequada.

(para referência futura.)

Muito possivelmente a maneira mais rápida de fazer isso seria a utilizam memória mapeada arquivos (memória de modo a copiar principalmente, eo OS manusear o arquivo lê / escreve via sua gestão paginação / memória).

arquivos de memória mapeada são suportadas no código gerenciado no .NET 4.0.

Mas, como observou, é necessário ao perfil, e esperar para alternar para código nativo para o máximo desempenho.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top