3

I'm in need to make a byte[] -> T extension method and need it to be fast (no need for it being pretty)

This function will be called 100's of 1000's of times in very short succession in an absolute performance critical environment.

We're currently optimizing on "ticks" level, every tick translates to a couple milliseconds higher in the callstack, thus the need of raw speed over maintainability (not how I like to design software, but the reasoning behind this is out of scope).

Consider the following code, it's clean and maintainable, but it's relatively slow (probably due to boxing and unboxing), Can this be optimized to be faster?

public static T ConvertTo<T>(this byte[] bytes, int offset = 0)
{
    var type = typeof(T);
    if (type == typeof(sbyte)) return bytes[offset].As<T>();
    if (type == typeof(byte)) return bytes[offset].As<T>();
    if (type == typeof(short)) return BitConverter.ToInt16(bytes, offset).As<T>();
    if (type == typeof(ushort)) return BitConverter.ToUInt32(bytes, offset).As<T>();
    if (type == typeof(int)) return BitConverter.ToInt32(bytes, offset).As<T>();
    if (type == typeof(uint)) return BitConverter.ToUInt32(bytes, offset).As<T>();
    if (type == typeof(long)) return BitConverter.ToInt64(bytes, offset).As<T>();
    if (type == typeof(ulong)) return BitConverter.ToUInt64(bytes, offset).As<T>();

    throw new NotImplementedException();
}

