Fastest way of reading and writing binary

前端 未结 4 754
渐次进展
渐次进展 2021-01-31 06:42

I\'m currently optimizing an application, one of the operations that is done very often is reading and writing binary. I need 2 types of functions:

Set(byte[] ta         


        
相关标签:
4条回答
  • Using Marc Gravell's Set1 to Set4 and the Set5 below, I get the following numbers on my 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;                
        }
    }
    

    Of course, it gets much faster when the byte array isn't pinned on each iteration but only once:

    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 讨论(0)
  • 2021-01-31 06:53

    Pointers are the way to go. Pinning objects with the fixed keyword is extremely cheap, and you avoid the overhead of calling functions like WriteInt32 and BlockCopy. For a "generic solution" you can simply use void* and use your own memcpy (since you're dealing with small amounts of data). However pointers do not work with true generics.

    0 讨论(0)
  • 2021-01-31 07:01

    You should do some profiling on your code to reveal whether this is the bottleneck. Also looking at your code it appears that you are using .Net function calls to write one byte to an unmanaged array, involving a pin on the memory and a call to unsafe code...

    You might be much better off declaring a .Net System.IO.MemoryStream and seeking and writing around to it, wherever possible using a stream writer to push your changes in, which should use less function calls and won't require unsafe code. You'll find the pointer stuff much more useful in C# if you are doing things like DSP, where you need to perform a single operation to every value in an array etc.

    EDIT: Let me also mention that depending on what you are doing you might find that the CPU caching will come into effect, if you can keep working on a single small area of memory that fits into the cache then you will end up with the best performance.

    0 讨论(0)
  • 2021-01-31 07:06

    Important: if you only need the one endian, see the pointer magic by wj32 / dtb


    Personally, I would be writing directly to a Stream (perhaps with some buffering), and re-using a shared buffer that I can generally assume is clean. Then you can make some shortcuts and assume index 0/1/2/3.

    Certainly don't use BitConverter, as that can't be used for both little/big-endian, which you require. I would also be inclined to just use bit-shifting rather than unsafe etc. It is actally the fastest, based on the following (so I'm glad that this is how I already do it my code here, look for 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 讨论(0)
提交回复
热议问题