c++ linux caching profiling

Linux C++: ¿cómo perder tiempo de perfil debido a errores de caché?



caching profiling (9)

Sé que puedo usar gprof para comparar mi código.

Sin embargo, tengo este problema: tengo un puntero inteligente que tiene un nivel adicional de indirección (piénselo como un objeto proxy).

Como resultado, tengo esta capa adicional que afecta prácticamente a todas las funciones y tornillos con el almacenamiento en caché.

¿Hay alguna manera de medir el tiempo que pierde mi CPU debido a errores de caché?

¡Gracias!


Aquí hay una especie de respuesta general .

Por ejemplo, si su programa está gastando, digamos, 50% de su tiempo en fallas de caché, entonces 50% del tiempo cuando lo pausa el contador del programa estará en las ubicaciones exactas donde está esperando las recuperaciones de memoria que están causando el caché falla.


Continuando con las líneas de la respuesta de @ Mike_Dunlavey:

Primero, obtenga un perfil basado en el tiempo, usando su herramienta favorita: VTune o PTU u OProf.

Luego, obtenga un perfil de caché perdido. Caché L1 falla, o caché L2 falla, o ...

Es decir, el primer perfil asocia un "tiempo pasado" con cada contador del programa. El segundo asocia un valor de "número de fallas de caché" con cada contador de programa.

Nota: A menudo "reduzco" los datos, resumiéndolos por función, o (si tengo la tecnología) por ciclo. O en contenedores de, digamos, 64 bytes. La comparación de los contadores de programa individuales a menudo no es útil, porque los contadores de rendimiento son confusos: el lugar donde ve que se informa una falta de caché suele ser varias instrucciones diferentes de las que realmente sucedieron.

OK, ahora grafica estos dos perfiles para compararlos. Aquí hay algunos gráficos que me parecen útiles:

Gráficos "Iceberg": el eje X es PC, el eje Y positivo es el tiempo, el acceso Y negativo es el caché falla. Busca lugares que suban y bajen.

(Los diagramas "Intercalados" también son útiles: la misma idea, el eje X es PC, el tiempo Y el eje Y falla en la caché, pero con líneas verticales estrechas de diferentes colores, típicamente rojo y azul. Lugares donde hay mucho tiempo y caché Las pérdidas gastadas tendrán líneas rojas y azules finamente intercaladas, casi se ve púrpura. Esto se extiende a las fallas de caché L2 y L3, todas en el mismo gráfico. Por cierto, es probable que desee "normalizar" los números, ya sea al% de la edad total falta tiempo o caché, o, mejor aún,% de edad del punto de datos máximo de tiempo o caché falla. Si obtiene la escala incorrecta, no verá nada).

Gráficos XY : para cada contenedor de muestreo (PC, o función, o bucle, o ...) trace un punto cuya coordenada X es el tiempo normalizado, y cuya coordenada Y es el caché normalizado . Si obtienes muchos puntos de datos en la esquina superior derecha, un gran% de tiempo de edad Y un gran% de caché de edad falla, eso es una evidencia interesante. O bien, olvide el número de puntos: si la suma de todos los porcentajes en la esquina superior es grande ...

Tenga en cuenta, desafortunadamente, que a menudo tiene que realizar estos análisis usted mismo. La última vez que revisé, VTune no lo hace por ti. He usado gnuplot y Excel. (Advertencia: Excel muere por encima de 64 mil puntos de datos).

Más consejos:

Si su puntero inteligente está en línea, puede obtener los conteos por todas partes. En un mundo ideal, usted podría rastrear sus PC a la línea original del código fuente. En este caso, es posible que desee diferir un poco la reducción: observe todas las PC individuales; volver a asignarlos a líneas de código fuente; y luego mapea esos en la función original. Muchos compiladores, por ejemplo, GCC, tienen opciones de tablas de símbolos que le permiten hacer esto.

Por cierto, sospecho que su problema NO es con el puntero inteligente que causa la caché. A menos que esté haciendo smart_ptr <int> en cualquier lugar. Si está haciendo smart_ptr <Obj>, y sizeof (Obj) + es mayor que decir, 4 * sizeof (Obj *) (y si el smart_ptr en sí mismo no es enorme), entonces no es mucho.

Es más probable que sea el nivel extra de indirección que hace el puntero inteligente que está causando su problema.

Casualmente, estaba hablando con un tipo en el almuerzo que tenía un apuntador inteligente de referencia que usaba un control, es decir, un nivel de indirección, algo así como

