Obtener ciclos de CPU usando RDTSC: ¿por qué el valor de RDTSC siempre aumenta?

Quiero obtener los ciclos de la CPU en un punto específico. Yo uso esta función en ese punto:

static __inline__ unsigned long long rdtsc(void) { unsigned long long int x; __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); return x; } 

El problema es que devuelve siempre un número creciente (en cada ejecución). Es como si se estuviera refiriendo al tiempo absoluto.

¿Estoy usando las funciones incorrectamente?

Siempre que su hilo permanezca en el mismo núcleo de CPU, la instrucción RDTSC seguirá devolviendo un número creciente hasta que se enrolle. Para una CPU de 2 GHz, esto sucede después de 292 años, por lo que no es un problema real. Probablemente no lo verás suceder. Si espera vivir tanto, asegúrese de que su computadora se reinicie, digamos, cada 50 años.

El problema con RDTSC es que no tiene garantía de que comience en el mismo momento en todos los núcleos de una CPU multinúcleo antigua y no garantiza que comience en el mismo punto en el tiempo en todas las CPU de una placa de CPU antigua. .
Los sistemas modernos generalmente no tienen tales problemas, pero el problema también se puede solucionar en sistemas más antiguos configurando la afinidad de un subproceso, de modo que solo se ejecute en una CPU. Esto no es bueno para el rendimiento de la aplicación, por lo que uno no debería hacerlo en general, pero para medir los tics, está bien.

(Otro “problema” es que muchas personas usan RDTSC para medir el tiempo, que no es lo que hace, pero usted escribió que quiere ciclos de CPU, así que está bien. Si usa RDTSC para medir el tiempo, puede tener sorpresas cuando el ahorro de energía o la hiperpotencia o cualquiera que sea la multitud de técnicas de cambio de frecuencia se llama kicks in. Para el tiempo real, el clock_gettime clock_gettime es sorprendentemente bueno en Linux).

Simplemente escribiría rdtsc dentro de la statement asm , que funciona muy bien para mí y es más legible que algún código hexadecimal oscuro. Suponiendo que es el código hexadecimal correcto (y dado que no se bloquea y devuelve un número cada vez mayor, parece que sí), su código es bueno.

Si desea medir el número de tics que toma un fragmento de código, desea una diferencia de tilde, solo necesita restar dos valores del contador en constante aumento. Algo así como uint64_t t0 = rdtsc(); ... uint64_t t1 = rdtsc() - t0; uint64_t t0 = rdtsc(); ... uint64_t t1 = rdtsc() - t0;
Tenga en cuenta que si se necesitan mediciones muy precisas y aisladas del código circundante, debe serializar, es decir, detener la tubería, antes de llamar a rdtsc (o usar rdtscp que solo es compatible con los procesadores más nuevos). La única instrucción de serialización que se puede usar en cada nivel de privilegio es cpuid .

En respuesta a la pregunta adicional en el comentario:

El TSC comienza en cero cuando enciende la computadora (y el BIOS restablece todos los contadores en todas las CPU al mismo valor, aunque algunos BIOS hace unos años no lo hicieron de manera confiable).

Por lo tanto, desde el punto de vista de su progtwig, el contador comenzó “algún tiempo desconocido en el pasado”, y siempre aumenta con cada tic del reloj que ve la CPU. Por lo tanto, si ejecuta la instrucción devolviendo ese contador ahora y en cualquier momento posterior en un proceso diferente, devolverá un valor mayor (a menos que la CPU se suspenda o apague en el medio). Las diferentes ejecuciones del mismo progtwig obtienen números mayores, porque el contador sigue creciendo. Siempre.

Ahora, clock_gettime(CLOCK_PROCESS_CPUTIME_ID) es una cuestión diferente. Este es el tiempo de CPU que el sistema operativo le ha dado al proceso. Comienza en cero cuando comienza su proceso. Un nuevo proceso comienza en cero, también. Por lo tanto, dos procesos consecutivos obtendrán números muy similares o idénticos, nunca crecientes.

