web-dev-qa-db-ja.com

File.Copy vs. Manual FileStream.Write for Copying File

私の問題は、ファイルコピーのパフォーマンスにあります。同じネットワーク上のWindows共有、FTPサイト、AmazonS3など、さまざまな場所にファイルシステム上の多くのファイルを移動する必要があるメディア管理システムがあります。すべて1つのWindowsネットワークにいるときは、ファイルをコピーするためのSystem.IO.File.Copy(source、destination)。多くの場合、入力ストリーム(MemoryStreamなど)だけなので、Copy操作を抽象化して入力ストリームと出力ストリームを取得しようとしましたが、パフォーマンスが大幅に低下しています。以下は、ディスカッションポイントとして使用するファイルをコピーするためのコードです。

public void Copy(System.IO.Stream inStream, string outputFilePath)
{
    int bufferSize = 1024 * 64;

    using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write))
    {

        int bytesRead = -1;
        byte[] bytes = new byte[bufferSize];

        while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0)
        {
            fileStream.Write(bytes, 0, bytesRead);
            fileStream.Flush();
        }
    }
}

これがFile.Copyよりもはるかに遅い理由を誰かが知っていますか?パフォーマンスを向上させるために何かできることはありますか?あるウィンドウの場所から別のウィンドウの場所にコピーしているかどうかを確認するために特別なロジックを挿入する必要があるのでしょうか。その場合、File.Copyを使用し、その他の場合はストリームを使用しますか?

ご感想と追加情報が必要かどうかをお知らせください。私はさまざまなバッファーサイズを試しましたが、64 KBのバッファーサイズが「小さな」ファイルに最適で、256 KB以上が「大きな」ファイルに適したバッファーサイズのようですが、どちらの場合でもFile.Copy( )。前もって感謝します!

33
jakejgordon

File.Copyは CopyFile Win32関数を中心に構築されており、この関数はMSのスタッフから多くの注意を払っています(コピーパフォーマンスの低下に関するこのVista関連のスレッドを思い出してください)。

メソッドのパフォーマンスを改善するいくつかの手がかり:

  1. 以前に多くの人が言ったように、サイクルからフラッシュメソッドを削除します。まったく必要ありません。
  2. バッファを増やすと効果的ですが、ファイル間操作、ネットワーク共有、またはftpサーバーの場合のみ、代わりに速度が低下します。 60 * 1024は、少なくともVistaより前のネットワーク共有に最適です。ほとんどの場合、ftp 32kで十分です。
  3. キャッシング戦略を提供することで(あなたの場合はシーケンシャルな読み取りと書き込み)OSを支援し、FileStreamコンストラクターのオーバーライドを FileOptions パラメーター(SequentalScan)で使用します。
  4. 非同期パターンを使用するとコピーを高速化できますが(特にネットワークからファイルへの場合に役立ちます)、これにはスレッドを使用せず、代わりにオーバーラップio(BeginRead、EndRead、BeginWrite、EndWriteを.netで)を使用し、忘れないでください。 FileStreamコンストラクターで非同期オプションを設定します( FileOptions を参照)

非同期コピーパターンの例:

int Readed = 0;
IAsyncResult ReadResult;
IAsyncResult WriteResult;

ReadResult = sourceStream.BeginRead(ActiveBuffer, 0, ActiveBuffer.Length, null, null);
do
{
    Readed = sourceStream.EndRead(ReadResult);

    WriteResult = destStream.BeginWrite(ActiveBuffer, 0, Readed, null, null);
    WriteBuffer = ActiveBuffer;

    if (Readed > 0)
    {
      ReadResult = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null);
      BackBuffer = Interlocked.Exchange(ref ActiveBuffer, BackBuffer);
    }

    destStream.EndWrite(WriteResult);
  }
  while (Readed > 0);
23
arbiter

リフレクターを散らすと、File.Copyが実際にWin32 APIを呼び出すことがわかります。

if (!Win32Native.CopyFile(fullPathInternal, dst, !overwrite))

解決する

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
internal static extern bool CopyFile(string src, string dst, bool failIfExists);

これはCopyFileのドキュメントです

7
Ed S.

アセンブラで慎重に作成した場合でも、独自のコードで根本的な何かを行うことでオペレーティングシステムを打ち負かすことはできません。

操作が最高のパフォーマンスで発生することを確認する必要があり、さまざまなソースを組み合わせて一致させたい場合は、リソースの場所を説明するタイプを作成する必要があります。次に、そのような2つの型を取得するCopyなどの関数を持つAPIを作成し、両方の説明を調べて、最高のパフォーマンスのコピーメカニズムを選択します。たとえば、両方の場所がWindowsファイルの場所であると判断した場合、File.Copy ORソースがWindowsファイルであるが宛先がHTTPである場合POST = WebRequestを使用します。

6
AnthonyWJones

3つの変更により、パフォーマンスが劇的に向上します。

  1. バッファーサイズを増やして、1MBを試してください(まあ、実験だけをしてください)
  2. FileStreamを開いたら、fileStream.SetLength(inStream.Length)を呼び出して、ディスク全体のブロック全体を前もって割り当てます(inStreamがシーク可能な場合にのみ機能します)。
  3. FileStream.Flush()を削除します。これは冗長であり、フラッシュが完了するまでブロックされるため、パフォーマンスに最大の影響を与える可能性があります。ストリームは破棄時にとにかくフラッシュされます。

これは私が試した実験で約3〜4倍速く見えました:

   public static void Copy(System.IO.Stream inStream, string outputFilePath)
    {
        int bufferSize = 1024 * 1024;

        using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write))
        {
            fileStream.SetLength(inStream.Length);
            int bytesRead = -1;
            byte[] bytes = new byte[bufferSize];

            while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0)
            {
                fileStream.Write(bytes, 0, bytesRead);
            }
       }
    }
4
Rob Levine

Mark Russinovichがこれに対する権威となります。

彼は blog エントリ Inside Vista SP1 File Copy Improvements にWindowsを要約して書いていますVista SP1による最新の技術。

私の準教育を受けた推測は、File.Copyが非常に多くの状況で最も堅牢であることだと思います。もちろん、それは特定の特定のケースでは意味しません、あなた自身のコードがそれを打ち負かすかもしれません...

1
lavinio

Flush呼び出しを削除して、ループの外に移動してください。

OSがIOをフラッシュするタイミングを最もよく知っている場合があります。内部バッファをより適切に使用できます。

1
Aviad Ben Dov

これは同様の答えです

ストリームのコンテンツを別のストリームにコピーするにはどうすればよいですか?

主な問題は、パフォーマンスをI/Oの速度にバインドするFlush()の呼び出しです。

1
sylvanaar

目立つのは、チャンクの読み取り、そのチャンクの書き込み、別のチャンクの読み取りなどです。

ストリーミング操作はマルチスレッドの有力な候補です。私の推測では、File.Copyはマルチスレッドを実装しています。

あるスレッドで読み取り、別のスレッドで書き込みを試みます。読み取りスレッドがバッファをいっぱいにするまで、書き込みスレッドがバッファの書き込みを開始しないように、スレッドを調整する必要があります。これを解決するには、2つのバッファーを使用します。1つは読み取り中に、もう1つは書き込み中に、フラグは現在、どのバッファーがどの目的で使用されているかを示します。

0
Eric J.