Is it possible to access unmanaged and managed array elements indiscriminately? - c#

An example will make this clear:
unsafe void ProcessUnmanagedBuffer( float * buffer, int length )
{
int i;
for( i = 0; i < length; i++ )
{
buffer[ i ] = ...;
}
}
void ProcessManagedBuffer( float[] buffer, int length )
{
int i;
for( i = 0; i < length; i++ )
{
buffer[ i ] = ...;
}
}
// common interface to avoid duplicate code?
void ProcessBuffer( IndexableThing buffer, int length )
Of course, there can't be a common interface since float * isn't a class.
The obvious solution is to wrap and override the indexer, but for performance critical code this is far from ideal.
The less obvious one would be to pin the float[] and get a pointer to it. Better, but pinning incurs some overhead too.
I suspect there is no good solution - any ideas?

Related

can i use this same as with byte to other types values / objects?

As my goal is to out perform the List<T>
i am testing arrays and found few starting points to get on testing
i have tested this before trying to capture bitmaps off screen,
and tests proved the usage is suffice.
my question is what data types could use this Copy() code except for byte[]
say i want a data storage unit to take the advantage of unmanaged / unsafe
public unsafe struct NusT
{
public unsafe int vi;
public unsafe bool vb;
}
instead of populating a list
i initialise the struct as follows : 1)
NusT n;
n.vi= 90;
n.vb=true
i have tested this after testing the folowing: 2)
NusT n = new NusT(){vi=90, vb=true};
this test was after testing :3)
NusT n = new NusT("90", true);
i think both last had same results but the first one is blazing fast, as i do not create an object so
NusT n-> instructions- 1
n.vi=90 -> instructions- 1
n.vb=true -> instructions- 1
now i minimized what i could and this started at the begining with a class:
whitch was even worse than 2 & 3 above as it also uses properties
class bigAndSlow
{
public int a { get; private set;}
public bool b { get; private set;}
public string c { get; private set;}
public bigAndSlow(int .. ,boo .. , string.. )
{
initialise ...
}
}
so now when the final decision is
public unsafe struct NusT
{
public unsafe int vi;
public unsafe bool vb;
}
how can i implement this blazingly fast data unit to use Copy() on
NusT[] NustyArr;
static unsafe void Copy(byte[] src, int srcIndex,
byte[] dst, int dstIndex, int count)
{
if (src == null || srcIndex < 0 ||
dst == null || dstIndex < 0 || count < 0)
{
throw new ArgumentException();
}
int srcLen = src.Length;
int dstLen = dst.Length;
if (srcLen - srcIndex < count ||
dstLen - dstIndex < count)
{
throw new ArgumentException();
}
// The following fixed statement pins the location of
// the src and dst objects in memory so that they will
// not be moved by garbage collection.
fixed (byte* pSrc = src, pDst = dst)
{
byte* ps = pSrc;
byte* pd = pDst;
// Loop over the count in blocks of 4 bytes, copying an
// integer (4 bytes) at a time:
for (int n = 0; n < count / 4; n++)
{
*((int*)pd) = *((int*)ps);
pd += 4;
ps += 4;
}
// Complete the copy by moving any bytes that weren't
// moved in blocks of 4:
for (int n = 0; n < count % 4; n++)
{
*pd = *ps;
pd++;
ps++;
}
}
}
static void Main(string[] args)
{
byte[] a = new byte[100];
byte[] b = new byte[100];
for (int i = 0; i < 100; ++i)
a[i] = (byte)i;
Copy(a, 0, b, 0, 100);
Console.WriteLine("The first 10 elements are:");
for (int i = 0; i < 10; ++i)
Console.Write(b[i] + " ");
Console.WriteLine("\n");
}
Yes, you can do this with any blittable type. The blittable types are primitive types (integer and float types, but not bool), one-dimensional arrays of blittable types and structures containing fields of blittable types only.
The structure NusT is not blittable because it contains bool field. Just change it to byte and you will get a blittable structure for which you can obtain a pointer.
Here is the code that works for any type:
static unsafe void UnsafeCopy<T>(T[] src, int srcIndex, T[] dst, int dstIndex, int count) where T : struct
{
if (src == null || srcIndex < 0 || dst == null || dstIndex < 0 || count < 0 || srcIndex + count > src.Length || dstIndex + count > dst.Length)
{
throw new ArgumentException();
}
int elem_size = Marshal.SizeOf(typeof(T));
GCHandle gch1 = GCHandle.Alloc(src, GCHandleType.Pinned);
GCHandle gch2 = GCHandle.Alloc(dst, GCHandleType.Pinned);
byte* ps = (byte*)gch1.AddrOfPinnedObject().ToPointer() + srcIndex * elem_size;
byte* pd = (byte*)gch2.AddrOfPinnedObject().ToPointer() + dstIndex * elem_size;
int len = count * elem_size;
try
{
// Loop over the count in blocks of 4 bytes, copying an
// integer (4 bytes) at a time:
for (int n = 0; n < len / 4; n++)
{
*((int*)pd) = *((int*)ps);
pd += 4;
ps += 4;
}
// Complete the copy by moving any bytes that weren't
// moved in blocks of 4:
for (int n = 0; n < len % 4; n++)
{
*pd = *ps;
pd++;
ps++;
}
}
finally
{
gch1.Free();
gch2.Free();
}
}
But I strongly advice you to use Array.Copy. It is already the most efficient way to copy arrays. See the benchmarks of copying array of 1M elements below:
byte[] Array.Copy: 57,491 us
byte[] FastCopy: 138,198 us
byte[] JustCopy: 792,399 us
byte[] UnsafeCopy: 138,575 us
byte[] MemCpy: 57,667 us
NusT[] Array.Copy: 1,197 ms
NusT[] JustCopy: 1,843 ms
NusT[] UnsafeCopy: 1,550 ms
NusT[] MemCpy: 1,208 ms
FastCopy is your copy function, UnsafeCopy is my templated function, JustCopy is a simple implementation for (int i = 0; i < src.Length; i++) dst[i] = src[i];. MemCpy is PInvoke call of msvcrt memcpy function.
The verdict is: using pointers in C# for performance improvement is a bad practice. JIT does not optimize the unsafe code. The best solution is to move performance critical code to native DLLs.

