質問
を取得したいのですが、 byte[]
から float[]
(おそらくキャスト経由で) 配列全体をループすることなく、できるだけ早く実行します。安全でないコードは問題ありません。ありがとう!
float 配列より 4 倍長いバイト配列を探しています (各 float は 4 バイトで構成されるため、バイト配列の次元は float 配列の 4 倍になります)。これを BinaryWriter に渡します。
編集:「時期尚早な最適化」を叫ぶ批評家たちへ:最適化する前に、ANTS プロファイラーを使用してこれをベンチマークしました。ファイルにはライトスルー キャッシュがあり、float 配列のサイズがディスク上のセクター サイズと正確に一致するため、速度が大幅に向上しました。バイナリ ライターは、次で作成されたファイル ハンドルをラップします。 pinvoke
'd win32 API。これにより関数呼び出しの数が減るため、最適化が行われます。
また、メモリに関しては、このアプリケーションは大量のメモリを使用する大規模なキャッシュを作成します。バイト バッファーを一度割り当てれば、それを何度も再利用できます。この特定のインスタンスでの 2 倍のメモリ使用量は、アプリ全体のメモリ消費量の丸め誤差に相当します。
したがって、ここでの教訓は、早まった仮定をしないことだと思います ;)
解決
変換を行わない場合は、Buffer.BlockCopy()をお勧めします。
public static void BlockCopy(
Array src,
int srcOffset,
Array dst,
int dstOffset,
int count
)
例:
float[] floatArray = new float[1000];
byte[] byteArray = new byte[floatArray.Length * 4];
Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);
他のヒント
これを行うには、汚い高速(安全でないコードではない)の方法があります:
[StructLayout(LayoutKind.Explicit)]
struct BytetoDoubleConverter
{
[FieldOffset(0)]
public Byte[] Bytes;
[FieldOffset(0)]
public Double[] Doubles;
}
//...
static Double Sum(byte[] data)
{
BytetoDoubleConverter convert = new BytetoDoubleConverter { Bytes = data };
Double result = 0;
for (int i = 0; i < convert.Doubles.Length / sizeof(Double); i++)
{
result += convert.Doubles[i];
}
return result;
}
これは機能しますが、 Mono CLR の新しいバージョン。唯一の奇妙なことは、 array.Length
がバイト長であることです。これは、配列に格納されている配列の長さを見て、この配列がバイト配列であったため、その長さはバイト長のままであるため、説明できます。インデクサーはDoubleが8バイトの大きさであると考えているため、そこで計算する必要はありません。
もう少し調べましたが、実際には MSDN 、 方法:属性を使用してC / C ++ユニオンを作成する(C#およびVisual Basic) 。したがって、将来のバージョンでサポートされる可能性があります。モノについてはわかりません。
時期尚早な最適化は諸悪の根源です。各 float を反復処理するという @Vlad の提案は、byte[] に切り替えるよりもはるかに合理的な答えです。次の要素数の増加に対する実行時間の表を見てください (50 回の実行の平均)。
Elements BinaryWriter(float) BinaryWriter(byte[])
-----------------------------------------------------------
10 8.72ms 8.76ms
100 8.94ms 8.82ms
1000 10.32ms 9.06ms
10000 32.56ms 10.34ms
100000 213.28ms 739.90ms
1000000 1955.92ms 10668.56ms
要素数が少ない場合、両者の間にほとんど違いはありません。膨大な数の要素の範囲に入ると、float[] から byte[] へのコピーに費やされる時間の方がメリットをはるかに上回ります。
したがって、シンプルなものを使用してください。
float[] data = new float[...];
foreach(float value in data)
{
writer.Write(value);
}
メモリのコピーと反復を回避する方法があります。
非常にいハックを使用して、(安全でない)メモリ操作を使用して、一時的に配列を別の型に変更できます。
このハックを32&amp;の両方でテストしました64ビットOS。したがって、ポータブルである必要があります。
ソース+サンプルの使用法は https://gist.github.com/1050703 で管理されています。便宜上、ここにも貼り付けます:
public static unsafe class FastArraySerializer
{
[StructLayout(LayoutKind.Explicit)]
private struct Union
{
[FieldOffset(0)] public byte[] bytes;
[FieldOffset(0)] public float[] floats;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct ArrayHeader
{
public UIntPtr type;
public UIntPtr length;
}
private static readonly UIntPtr BYTE_ARRAY_TYPE;
private static readonly UIntPtr FLOAT_ARRAY_TYPE;
static FastArraySerializer()
{
fixed (void* pBytes = new byte[1])
fixed (void* pFloats = new float[1])
{
BYTE_ARRAY_TYPE = getHeader(pBytes)->type;
FLOAT_ARRAY_TYPE = getHeader(pFloats)->type;
}
}
public static void AsByteArray(this float[] floats, Action<byte[]> action)
{
if (floats.handleNullOrEmptyArray(action))
return;
var union = new Union {floats = floats};
union.floats.toByteArray();
try
{
action(union.bytes);
}
finally
{
union.bytes.toFloatArray();
}
}
public static void AsFloatArray(this byte[] bytes, Action<float[]> action)
{
if (bytes.handleNullOrEmptyArray(action))
return;
var union = new Union {bytes = bytes};
union.bytes.toFloatArray();
try
{
action(union.floats);
}
finally
{
union.floats.toByteArray();
}
}
public static bool handleNullOrEmptyArray<TSrc,TDst>(this TSrc[] array, Action<TDst[]> action)
{
if (array == null)
{
action(null);
return true;
}
if (array.Length == 0)
{
action(new TDst[0]);
return true;
}
return false;
}
private static ArrayHeader* getHeader(void* pBytes)
{
return (ArrayHeader*)pBytes - 1;
}
private static void toFloatArray(this byte[] bytes)
{
fixed (void* pArray = bytes)
{
var pHeader = getHeader(pArray);
pHeader->type = FLOAT_ARRAY_TYPE;
pHeader->length = (UIntPtr)(bytes.Length / sizeof(float));
}
}
private static void toByteArray(this float[] floats)
{
fixed(void* pArray = floats)
{
var pHeader = getHeader(pArray);
pHeader->type = BYTE_ARRAY_TYPE;
pHeader->length = (UIntPtr)(floats.Length * sizeof(float));
}
}
}
使用方法は次のとおりです。
var floats = new float[] {0, 1, 0, 1};
floats.AsByteArray(bytes =>
{
foreach (var b in bytes)
{
Console.WriteLine(b);
}
});
unsafe
および fixed
を使用して byte *
ポインターを取得できますが、 byte *
を変換することはできませんライターがデータコピーを実行せずにパラメーターとして受け入れるために、 byte []
に。メモリフットプリントを2倍にし、データをディスクに出力するために実行する必要のある避けられない反復に余分な反復を追加するので、これは望ましくありません。
代わりに、フロートの配列を反復処理し、各 float
をライターに個別に書き込む、 Write(double)
メソッド。ライター内部でのバッファリングのため、高速です。 sixlettervariables
の番号を参照してください。
LudicrousSpeedSerializationというクラスがあり、次の安全でないメソッドが含まれています:
static public byte[] ConvertFloatsToBytes(float[] data)
{
int n = data.Length;
byte[] ret = new byte[n * sizeof(float)];
if (n == 0) return ret;
unsafe
{
fixed (byte* pByteArray = &ret[0])
{
float* pFloatArray = (float*)pByteArray;
for (int i = 0; i < n; i++)
{
pFloatArray[i] = data[i];
}
}
}
return ret;
}
基本的には舞台裏でforループを実行しますが、1行でジョブを実行します
byte[] byteArray = floatArray.Select(
f=>System.BitConverter.GetBytes(f)).Aggregate(
(bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });