2010-01-10 6 views
10

J'optimise actuellement une application, l'une des opérations qui se fait très souvent est la lecture et l'écriture binaire. Je besoin de 2 types de fonctions:Méthode la plus rapide de lecture et d'écriture binaire

Set(byte[] target, int index, int value); 

int Get(byte[] source, int index); 

Ces fonctions sont nécessaires pour faire court signé et non signé, int et long en grand et peu d'ordre endian.

Voici quelques exemples que j'ai fait, mais j'ai besoin d'une évaluation des avantages et des inconvénients:

première méthode consiste à utiliser le maréchal d'écrire la valeur dans la mémoire de l'octet [], le second est l'utilisation pointeurs simples pour ce faire et le troisième utilise BitConverter et BlockCopy pour ce faire

unsafe void Set(byte[] target, int index, int value) 
{ 
    fixed (byte* p = &target[0]) 
    { 
     Marshal.WriteInt32(new IntPtr(p), index, value); 
    } 
} 

unsafe void Set(byte[] target, int index, int value) 
{ 
    int* p = &value; 
    for (int i = 0; i < 4; i++) 
    { 
     target[offset + i] = *((byte*)p + i); 
    } 
} 

void Set(byte[] target, int index, int value) 
{ 
    byte[] data = BitConverter.GetBytes(value); 
    Buffer.BlockCopy(data, 0, target, index, data.Length); 
} 

et voici la lecture/méthodes Get:

le premier utilise le maréchal pour lire la valeur de l'octet [], la seconde utilise plaine p ointers et le troisième est d'utiliser à nouveau BitConverter:

unsafe int Get(byte[] source, int index) 
{ 
    fixed (byte* p = &source[0]) 
    { 
     return Marshal.ReadInt32(new IntPtr(p), index); 
    } 
} 

unsafe int Get(byte[] source, int index) 
{ 
    fixed (byte* p = &source[0]) 
    { 
     return *(int*)(p + index); 
    } 
} 

unsafe int Get(byte[] source, int index) 
{ 
    return BitConverter.ToInt32(source, index); 
} 

vérification des frontières doit être fait, mais ne fait pas partie de ma question encore ...

Je serais heureux si quelqu'un peut dire quelle serait la le meilleur et le plus rapide dans ce cas ou donnez-moi d'autres solutions sur lesquelles travailler. Une solution générique serait préférable


Je viens de faire quelques tests de performance, voici les résultats:

Set Marshal: 45 ms, Set pointeur: 48 ms, Set BitConverter: 71 ms Get Marshal: 45 ms, Get Pointer: 26 ms, Get BitConverter: 30 ms

il semble que l'utilisation de pointeurs est la façon rapide, mais je pense que Marshal et BitConverter font une vérification interne ... quelqu'un peut-il vérifier cela?

+1

Vous avez le code, pourquoi ne vous exécutez pas et test avec un 'Stopwatch'? –

+0

: /: \ vous avez raison, je vais le faire rapidement et modifier ma question, mais ce n'est pas le seul point de mon message. Je cherche des alternatives et peut-être des façons génériques de le faire aussi – haze4real

+2

Souris levé à cette question: la conversion en binaire ne devrait être nécessaire que pour les E/S. L'opération d'E/S elle-même est toujours plus lente de plusieurs ordres de grandeur que de masser les bits. La meilleure optimisation ne peut vous acheter plus de quelques pour cent d'amélioration. –

Répondre

9

Important: si vous avez besoin d'une endian, voir la magie du pointeur par wj32/DTB


Personnellement, je serais en train d'écrire directement à un Stream (peut-être avec une certaine mise en mémoire tampon), et re -utiliser un tampon partagé que je peux généralement supposer est propre.Ensuite, vous pouvez faire quelques raccourcis et assumer l'index 0/1/2/3.

Certainement n'utilisez pas BitConverter, car cela ne peut pas être utilisé à la fois pour little/big-endian, ce dont vous avez besoin. Je serais également enclin à utiliser le décalage plutôt que dangereux, etc. Il est le plus rapide, basé sur ce qui suit (donc je suis heureux que c'est comme ça que je le fais mon code here, cherchez EncodeInt32Fixed):

Set1: 371ms 
Set2: 171ms 
Set3: 993ms 
Set4: 91ms <==== bit-shifting ;-p 
Code

:

using System; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 
static class Program 
{ 
    static void Main() 
    { 
     const int LOOP = 10000000, INDEX = 100, VALUE = 512; 
     byte[] buffer = new byte[1024]; 
     Stopwatch watch; 

     watch = Stopwatch.StartNew(); 
     for (int i = 0; i < LOOP; i++) 
     { 
      Set1(buffer, INDEX, VALUE); 
     } 
     watch.Stop(); 
     Console.WriteLine("Set1: " + watch.ElapsedMilliseconds + "ms"); 

     watch = Stopwatch.StartNew(); 
     for (int i = 0; i < LOOP; i++) 
     { 
      Set2(buffer, INDEX, VALUE); 
     } 
     watch.Stop(); 
     Console.WriteLine("Set2: " + watch.ElapsedMilliseconds + "ms"); 

     watch = Stopwatch.StartNew(); 
     for (int i = 0; i < LOOP; i++) 
     { 
      Set3(buffer, INDEX, VALUE); 
     } 
     watch.Stop(); 
     Console.WriteLine("Set3: " + watch.ElapsedMilliseconds + "ms"); 

     watch = Stopwatch.StartNew(); 
     for (int i = 0; i < LOOP; i++) 
     { 
      Set4(buffer, INDEX, VALUE); 
     } 
     watch.Stop(); 
     Console.WriteLine("Set4: " + watch.ElapsedMilliseconds + "ms"); 

     Console.WriteLine("done"); 
     Console.ReadLine(); 
    } 
    unsafe static void Set1(byte[] target, int index, int value) 
    { 
     fixed (byte* p = &target[0]) 
     { 
      Marshal.WriteInt32(new IntPtr(p), index, value); 
     } 
    } 

    unsafe static void Set2(byte[] target, int index, int value) 
    { 
     int* p = &value; 
     for (int i = 0; i < 4; i++) 
     { 
      target[index + i] = *((byte*)p + i); 
     } 
    } 

    static void Set3(byte[] target, int index, int value) 
    { 
     byte[] data = BitConverter.GetBytes(value); 
     Buffer.BlockCopy(data, 0, target, index, data.Length); 
    } 
    static void Set4(byte[] target, int index, int value) 
    { 
     target[index++] = (byte)value; 
     target[index++] = (byte)(value >> 8); 
     target[index++] = (byte)(value >> 16); 
     target[index] = (byte)(value >> 24); 
    } 
} 
+0

Je ne pense pas que Stream serait une bonne solution, le problème est qu'il peut être nécessaire de chercher et les données ne sont pas toujours lues et écrites séquentiellement. Un autre problème serait endianess .. – haze4real

+0

Fine - ne pas utiliser le flux; mais voir 'Set4' et la preuve de performance ... –

+0

J'ai besoin de vérifier cela en premier et peut accepter cette réponse comme solution, qu'en est-il obtenir/lire? – haze4real

1

Vous devez effectuer un profilage sur votre code pour indiquer s'il s'agit du goulot d'étranglement. En regardant également votre code, il apparaît que vous utilisez des appels de fonction .Net pour écrire un octet sur un tableau non géré, impliquant une broche dans la mémoire et un appel à un code dangereux ...

Vous pourriez être beaucoup mieux de déclarer un .Net System.IO.MemoryStream et en cherchant et en écrivant autour de lui, chaque fois que possible en utilisant un écrivain de flux pour pousser vos changements, ce qui devrait utiliser moins d'appels de fonction et ne nécessitera pas de code dangereux. Vous trouverez le pointeur beaucoup plus utile en C# si vous faites des choses comme DSP, où vous devez effectuer une seule opération à chaque valeur dans un tableau, etc.