public static T As<T>(this object o)
{
    return (T)o;
}
Riaan Walters
  • 2,587
  • 2
  • 18
  • 30
  • I *think* that perhaps this question is better suited on [CodeReview](http://codereview.stackexchange.com), but my first step to optimize the run time performance on this would be to measure where the hotspots are. Make sure to include all the code that is using it as well, you might even find that the biggest gain in run time performance would be somewhere else completely. Also, do you really *need* it to be generic, could you do with a `ConvertToByte`, `ConvertToInt32`, etc.? I also question the LINQ code there, wouldn't the first two be just: `(T)(sbyte)bytes[offset]`? – Lasse V. Karlsen Aug 22 '15 at 09:29
  • If this question could be moved to CodeReview, it would be appreciated, I suppose it could be written as Extension-per-type instead of generic, which would get rid of all [un]boxing, would that make a major difference – Riaan Walters Aug 22 '15 at 09:31
  • Additionally, embed the cast into the method itself. Get rid of the `As` method. – Lasse V. Karlsen Aug 22 '15 at 09:31
  • The only way to know what would make a major difference is to first measure your code using a profiler, then figure out which parts along the hotspot you can change, make a guestimate on how much improvement such a change could potentially give, if enough then make the change, and then profile again, it's basically the same advice you should/would get for any performance question. – Lasse V. Karlsen Aug 22 '15 at 09:32
  • `bytes.Skip(offset).Take(1).Single()` why?? just index it as an array – harold Aug 22 '15 at 13:30

2 Answers2

6

I also needed this and I have found that conversion to these primitive types can be done faster using pointers and unsafe code. Like:

public unsafe int ToInt(byte[] bytes, int offset)
{
    fixed(byte* ptr = bytes)
    {
         return *(int*)(ptr + offset);
    }
}

But C# unfortunately does not support generic pointer types, so I had to write this peace of code in IL whitch doesn't care much about generic constraints:

.method public hidebysig static !!T  Read<T>(void* ptr) cil managed
{
    .maxstack  8
    nop         
    ldarg.0     
    ldobj !!T
    ret 
}

It's not pretty but it seems to work in my case. Be careful when using this method - you can pass any type as argument and I don't know what it will do.

You can find the entire il file on github or download compiled assembly https://github.com/exyi/RaptorDB-Document/blob/master/GenericPointerHelpers/GenericPointerHelpers.dll?raw=true I have few more helpers there.

EDIT: I have forgotten to write how to use this method. Assuming you have compiled the method in GenericPointerHelper class like me you can implement your ConvertTo method bit similar to my ToInt:

public unsafe T ConvertTo<T>(byte[] bytes, int offset)
    where T: struct // not needed to work, just to eliminate some errors
{
    fixed(byte* ptr = bytes)
    {
         return GenericPointerHelper.Read<T>(ptr + offset);
    }
}
exyi
  • 452
  • 3
  • 12
  • 2
    Very interesting, will have to dig into this, thankyou, I will mark this as the answer in a couple days, unless there is a more practical answer – Riaan Walters Aug 22 '15 at 16:19
1

Nothing can beat the performance of exyi's provided solution. I have no problems with unsafe code, but something in the corresponding BitConverter class methods (http://referencesource.microsoft.com/#mscorlib/system/bitconverter.cs) really concerns me - they do alignment checks and use different implementation for unaligned cases. Below is a much safer pure C# solution - it's probably a little bit slower (but that should be measured, you never know), but should be much faster than the original. As a bonus, I've added explicit name methods (similar to those in BitConverter) additional to using bytes.To<int>() (which is ok, but kind of weird) you can use more convenient bytes.ToInt32() (which should be faster than generic method).

public static class BitConverter<T> where T : struct
{
    public static readonly Func<byte[], int, T> To = GetFunc();
    static Func<byte[], int, T> GetFunc()
    {
        var type = typeof(T);
        if (type == typeof(bool)) return Cast(BitConverter.ToBoolean);
        if (type == typeof(sbyte)) return Cast(Extensions.ToSByte);
        if (type == typeof(byte)) return Cast(Extensions.ToByte);
        if (type == typeof(short)) return Cast(BitConverter.ToInt16);
        if (type == typeof(ushort)) return Cast(BitConverter.ToUInt16);
        if (type == typeof(int)) return Cast(BitConverter.ToInt32);
        if (type == typeof(uint)) return Cast(BitConverter.ToUInt32);
        if (type == typeof(long)) return Cast(BitConverter.ToInt64);
        if (type == typeof(ulong)) return Cast(BitConverter.ToUInt64);
        if (type == typeof(float)) return Cast(BitConverter.ToSingle);
        if (type == typeof(double)) return Cast(BitConverter.ToDouble);
        throw new NotSupportedException();
    }
    static Func<byte[], int, T> Cast<U>(Func<byte[], int, U> func) { return (Func<byte[], int, T>)(object)func; }
}

public static class Extensions
{
    public static bool ToBoolean(this byte[] bytes, int offset = 0) { return BitConverter.ToBoolean(bytes, offset); }
    public static sbyte ToSByte(this byte[] bytes, int offset = 0) { return unchecked((sbyte)bytes[offset]); }
    public static byte ToByte(this byte[] bytes, int offset = 0) { return bytes[offset]; }
    public static short ToInt16(this byte[] bytes, int offset = 0) { return BitConverter.ToInt16(bytes, offset); }
    public static ushort ToUInt16(this byte[] bytes, int offset = 0) { return BitConverter.ToUInt16(bytes, offset); }
    public static int ToInt32(this byte[] bytes, int offset = 0) { return BitConverter.ToInt32(bytes, offset); }
    public static uint ToUInt32(this byte[] bytes, int offset = 0) { return BitConverter.ToUInt32(bytes, offset); }
    public static long ToInt64(this byte[] bytes, int offset = 0) { return BitConverter.ToInt64(bytes, offset); }
    public static ulong ToUInt64(this byte[] bytes, int offset = 0) { return BitConverter.ToUInt64(bytes, offset); }
    public static float ToSingle(this byte[] bytes, int offset = 0) { return BitConverter.ToSingle(bytes, offset); }
    public static double ToDouble(this byte[] bytes, int offset = 0) { return BitConverter.ToDouble(bytes, offset); }
    public static T To<T>(this byte[] bytes, int offset = 0) where T : struct { return BitConverter<T>.To(bytes, offset); }
}
Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343