2011-02-07 9 views
14

En nuestra aplicación, tenemos una matriz de bytes muy grande y tenemos que convertir estos bytes en diferentes tipos. Actualmente, usamos BitConverter.ToXXXX() para este propósito. Nuestros grandes bateadores son, ToInt16 y ToUInt64.Fundición rápida en C# usando BitConverter, ¿puede ser más rápido?

Para UInt64, nuestro problema es que la secuencia de datos tiene realmente 6 bytes de datos para representar un número entero grande. Puesto que no hay función nativa para convertir 6-bytes de datos a UInt64, tenemos:

UInt64 value = BitConverter.ToUInt64() & 0x0000ffffffffffff; 

Nuestro uso de ToInt16 es más simple, no tiene que hacer ningún tipo de manipulación de bits.

Hacemos tantas de estas 2 operaciones que quería preguntarle a la comunidad SO si hay una forma más rápida de realizar estas conversiones. En este momento, estas dos funciones consumen aproximadamente el 20% de nuestros ciclos completos de CPU.

+8

El rendimiento entero no es probable que sea su problema. Procesar arreglos grandes casi siempre hace que el lento bus RAM sea el cuello de botella. Preste atención al contador de rendimiento "Falta el último nivel caché" en la salida de su generador de perfiles. –

+0

@Hans: Definitivamente es correcto: estamos limitados por la memoria. Pero para eso, no sé qué hacer. Tenemos una gran matriz y debemos atravesar cada byte para extraer los datos. A medida que avance linealmente en la matriz, probablemente el prefetcher de hardware se ajuste al patrón de acceso y, más allá de eso, no sé qué más se puede hacer. --gracias al – SomethingBetter

Respuesta

6

¿Ha pensado en usar punteros de memoria directamente? No puedo responder por su desempeño, pero es un truco común en C++ \ C ...

 byte[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 ,9,10,11,12,13,14,15,16}; 

     fixed (byte* a2rr = &arr[0]) 
     { 

      UInt64* uint64ptr = (UInt64*) a2rr; 
      Console.WriteLine("The value is {0:X2}", (*uint64ptr & 0x0000FFFFFFFFFFFF)); 
      uint64ptr = (UInt64*) ((byte*) uint64ptr+6); 
      Console.WriteLine("The value is {0:X2}", (*uint64ptr & 0x0000FFFFFFFFFFFF)); 
     } 

Tendrá que hacer su asamblea "no seguro" en las configuraciones de generación, así como indicar el método de que también estarías haciendo esto inseguro. También estás vinculado al pequeño endian con este enfoque.

+0

Esta resultó ser la manera más rápida de hacerlo, al menos hasta ahora. – SomethingBetter

+1

Tenga cuidado con esto. Si quiere leer uno de esos números de 6 bytes al final de una matriz, obtendrá una excepción. Es decir, si en el ejemplo anterior la matriz tenía solo 12 bytes de longitud, obtendría una excepción al leer el segundo valor. –

2

Por qué no:

UInt16 valLow = BitConverter.ToUInt16(); 
UInt64 valHigh = (UInt64)BitConverter.ToUInt32(); 
UInt64 Value = (valHigh << 16) | valLow; 

Usted puede hacer que una sola declaración, aunque el compilador JIT probablemente hacer eso de forma automática.

Eso le impedirá leer esos dos bytes adicionales que termina tirando.

Si eso no reduce la CPU, entonces es probable que desee escribir su propio convertidor que lea los bytes directamente desde el búfer. Puede usar indexación de matriz o, si lo considera necesario, código inseguro con punteros.

Tenga en cuenta que, como señaló un comentarista, si utiliza alguna de estas sugerencias, entonces o está limitado a un "endian-ness" particular, o tendrá que escribir su código para detectar poco/grande Endian y reaccionar en consecuencia. La muestra de código que mostré arriba funciona para little endian (x86).

+2

Deberías mencionar que esto funciona para un endianness dado (pienso poco, pero siempre estoy mezclando los dos). Puede o no importarle al OP. –

+0

@Martinho: Buen punto. He actualizado mi respuesta. –

+0

Seguí tu sugerencia inicial pensando que leer esos 2 bytes adicionales y expulsarlos debe estar ralentizándome, pero resulta que esta es la forma más lenta de hacerlo. Supongo que dado que los datos ya están en caché, realmente no importa leer 8 o 6 bytes a la vez. Su segunda sugerencia, el código @Jimmy proporcionado como respuesta funciona mucho más rápido. - Gracias – SomethingBetter

4

Usted puede utilizar la clase System.Buffer copiar toda una serie a otra matriz de un tipo diferente como una forma rápida, 'block copy' operación:

El método BlockCopy accede a los bytes en el array de parámetros src usando compensaciones en memoria, no programación de construcciones como índices o límites de matriz superior e inferior.

Los tipos de matriz deben ser de tipo "primitivo", deben alinearse y la operación de copia es sensible a endian. En su caso de enteros de 6 bytes, no puede alinearse con ninguno de los tipos "primitivos" de .NET, a menos que pueda obtener la matriz fuente con dos bytes de relleno para cada seis, que luego se alineará con Int64. Pero este método funcionará para matrices de Int16, lo que puede acelerar algunas de sus operaciones.

+0

Gracias por la información de System.Buffer.BlockCopy. En nuestro caso, UInt64 e Int16s están intercalados en la matriz, por lo que BlockCopy no funcionará para nosotros, pero esta información fue útil, podemos usar este método en el futuro. – SomethingBetter

1

Consulte mi respuesta para una pregunta similar here. Es la misma manipulación de memoria insegura que en la respuesta de Jimmy, pero de una manera más "amigable" para los consumidores. Te permitirá ver tu matriz byte como UInt64 matriz.

Cuestiones relacionadas