私の問題は、ファイルコピーのパフォーマンスにあります。同じネットワーク上の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( )。前もって感謝します!
File.Copyは CopyFile Win32関数を中心に構築されており、この関数はMSのスタッフから多くの注意を払っています(コピーパフォーマンスの低下に関するこのVista関連のスレッドを思い出してください)。
メソッドのパフォーマンスを改善するいくつかの手がかり:
非同期コピーパターンの例:
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);
リフレクターを散らすと、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);
アセンブラで慎重に作成した場合でも、独自のコードで根本的な何かを行うことでオペレーティングシステムを打ち負かすことはできません。
操作が最高のパフォーマンスで発生することを確認する必要があり、さまざまなソースを組み合わせて一致させたい場合は、リソースの場所を説明するタイプを作成する必要があります。次に、そのような2つの型を取得するCopy
などの関数を持つAPIを作成し、両方の説明を調べて、最高のパフォーマンスのコピーメカニズムを選択します。たとえば、両方の場所がWindowsファイルの場所であると判断した場合、File.Copy ORソースがWindowsファイルであるが宛先がHTTPである場合POST = WebRequestを使用します。
3つの変更により、パフォーマンスが劇的に向上します。
これは私が試した実験で約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);
}
}
}
Mark Russinovichがこれに対する権威となります。
彼は blog エントリ Inside Vista SP1 File Copy Improvements にWindowsを要約して書いていますVista SP1による最新の技術。
私の準教育を受けた推測は、File.Copyが非常に多くの状況で最も堅牢であることだと思います。もちろん、それは特定の特定のケースでは意味しません、あなた自身のコードがそれを打ち負かすかもしれません...
Flush呼び出しを削除して、ループの外に移動してください。
OSがIOをフラッシュするタイミングを最もよく知っている場合があります。内部バッファをより適切に使用できます。
目立つのは、チャンクの読み取り、そのチャンクの書き込み、別のチャンクの読み取りなどです。
ストリーミング操作はマルチスレッドの有力な候補です。私の推測では、File.Copyはマルチスレッドを実装しています。
あるスレッドで読み取り、別のスレッドで書き込みを試みます。読み取りスレッドがバッファをいっぱいにするまで、書き込みスレッドがバッファの書き込みを開始しないように、スレッドを調整する必要があります。これを解決するには、2つのバッファーを使用します。1つは読み取り中に、もう1つは書き込み中に、フラグは現在、どのバッファーがどの目的で使用されているかを示します。