12

配列全体を(おそらくキャストを介して)ループせずに、できるだけ早くabyte[]から aを取得したいと思います。float[]安全でないコードは問題ありません。ありがとう!

float 配列よりも 4 倍長いバイト配列を探しています (各 float は 4 バイトで構成されているため、バイト配列の次元は float 配列の 4 倍になります)。これを BinaryWriter に渡します。

編集:「時期尚早の最適化」を叫んでいる批評家へ:最適化する前に、ANTSプロファイラーを使用してこれをベンチマークしました。ファイルにはライトスルー キャッシュがあり、float 配列はディスク上のセクター サイズと正確に一致するサイズになっているため、速度が大幅に向上しました。pinvokeバイナリ ライターは、 'd win32 APIで作成されたファイル ハンドルをラップします。これにより関数呼び出しの数が減るため、最適化が行われます。

また、メモリに関しては、このアプリケーションは大量のメモリを使用する大規模なキャッシュを作成します。バイト バッファーを 1 回割り当てると、何度も再利用できます。この特定のインスタンスでの 2 倍のメモリ使用量は、アプリの全体的なメモリ消費量の丸め誤差になります。

したがって、ここでの教訓は、時期尚早の仮定をしないことだと思います ;)

4

8 に答える 8

22

これを行うには、ダーティーで高速な(安全でないコードではない)方法があります。

[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 バイトの大きさであると考えているため、計算は必要ありません。

私はそれをもう少し探しましたが、実際にはMSDNHow to: Create a C/C++ Union by Using Attributes (C# and Visual Basic)で説明されているので、これは将来のバージョンでサポートされる可能性があります。Monoについてはよくわかりません。

于 2009-03-06T15:46:35.983 に答える
21

時期尚早の最適化は諸悪の根源です! 各フロートを反復するという@Vladの提案は、バイト[]に切り替えるよりもはるかに合理的な答えです。要素数を増やした場合の実行時間の次の表を見てください (平均 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

少数の要素の場合、2 つの間にほとんど違いはありません。膨大な数の要素範囲に入ると、float[] から byte[] へのコピーに費やされる時間は、メリットをはるかに上回ります。

したがって、単純なものを使用してください。

float[] data = new float[...];
foreach(float value in data)
{
    writer.Write(value);
}
于 2009-03-06T15:37:32.603 に答える
17

メモリのコピーと反復を回避する方法があります。

(安全でない)メモリ操作を使用して、配列を別の型に一時的に変更するために、本当に醜いハックを使用できます。

このハックは 32 ビット OS と 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);
    }
});
于 2010-08-26T16:30:10.227 に答える
7

変換を行いたくない場合は、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);
于 2009-03-06T15:05:58.453 に答える
3

BinaryWriterにこれを任せたほうがいいです。使用する方法に関係なく、データセット全体で反復処理が行われるため、バイトを操作しても意味がありません。

于 2009-03-06T15:23:08.503 に答える
1

byte*を使用してポインターを取得することはできますが、データ コピーを実行せずにライターがパラメーターとして受け入れるために、をに変換することはできません。メモリ使用量が 2 倍になり、データをディスクに出力するために実行する必要がある避けられない反復に余分な反復が追加れるため、これは望ましくありません。unsafefixedbyte*byte[]

代わりに、float の配列を繰り返し処理しfloat、メソッドを使用してそれぞれを個別に writer にWrite(double)書き込む方がよいでしょう。ライター内のバッファリングにより、それでも高速です。sixlettervariablesの番号を参照してください。

于 2009-03-06T14:44:53.043 に答える
0

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;
    }
于 2009-03-06T16:39:44.217 に答える
-3

基本的には舞台裏で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(); });
于 2009-03-06T15:22:02.953 に答える