Initialize a collection of byte* in C#

My unsafe method accepts a collection byte[]s. All of these byte[]s are of the same size.
I need to iterate over them all, searching for certain patterns. The search is inherently reinterpret-cast style: at each offset, I need to consider a value as if it were a float, a double, a short, an int, etc. So getting a byte* for each input byte[] and incrementing it on each iteration seems like a natural approach.
Unfortunately I can't find any way to create a collection of byte* - or, more specifically, to initialize it from a collection of arrays. Any ideas?
Here's a somewhat contrived version of the task:
static unsafe void SearchIteration(List<byte[]> arrays, byte[] resultArr)
{
fixed (byte* resultFix = resultArr)
{
byte* resultPtr = resultFix;
byte*[] pointers = new byte*[arrays.Count];
<some code to fix all the arrays and store the pointers in "pointers">
int remaining = resultArr.Length;
while (remaining > 0)
{
<look at resultPtr and each of the pointers and update *resultPtr>
remaining--;
for (int i = 0; i < pointers.Length; i++)
pointers[i]++;
}
}
}
Essentially the question is how to initialize pointers with the addresses of arrays, while pinning the arrays so that GC doesn't move them.
use GCHandle.Alloc() from System.Runtime.InteropServices:
var handles = new GCHandle[arrays.Count];
byte*[] pointers = new byte*[arrays.Count];
for(int i = 0; i < arrays.Count; ++i)
{
handles[i] = GCHandle.Alloc(arrays[i], GCHandleType.Pinned);
pointers[i] = (byte*)handles[i].AddrOfPinnedObject();
}
try
{
/* process pointers */
}
finally
{
for(int i = 0; i < arrays.Count; ++i)
{
handles[i].Free();
}
}

c# Wrapper to native c++ code, wrapping a parameter which is a pointer to an array

