patterncsharpModerate
Performance Byte[] to Generic
Viewed 0 times
byteperformancegeneric
Problem
I'm in need to make a
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?
And yes it needs to be generic, sadly
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(this byte[] bytes, int offset = 0)
{
var type = typeof(T);
if (type == typeof(sbyte)) return bytes[offset].As();
if (type == typeof(byte)) return bytes[offset].As();
if (type == typeof(short)) return BitConverter.ToInt16(bytes, offset).As();
if (type == typeof(ushort)) return BitConverter.ToUInt32(bytes, offset).As();
if (type == typeof(int)) return BitConverter.ToInt32(bytes, offset).As();
if (type == typeof(uint)) return BitConverter.ToUInt32(bytes, offset).As();
if (type == typeof(long)) return BitConverter.ToInt64(bytes, offset).As();
if (type == typeof(ulong)) return BitConverter.ToUInt64(bytes, offset).As();
throw new NotImplementedException();
}
public static T As(this object o)
{
return (T)o;
}And yes it needs to be generic, sadly
Solution
First, you seem to have a minor typo on this line:
That should be:
Just as well, you have a bug in here that is a pretty big one (causes exception on any attempts to convert anything to
Should be:
If you really need speed, you should probably not use the
See this answer for a comparison.
You should also not use
Two improvements we can make immediately:
Performance Evaluation
The performance difference, when those changes are made is significant for certain types of
Now, to explain,
By removing
Also note that we returned the exact same values for all situations.
Just how bad is performance dragging because of the boxing?
Badly. If we consider another method,
Now that is with a method that only returns an
This leads us to be able to conclude that, due to the boxing of the generic types, and recasting them, we lose a lot of performance. (Around 500% or so right-off-the-bat.) The issues isn't the fact that you use generics, the issue is that you use boxing several times within, which creates extra overhead.
Using an
While it would make sense to think that this would be fairly quick, with no drawbacks, this is actually much slower than inline calls due to the overhead of having an additional method call. Replacing
In fact, upon further research, it seems that if you replace the
The performance difference between
Food for thought.
Code
Here's the code I used for conversions:
```
public static class Extensions
{
public static T ConvertTo1(this byte[] bytes, int offset = 0)
{
var type = typeof(T);
if (type == typeof(sbyte)) return (T)(object)((sbyte)bytes
if (type == typeof(ushort)) return BitConverter.ToUInt32(bytes, offset).As();That should be:
if (type == typeof(ushort)) return BitConverter.ToUInt16(bytes, offset).As();Just as well, you have a bug in here that is a pretty big one (causes exception on any attempts to convert anything to
sbyte with your method):if (type == typeof(sbyte)) return bytes[offset].As();Should be:
if (type == typeof(sbyte)) return ((sbyte)bytes[offset]).As();If you really need speed, you should probably not use the
BitConverter class in this situation. Use bitwise operators, as they are much faster.See this answer for a comparison.
You should also not use
.As either, and instead cast within the method using: (T)(object). This eliminates unnecessary stack overhead.Two improvements we can make immediately:
- Replace all the
BitConverterwork with bitwise work.
- Replace all the
.As()with(T)(object)casts instead.
Performance Evaluation
The performance difference, when those changes are made is significant for certain types of
T.ConvertTo1 on 50000000 rounds, time (in ms) taken: 3465
ConvertTo1 on 50000000 rounds, time (in ms) taken: 4217
ConvertTo1 on 50000000 rounds, time (in ms) taken: 5586
ConvertTo1 on 50000000 rounds, time (in ms) taken: 7665
ConvertTo2 on 50000000 rounds, time (in ms) taken: 4995
ConvertTo2 on 50000000 rounds, time (in ms) taken: 5775
ConvertTo2 on 50000000 rounds, time (in ms) taken: 6945
ConvertTo2 on 50000000 rounds, time (in ms) taken: 8492
ConvertToInt on 50000000 rounds, time (in ms) taken: 1092
Verifying results of conversions are same:
SByte test: 1, 1: True
Byte test: 1, 1: True
Short test: 10497, 10497: True
UShort test: 10497, 10497: True
Int test: 755378433, 755378433: True
UInt test: 755378433, 755378433: True
Long test: 4041804345027995905, 4041804345027995905: True
ULong test: 4041804345027995905, 4041804345027995905: TrueNow, to explain,
ConvertTo1 is the optimized method, ConvertTo2 is the original, and ConvertToInt is a strongly-typed method for converting directly to int.By removing
.As() and replacing with (T)(object), and replacing all the BitConverter work with bitwise work, we cut our work down to:- 69% original time for
byte
- 73% original time for
short
- 80% original time for
int
- 90% original time for
long
Also note that we returned the exact same values for all situations.
Just how bad is performance dragging because of the boxing?
Badly. If we consider another method,
ConvertTo3 which is strongly typed around an int parameter, we can come up with the following result:ConvertToInt on 50000000 rounds, time (in ms) taken: 1034Now that is with a method that only returns an
int value.This leads us to be able to conclude that, due to the boxing of the generic types, and recasting them, we lose a lot of performance. (Around 500% or so right-off-the-bat.) The issues isn't the fact that you use generics, the issue is that you use boxing several times within, which creates extra overhead.
Using an
.As() methodWhile it would make sense to think that this would be fairly quick, with no drawbacks, this is actually much slower than inline calls due to the overhead of having an additional method call. Replacing
.As() with (T)(object) right out of the gate gave us significant performance back. It may even be acceptable to only make that change and continue using BitConverter.In fact, upon further research, it seems that if you replace the
ConvertTo2 method I supplied with this variant:public static T ConvertTo2(this byte[] bytes, int offset = 0)
{
var type = typeof(T);
if (type == typeof(sbyte)) return (T)(object)((sbyte)bytes[offset]);
if (type == typeof(byte)) return (T)(object)bytes[offset];
if (type == typeof(short)) return (T)(object)BitConverter.ToInt16(bytes, offset);
if (type == typeof(ushort)) return (T)(object)BitConverter.ToUInt16(bytes, offset);
if (type == typeof(int)) return (T)(object)BitConverter.ToInt32(bytes, offset);
if (type == typeof(uint)) return (T)(object)BitConverter.ToUInt32(bytes, offset);
if (type == typeof(long)) return (T)(object)BitConverter.ToInt64(bytes, offset);
if (type == typeof(ulong)) return (T)(object)BitConverter.ToUInt64(bytes, offset);
throw new NotImplementedException();
}The performance difference between
ConvertTo1 and ConvertTo2 is very minimal. (Though, removing the BitConverter work still does seem to make it slightly faster, the difference is very insignificant at this level.)Food for thought.
Code
Here's the code I used for conversions:
```
public static class Extensions
{
public static T ConvertTo1(this byte[] bytes, int offset = 0)
{
var type = typeof(T);
if (type == typeof(sbyte)) return (T)(object)((sbyte)bytes
Code Snippets
if (type == typeof(ushort)) return BitConverter.ToUInt32(bytes, offset).As<T>();if (type == typeof(ushort)) return BitConverter.ToUInt16(bytes, offset).As<T>();if (type == typeof(sbyte)) return bytes[offset].As<T>();if (type == typeof(sbyte)) return ((sbyte)bytes[offset]).As<T>();ConvertTo1<byte> on 50000000 rounds, time (in ms) taken: 3465
ConvertTo1<short> on 50000000 rounds, time (in ms) taken: 4217
ConvertTo1<int> on 50000000 rounds, time (in ms) taken: 5586
ConvertTo1<long> on 50000000 rounds, time (in ms) taken: 7665
ConvertTo2<byte> on 50000000 rounds, time (in ms) taken: 4995
ConvertTo2<short> on 50000000 rounds, time (in ms) taken: 5775
ConvertTo2<int> on 50000000 rounds, time (in ms) taken: 6945
ConvertTo2<long> on 50000000 rounds, time (in ms) taken: 8492
ConvertToInt on 50000000 rounds, time (in ms) taken: 1092
Verifying results of conversions are same:
SByte test: 1, 1: True
Byte test: 1, 1: True
Short test: 10497, 10497: True
UShort test: 10497, 10497: True
Int test: 755378433, 755378433: True
UInt test: 755378433, 755378433: True
Long test: 4041804345027995905, 4041804345027995905: True
ULong test: 4041804345027995905, 4041804345027995905: TrueContext
StackExchange Code Review Q#101636, answer score: 15
Revisions (0)
No revisions yet.