5

I'm trying to achieve a static cast like coercion that doesn't result in copying of any data. A naive static cast does not work

let pkt = byte_buffer :> PktHeader

FS0193: Type constraint mismatch. The type byte[] is not compatible with type PktHeader The type 'byte[]' is not compatible with the type 'PktHeader' (FS0193) (program)

where the packet is initially held in a byte array because of the way System.Net.Sockets.Socket.Receive() is defined. The low level packet struct is defined something like this

[<Struct; StructLayout(LayoutKind.Explicit)>]
type PktHeader =
  [<FieldOffset(0)>] val mutable field1: uint16
  [<FieldOffset(2)>] val mutable field2: uint16
  [<FieldOffset(4)>] val mutable field3: uint32
   .... many more fields follow ....

Efficiency is important in this real world scenario because wasteful copying of data could rule out F# as an implementation language. How do you achieve zero copy efficiencies in this scenario?

EDIT on Nov 29 my question was predicated on the implicit belief that a C/C++/C# style unsafe static cast is a useful construct, as if this is self evident. However, on 2nd thought this kind of cast is not idiomatic in F# since it is inherently an imperative language technique fraught with peril. For this reason I've accepted the answer by V.B. where SBE/FlatBuffers data access is promulgated as best practice.

  • just to make sure: have you tried `Marshal.Copy` and stuff (basically what you would do in C# ... I never did it but IMO it should work just the same) – Random Dev Nov 26 '15 at 14:21
  • see [this question](https://stackoverflow.com/questions/6335153/casting-a-byte-array-to-a-managed-structure) for quite a few techniques – Random Dev Nov 26 '15 at 14:22
  • @Carsten I'm not interop'ing with external code, this is all pure .NET code found in standard language libraries. Not seeing how Marshal.Copy helps. –  Nov 26 '15 at 14:31
  • see my answer - the question I linked has quite a few approaches some with `Marshal.Copy` too - and you are interop'ing with the memory-layout ;) (no really there is quite powerful low-level magic hidden in `Marshal` and co) – Random Dev Nov 26 '15 at 14:36
  • One way is to interop with C# glue code where the _fixed_ keyword is available in an _unsafe_ context. –  Nov 26 '15 at 15:20
  • 3
    There's simply no way this could be possible with .NET, because an object (even of primitive type) is never just a simple little block of bytes, it only _marshals_ that way. (How else could reflection work?) This could be done in a single copy at best, but zero-copy with .NET is wishful thinking. – ildjarn Nov 27 '15 at 00:50
  • here is a C# example I found http://lists.ximian.com/pipermail/mono-devel-list/2005-May/012005.html, see the unsafe cast for yourself. This is exactly what I want but seems to be missing from F# at this time –  Nov 27 '15 at 02:34
  • if you have a solution in C# and cannot simply port it to F# than put it in a lib and reference it ... it's all in the CLR in the end – Random Dev Nov 27 '15 at 18:02
  • that's what I ended up doing - a minimal parameterized C# static method in a static class that takes a Func param and some state and "trampolines" back into the F# code that called it –  Nov 27 '15 at 18:28
  • 2
    but make sure to run performance tests - maybe the call-overhead is more expensive than a simple marshaled copy (depends of course on the size of your structure, ...) – Random Dev Nov 27 '15 at 19:09

2 Answers2

2

A pure F# approach for conversion

let convertByteArrayToStruct<'a when 'a : struct> (byteArr : byte[]) = 
    let handle = GCHandle.Alloc(byteArr, GCHandleType.Pinned)
    let structure = Marshal.PtrToStructure (handle.AddrOfPinnedObject(), typeof<'a>)
    handle.Free()
    structure :?> 'a

This is a minimum example but I'd recommend introducing some checks on the length of the byte array because, as it's written there, it will produce undefined results if you give it a byte array which is too short. You could check against Marshall.SizeOf(typeof<'a>).


There is no pure F# solution to do a less safe conversion than this (and this is already an approach prone to runtime failure). Alternative options could include interop with C# to use unsafe and fixed to do the conversion.

Ultimately though, you are asking for a way to subvert the F# type system which is not really what the language is designed for. One of the principle advantages of F# is the power of the type system and it's ability to help you produce statically verifiable code.

TheInnerLight
  • 12,034
  • 1
  • 29
  • 52
  • I guess you cannot see deleted answers yet ... the OP wants a zero-copy solution and is not happy about `Marshal.PtrToStructure` – Random Dev Nov 26 '15 at 18:04
  • @Carsten I think I can solve the zero-copy problem, not sure what the alternative to `Marshal.PtrToStructure` is though. Do you know what they're not happy about specifically? – TheInnerLight Nov 26 '15 at 18:17
  • this was the last comment to my answer before I deleted it: "I'm open to any C# or F# technique that doesn't copy the whole byte array, a tiny overhead is ok but not a full copy. Still a F# snippet would be ideal" – Random Dev Nov 26 '15 at 19:19
  • sorry @TheInnerLight, your answer doesn't solve the problem, cannot accept it –  Nov 27 '15 at 15:42
  • @snik197 Can you explain what exactly it is you need that this answer doesn't do? – TheInnerLight Nov 27 '15 at 16:05
  • your answer is doing a full copy of the array via method Marshal.PtrToStructure which is **not a zero copy** operation. In C, C++ and C# you can apply unsafe static casts from 1 data structure to another, and dereference fields from that point on at your own risk. Risks include alignment faults, overrunning bounds, etc. When dealing with large arrays it is imperative to avoid wasteful data copies. –  Nov 27 '15 at 16:45
  • @snik197 I've updated my answer. Ultimately, I think you are trying to do something that F# isn't designed for. Out of interest, do you have strong evidence that this is really a performance bottleneck? If it is, by all mean, make a C# function to perform the conversion but otherwise I think you'll be giving up a lot of safety in exchange for minimal gain. – TheInnerLight Nov 27 '15 at 18:08
  • I'll wait a little longer to be sure there is "no pure F# solution" and then may accept your answer. Currently I am trying to showcase the power of F# to disbelievers and I cannot afford getting cheap shots w.r.t. efficiency. –  Nov 27 '15 at 18:34
  • 1
    @snik197 I sympathise with the problem but I think ultimately you'll need to sell it based on the strengths of F# rather than the strengths of languages like C and C++. Most of the gains over C# that I've garnered from F# commercially have come from shorter development times, more opportunities for code re-use and more verifiable and reliable code rather than performance. I have however achieved occasional, but significant, performance gains over C# through the careful use of `inline`. – TheInnerLight Nov 27 '15 at 19:11
0

F# and very low-level performance optimizations are not best friends, but then... some smart people do magic even with Java, which doesn't have value types and real generic collections for them.

1) I am a big fan of a flyweight pattern lately. If you architecture allows for it, you could wrap a byte array and access struct members via offsets. A C# example here. SBE/FlatBuffers even have tools to generate a wrapper automatically from a definition.

2) If you could stay within unsafe context in C# to do the work, pointer casting is very easy and efficient. However, that requires pinning the byte array and keeping its handle for later release, or staying within fixed keyword. If you have many small ones without a pool, you could have problems with GC.

3) The third option is abusing .NET type system and cast a byte array with IL like this (this could be coded in F#, if you insist :) ):

static T UnsafeCast(object value) {
 ldarg.1 //load type object
 ret //return type T
}

I tried this option and even have a snippet somewhere if you need, but this approach makes me uncomfortable because I do not understand its consequences to GC. We have two objects backed by the same memory, what would happen when one of them is GCed? I was going to ask a new question on SO about this detail, will post it soon.


The last approach could be good for arrays of structs, but for a single struct it will box it or copy it anyway. Since structs are on the stack and passed by value, you will probably get better results just by casting a pointer to byte[] in unsafe C# or using Marshal.PtrToStructure as in another answer here, and then copy by value. Copying is not the worst thing, especially on the stack, but allocation of new objects and GC is the enemy, so you need byte arrays pooled and this will add much more to the overall performance than you struct casting issue.

But if your struct is very big, option 1 could still be better.

Community
  • 1
  • 1
V.B.
  • 6,236
  • 1
  • 33
  • 56