2017-01-09 29 views
3

C#(.NET 4.5)を使用するファイルのセットを複数の場所にコピーしたいとします(たとえば、フォルダの内容をコンピュータに接続された2台のUSBドライブにコピーします)。
foreachループとFile.Copyを使用するより効率的な方法がありますか?複数のファイルを複数の場所にコピーする

(可能な)解決策に向けて作業してください。

私の最初の考えは、マルチスレッドアプローチのようなものでした。いくつかの読書と研究の後、何らかの並列処理や非同期処理を盲目的に設定することは、IOに関しては(Why is Parallel.ForEach much faster then AsParallel().ForAll() even though MSDN suggests otherwise?のように)良い考えではないことがわかりました。

ボトルネックは、特に従来のドライブの場合は、同期して読み書きできるため、ディスクです。それは私に考えさせました。もしそれを一度読んだら複数の場所に出力すればどうなりますか?結局のところ、私のUSBドライブのシナリオでは、私は複数の(出力)ディスクを扱っています。

どうすればいいのか分かりません。私が見たアイディア(Copy same file from multiple threads to multiple destinations)は、各ファイルのすべてのバイトをメモリに読み込んだ後、宛先をループして次のファイルに移動する前に各位置にバイトを書き出すことでした。ファイルが大きければ、それは悪い考えです。私がコピーするファイルの一部はビデオで、1 GB(またはそれ以上)になる可能性があります。 1GBのファイルを別のディスクにコピーするだけでメモリにロードすることをお勧めします。

大容量ファイルに柔軟性を持たせるために、私が得た最も近いものは以下の通りです(How to copy one file to many locations simultaneouslyに基づいています)。このコードの問題は、私はまだ単一の読み取りと複数の書き込みが起こっていないということです。現在、マルチリードとマルチライトです。このコードをさらに最適化する方法はありますか?チャンクをメモリに読み込み、次のチャンクに移動する前にそのチャンクを各デスティネーションに書き込むことができますか(上記のアイデアと同じですが、全体の代わりにチャンクされたファイル)?あなたは、読み取り/書き込み操作のためのいくつかのasync/await構造を導入しようとすることができるので、一般的には

files.ForEach(fileDetail => 
    Parallel.ForEach(fileDetail.DestinationPaths, new ParallelOptions(), 
     destinationPath => 
     { 
      using (var source = new FileStream(fileDetail.SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read)) 
      using (var destination = new FileStream(destinationPath, FileMode.Create)) 
      { 
       var buffer = new byte[1024]; 
       int read; 

       while ((read = source.Read(buffer, 0, buffer.Length)) > 0) 
       { 
        destination.Write(buffer, 0, read); 
       } 
      } 
     })); 

答えて

2

IO操作は、あなたのコードの外部で実行されるいくつかのハードウェアの操作があるとasynchronousとして考慮されるべきであるので、あなたは実行を継続することができますハードウェア操作中に

while ((read = await source.ReadAsync(buffer, 0, buffer.Length)) > 0) 
{ 
    await destination.WriteAsync(buffer, 0, read); 
} 

ます。また、この作品を作るためにasyncとしてあなたラムダデリゲートをマークする必要があります:

async destinationPath => 
... 

そして、あなたは結果のタスクのすべての方法を待つ必要があります。あなたはここでより多くの情報を見つけることがあります。

Parallel foreach with asynchronous lambda

Nesting await in Parallel.ForEach

+0

あなたの助けてくれてありがとう@VMAtm - 私は先週私のコードを調整していて、並列非同期書き込み( 'Task.WaitAll'を使って)で一度読み込みを管理しました。私はまだ別の(.NET)プログラムで見たスピードには達していないようですが、アプリケーションディレクトリの_USBLib.dll_に気づいたので、コピー用に別のものを使用しているのだろうかと思います。 IOバッファについて多くのことを知っていますか?バッファを何か大きいもの(通常の81920バイトの代わりに60MBのようなもの)にプッシュすると気づいたことがありますが、それは速いですが、大きなバッファは良いアイデアですか? – Pete

+0

偶然にも、私は今日のバッファサイズの調査を行いました。私が言う限りでは、より大きなバッファのパフォーマンスを測定する必要があります。何人かは、「81920」は256KB 。それはあなた次第です。ただそれを測定してください。もし失敗すれば60 MBの危険性があります。おそらくそのデータはすべて失われます。 – VMAtm

+1

さて、バッファサイズでいくつかのテストをする必要があるようです。あなたのヒントとポインタをありがとう。私は実際にあなたが応答を投稿するのに気をつけて、あなたが有用な洞察とリンクを与えたので、あなたに答えを与えるでしょう(私は数日前にそれを行う方法を考え出しました)。 – Pete

1

私はこの質問に出くわす誰のための私の現在のソリューションを投稿しようと思いました。

誰かがこれを行うためのより効率的な/早い方法を発見した場合は、教えてください!

私のコードは、コピーを同期して実行するよりも少し早くファイルをコピーするようですが、私が望むほど高速ではありません。パフォーマンスは.NETバージョンとシステムによって異なることに注意する必要があります(私は2.9GHz i5(5287U-2コア/ 4スレッド)+ 16GB RAMを搭載した13インチのMBPで.NET 4.5.2でWin 10を使用しています) 。私は方法の最良組み合わせ(例えば、FileStream.Write,、BinaryWriter.Write)とバッファサイズがまだわからない。

foreach (var fileDetail in files) 
{ 
    foreach (var destinationPath in fileDetail.DestinationPaths) 
     Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); 

    // Set up progress 
    FileCopyEntryProgress progress = new FileCopyEntryProgress(fileDetail); 

    // Set up the source and outputs 
    using (var source = new FileStream(fileDetail.SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan)) 
    using (var outputs = new CompositeDisposable(fileDetail.DestinationPaths.Select(p => new FileStream(p, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize)))) 
    { 
     // Set up the copy operation 
     var buffer = new byte[bufferSize]; 
     int read; 

     // Read the file 
     while ((read = source.Read(buffer, 0, buffer.Length)) > 0) 
     { 
      // Copy to each drive 
      await Task.WhenAll(outputs.Select(async destination => await ((FileStream)destination).WriteAsync(buffer, 0, read))); 

      // Report progress 
      if (onDriveCopyFile != null) 
      { 
       progress.BytesCopied = read; 
       progress.TotalBytesCopied += read; 

       onDriveCopyFile.Report(progress); 
      } 
     } 
    } 

    if (ct.IsCancellationRequested) 
     break; 
} 

私は、反応性の拡張機能からCompositeDisposablehttps://github.com/Reactive-Extensions/Rx.NET)を使用しています。

+0

['AsyncEnumerator'](https://www.nuget.org/packages/AsyncEnumerator/1.1.0)と[ForEachAsync](https://blogs.msdn.microsoft.com/pfxteam/2012)を調べることもできます。/03/05/implementation-a-simple-foreachasync-part-2 /) – VMAtm

関連する問題