clock_gettime(CLOCK_MONOTONIC_RAW) está más cerca de cómo funciona RDTSC (y en algunos sistemas más antiguos se implementa con él). Devuelve un valor que siempre aumenta. Hoy en día, esto es típicamente un HPET. Sin embargo, esto es realmente tiempo , y no tics . Si su computadora entra en un estado de baja energía (por ejemplo, funcionando a la mitad de la frecuencia normal), seguirá avanzando al mismo ritmo.

Hay mucha información confusa y / o errónea sobre el TSC, así que pensé en intentar aclarar un poco.

Cuando Intel introdujo por primera vez el TSC (en las CPUs Pentium originales), estaba claramente documentado contar los ciclos (y no el tiempo). Sin embargo, en aquel entonces las CPU funcionaban en su mayoría a una frecuencia fija, por lo que algunas personas ignoraron el comportamiento documentado y lo usaron para medir el tiempo (en particular, los desarrolladores del kernel de Linux). Su código se rompió en CPUs posteriores que no se ejecutan a una frecuencia fija (debido a la administración de energía, etc.). Alrededor de ese momento, otros fabricantes de CPU (AMD, Cyrix, Transmeta, etc.) se confundieron y algunos implementaron TSC para medir ciclos y algunos lo implementaron para medir el tiempo y algunos lo hicieron configurable (a través de un MSR).

Luego, los sistemas “multi-chip” se volvieron más comunes para los servidores; e incluso después se introdujo multi-core. Esto condujo a pequeñas diferencias entre los valores de TSC en diferentes núcleos (debido a diferentes tiempos de inicio); pero lo más importante es que también generó diferencias importantes entre los valores de TSC en diferentes CPU causadas por CPUs que funcionan a diferentes velocidades (debido a la administración de energía y / u otros factores).

Las personas que intentaban usarlo incorrectamente desde el principio (personas que lo usaban para medir el tiempo y no los ciclos) se quejaban mucho, y finalmente convencieron a los fabricantes de CPU de estandarizar al hacer que el TSC midiera el tiempo y no los ciclos.

Por supuesto, esto fue un desastre, por ejemplo, se necesita una gran cantidad de código solo para determinar lo que realmente mide el TSC si admite todas las CPU de 80×86; y diferentes tecnologías de administración de energía (incluyendo cosas como SpeedStep, pero también cosas como estados de suspensión) pueden afectar el TSC de diferentes maneras en diferentes CPU; por lo tanto, AMD introdujo un indicador “invariante de TSC” en CPUID para decirle al sistema operativo que el TSC se puede usar para medir el tiempo correctamente.

Todas las CPU recientes de Intel y AMD han sido así por un tiempo: el TSC cuenta el tiempo y no mide los ciclos en absoluto. Esto significa que si desea medir ciclos, debe usar contadores de monitoreo de rendimiento (específicos del modelo). Desafortunadamente, los contadores de monitoreo de rendimiento son un desastre aún peor (debido a la naturaleza específica de su modelo y su configuración intrincada).

buenas respuestas ya, y Damon ya lo mencionó de alguna manera en su respuesta, pero lo agregaré de la entrada real del manual x86 (volumen 2, 4-301) para RDTSC:

Carga el valor actual del contador de marca de tiempo del procesador (un MSR de 64 bits) en los registros EDX: EAX. El registro EDX se carga con los 32 bits de orden superior del MSR y el registro EAX se carga con los 32 bits de orden inferior. (En los procesadores que admiten la architecture Intel 64, se eliminan los 32 bits de orden superior de RAX y RDX).

El procesador monótonamente incrementa el contador de marca de tiempo MSR cada ciclo de reloj y lo restablece a 0 cada vez que se reinicia el procesador. Consulte “Contador de sellos de tiempo” en el Capítulo 17 del Manual del desarrollador de software Intel® 64 e IA-32 Architectures, Volumen 3B , para obtener detalles específicos del comportamiento del contador de marca de tiempo.