I have the following simple DLL in c++ un-managed code;
extern "C" __declspec(dllexport) void ArrayMultiplier(float (*pointerArray)[3], int scalar, int length);
void ArrayMultiplier(float (*pointerArray)[3], int scalar, int length)
{
for (int i = 0 ; i < length ; length++)
{
for (int j = 0; j < 3; j++)
{
pointerArray[i][j] = pointerArray[i][j] * scalar;
}
}
}
I have tried writing the following wrapper function for the above in c#:
[DllImport("sample.dll")]
public static extern void ArrayMultiplier(ref float elements, int scalar, int length);
where elements is a 2 dimentional 3x3 array:
public float[][] elements =
{
new float[] {2,5,3},
new float [] {4,8,6},
new float [] {5,28,3}
};
The code given above compiles, but the program crashes when the wrapper function is called:
Wrapper.ArrayMultiplier(ref elements, scalar, length);
Please help me here, and tell me whats wrong with the code above, or how a wrapper can be written for a simple c++ function:
void SimpleFunction(float (*pointerToArray)[3]);
Thank you all in advance
There are a few ways to do this.
The unsafe route, which works well with 2D arrays (that you have):
[DllImport("fastprocessing.dll", EntryPoint = "MyFunc")]
public static extern void MyFuncViaDLL(int inPtr, int outPtr, int inSize1, int size2, int param);
called via
private unsafe float[] MyFunc(float[] inData, int inSize1, int inSize2, int param1, int param2) {
float[] theOutData = new float[inChannelData.Length];
fixed (float* inBufferPtr = &inChannelData[0]) {
fixed (float* outBufferPtr = &theOutData[0]) {
MyFuncViaDLL((int)inBufferPtr, (int)outBufferPtr, inSize1, inSize2, param);
}
}
return theOutData;
}
That will work in an unsafe way, but you'd need to change your input arrays into 1D arrays. I think that's a better idea anyway, but that's just the way that I think.
If you want to be safe about it, add another parameter that is the size of the array itself, and then do some marshalling. Again, though, you'll need to go into 1D arrays:
Instead, you want to do some marshalling, like so:
[DllImport("fastprocessing.dll", EntryPoint = "MyFunc")]
public static extern void MyFuncViaDLL([MarshalAs(UnmanagedType.LPArray)]float[] inPtr, int size1, int size2, int totalSize, int param2);
Then just call the function directly:
MyFuncViaDLL(array, size1, size2, size1*size2, param1, param2);
Your C++ would then change to:
void ArrayMultiplier(float *pointerArray, int inSize1, int inSize2, int inTotalSize, int scalar)
{
int i, j, index;
for (i = 0 ; i < size1; i++)//note that length++ would be very very wrong here
{
for (j = 0; j < size2; j++)
{
index = i*size2 + j;
if(index >= inTotalSize) { return; } //avoid walking off the end
pointerArray[i*size2 + j] *= scalar;
}
}
}
If you want, you can add in the check against total length to ensure that you don't walk off the end, but that'll be a pretty big speed hit (enough to want to not use C++), as if statements aren't free.
Having done all of that, however, I have to ask-- why not just do this directly in C#, and save yourself the hassle of interop services like marshalling? C++ tends to be faster for complicated things, but for a quick array walk, I've seen C# behave pretty well. It can be pretty quick in C# too, once it's a 1D array:
int i;
for (i = 0; i < array.length; i++){
array[i] *= scalar;
}

Fastest way of reading and writing binary

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[] target, int index, int value);
int Get(byte[] source, int index);
These functions are needed for signed and unsigned short, int and long in big and little endian order.
Here are some examples i've made, but i need a evaluation about the advantages and disadvantages:
first method is using Marshal to write the value into the memory of the byte[], the second is using plain pointers to accomplish this and the third uses BitConverter and BlockCopy to do this
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);
}
And here are the Read/Get methods:
the first is using Marshal to read the value from the byte[], the second is using plain pointers and the third is using BitConverter again:
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);
}
boundary checking needs to be done but isn't part of my question yet...
I would be pleased if someone can tell what would be the best and fastest way in this case or give me some other solutions to work on. A generic solution would be preferable
I Just did some performance testing, here are the results:
Set Marshal: 45 ms, Set Pointer: 48 ms, Set BitConverter: 71 ms
Get Marshal: 45 ms, Get Pointer: 26 ms, Get BitConverter: 30 ms
it seems that using pointers is the fast way, but i think Marshal and BitConverter do some internal checking... can someone verify this?
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);
}
}
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");
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.
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.

What is the fastest way to convert a float[] to a byte[]?

