Home > Net >  C# performance - pointer to span in a hot loop
C# performance - pointer to span in a hot loop

Time:01-18

I'm looking for a faster alternative to BitConverter:

But! Inside a "hot loop":

//i_k_size = 8 bytes 
while (fs.Read(ba_buf, 0, ba_buf.Length) > 0 && dcm_buf_read_ctr < i_buf_reads)
{
    Span<byte> sp_data = ba_buf.AsSpan();
    for (int i = 0; i < ba_buf.Length; i  = i_k_size)
    {
        UInt64 k = BitConverter.ToUInt64(sp_data.Slice(i, i_k_size));
    }
 }

My efforts to integrate a pointer with conversion - made performance worse. Can a pointer be used to maki it faster with span?

Below is the benchmark: pointer 2 array is 2x faster

Actually I want this code to be used instead of BitConverter:

public static int l_1gb = 1073741824;
static unsafe void Main(string[] args)
{
    Random rnd = new Random();
    Stopwatch sw1 = new();
    sw1.Start();
    byte[] k = new byte[8];

    fixed (byte* a2rr = &k[0])
    {
        for (int i = 0; i < 1000000000; i  )
        {
            rnd.NextBytes(k);
            //UInt64 p1 = BitConverter.ToUInt64(k); 
            //time: 10203.824
            //time: 10508.981
            //time: 10246.784
            //time: 10285.889

            //UInt64* uint64ptr = (UInt64*)a2rr;
            //x2 performance !
            UInt64 p2 = *(UInt64*)a2rr;

            //time: 4609.814
            //time: 4588.157
            //time: 4634.494
        }
    }
    Console.WriteLine($"time: {Math.Round(sw1.Elapsed.TotalMilliseconds, 3)}");
}

CodePudding user response:

Assuming ba_buf is a byte[], a very easy and efficient way to run your loop is as such:

foreach(var value in MemoryMarshal.Cast<byte, ulong>(ba_buf))
   // work with value here 

If you need to finesse the buffer (for example, to cut off parts of it), use AsSpan(start, count) on it first.

CodePudding user response:

You can optimise this quite a lot by initialising some spans outside the reading loop and then read directly into a Span<byte> and access the data via a Span<ulong> like so:

int buf_bytes = sizeof(ulong) * 1024; // Or whatever buffer size you need.
var ba_buf    = new byte[buf_bytes];
var span_buf  = ba_buf.AsSpan();
var data_span = MemoryMarshal.Cast<byte, ulong>(span_buf);

while (true)
{
    int count = fs.Read(span_buf) / sizeof(ulong);

    if (count == 0)
        break;

    for (int i = 0; i < count; i  )
    {
        // Do something with data_span[i]

        Console.WriteLine(data_span[i]); // Put your own processing here.
    }
}

This avoids memory allocation as much as possible. It terminates the reading loop when it runs out of data, and if the number of bytes returned is not a multiple of sizeof(ulong) it ignores the extra bytes.

It will always read all the available data, but if you want to terminate it earlier you can add code to do so.

As an example, consider this code which writes 2,000 ulong values to a file and then reads them back in using the code above:

using (var output = File.OpenWrite("x"))
{
    for (ulong i = 0; i < 2000;   i)
    {
        output.Write(BitConverter.GetBytes(i));
    }
}

using var fs = File.OpenRead("x");

int buf_bytes = sizeof(ulong) * 1024; // Or whatever buffer size you need.
var ba_buf    = new byte[buf_bytes];
var span_buf  = ba_buf.AsSpan();
var data_span = MemoryMarshal.Cast<byte, ulong>(span_buf);

while (true)
{
    int count = fs.Read(span_buf) / sizeof(ulong);

    if (count == 0)
        break;

    for (int i = 0; i < count; i  )
    {
        // Do something with data_span[i]

        Console.WriteLine(data_span[i]); // Put your own processing here.
    }
}
  • Related