Convertir eficientemente la matriz de bytes a decimal

Si tengo una matriz de bytes y quiero convertir un bloque contiguo de 16 bytes de esa matriz, que contiene la representación de un Decimal .net, en una estructura Decimal correcta, ¿cuál es la forma más eficiente de hacerlo?

Aquí está el código que apareció en mi perfil como el mayor consumidor de CPU en un caso que estoy optimizando.

 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 deshacerme de MemoryStream y BinaryReader , pensé que BitConverter.ToInt32(src, offset + x) una matriz de BitConverter.ToInt32(src, offset + x) s en el constructor de Decimal(Int32[]) sería más rápido que la solución que presento a continuación, pero la versión a continuación 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]); } 

Esto es 10 veces más rápido que el combo MemoryStream/BinaryReader , y lo probé con un montón de valores extremos para asegurarme de que funciona, pero la representación decimal no es tan directa como la de otros tipos primitivos, así que todavía no estoy Convencido de que funciona para el 100% de los posibles valores decimales.

Sin embargo, en teoría, podría haber una manera de copiar esos 16 bytes contiguos en algún otro lugar de la memoria y declarar que se trata de un decimal, sin ninguna verificación. ¿Alguien es consciente de un método para hacer esto?

(Solo hay un problema: aunque los decimales se representan como 16 bytes, algunos de los valores posibles no constituyen decimales válidos, por lo que hacer un memcpy no memcpy podría potencialmente romper cosas …)

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

@Eugene Beresovksy leer desde un flujo es muy costoso. MemoryStream es ciertamente una herramienta potente y versátil, pero tiene un costo bastante alto para una lectura directa de una matriz binaria. Tal vez debido a esto el segundo método funciona mejor.

Tengo una tercera solución para usted, pero antes de escribirla, 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 base en un binario sin preocuparse por lo canónico de System.Decimal . Es el inverso del método de extracción de bit .net predeterminado:

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

EDITADO:

Es posible que esta solución no funcione mejor, pero tampoco tenga 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...)" .

Aunque esta es una pregunta antigua, estaba un poco intrigado, así que decidí realizar algunos experimentos. Comencemos con el código del 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 línea de base. También probé Buffer.BlockCopy a un int[4] , lo que nos da 0,42 segundos para la deserialización. Usando el método descrito en la pregunta, la deserialización se reduce a 0.29s.

Sin embargo, en teoría, podría haber una manera de copiar esos 16 bytes contiguos en algún otro lugar de la memoria y declarar que se trata de un decimal, sin ninguna verificación. ¿Alguien es consciente de un método para hacer esto?

Bueno, sí, la forma más rápida de hacer esto es usar código inseguro, lo cual está bien aquí porque los 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 eso es lo más rápido que se va a conseguir ... aún así, tienes que aceptar inseguros aquí, y asumo que las cosas se escriben de la misma manera que se leen.