Si tengo una matriz de bytes y quieres convertir un contiguos de 16 bytes del bloque de la matriz, que contiene .neto de la representación de un Decimal, en Decimal struct, ¿cuál es la forma más eficiente de hacerlo?

Aquí está el código que se mostró en el generador de perfiles como el más grande de la CPU de los consumidores en un caso que estoy optimización.

public static decimal ByteArrayToDecimal(byte[] src, int offset)
{
    using (MemoryStream stream = new MemoryStream(src))
    {
        stream.Position = offset;
        using (BinaryReader reader = new BinaryReader(stream))
            return reader.ReadDecimal();
    }
}

Para deshacerse de MemoryStream y BinaryReader, pensé que la alimentación de una matriz de BitConverter.ToInt32(src, offset + x)s en el Decimal(Int32[]) constructor iba a ser más rápido que la solución que presentamos a continuación, pero la versión de abajo, es, curiosamente, el doble de rápido.

const byte DecimalSignBit = 128;
public static decimal ByteArrayToDecimal(byte[] src, int offset)
{
    return new decimal(
        BitConverter.ToInt32(src, offset),
        BitConverter.ToInt32(src, offset + 4),
        BitConverter.ToInt32(src, offset + 8),
        src[offset + 15] == DecimalSignBit,
        src[offset + 14]);
}

Este es 10 veces más rápido como el MemoryStream/BinaryReader combo, y he probado con un montón de valores extremos para asegurarse de que funciona, pero la representación decimal no es tan sencillo como el de otros tipos primitivos, así que todavía no estoy convencido de que funciona el 100% de los posibles valores decimales.

En teoría, sin embargo, podría ser una forma de copiar los 16 byte contiguo a algún otro lugar de la memoria y declarar que para ser un Decimal, sin ningún tipo de comprobaciones. Es alguien consciente de un método para hacer esto?

(Solo hay un problema: a Pesar de que los decimales se representan como 16 bytes, algunos de los posibles valores que no constituyen válido decimales, de modo que hacer un desactivadamemcpy podría romper cosas…)

O hay alguna otra manera más rápida?

  • Hay casos en los que tenga varios valores decimales en una fila en la matriz? Si no, no puedo pensar de una manera más rápida.
  • El problema aquí no es que BinaryReader es tan lento, es que el Decimal constructor es muy rápido. Así la sobrecarga de la construcción de estos objetos se hace evidente en las pruebas a/B. La seguridad y la velocidad son objetivos que están en conflicto.
  • Yo no he dicho BinaryReader es lento. Pero pasando por innecesario indirections de ningún tipo, obviamente, ralentiza las cosas, no importa lo rápido que son. Si hubiera tenido un BinaryReader para empezar, en lugar de una matriz de bytes, dudo de que habría alguna forma más rápida para leer un número decimal a partir de ella que llamar a su ReadDecimal método.

