[編集]Windows APIはBinaryWriterよりもはるかに速いようです - 私のテストは正しいですか?
@VilleKrumlindeのおかげで、誤ってコードアナライザーの警告を回避しようとしたときに誤って導入したバグを修正しました。私は誤ってファイルの長さをリセットしていた「重複した」ファイル操作を有効にしていました。これで問題は解決されました。問題なく同じストリームに対してFastWrite()
を複数回呼び出すことができます。
[終了編集]
概要
私は、ディスクへの構造体の配列を書き込みする2つの異なる方法を比較するために、いくつかのタイミングテストをやっています。私は、知覚される知恵は、I/Oコストが他のものと比べて非常に高く、他のものを最適化するのにあまり時間を費やす価値がないということです。
ただし、私のタイミングテストではそうでないことが示されています。私は間違いを犯しています(これは完全に可能です)、私の最適化は本当に重要です。
歴史
は、まず、いくつかの歴史:このFastWrite()
方法はもともと、従来のC++プログラムによって消費されたファイルへの書き込みの構造体をサポートするために、数年前に書かれた、と我々はまだ、この目的のためにそれを使用しています。 (対応するFastRead()
メソッドもあります)主にblittable構造体の配列をファイルに書き込むことを容易にするために書かれたもので、スピードは第二の関心事でした。
私は、このような最適化が単にBinaryWriter
を使用するよりもはるかに速いとは言われていないので、ついに弾丸に噛まれ、いくつかのタイミングテストを実施しました。 50倍高速BinaryWriter
を使用して同等以上 - 結果は
それは私FastWrite()
方法が30であること表示されます...私を驚かせています。それはばかげているように見えるので、誰かがエラーを見つけることができるかどうか確認するために私のコードをここに掲載しています。
システム仕様
- デバッガの外部から実行のx86リリースビルドを、テストされました。
- Windows 8、x64、16GBのメモリで動作します。
- 通常のハードドライブ(SSDではなく)で実行します。
SlowWrite() took 00:00:02.0747141 FastWrite() took 00:00:00.0318139 SlowWrite() took 00:00:01.9205158 FastWrite() took 00:00:00.0327242 SlowWrite() took 00:00:01.9289878 FastWrite() took 00:00:00.0321100 SlowWrite() took 00:00:01.9374454 FastWrite() took 00:00:00.0316074
あなたが見ることができるように、それはそうです:Visual Studioの2012による.NET 4(そう純4.5がインストールされている)
私の結果は
結果を用いて
FastWrite()
が50倍高速であることを示します。 ここに私のテストコードです。テストを実行した後、2つのファイルをバイナリ比較して、それらが実際に同一であることを確認しました。FastWrite()
およびSlowWrite()
が同一のファイルを生成しました)。
は、あなたがそれを作ることができるものを参照してください。 :)
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.Win32.SafeHandles;
namespace ConsoleApplication1
{
internal class Program
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TestStruct
{
public byte ByteValue;
public short ShortValue;
public int IntValue;
public long LongValue;
public float FloatValue;
public double DoubleValue;
}
static void Main()
{
Directory.CreateDirectory("C:\\TEST");
string filename1 = "C:\\TEST\\TEST1.BIN";
string filename2 = "C:\\TEST\\TEST2.BIN";
int count = 1000;
var array = new TestStruct[10000];
for (int i = 0; i < array.Length; ++i)
array[i].IntValue = i;
var sw = new Stopwatch();
for (int trial = 0; trial < 4; ++trial)
{
sw.Restart();
using (var output = new FileStream(filename1, FileMode.Create))
using (var writer = new BinaryWriter(output, Encoding.Default, true))
{
for (int i = 0; i < count; ++i)
{
output.Position = 0;
SlowWrite(writer, array, 0, array.Length);
}
}
Console.WriteLine("SlowWrite() took " + sw.Elapsed);
sw.Restart();
using (var output = new FileStream(filename2, FileMode.Create))
{
for (int i = 0; i < count; ++i)
{
output.Position = 0;
FastWrite(output, array, 0, array.Length);
}
}
Console.WriteLine("FastWrite() took " + sw.Elapsed);
}
}
static void SlowWrite(BinaryWriter writer, TestStruct[] array, int offset, int count)
{
for (int i = offset; i < offset + count; ++i)
{
var item = array[i]; // I also tried just writing from array[i] directly with similar results.
writer.Write(item.ByteValue);
writer.Write(item.ShortValue);
writer.Write(item.IntValue);
writer.Write(item.LongValue);
writer.Write(item.FloatValue);
writer.Write(item.DoubleValue);
}
}
static void FastWrite<T>(FileStream fs, T[] array, int offset, int count) where T: struct
{
int sizeOfT = Marshal.SizeOf(typeof(T));
GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
uint bytesWritten;
uint bytesToWrite = (uint)(count * sizeOfT);
if
(
!WriteFile
(
fs.SafeFileHandle,
new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64() + (offset*sizeOfT)),
bytesToWrite,
out bytesWritten,
IntPtr.Zero
)
)
{
throw new IOException("Unable to write file.", new Win32Exception(Marshal.GetLastWin32Error()));
}
Debug.Assert(bytesWritten == bytesToWrite);
}
finally
{
gcHandle.Free();
}
}
[DllImport("kernel32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool WriteFile
(
SafeFileHandle hFile,
IntPtr lpBuffer,
uint nNumberOfBytesToWrite,
out uint lpNumberOfBytesWritten,
IntPtr lpOverlapped
);
}
}
フォローアップは
次のように私はまた、@ErenErsönmezによって提案されたコードをテストしている(と私はすべての3つのファイルは、テストの終わりに同一であることを確認さ):
static void ErenWrite<T>(FileStream fs, T[] array, int offset, int count) where T : struct
{
// Note: This doesn't use 'offset' or 'count', but it could easily be changed to do so,
// and it doesn't change the results of this particular test program.
int size = Marshal.SizeOf(typeof(TestStruct)) * array.Length;
var bytes = new byte[size];
GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
var ptr = new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64());
Marshal.Copy(ptr, bytes, 0, size);
fs.Write(bytes, 0, size);
}
finally
{
gcHandle.Free();
}
}
私はそのコードのテストを追加し、同時にファイルは263K(妥当なサイズ)になるようにoutput.Position = 0;
という行を削除しました。これらの変更により
、結果は以下のとおりです。
NOTEルックでFastWrite()
時間がある遅くどのくらいのときないキープバックファイルポインタをリセットし、ゼロに!:
SlowWrite() took 00:00:01.9929327
FastWrite() took 00:00:00.1152534
ErenWrite() took 00:00:00.2185131
SlowWrite() took 00:00:01.8877979
FastWrite() took 00:00:00.2087977
ErenWrite() took 00:00:00.2191266
SlowWrite() took 00:00:01.9279477
FastWrite() took 00:00:00.2096208
ErenWrite() took 00:00:00.2102270
SlowWrite() took 00:00:01.7823760
FastWrite() took 00:00:00.1137891
ErenWrite() took 00:00:00.3028128
あなたは全くのWindows APIを使用せずにマーシャリングを使用して、ほぼ同じ速度を達成できるようなので、それが見えます。唯一の欠点は、Erenのメソッドが構造体の配列全体をコピーしなければならないことです。メモリが限られていると問題になる可能性があります。
_Well_、タイトルはよかったですか?最初のループは 'FastWrite'及び第二のループは' SlowWrite'呼び出しを呼び出す場合http://meta.stackexchange.com/questions/10647/how-do-i-write-a-good-title –
結果は何ですか? –
@DanielHilgarthそれは違いはありませんが、(私はそのような影響を最小限に抑えるために外側のループを持っているので)期待していませんでした。 –