Frage

Ich habe eine große Datei in viele kleinere Dateien aufteilen. Jede der Zieldatei wird durch einen Versatz und Länge wie die Anzahl von Bytes definiert ist. Ich verwende den folgenden Code ein:

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

In Anbetracht, dass ich diese Funktion über 100.000 Mal nennen habe, ist es bemerkenswert langsam.

  1. Gibt es eine Möglichkeit, den Writer mit dem Reader direkt zu machen? (Das heißt, ohne tatsächlich den Inhalt in die Puffer in dem Speicher zu laden.)
War es hilfreich?

Lösung

Ich glaube nicht, dass es etwas in .NET einen Abschnitt einer Datei kopieren zu lassen, ohne sie im Speicher gepuffert werden. Aber es scheint mir, dass dies ohnehin ineffizient ist, da es die Eingabedatei öffnen muss und oft suchen. Wenn Sie nur Splitting die Datei auf, warum die Eingabedatei nicht einmal öffnen, und dann schreiben Sie einfach so etwas wie:

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

Dies hat eine geringe Ineffizienz bei jedem Aufruf einen Puffer zu schaffen - Sie könnten die Puffer einmal erstellt werden sollen und dass in die Methode übergeben auch:

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

Beachten Sie, dass dies auch den Ausgangsstrom schließt (aufgrund der using-Anweisung), die Ihr ursprünglicher Code nicht.

Der wichtige Punkt ist, dass diese mehr die Betriebssystemdatei Pufferung effizient nutzen werden, weil Sie den gleichen Eingangsstrom wiederverwenden, anstatt die Datei zu Beginn der Wiedereröffnung und dann suchen.

I denken es wird deutlich schneller sein, aber natürlich müssen Sie es versuchen müssen, um zu sehen ...

Dies setzt voraus, zusammenhängende Abschnitte, natürlich. Wenn Sie Bits der Datei überspringen müssen, können Sie, dass von außerhalb des Verfahrens tun. Auch, wenn Sie sind sehr kleine Dateien zu schreiben, können Sie für diese Situation optimieren zu - der einfachste Weg, das zu tun, würde wahrscheinlich ein BufferedStream den Eingabestrom gewickelt wird.

Andere Tipps

Die schnellste Art und Weise zu tun, Datei-I / O von C # ist die Windows-Readfile und Writefile Funktionen zu nutzen. Ich habe eine C # Klasse geschrieben, die diese Fähigkeit sowie ein Benchmarking-Programm, das, einschließlich Binary und Binary bei differnet E / A-Methoden sieht kapselt. Sehen Sie mein Blog-Post an:

http://designingefficientsoftware.wordpress.com / 2011/03/03 / effizient-file-io-from-csharp /

Wie groß ist length? Sie können besser tun, um eine feste Größe wiederverwenden (mäßig groß, aber nicht obszön) Puffer und BinaryReader vergessen ... nur Stream.Read und Stream.Write verwenden.

(edit) so etwas wie:

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

Sie sollten nicht wieder zu öffnen, um die Quelldatei jedes Mal, wenn Sie eine Kopie machen, besser öffnen Sie es einmal und die daraus resultierenden Binary an die Kopierfunktion übergeben. Auch könnte es helfen, wenn Sie bestellen Sie Ihr suchen, so dass Sie nicht große Sprünge in der Datei machen.

Wenn die Längen nicht zu groß sind, können Sie auch versuchen Gruppe mehrere Kopie Anrufe durch Versätze Gruppierung, die nahe zueinander sind und das Lesen des gesamten Blocks Sie sie benötigen, zum Beispiel:

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

kann auf eine Lese gruppiert werden:

offset = 1234, length = 1074

Dann nur Sie haben zu „suchen“ in Ihrem Puffer und können die drei neuen Dateien von dort schreiben, ohne noch einmal lesen zu müssen.

Haben Sie darüber nachgedacht, die CCR verwenden, da Sie schreiben, Dateien trennen Sie alles parallel tun können (lesen und schreiben) und die CCR macht es sehr einfach, dies zu tun.

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

Dieser Code Beiträge Offsets zu einem CCR-Port, der einen Thread verursacht wird erstellt den Code in der Split-Methode auszuführen. Dies bewirkt, dass Sie die Datei mehrere Male öffnen, aber wird von der Notwendigkeit zur Synchronisation befreien. Sie können es mehr Speicher effizienter machen, aber Sie werden Geschwindigkeit opfern.

Das erste, was ich empfehlen würde ist, Maß zu nehmen. Wo verlieren Sie Ihre Zeit? Ist es in der Lese- oder Schreib?

Über 100.000 Zugriffe (Summe der Zeiten): Wie viel Zeit verbringen wir den Puffer-Array Zuweisung? Wie viel Zeit verbringen wir die Datei zum Lesen öffnen (ist es die gleiche Datei jedes Mal?) Wie viel Zeit wird in Lese- und Schreiboperationen ausgegeben?

Wenn Sie nicht jede Art von Transformation auf die Datei tun, brauchen Sie ein Binary, oder können Sie einen Filestream für schreibt verwenden? (Versuchen Sie es, tun Sie identische Ausgabe erhalten? Es an der Zeit ist zu retten?)

Mit Filestream + Stream Ich weiß, es ist möglich, massive Dateien in kurzer Zeit zu erstellen (weniger als 1 min 30 Sekunden). Ich erzeugen drei Dateien 700+ Megabyte aus einer Datei mit dieser Technik in Höhe von insgesamt.

Ihr primäres Problem mit dem Code ist Sie verwendet, dass Sie jedes Mal eine Datei öffnen. Das ist die Schaffung Datei-I / O-Overhead.

Wenn Sie die Namen der Dateien, wussten Sie vor der Zeit zu erzeugen würden, könnten Sie die File.OpenWrite in einem separaten Verfahren extrahieren; es wird die Geschwindigkeit erhöhen. Ohne den Code zu sehen, die bestimmt, wie Sie die Dateien spalten, ich glaube nicht, dass Sie viel schneller erhalten können.

Keiner schlägt Threading? Das Schreiben der kleineren Dateien sieht aus wie Textbuch Beispiel, wo Fäden sind nützlich. Stellen Sie eine Reihe von Themen bis zu den kleineren Dateien zu erstellen. Auf diese Weise können Sie sie alle parallel erstellen und Sie müssen nicht warten, jeder zu beenden. Meine Vermutung ist, dass die Dateien (Bandscheibenoperation) zu schaffen dauert viel länger als die Daten Aufspaltung. und natürlich sollten Sie zuerst überprüfen, dass ein sequentieller Ansatz nicht ausreichend ist.

(Für zukünftige Referenz.)

Ganz möglicherweise der schnellste Weg, dies zu tun wäre, um Speicherdateien abgebildet zu verwenden (so in erster Linie das Kopieren von Speicher und das Betriebssystem die Datei Umgang liest / über seinen Paging / Speicherverwaltung schreibt).

Memory Mapped-Dateien in verwaltetem Code in .NET 4.0 unterstützt werden.

Aber wie bereits erwähnt, müssen Sie ein Profil erstellen, und erwarten für maximale Leistung zu nativen Code zu wechseln.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top