views:

436

answers:

5

In my C# application, I have a large struct (176 bytes) that is passed potentially a hundred thousand times per second to a function. This function then simply takes a pointer to the struct and passes the pointer to unmanaged code. Neither the function nor the unmanaged code will make any modifications to the struct.

My question is, should I pass the struct to the function by value or by reference? In this particular case, my guess is that passing by reference would be much faster than pushing 176 bytes onto the call stack, unless the JIT happens to recognize that the struct is never modified (my guess is it can't recognize this since the struct's address is passed to unmanaged code) and optimizes the code.

Since we're at it, let's also answer the more general case where the function does not pass the struct's pointer to unmanaged code, but instead performs some read-only operation on the contents of the struct. Would it be faster to pass the struct by reference? Would in this case the JIT recognize that the struct is never modified and thus optimize? Presumably it is not more efficient to pass a 1-byte struct by reference, but at what struct size does it become better to pass a struct by reference, if ever?

Thanks.

EDIT:

As pointed out below, it's also possible to create an "equivalent" class for regular use, and then use a struct when passing to unmanaged code. I see two options here:

1) Create a "wrapper" class that simply contains the struct, and then pin and pass a pointer to the struct to the unmanaged code when necessary. A potential issue I see is that pinning has its own performance consequences.

2) Create an equivalent class whose fields are copied to the struct when the struct is needed. But copying would take a lot of time and seems to me to defeat the point of passing by reference in the first place.

EDIT:

As mentioned a couple times below, I could certainly just measure the performance of each of these methods. I will do this and post the results. However, I am still interested in seeing people's answers and reasonings from an intellectual perspective.

+7  A: 

Before you ask whether or not you should pass the struct by reference, you should ask yourself why you've got such an enormous struct in the first place. Does it really need to be a struct? If you need to use a struct at some point for P/Invoke, would it be worth having a struct just for that, and then the equivalent class elsewhere?

A struct that big is very, very unusual...

See the Design Guidelines for Developing Class Libraries section on Choosing Between Classes and Structures for more guidance on this.

Jon Skeet
A struct period is quite unusual
George Mauer
This particular struct is being passed directly to a hardware device driver, so yes, in my case it *does* need to be a struct. It also now occurs to me that I could wrap the struct in a class and then pin the struct when I need the pointer, but pinning may introduce other performance issues.
Walt D
I'd look more at the option (that Jon mentions) of having a struct for the P/Invoke and a calss for regular use. With some implicit conversion operators it could work very sweetly.
Marc Gravell
The struct might contain other structs, which in turn might contain other structs - and such a beast can certainly be very large.
Justice
@Justice: In that case, it should either be just for P/Invoke, or should be redesigned. A "very large" struct is a big design smell.
Jon Skeet
+2  A: 

The only way you can get an answer to this question is to code up both and measure the performance.

You mention unmanaged/managed interop. My experience is that it takes a surprisingly long time to to the interop. You could try changing your code from:

void ManagedMethod(MyStruct[] items) {
  foreach (var item in items) {
    unmanagedHandle.ProcessOne(item);
  }
}

To:

void ManagedMethod(MyStruct[] items) {
  unmanagedHandle.ProcessMany(items, items.Count);
}

This technique helped me in a similar case, but only measurements will tell you if it works for your case.

Hallgrim
I think you have misunderstood my particular scenario. I am only passing a single item, not an array of items, albeit many many times per second.
Walt D
+1  A: 

I did some very informal profiling, and the results indicate that, for my particular application, there is a modest performance gain for passing by reference. For by-value I got about 10,050,000 calls per second, whereas for by-reference I got about 11,200,000 calls per second.

Your mileage may vary.

Walt D
A: 

Isn't this something a decent compiler should be optimizing automatically, provided the struct is not modified by the callee?

dsimcha
A: 

Why not just use a class instead, and pass your class to your P/Invoke function?

Using class will pass nicely around in your managed code, and will work the same as passing a struct by reference to a P/Invoke function.

e.g.

// What you have
public struct X
{
   public int data;
}
[DllImport("mylib.dll")]
static extern void Foo( ref X arg);

// What you could do
[StructLayout(LayoutKind.Sequential)]
public class Y
{
    public int data;
}
[DllImport("mylib.dll")]
static extern void Bar( Y arg );
Wil S