2009-01-26 11 views
15

En mi aplicación C#, tengo una gran estructura (176 bytes) que se transfiere potencialmente cien mil veces por segundo a una función. Esta función simplemente toma un puntero a la estructura y pasa el puntero a un código no administrado. Ni la función ni el código no administrado harán modificaciones a la estructura.En .Net, ¿cuándo debo pasar las estructuras por referencia por motivos de rendimiento?

Mi pregunta es, ¿debería pasar la estructura a la función por valor o por referencia? En este caso particular, mi suposición es que pasar por referencia sería mucho más rápido que empujar 176 bytes en la pila de llamadas, a menos que el JIT reconozca que la estructura nunca se modifica (mi suposición es que no puede reconocer esto ya que la estructura la dirección se pasa al código no administrado) y optimiza el código.

Dado que estamos en ello, también vamos a contestar el caso más general en que la función hace no pasar el puntero del struct de código no administrado, sino que realiza alguna operación de sólo lectura en el contenido de la estructura. ¿Sería más rápido pasar la estructura por referencia? En este caso, ¿el JIT reconocería que la estructura nunca se modifica y, por lo tanto, se optimiza? Presumiblemente es no más eficiente pasar una estructura de 1 byte por referencia, pero ¿a qué tamaño de estructura se vuelve mejor pasar una estructura por referencia, si es que alguna vez?

Gracias.

EDIT:

Como se señala más adelante, también es posible crear una clase de "equivalente" para el uso regular, y luego usar una estructura al pasar a código no administrado. Veo dos opciones aquí:

1) Cree una clase "envoltorio" que simplemente contenga la estructura, y luego ancle y pase un puntero a la estructura al código no administrado cuando sea necesario. Un posible problema que veo es que pinning tiene sus propias consecuencias en el rendimiento.

2) Cree una clase equivalente cuyos campos se copian en la estructura cuando se necesita la estructura. Pero la copia tomaría mucho tiempo y me parece que para derrotar el punto de pasar por referencia en primer lugar.

EDIT:

Como se ha mencionado un par de veces por debajo, no me resultaba difícil simplemente medir el rendimiento de cada uno de estos métodos. I haga esto y publique los resultados. Sin embargo, todavía estoy interesado en ver las respuestas y los razonamientos de las personas desde una perspectiva intelectual.

+1

Pregunta interesante. Por supuesto, la respuesta obvia es que la situación de cada persona es diferente, no debe optimizarla prematuramente, y debe perfilar su aplicación si va a intentar aumentar el rendimiento. Dicho esto, tengo curiosidad acerca de algunas respuestas reales. –

+0

Esa es una estructura bastante grande, ¿por qué no convertirla en una clase? Si vas a pasarlo por ref, de todos modos se pierde el propósito de convertirlo en una estructura. –

+0

Un puntero a esta estructura se pasa directamente a un controlador de dispositivo de hardware, por lo que sí, debe ser una estructura. Mantenerlo como una estructura y simplemente obtener un puntero es, supongo, mucho más rápido que ordenar un objeto equivalente. –

Respuesta

6

Hice algunos perfiles muy informales, y los resultados indican que, para mi aplicación en particular, hay una pequeña ganancia de rendimiento para pasar por referencia. Por valor-valor recibí alrededor de 10,050,000 llamadas por segundo, mientras que por referencia obtuve alrededor de 11,200,000 llamadas por segundo.

Su kilometraje puede variar.

+0

¿Cuál era el tamaño de tus estructuras? – Timo

+0

176 bytes cada uno. (Se pasan por un puntero al controlador de gráficos, por lo que deben ser estructuras y no clases). –

7

Antes de preguntar si debe o no pasar la estructura por referencia, debe preguntarse por qué tiene una estructura tan enorme en primer lugar. ¿Es Realmente necesita ser una estructura? Si necesita utilizar una estructura en algún punto para P/Invoke, ¿valdría la pena tener una estructura solo para eso, y luego la clase equivalente en otro lugar?

una estructura tan grande es muy, muy raro ...

Ver la sección de Design Guidelines for Developing Class LibrariesChoosing Between Classes and Structures para obtener más orientación sobre este.

+0

Un período de estructura es bastante inusual –

+2

Esta estructura en particular se pasa directamente a un controlador de dispositivo de hardware, así que sí, en mi caso * necesita * ser una estructura. También ahora se me ocurre que podría envolver la estructura en una clase y luego fijar la estructura cuando necesito el puntero, pero pinning puede presentar otros problemas de rendimiento. –

+0

Me gustaría ver más en la opción (que Jon menciona) de tener una estructura para el P/Invoke y un calss para el uso regular. Con algunos operadores de conversión implícitos podría funcionar muy dulcemente. –

2

La única manera en que puede obtener una respuesta a esta pregunta es codificar ambas y medir el rendimiento.

Menciona la interoperabilidad no gestionada/gestionada. Mi experiencia es que toma un tiempo sorprendentemente largo para la interoperabilidad.Usted podría intentar cambiar el código de:

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

Para:

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

Esta técnica me ayudó en un caso similar, pero sólo mediciones le dirá si funciona para su caso.

+0

Creo que has entendido mal mi situación particular.Solo estoy pasando un elemento, no un conjunto de elementos, aunque muchas veces por segundo. –

-1

¿Por qué no utilizar una clase en su lugar, y pasar su clase a su función P/Invocar?

Usar la clase pasará muy bien en su código administrado, y funcionará igual que pasar una estructura por referencia a una función P/Invocar.

p. Ej.

// 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); 
Cuestiones relacionadas