EDIT: Permettez-moi également de mentionner que selon ce que vous faites, vous pourriez trouver que la mise en cache du processeur va entrer en vigueur, si vous pouvez continuer à travailler sur une seule petite zone de mémoire qui rentre dans le cache, vous obtiendrez les meilleures performances.

+0

Le problème est que cela peut être un goulot d'étranglement car l'application communique avec une charge de différents périphériques réseau et s'exécute sur des machines à faible coût, certains de ces périphériques utilisent des protocoles lourds, d'autres non. Connaissez-vous un bon moyen de profiler les interfaces? problème sera le retard variable du réseau .. – haze4real

2

Pointeurs sont la voie à suivre. L'épinglage des objets avec le mot-clé fixe est extrêmement bon marché, et vous évitez le surcoût des fonctions d'appel comme WriteInt32 et BlockCopy. Pour une "solution générique", vous pouvez simplement utiliser void * et utiliser votre propre memcpy (puisque vous avez affaire à de petites quantités de données). Cependant, les pointeurs ne fonctionnent pas avec les vrais génériques.

+0

Ensuite, vous n'avez clairement pas écrit votre code correctement. Pensez-vous vraiment que l'utilisation de bit-shifting (environ 8 instructions pour chaque Int32 au moins *) est plus rapide que l'utilisation d'une simple instruction mov? Et je parle d'épingler le tampon en dehors de la boucle. – wj32

+0

pouvez-vous m'en donner un exemple en C#?vous parlez de la deuxième méthode Set, n'est-ce pas? J'ai juste besoin des types de valeur simples: Int16, UInt16, Int32, UInt32, Int64, UInt64 – haze4real

+1

fixe (octet * b = tableau) {pour (...) * (int *) (b + offset) = valeur; }}. Exactement ce que vous avez dans votre méthode Get. Regardez la fenêtre de désassemblage si vous ne croyez pas que c'est en fait la méthode la plus rapide. – wj32

7

L'utilisation de Marc Gravell Set1 à Set4 et Set5 ci-dessous, je reçois les chiffres suivants sur ma machine:

Set1: 197ms 
Set2: 102ms 
Set3: 604ms 
Set4: 68ms 
Set5: 55ms <==== pointer magic ;-p 

code:

unsafe static void Set5(byte[] target, int index, int value) 
{ 
    fixed (byte* p = &target[index]) 
    { 
     *((int*)p) = value;     
    } 
} 

Bien sûr, il obtient beaucoup plus rapide lorsque le tableau d'octets n'est pas pinne d à chaque itération, mais une seule fois:

Set6: 10ms (little endian) 
Set7: 85ms (big endian) 

code:

if (!BitConverter.IsLittleEndian) 
{ 
    throw new NotSupportedException(); 
} 

watch = Stopwatch.StartNew(); 
fixed (byte* p = buffer) 
{ 
    for (int i = 0; i < LOOP; i++) 
    { 
     *((int*)(p + INDEX)) = VALUE; 
    } 
} 
watch.Stop(); 
Console.WriteLine("Set6: " + watch.ElapsedMilliseconds + "ms"); 

watch = Stopwatch.StartNew(); 
fixed (byte* p = buffer) 
{ 
    for (int i = 0; i < LOOP; i++) 
    { 
     *((int*)(p + INDEX)) = System.Net.IPAddress.HostToNetworkOrder(VALUE); 
    } 
} 
watch.Stop(); 
Console.WriteLine("Set7: " + watch.ElapsedMilliseconds + "ms"); 
+0

le problème ici serait le surdébit généré par l'endian swaping – haze4real

+0

Cool; J'ai ajouté une mise à jour, mais l'endianness est toujours un problème ici. Je n'ai plus de voix aujourd'hui, mais un +1 +1 –

+0

@ haze4real virtuel: le temps supplémentaire produit par l'échange endian n'est pas si énorme. Exemple mis à jour. – dtb

Questions connexes