Última consideração sobre o uso de RDTSC

Vocês devem ter percebido que, algumas vezes, usar a função _rdtsc(), em x86intrin.h pode não ser tão preciso assim. Especialmente se a função testada está no início do bloco do main(). Nesses casos é comum obter leituras bem altas, maiores do que deveriam ser…

Esse fenômeno está intimamente ligado ao preenchimento dos caches de dados e de instruções (existem 2, como você já sabe). Para solucionar esse problema é interessante fazer um pré-aquecimento dos caches, executando o código sob teste duas ou mais vezes.

Observe o comportamento do código abaixo:

#include <stdio.h>
#include <x86intrin.h>
#include <cpuid.h>
#include <inttypes.h>

int main(void)
{
  int i;
  uint32_t a, b, c, d;
  uint64_t y;

  __cpuid(0, a, b, c, d);
  y = _rdtsc();
  for (i = 0; i < 10; i++);
  y = _rdtsc() - y;

  printf("%lld\n", y);

  return 0;
}

Ao compilar esse código sem otimizações e executá-lo, você obterá uma contagem alta de ciclos. É comum obter valores bem altos (8000 ciclos, por exemplo). Em contrapartida, se fizermos assim:

#include <stdio.h>
#include <x86intrin.h>
#include <cpuid.h>
#include <inttypes.h>

int main(void)
{
  int i, x;
  uint32_t a, b, c, d;
  uint64_t y;

  x = 0;
  do {
    __cpuid(0, a, b, c, d);
    y = _rdtsc();
    for (i = 0; i < 10; i++);
    y = _rdtsc() - y;
  } while (++x < 2);

  printf("%lld\n", y);

  return 0;
}

Vocẽ obterá a contagem correta (cerca de 217).

Executar o mesmo código algumas vezes garante que tanto os dados quanto as instruções já estejam nos caches, diminuindo o problema.

Uma maneira de evitar a criação de loops é construir uma função, em outro módulo. Funções em módulos diferentes são compilados como funções mesmo, ao invés de torná-las inline (o que pode acontecer quando compilamos funções no mesmo módulo). Funções estão contidas no espaço de endereçamento “fixo” e, por isso, chamá-las duas vezes melhora muito o efeito do preenchimento do cache de instruções. Ainda, as variáveis locais tendem a ser alocadas no mesmo lugar da pilha, colocando-as no cache de dados.

Lembro a vocês que é importante a chamada a __cpuid(), como mostrada acima, para serializar as unidades de execução da CPU. Essa é a única função que funciona no ring 3 que tem essa característica. O termo “serialização”, como creio ter explicado em outro post, significa que a CPU executará todas as instruções pendentes, quando encontrar a instrução CPUID, antes de continuar o processamento na instrução seguinte, garantindo uma medida correta do seu código (sem os efeitos das “instruções pendentes”).

É isso….

Anúncios

2 comentários sobre “Última consideração sobre o uso de RDTSC

  1. Entao, não seria melhor fazer uma amostra um pouco maior e analisar estatisticamente os resultados ? Dependendo do tipo de função, talvez uma “média” simples já seja um valor confiável.

    1. O problema é que a primeira medida será muito alta, alterando a média em, talvez, milhares de ciclos.
      Ex: Se a primeira medida retornar 8000 ciclos e as próximas 9 retornarem 200, a média seria:

      media = (∑x(n))/n, para 1 <= n <= 10

      Ou seja, (8000+9*200)/10 = 9800/10 = 980 ciclos (quase 5 vezes maior que os 200 esperados).

      Mesmo que fizessemos 500 medições, o valor médio seria de 216 ciclos (8% acima do esperado)…

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