Question

Je voudrais obtenir un octet [] d'un float [] aussi rapidement que possible, sans avoir à parcourir tout le tableau (via un transt, probablement). Un code non sécurisé convient. Merci!

Je recherche un tableau d'octets 4 fois plus long que le tableau float (la dimension du tableau d'octets sera 4 fois supérieure à celle du tableau float, car chaque float est composé de 4 octets). Je vais passer cela à un BinaryWriter.

MODIFIER : Pour ceux qui critiquent "l'optimisation prématurée": J'ai analysé cela en utilisant le profileur ANTS avant de l'optimiser. Il y a eu une augmentation significative de la vitesse car le fichier dispose d'un cache en écriture directe et la baie float est exactement dimensionnée pour correspondre à la taille du secteur sur le disque. Le rédacteur binaire encapsule un descripteur de fichier créé avec l’API pinvoke 'd win32. L’optimisation se produit car elle réduit le nombre d’appels de fonctions.

Et en ce qui concerne la mémoire, cette application crée des caches massifs qui utilisent beaucoup de mémoire. Je peux allouer le tampon d'octets une fois et le réutiliser plusieurs fois - la double utilisation de la mémoire dans ce cas particulier équivaut à une erreur d'arrondi dans la consommation de mémoire globale de l'application.

Donc, je suppose que la leçon à tirer ici est de ne pas faire de suppositions prématurées;)

Était-ce utile?

La solution

Si vous ne voulez aucune conversion, je suggérerais Buffer.BlockCopy ().

public static void BlockCopy(
    Array src,
    int srcOffset,
    Array dst,
    int dstOffset,
    int count
)

Par exemple:

float[] floatArray = new float[1000];
byte[] byteArray = new byte[floatArray.Length * 4];

Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);

Autres conseils

Il existe un moyen rapide et rapide de faire ceci (pas de code dangereux) :

[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;
}

Cela fonctionnera, mais je ne suis pas sûr du support sur Mono ou des versions plus récentes du CLR . La seule chose étrange est que array.Length est la longueur en octets. Cela peut s'expliquer par le fait qu'il examine la longueur du tableau stocké avec le tableau et que, comme ce tableau était un tableau d'octets, cette longueur sera toujours en longueur d'octets. L’indexeur pense que le double a une taille de huit octets, aucun calcul n’est donc nécessaire.

Je l'ai cherché un peu plus et c'est décrit dans MSDN , Comment: créer une union C / C ++ à l'aide d'attributs (C # et Visual Basic) , il est donc probable que cela sera pris en charge dans les versions futures. Je ne suis toutefois pas sûr de Mono.

L’optimisation prématurée est la racine de tout mal! La suggestion de @ Vlad d'itérer chaque flottant est une réponse bien plus raisonnable que de passer à un octet []. Prenez le tableau ci-dessous des durées d’exécution pour un nombre croissant d’éléments (moyenne de 50 exécutions):

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

Il y a peu de différence entre les deux pour un petit nombre d'éléments. Une fois que vous entrez dans la vaste gamme d’éléments, le temps passé à copier du flottant [] à l’octet [] dépasse de loin les avantages.

Alors, allez avec ce qui est simple:

float[] data = new float[...];
foreach(float value in data)
{
    writer.Write(value);
}

Il existe un moyen d'éviter la copie et l'itération en mémoire.

Vous pouvez utiliser un hack vraiment moche pour changer temporairement votre tableau en un autre type en utilisant une manipulation de mémoire (non sécurisée).

J'ai testé ce piratage dans 32 & amp; Le système d'exploitation 64 bits devrait donc être portable.

L'utilisation source + sample est conservée à l'adresse https://gist.github.com/1050703 . mais pour votre commodité, je le collerai ici aussi:

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));
        }
    }
}

Et l'utilisation est:

var floats = new float[] {0, 1, 0, 1};
floats.AsByteArray(bytes =>
{
    foreach (var b in bytes)
    {
        Console.WriteLine(b);
    }
});

Vous feriez mieux de laisser BinaryWriter faire cela pour vous . Il y aura une itération sur votre ensemble de données quelle que soit la méthode utilisée, il est donc inutile de jouer avec les octets.

Bien que vous puissiez obtenir un pointeur octet * à l'aide de unsafe et de fixe , vous ne pouvez pas convertir le octet * . octet [] pour que le rédacteur l'accepte en tant que paramètre sans effectuer de copie de données. Ce que vous ne voulez pas faire, car cela doublera votre empreinte mémoire et ajoutera une itération supplémentaire par rapport à l’itération inévitable à effectuer pour exporter les données sur le disque.

Au lieu de cela, vous feriez mieux de parcourir le tableau de flottants et d'écrire chaque flottant individuellement dans le rédacteur , à l'aide de la touche Write (double) méthode. Ce sera toujours rapide en raison de la mise en mémoire tampon dans le rédacteur. Voir les numéros de sixlettervariables .

Nous avons une classe appelée LudicrousSpeedSerialization et elle contient la méthode non sécurisée suivante:

    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;
    }

Bien qu’il fasse essentiellement une boucle for en coulisse, il fait le travail en une seule ligne

byte[] byteArray = floatArray.Select(
                    f=>System.BitConverter.GetBytes(f)).Aggregate(
                    (bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top