I would like to get a byte[] from a float[] as quickly as possible, without looping through the whole array (via a cast, probably). Unsafe code is fine. Thanks!
I am looking for a byte array 4 time longer than the float array (the dimension of the byte array will be 4 times that of the float array, since each float is composed of 4 bytes). I'll pass this to a BinaryWriter.
EDIT:
To those critics screaming "premature optimization":
I have benchmarked this using ANTS profiler before I optimized. There was a significant speed increase because the file has a write-through cache and the float array is exactly sized to match the sector size on the disk. The binary writer wraps a file handle created with pinvoke'd win32 API. The optimization occurs since this lessens the number of function calls.
And, with regard to memory, this application creates massive caches which use plenty of memory. I can allocate the byte buffer once and re-use it many times--the double memory usage in this particular instance amounts to a roundoff error in the overall memory consumption of the app.
So I guess the lesson here is not to make premature assumptions ;)
There is a dirty fast (not unsafe code) way of doing this:
[StructLayout(LayoutKind.Explicit)]
struct BytetoDoubleConverter
{
[FieldOffset(0)]
public Byte[] Bytes;
[FieldOffset(0)]
public Double[] Doubles;
}
//...
static Double Sum(byte[] data)
{
BytetoDoubleConverter convert = new BytetoDoubleConverter { Bytes = data };
Double result = 0;
for (int i = 0; i < convert.Doubles.Length / sizeof(Double); i++)
{
result += convert.Doubles[i];
}
return result;
}
This will work, but I'm not sure of the support on Mono or newer versions of the CLR. The only strange thing is that the array.Length is the bytes length. This can be explained because it looks at the array length stored with the array, and because this array was a byte array that length will still be in byte length. The indexer does think about the Double being eight bytes large so no calculation is necessary there.
I've looked for it some more, and it's actually described on MSDN, How to: Create a C/C++ Union by Using Attributes (C# and Visual Basic), so chances are this will be supported in future versions. I am not sure about Mono though.
Premature optimization is the root of all evil! #Vlad's suggestion to iterate over each float is a much more reasonable answer than switching to a byte[]. Take the following table of runtimes for increasing numbers of elements (average of 50 runs):
Elements BinaryWriter(float) BinaryWriter(byte[])
-----------------------------------------------------------
10 8.72ms 8.76ms
100 8.94ms 8.82ms
1000 10.32ms 9.06ms
10000 32.56ms 10.34ms
100000 213.28ms 739.90ms
1000000 1955.92ms 10668.56ms
There is little difference between the two for small numbers of elements. Once you get into the huge number of elements range, the time spent copying from the float[] to the byte[] far outweighs the benefits.
So go with what is simple:
float[] data = new float[...];
foreach(float value in data)
{
writer.Write(value);
}
There is a way which avoids memory copying and iteration.
You can use a really ugly hack to temporary change your array to another type using (unsafe) memory manipulation.
I tested this hack in both 32 & 64 bit OS, so it should be portable.
The source + sample usage is maintained at https://gist.github.com/1050703 , but for your convenience I'll paste it here as well:
public static unsafe class FastArraySerializer
{
[StructLayout(LayoutKind.Explicit)]
private struct Union
{
[FieldOffset(0)] public byte[] bytes;
[FieldOffset(0)] public float[] floats;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct ArrayHeader
{
public UIntPtr type;
public UIntPtr length;
}
private static readonly UIntPtr BYTE_ARRAY_TYPE;
private static readonly UIntPtr FLOAT_ARRAY_TYPE;
static FastArraySerializer()
{
fixed (void* pBytes = new byte[1])
fixed (void* pFloats = new float[1])
{
BYTE_ARRAY_TYPE = getHeader(pBytes)->type;
FLOAT_ARRAY_TYPE = getHeader(pFloats)->type;
}
}
public static void AsByteArray(this float[] floats, Action<byte[]> action)
{
if (floats.handleNullOrEmptyArray(action))
return;
var union = new Union {floats = floats};
union.floats.toByteArray();
try
{
action(union.bytes);
}
finally
{
union.bytes.toFloatArray();
}
}
public static void AsFloatArray(this byte[] bytes, Action<float[]> action)
{
if (bytes.handleNullOrEmptyArray(action))
return;
var union = new Union {bytes = bytes};
union.bytes.toFloatArray();
try
{
action(union.floats);
}
finally
{
union.floats.toByteArray();
}
}
public static bool handleNullOrEmptyArray<TSrc,TDst>(this TSrc[] array, Action<TDst[]> action)
{
if (array == null)
{
action(null);
return true;
}
if (array.Length == 0)
{
action(new TDst[0]);
return true;
}
return false;
}
private static ArrayHeader* getHeader(void* pBytes)
{
return (ArrayHeader*)pBytes - 1;
}
private static void toFloatArray(this byte[] bytes)
{
fixed (void* pArray = bytes)
{
var pHeader = getHeader(pArray);
pHeader->type = FLOAT_ARRAY_TYPE;
pHeader->length = (UIntPtr)(bytes.Length / sizeof(float));
}
}
private static void toByteArray(this float[] floats)
{
fixed(void* pArray = floats)
{
var pHeader = getHeader(pArray);
pHeader->type = BYTE_ARRAY_TYPE;
pHeader->length = (UIntPtr)(floats.Length * sizeof(float));
}
}
}
And the usage is:
var floats = new float[] {0, 1, 0, 1};
floats.AsByteArray(bytes =>
{
foreach (var b in bytes)
{
Console.WriteLine(b);
}
});
If you do not want any conversion to happen, I would suggest Buffer.BlockCopy().
public static void BlockCopy(
Array src,
int srcOffset,
Array dst,
int dstOffset,
int count
)
For example:
float[] floatArray = new float[1000];
byte[] byteArray = new byte[floatArray.Length * 4];
Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);
You're better-off letting the BinaryWriter do this for you. There's going to be iteration over your entire set of data regardless of which method you use, so there's no point in playing with bytes.
Although you can obtain a byte* pointer using unsafe and fixed, you cannot convert the byte* to byte[] in order for the writer to accept it as a parameter without performing data copy. Which you do not want to do as it will double your memory footprint and add an extra iteration over the inevitable iteration that needs to be performed in order to output the data to disk.
Instead, you are still better off iterating over the array of floats and writing each float to the writer individually, using the Write(double) method. It will still be fast because of buffering inside the writer. See sixlettervariables's numbers.
Using the new Span<> in .Net Core 2.1 or later...
byte[] byteArray2 = MemoryMarshal.Cast<float, byte>(floatArray).ToArray();
Or, if Span can be used instead, then a direct reinterpret cast can be done: (very fast - zero copying)
Span<byte> byteArray3 = MemoryMarshal.Cast<float, byte>(floatArray);
// with span we can get a byte, set a byte, iterate, and more.
byte someByte = byteSpan[2];
byteSpan[2] = 33;
I did some crude benchmarks. The time taken for each is in the comments. [release/no debugger/x64]
float[] floatArray = new float[100];
for (int i = 0; i < 100; i++) floatArray[i] = i * 7.7777f;
Stopwatch start = Stopwatch.StartNew();
for (int j = 0; j < 100; j++)
{
start.Restart();
for (int k = 0; k < 1000; k++)
{
Span<byte> byteSpan = MemoryMarshal.Cast<float, byte>(floatArray);
}
long timeTaken1 = start.ElapsedTicks; ////// 0 ticks //////
start.Restart();
for (int k = 0; k < 1000; k++)
{
byte[] byteArray2 = MemoryMarshal.Cast<float, byte>(floatArray).ToArray();
}
long timeTaken2 = start.ElapsedTicks; ////// 26 ticks //////
start.Restart();
for (int k = 0; k < 1000; k++)
{
byte[] byteArray = new byte[sizeof(float) * floatArray.Length];
for (int i = 0; i < floatArray.Length; i++)
BitConverter.GetBytes(floatArray[i]).CopyTo(byteArray, i * sizeof(float));
}
long timeTaken3 = start.ElapsedTicks; ////// 1310 ticks //////
start.Restart();
for (int k = 0; k < 1000; k++)
{
byte[] byteArray = new byte[sizeof(float) * floatArray.Length];
Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);
}
long timeTaken4 = start.ElapsedTicks; ////// 33 ticks //////
start.Restart();
for (int k = 0; k < 1000; k++)
{
byte[] byteArray = new byte[sizeof(float) * floatArray.Length];
MemoryStream memStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memStream);
foreach (float value in floatArray)
writer.Write(value);
writer.Close();
}
long timeTaken5 = start.ElapsedTicks; ////// 1080 ticks //////
Console.WriteLine($"{timeTaken1/10,6} {timeTaken2 / 10,6} {timeTaken3 / 10,6} {timeTaken4 / 10,6} {timeTaken5 / 10,6} ");
}
We have a class called LudicrousSpeedSerialization and it contains the following unsafe method:
static public byte[] ConvertFloatsToBytes(float[] data)
{
int n = data.Length;
byte[] ret = new byte[n * sizeof(float)];
if (n == 0) return ret;
unsafe
{
fixed (byte* pByteArray = &ret[0])
{
float* pFloatArray = (float*)pByteArray;
for (int i = 0; i < n; i++)
{
pFloatArray[i] = data[i];
}
}
}
return ret;
}
Although it basically does do a for loop behind the scenes, it does do the job in one line
byte[] byteArray = floatArray.Select(
f=>System.BitConverter.GetBytes(f)).Aggregate(
(bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });

Categories