Pregunta

Tengo una firma de método C ++ que se ve así:

    static extern void ImageProcessing(
        [MarshalAs(UnmanagedType.LPArray)]ushort[] inImage,
        [MarshalAs(UnmanagedType.LPArray)]ushort[] outImage,
        int inYSize, int inXSize);

He incluido la función en métodos de temporización, tanto internos como externos. Internamente, la función se ejecuta a 0.24s. Externamente, la función se ejecuta en 2.8s, o aproximadamente 12 veces más lento. ¿Que esta pasando? ¿Me está frenando tanto la clasificación? Si es así, ¿cómo puedo evitar eso? ¿Debo ir a un código inseguro y usar punteros o algo así? Estoy un poco desconcertado sobre de dónde proviene el costo del tiempo extra.

¿Fue útil?

Solución 3

La respuesta es, lamentablemente, mucho más mundana que estas sugerencias, aunque ayudan. Básicamente, me equivoqué con la forma en que estaba cronometrando.

El código de tiempo que estaba usando era este:

Ipp32s timer;
ippGetCpuFreqMhz(&timer);
Ipp64u globalStart = ippGetCpuClocks();
globalStart = ippGetCpuClocks() *2 - globalStart; //use this method to get rid of the overhead of getting clock ticks

      //do some stuff

Ipp64u globalEnd = ippGetCpuClocks(); 
globalEnd = ippGetCpuClocks() *2 - globalEnd;
std::cout << "total runtime: " << ((Ipp64f)globalEnd - (Ipp64f)globalStart)/((Ipp64f)timer *1000000.0f) << " seconds" << std::endl;

Este código es específico del compilador de Intel y está diseñado para proporcionar mediciones de tiempo extremadamente precisas. Desafortunadamente, esa precisión extrema significa un costo de aproximadamente 2.5 segundos por carrera. Al eliminar el código de tiempo, se eliminó esa restricción de tiempo.

Sin embargo, todavía parece haber un retraso en el tiempo de ejecución: el código informaría 0.24 s con ese código de tiempo activado, y ahora informa un tiempo de aproximadamente 0.35s, lo que significa que hay un costo de velocidad de aproximadamente 50%.

Cambiando el código a esto:

  static extern void ImageProcessing(
     IntPtr inImage, //[MarshalAs(UnmanagedType.LPArray)]ushort[] inImage,
     IntPtr outImage, //[MarshalAs(UnmanagedType.LPArray)]ushort[] outImage,
     int inYSize, int inXSize);

y llamado como:

        unsafe {
            fixed (ushort* inImagePtr = theInputImage.DataArray){
                fixed (ushort* outImagePtr = theResult){
                    ImageProcessing((IntPtr)inImagePtr,//theInputImage.DataArray,
                        (IntPtr)outImagePtr,//theResult,
                        ysize,
                        xsize);
                }
            }
        }

reduce el tiempo de ejecución a 0.3 s (promedio de tres ejecuciones). Todavía es demasiado lento para mis gustos, pero una mejora de velocidad de 10x ciertamente está dentro del ámbito de la aceptabilidad de mi jefe.

Otros consejos

Eche un vistazo a este artículo . Si bien se centra en Compact Framework, los principios generales también se aplican al escritorio. Una cita relevante de la sección de análisis es la siguiente:

  

La llamada administrada no llama directamente al método nativo. En su lugar, llama a un método de código auxiliar JITted que debe realizar algunas rutinas generales, como las llamadas para determinar el estado de Preemption de GC (para determinar si un GC está pendiente y tenemos que esperar). También es posible que parte del código de clasificación se JITTE en el código auxiliar también. Todo esto lleva tiempo.

Editar : También vale la pena leer este artículo del blog en perf del código JITted - nuevamente, CF específico, pero aún relevante. También hay un artículo que cubre la profundidad de la pila de llamadas y su impacto en el rendimiento , aunque este probablemente sea específico de CF (no probado en el escritorio).

¿Ha intentado cambiar los dos parámetros de matriz a IntPtr? PInvoke está en su punto más rápido cuando todos los tipos en la firma de clasificación son intercambiables. Esto significa que Pinvoke se reduce a una mera memoria para obtener los datos de un lado a otro.

En mi equipo, hemos encontrado que la forma más eficaz de administrar nuestra capa PInvoke es

  1. Garantiza que todo lo que se está Marshall'd es blittable
  2. Pague el precio de los tipos de Marshal manualmente, como las matrices, manipulando una clase IntPtr según sea necesario. Esto es muy trivial ya que tenemos muchos métodos / clases de envoltura.

Como con cualquier " esto será más rápido " respuesta, necesitará perfilar esta es su propia base de código. Solo llegamos a esta solución después de considerar y perfilar varios métodos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top