Funções intrínsecas e o RDTSC final

Meu amigo MaRZ, co-autor deste blog, não gosta muito de features pouco portáveis. Aliás, pouca gente gosta. Nas rotinas para leitura do Time Stamp Counter (TSC) usei um pouquinho de assembly que, é claro, é dependente de arquitetura (funciona em máquinas Intel e AMD). Mesmo nessas arquiteturas, alguns processadores simplesmente não têm as instruções CPUID e RDTSC! E isso é um problema…

Acontece que o próprio GCC já tem essas rotinas. Além das funções intrinsecas para SSE, o header x86intrin.h também inclui funções como _rdtsc(). O macro __cpuid também é definido, só que no header cpuid.h.

As rotinas continuam não funcionando fora de plataformas compatíveis com Intel, mas, pelo menos, estão definidas nos headers que a distribuição do compilador fornece.

De posse dessa informação, as minhas rotinas finais para o diagnóstico de performance ficam assim:

/* rdtsc.c */
#include <stdio.h>
#include <x86intrin.h>
#include <cpuid.h>
#include "rdtsc.h"

static uint64_t last;
static uint64_t calibration_value = 0ULL;

/* É necessário usar a opção -funroll-loops
   para desenrolar o loop abaixo! */
static void calibrate(void)
{
  int i;
  int a, b, c, d;
  uint64_t t;

  calibration_value = 0ULL;

  /* Chama CPUID para serialização! */
  for (i = 0; i < 128; i++)
  {
    __cpuid(0, a, b, c, d);
    t = _rdtsc();
    calibration_value += _rdtsc() - t;
  }
  calibration_value /= 128;
}

void begin_tsc(void)
{
  static int calibrated = 0;
  int a, b, c, d;

  if (!calibrated)
  {
    calibrate();
    calibrated++;
  }

  /* Chama CPUID para serialização! */
  __cpuid(0, a, b, c, d);
  last = _rdtsc();
}

void end_tsc(uint64_t *pCount)
{
  int a, b, c, d;
  __cpuid(0, a, b, c, d);
  *pCount = _rdtsc() - last - calibration_value;  

  /* Às vezes a diferença pode ser menor que o
     valor da calibração! */
  if ((int64_t)*pCount < 0)
    *pCount = 0;
}

void show_tsc(const char *msg, const uint64_t u64Value)
{
  fprintf(stderr, "%s: %llu\n", msg, u64Value);
}

Note que a função de calibração é chamada apenas na prineira vez que begin_tsc() for chamada.

Pelos meus testes, as funções begin_tsc() e end_tsc(), acima, têm um erro de ±5 ciclos (em média).

O header rdtsc.h fica assim:

#ifndef RDTSC_INCLUDED
#define RDTSC_INCLUDED

#include <inttypes.h>

void begin_tsc(void);
void end_tsc(uint64_t *);
void show_tsc(const char *, uint64_t);

#endif

Só nos reta criar a biblioteca (estática) e está tudo pronto:

$ gcc -mtune=pentium -O3 -fomit-frame-pointer -funroll-loops -c rdtsc.c
$ ar rcs /usr/local/lib/librdtsc.a rdtsc.o
$ cp rdtsc.h /usr/local/include/ 

Daqui pra frente, para usar as rotinas, basta usar o header rdtsc.h e adicionar a opção -lrdtsc na hora de linkar.

Update:

Você notará que corrigi o código acima adicionando uma chamada a __cpuid em end_tsc(). Também coloquei a chamada a __cpuid(), na rotina de calibração, dentro do loop. Isso deve-se ao fato de que o processador deve ser serializado antes de chamar a instrução rdtsc, sempre!

A rotina anterior apresentou algumas leituras malucas (testei com as rotinas de cálculo de produtos vetoriais e a que usava SSE 10 vezes mais lenta que a rotina “normal” – o que é impossível!).

Se houverem outros problemas, aviso e altero a rotina.

 

 

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s