C# で超高速のファイル ストリーミング コードを記述するにはどうすればよいですか?
-
12-09-2019 - |
質問
巨大なファイルを多数の小さなファイルに分割する必要があります。各宛先ファイルは、オフセットとバイト数としての長さによって定義されます。次のコードを使用しています。
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 回呼び出す必要があることを考えると、非常に遅いです。
- Writer を Reader に直接接続する方法はありますか?(つまり、実際に内容をメモリ内のバッファにロードする必要はありません。)
解決
私はそれをメモリにバッファリングせずにファイルのセクションをコピーできるようにする.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ステートメントに)出力ストリームを閉じることに注意してください。
重要な点は、同じ入力ストリームを再利用するので、これは代わりに、最初にファイルを再開してから求めているの、より効率的にオペレーティング・システム・ファイルのバッファリングを使用するということです。
私は<全角>
... のそれはかなり速くなりますが、明らかにあなたが見るためにそれをしようとする必要があると思いますこれは当然のことながら、連続したチャンクを前提としています。あなたはファイルのビットをスキップする必要がある場合は、メソッドの外からそれを行うことができます。あなたは非常に小さなファイルを書いている場合も、あなたもそのような状況のために最適化することをお勧めします - それを行うための最も簡単な方法は、おそらく<のhref = "http://msdn.microsoft.com/en-を導入することであろう私たち/ライブラリ/ system.io.bufferedstream.aspx」のrel = "noreferrer">入力ストリームをラップする BufferedStream
ます。
他のヒント
のC#からファイルI / Oを行うための最速の方法は、WindowsのReadFileとWriteFile関数の機能を使用することです。私は、この機能だけでなく、BinaryReaderとBinaryWriterを含むdiffernetのI / O方法、見ベンチマークプログラムをカプセル化するC#クラスを書かれています。で私のブログの記事を参照してください。
http://designingefficientsoftware.wordpress.com / 2011/03/03 /効率的なファイル・IO-から-CSHARP / の
length
はどれくらい大きいですか?あなただけBinaryReader
とStream.Read
を使用して...、固定サイズ(適度に大きな、しかしわいせつではない)バッファを再利用してStream.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;
}
}
}
あなたは一度それをより良いオープン、ソースファイルにコピーを行うたびに再オープンし、コピー機能に結果BinaryReaderを渡すべきではありません。また、あなたはあなたが求めて注文した場合、それが役立つかもしれないので、あなたは、ファイル内の大ジャンプをすることはありません。
の長さが大きすぎない場合、あなたはまた、例えば、お互いに近いオフセットをグループ化し、あなたが彼らのために必要なブロック全体を読み取ることによって、グループのいくつかのコピーの呼び出しに試すことができます:
offset = 1234, length = 34
offset = 1300, length = 40
offset = 1350, length = 1000
1回の読み取りに分類することができます:
offset = 1234, length = 1074
次に、あなただけのバッファに「模索」しなければならないと再び読まなくてそこから3つの新しいファイルを書き込むことができます。
あなたは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);
}
このコードのポストオフセットスプリット方式でコードを実行するために作成されるスレッドの原因とCCRポートに接続します。これは、ファイルを開くために複数回を引き起こすが、同期の必要性を取り除きます。あなたはそれがより多くのメモリ効率的に行うことができますが、スピードを犠牲にする必要があります。
私が推薦する最初の事は測定を取ることです。どこであなたの時間を失っていますか?それは、読み取り、または書き込み中ですか?
10万アクセス(回合計): どのくらいの時間は、バッファ配列を割り当てる費やされていますか? 読み取りのためのファイルを開くどのくらいの時間を費やしている(それは毎回同じファイルであります?) 読み取りおよび書き込み操作に費やされているどのくらいの時間?
ファイルに変換のいずれかのタイプを行っていない場合は、BinaryWriterが必要なのか、またはあなたが書き込みのためにファイルストリームを使用することができますか? (あなたが同一の出力を得るのですか、それを試して?それは時間を節約していますか?)
のFileStream +のStreamWriterを使用して、私はそれは少し時間(1分未満30秒)で大規模なファイルを作成することができます知っています。私はその技術を用いて、一つのファイルから700 +メガバイトの合計3つのファイルを生成します。
あなたが使用しているコードであなたの主な問題は、ファイルごとに開いているということです。それは、ファイルI / Oのオーバーヘッドを作成しています。
あなたは事前に生成されるファイルの名前を知っていた場合は、別の方法にFile.OpenWriteを抽出できました。それは速度が向上します。あなたは、ファイルを分割する方法を決定するコードを見ることなく、私はあなたがはるかに高速に得ることができるとは思わない。
誰スレッドを示唆していませんか?小さなファイルを書き込むと、スレッドが有用でどこの教科書の例のように見えます。小さいファイルを作成するために、スレッドの束を設定します。この方法は、あなたが並列にそれらすべてを作成することができますし、それぞれが終了するのを待つ必要はありません。私の仮定は、ファイル(ディスク操作)を作成すると、データを分割よりWAY時間がかかるということです。そしてもちろん、あなたはシーケンシャルなアプローチが適切でないことを最初に確認する必要があります。
(将来の参照のために。)
恐らくこれがメモリを使用することです行うための最速の方法は、ファイル(そう主にメモリをコピーし、ファイルを扱うOSは/そのページング/メモリ管理を経由して読み書きを行う)がマッピングされます。
メモリマップドファイルは、.NET 4.0で、マネージコードでサポートされています。
しかし述べたように、プロファイル、および最大のパフォーマンスのためのネイティブコードに切り替えることを期待する必要があります。