template<typename T> class refcntptr { refcnt_handle<T> handle; public: refcntptr(T*obj) { this->handle = new refcnt_handle<T>(); this->handle->ptr = obj; this->handle->count = 1; } }; template<typename T> class refcnt_handle { T* ptr; int count; friend refcnt_ptr<T>; };

(No lo codificaría de esta manera, pero sirve para la exposición).

El doble indirecto this-> handle-> ptr puede ser un gran problema de rendimiento. O incluso un triple indirecto, this-> handle-> ptr-> campo. Por lo menos, en una máquina con 5 cachés L1 de ciclo, cada uno de estos campos-> handle-> ptr-> tomaría 10 ciclos. Y será mucho más difícil de superponer que una persecución de un solo puntero. Pero, lo que es peor, si cada uno de ellos es un caché L1, incluso si hubiera tan solo 20 ciclos para el L2 ... bueno, es mucho más difícil ocultar 2 * 20 = 40 ciclos de latencia de falta de memoria caché, que una sola falla L1.

En general, es un buen consejo para evitar niveles de direccionamiento indirecto en los punteros inteligentes. En lugar de señalar un identificador, al que apuntan todos los punteros inteligentes, que apunta hacia el objeto, puede hacer que el puntero inteligente sea más grande al hacer que apunte tanto al objeto como al controlador. (Que luego ya no es lo que comúnmente se llama un identificador, sino que se parece más a un objeto de información).

P.ej

template<typename T> class refcntptr { refcnt_info<T> info; T* ptr; public: refcntptr(T*obj) { this->ptr = obj; this->info = new refcnt_handle<T>(); this->info->count = 1; } }; template<typename T> class refcnt_info { T* ptr; // perhaps not necessary, but useful. int count; friend refcnt_ptr<T>; };

De todos modos, un perfil de tiempo es tu mejor amigo.

Oh, sí, el hardware Intel EMON también puede decirle cuántos ciclos esperó en una PC. Eso puede distinguir una gran cantidad de fallas L1 de un pequeño número de fallas L2.


Depende del sistema operativo y la CPU que esté utilizando. Por ejemplo, para Mac OS X y x86 o ppc, Shark hará perfiles de caché. Lo mismo para Zoom en Linux.


Linux es compatible con perf desde 2.6.31 en. Esto le permite hacer lo siguiente:

  • compila tu código con -g para tener información de depuración incluida
  • ejecute su código, por ejemplo, utilizando el último nivel de memoria caché pierde contadores: perf record -e LLC-loads,LLC-load-misses yourExecutable
  • ejecutar perf report
    • después de reconocer el mensaje inicial, seleccione la línea LLC-load-misses ,
    • entonces, por ejemplo, la primera función y
    • luego annotate Debería ver las líneas (en el código de ensamblado, rodeado por el código fuente original) y un número que indica qué fracción del último nivel de caché falla para las líneas en las que se produjeron errores de caché.

Mi consejo sería usar PTU (Performance Tuning Utility) de Intel.

Esta utilidad es el descendiente directo de VTune y proporciona el mejor perfilador de muestreo disponible disponible. Podrá rastrear dónde está pasando la CPU o perder tiempo (con la ayuda de los eventos de hardware disponibles), y esto sin desaceleración de su aplicación o perturbación del perfil. Y, por supuesto, podrá recopilar todos los eventos de errores de línea de caché que esté buscando.


Otra herramienta para el perfil basado en contador de rendimiento de CPU es opérfilo . Puede ver sus resultados usando kcachegrind.


Puede encontrar una herramienta que accede a los contadores de rendimiento de la CPU. Probablemente hay un registro en cada núcleo que cuenta L1, L2, etc. Alternativamente, Cachegrind realiza una simulación de ciclo por ciclo.

Sin embargo, no creo que sea perspicaz. Sus objetos proxy son presumiblemente modificados por sus propios métodos. Un perfilador convencional le dirá cuánto tiempo están tomando esos métodos. Ninguna herramienta de perfil le diría cómo mejoraría el rendimiento sin esa fuente de contaminación de la memoria caché. Se trata de reducir el tamaño y la estructura del conjunto de trabajo del programa, lo que no es fácil de extrapolar.

Apareció una búsqueda rápida en Google boost::intrusive_ptr que podría interesarle. No parece ser compatible con algo como weak_ptr , pero la conversión de su programa podría ser trivial, y entonces sabría con certeza el costo de los recuentos de ref no intrusivos.


Puedes probar cachegrind y su front-end kcachegrind.


Si está ejecutando un procesador AMD, puede obtener CodeAnalyst , aparentemente gratis como en la cerveza.