Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What exactly is 'fixed' doing?

Tags:

c#

.net

I have some code that looks like this, in an unsafe context:

 ValidatePartialOperation(array, startingOffset, runLength);

 fixed (double* _op = array)
 {
     double* op = _op + startingOffset;
     callSomething(op, runLength);
 }

And I have that copy+pasted in a couple of different places. But I hate having that kind of validation and pointer arithmetic in multiple places, so I'd like to combine together the logic in to a single line that looks something like:

 double* op = preCall(array, startingOffset, runLength);
 callSomething(op, runLength);
 postCall(array);

Or even better:

 using (double* op = preCall(array, startingOffset, runLength))
 {
     callSomething(op, runLength);
 }

But whatever happens I can't afford to lose performance from the 'fixed' version.

My plan right now is to mimic what the fixed statement is doing, but I don't actually know what that is. Presumably some try-catch block with a pinning operation?

like image 755
Jay Lemmon Avatar asked Dec 21 '25 10:12

Jay Lemmon


2 Answers

Sure, you can do that. Whether it will meet your performance needs, I don't know; you should measure and find out.

To fix an array in place and obtain a pointer without using the fixed statement you can use a GCHandle object. Call GCHandle.Alloc with the "pinned" handle type, passing in the array and you'll get back an IntPtr which you can safely cast to a pointer. The array will stay pinned until Free is called on the GCHandle, so make sure you do not lose track of the GCHandle. You are wrecking garbage collector performance as long as that handle is outstanding.

But my advice is to use the fixed statement. That's what it's for.

like image 127
Eric Lippert Avatar answered Dec 24 '25 01:12

Eric Lippert


Just for future readers, I think I've pretty well figured it out, though with a few educated guesses.

Basically it seems that fixed is the C# way to invoke the C++/CLI pin_ptr. These are special variables that must be declared as local variables on the stack. They don't seem to communicate with the GC directly, so they're very light weight. Instead, when the GC runs it's smart enough to scour the callstacks of all active threads and check to see if any functions' variables are these special pinning pointers. If they are, whatever they point at is marked as pinned and won't be moved in memory during garbage collection.

By comparison, GCHandle.Alloc(obj, GCHandleType.Pinned) actually communicates with the GC and places the object in a list of objects not to be moved. Which means every time you GCHandle.Alloc and then Free, you're adding and removing elements to the list and doing work. Pinning pointers are an entirely passive mechanism and don't need to do any extra work. That also explains why you can't change the value of a fixed pointer: the managed object it points to is only guaranteed to be fixed as long as the fixed pointer points to it. And if the fixed pointer pointed to a different object, it would now be pinned instead. And if you set the fixed pointer to null, even for just a moment, it would no longer be pinning anything and all pointer math you've done so far would be invalidated.

Which explains the performance hit when I tried to switch to GCHandles. So fixed isn't just the best tool, it's the only tool, at least when performance is important. Even if the syntax is sometimes awkward.

like image 25
Jay Lemmon Avatar answered Dec 24 '25 01:12

Jay Lemmon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!