42

巨大なファイルを多数の小さなファイルに分割する必要があります。各宛先ファイルは、バイト数としてのオフセットと長さによって定義されます。私は次のコードを使用しています:

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

この関数を約 100,000 回呼び出さなければならないことを考えると、非常に遅いです。

  1. Writer を Reader に直接接続する方法はありますか? (つまり、内容をメモリ内のバッファに実際にロードすることはありません。)
4

9 に答える 9

49

メモリにバッファリングせずにファイルのセクションをコピーできるようにするものが .NET 内にあるとは思えません。ただし、入力ファイルを開いて何度もシークする必要があるため、これはとにかく非効率的だと思います。ファイルを分割するだけの場合は、入力ファイルを一度開いてから、次のように記述してください。

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

これには、呼び出しごとにバッファーを作成するのに少し非効率性があります。バッファーを一度作成して、それをメソッドにも渡したい場合があります。

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

これにより、元のコードでは閉じられなかった (using ステートメントによる) 出力ストリームも閉じられることに注意してください。

重要な点は、最初にファイルを再度開いてシークするのではなく、同じ入力ストリームを再利用するため、これによりオペレーティング システムのファイル バッファリングがより効率的に使用されることです。

かなり高速になると思いますが、明らかに試してみる必要があります...

もちろん、これは連続したチャンクを想定しています。ファイルの一部をスキップする必要がある場合は、メソッドの外部から実行できます。また、非常に小さなファイルを書き込んでいる場合は、その状況に合わせて最適化することもできます。これを行う最も簡単な方法は、おそらくBufferedStream入力ストリームのラッピングを導入することです。

于 2009-06-05T13:49:02.120 に答える
29

C# からファイル I/O を実行する最速の方法は、Windows の ReadFile および WriteFile 関数を使用することです。この機能をカプセル化する C# クラスと、BinaryReader や BinaryWriter などのさまざまな I/O メソッドを調べるベンチマーク プログラムを作成しました。次のブログ投稿を参照してください。

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

于 2011-03-03T22:55:38.683 に答える
6

の大きさはlength?固定サイズの (適度に大きいがわいせつではない) バッファーを再利用しBinaryReaderたほうがよいかもしれません。Stream.ReadStream.Write

(編集)次のようなもの:

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;
        }
    }        
}
于 2009-06-05T13:48:11.043 に答える
3

コピーを行うたびにソース ファイルを再度開くのではなく、一度開いて、結果の BinaryReader をコピー関数に渡すことをお勧めします。また、ファイル内で大きなジャンプを行わないように、シークを順序付けすると役立つ場合があります。

長さが大きすぎない場合は、互いに近いオフセットをグループ化し、それらに必要なブロック全体を読み取ることにより、いくつかのコピー呼び出しをグループ化することもできます。次に例を示します。

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

1 つの読み取りにグループ化できます。

offset = 1234, length = 1074

次に、バッファを「シーク」するだけで、そこから 3 つの新しいファイルを再度読み取ることなく書き込むことができます。

于 2009-06-05T13:49:50.650 に答える
3

個別のファイルに書き込みを行っているため、CCR の使用を検討したことがありますか? すべてを並行して実行 (読み取りと書き込み) できます。CCR を使用すると、これを非常に簡単に行うことができます。

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

このコードは、Split メソッドでコードを実行するスレッドを作成する CCR ポートにオフセットを送信します。これにより、ファイルを複数回開くことになりますが、同期の必要がなくなります。メモリ効率を高めることはできますが、速度を犠牲にする必要があります。

于 2009-06-05T14:57:07.380 に答える
1

FileStream + StreamWriter を使用すると、大量のファイルを短時間 (1 分 30 秒未満) で作成できることがわかっています。その手法を使用して、1 つのファイルから合計 700 MB 以上のファイルを 3 つ生成します。

使用しているコードの主な問題は、毎回ファイルを開いていることです。これにより、ファイル I/O オーバーヘッドが発生します。

生成するファイルの名前が事前にわかっている場合は、File.OpenWrite を別のメソッドに抽出できます。それは速度を上げます。ファイルをどのように分割するかを決定するコードを見なければ、これ以上速くなることはないと思います。

于 2009-06-05T15:31:38.527 に答える
1

最初にお勧めするのは、測定を行うことです。どこで時間を失っていますか?それは読み取り中ですか、それとも書き込み中ですか?

100,000 回を超えるアクセス (時間の合計): バッファー配列の割り当てに費やされた時間は? 読み取りのためにファイルを開くのにかかる時間 (毎回同じファイルですか?) 読み取り操作と書き込み操作にかかる時間はどれくらいですか?

ファイルに対して何らかの変換を行っていない場合、BinaryWriter が必要ですか、それとも書き込みにファイルストリームを使用できますか? (試してみてください。同じ出力が得られますか?時間を節約できますか?)

于 2009-06-05T13:52:43.093 に答える
0

スレッド化を提案する人はいませんか? 小さなファイルを書くことは、スレッドが役立つ場所の教科書の例のように見えます。小さなファイルを作成するために、一連のスレッドをセットアップします。このようにして、それらをすべて並行して作成でき、それぞれが完了するのを待つ必要はありません。私の仮定では、ファイルの作成 (ディスク操作) は、データを分割するよりもはるかに時間がかかります。もちろん、逐次的なアプローチが適切でないことを最初に確認する必要があります。

于 2009-06-05T14:21:59.043 に答える
-1

(今後の参考のために。)

おそらくこれを行う最も速い方法は、メモリマップファイルを使用することです(つまり、主にメモリをコピーし、OSがページング/メモリ管理を介してファイルの読み取り/書き込みを処理します)。

メモリ マップト ファイルは、.NET 4.0 のマネージ コードでサポートされています。

ただし、前述のように、プロファイルを作成する必要があり、最大のパフォーマンスを得るにはネイティブ コードに切り替える必要があります。

于 2009-06-05T14:08:27.307 に答える