2 Comentarios

  1. 3

    Aunque esta es una vieja pregunta, yo estaba un poco intrigado, así que decidió correr algunos experimentos. Vamos a empezar con el código de experimento.

    static void Main(string[] args)
    {
        byte[] serialized = new byte[16 * 10000000];
    
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < 10000000; ++i)
        {
            decimal d = i;
    
            //Serialize
            using (var ms = new MemoryStream(serialized))
            {
                ms.Position = (i * 16);
                using (var bw = new BinaryWriter(ms))
                {
                    bw.Write(d);
                }
            }
        }
        var ser = sw.Elapsed.TotalSeconds;
    
        sw = Stopwatch.StartNew();
        decimal total = 0;
        for (int i = 0; i < 10000000; ++i)
        {
            //Deserialize
            using (var ms = new MemoryStream(serialized))
            {
                ms.Position = (i * 16);
                using (var br = new BinaryReader(ms))
                {
                    total += br.ReadDecimal();
                }
            }
        }
        var dser = sw.Elapsed.TotalSeconds;
    
        Console.WriteLine("Time: {0:0.00}s serialization, {1:0.00}s deserialization", ser, dser);
        Console.ReadLine();
    }

    Resultado: Time: 1.68s serialization, 1.81s deserialization. Esta es nuestra base. También traté de Buffer.BlockCopy a un int[4], lo que nos da 0.42 s para la deserialización. Utilizando el método descrito en la pregunta, deserialización va 0.29 s.

    En teoría, sin embargo, podría ser una forma de copiar los 16 contiguos
    byte a algún otro lugar de la memoria y declarar que para ser un Decimal,
    sin ninguna comprobación. Es alguien consciente de un método para hacer esto?

    Bueno, sí, la manera más rápida de hacer esto es utilizar un código inseguro, lo cual está bien aquí porque decimales son tipos de valor:

    static unsafe void Main(string[] args)
    {
        byte[] serialized = new byte[16 * 10000000];
    
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < 10000000; ++i)
        {
            decimal d = i;
    
            fixed (byte* sp = serialized)
            {
                *(decimal*)(sp + i * 16) = d;
            }
        }
        var ser = sw.Elapsed.TotalSeconds;
    
        sw = Stopwatch.StartNew();
        decimal total = 0;
        for (int i = 0; i < 10000000; ++i)
        {
            //Deserialize
            decimal d;
            fixed (byte* sp = serialized)
            {
                d = *(decimal*)(sp + i * 16);
            }
    
            total += d;
        }
        var dser = sw.Elapsed.TotalSeconds;
    
        Console.WriteLine("Time: {0:0.00}s serialization, {1:0.00}s deserialization", ser, dser);
    
        Console.ReadLine();
    }

    En este punto, nuestro resultado es: Time: 0.07s serialization, 0.16s deserialization. Bastante seguro de que es el más rápido de esto se va a poner… aún así, tienes que aceptar inseguro aquí, y supongo que las cosas se escribe de la misma manera como se lee.

  2. 3

    @Eugene Beresovksy leer una secuencia de comandos es muy costoso. MemoryStream es sin duda una herramienta potente y versátil, pero tiene un muy alto costo para una lectura directa de una matriz binaria. Tal vez por ello el segundo método funciona mejor.

    Tengo un 3er solución para usted, pero antes de escribir esto, es necesario decir que no he probado el rendimiento de la misma.

    public static decimal ByteArrayToDecimal(byte[] src, int offset)
    {
        var i1 = BitConverter.ToInt32(src, offset);
        var i2 = BitConverter.ToInt32(src, offset + 4);
        var i3 = BitConverter.ToInt32(src, offset + 8);
        var i4 = BitConverter.ToInt32(src, offset + 12);
    
        return new decimal(new int[] { i1, i2, i3, i4 });
    }

    Esta es una manera de hacer que el edificio se basa en un binario sin tener que preocuparse acerca de la canónica de System.Decimal. Es la inversa de la predeterminada .net bits método de extracción:

    System.Int32[] bits = Decimal.GetBits((decimal)10);

    EDITADO:

    Esta solución tal vez no realizar mejor, pero también no tienen este problema: "(There's only one problem: Although decimals are represented as 16 bytes, some of the possible values do not constitute valid decimals, so doing an uncheckedmemcpy could potentially break things...)".

    • A pesar de que su solución es la más sencilla, es sólo un medio tan rápido como el mío, por extraño que parezca. Leer el texto entre el 2 fragmentos de código en mi pregunta y usted encontrará que hice ya intente esto, sin faltas de ortografía en el código, ya que no se realizó. En términos de corrección, que no es mejor ni peor que mi solución, a menos que haya un error (por ejemplo, un rango de verificación que debe ser agregado), la cual debe ser reparable, e incurrir en un costo en el rendimiento que dudo que lo haría como «lento» como el new Decimal(Int32[]) solución.
    • Sé que este es un viejo post, pero me preguntaba si se trató de la variante int[] tmp = new int[4]; Buffer.BlockCopy(src, offset, tmp, 0, 16); return new decimal(tmp);. BitConverter es bastante lento, por lo que podría inclinar la respuesta a esta solución un poco.

Dejar respuesta

Please enter your comment!
Please enter your name here