Eu sou uma besta quadrada… admito… Em um post anterior afirmei (pelo menos acho que fiz isso) que usar floats ao invés de doubles é mais performático, em se tratando de operações em ponto flutuante. Sempre tive essa crença por dois motivos:
- float é um tipo menor do que double. O primeiro tem exatamente 32 bits de tamanho. O segundo, 64. É lógico pensar que as inicializações e atribuições sejam feitas de forma mais veloz com floats do que com doubles, já que apenas um registrador genérico estará envolvido;
- Pelo fato de float ser menor e ter menos precisão do que doubles, assumi que as operações (adição, subtração, multiplicação, divisão, funções trigonométricas, etc) fossem também mais rápidas
#include <stdint.h> #include "rdtsc.h" int main(int argc, char **argv) { float fa, fb, fc; double da, db, dc; long double lda, ldb, ldc; uint64_t t; int cnt; fb = fc = 0.1f; BEGIN_RDTSC(t); for (cnt = 0; cnt < 1000; cnt++) fa = fb + fc; END_RDTSC(t); printf_rdtsc_results("1000 float adds", t); db = dc = 0.1; BEGIN_RDTSC(t); for (cnt = 0; cnt < 1000; cnt++) da = db + dc; END_RDTSC(t); printf_rdtsc_results("1000 double adds", t); ldb = ldc = 0.1; BEGIN_RDTSC(t); for (cnt = 0; cnt < 1000; cnt++) lda = ldb + ldc; END_RDTSC(t); printf_rdtsc_results("1000 long double adds", t); return 0; }
Adicionei a função print_rdtsc_results() em rdtsc.c:
void printf_rdtsc_results(const char *sz, uint64_t t) { printf("%s: %llu cycles\n", sz, t); }
Ao compilar o código e executá-lo, eis o que obtive:
$ gcc -mtune=native -O0 -fomit-frame-pointer -o test test.c rdtsc.c $ ./test 1000 float adds: 9849 cycles 1000 double adds: 5901 cycles 1000 long double adds: 8295 cycles
Ou seja, operações de adição com floats são cerca de 67% mais lentas do que as mesmas operações com doubles. São tão performáticas quanto o uso de long doubles (que tem 80 bits – 10 bytes de tamanho – e não podem ser copiadas de uma só vez pelo processador!).
A explicação para essas discrepâncias é a seguinte: Todas as operações em ponto flutuante são feitas como long doubles na arquitetura Intel. Quando usamos float, o processador tem o trabalho de convertê-lo para o tipo long double. Essa conversão toma tempo desnecessário… O mesmo acontece com o tipo double, mas a conversão é mais rápida. Quanto ao tipo long double, como expliquei, o problema não está na conversão, mas no ciclo adicional necessário para copiar os 10 bytes para a pilha do processador. Os 64 bits inferiores são copiados num ciclo e os 16 superiores no outro.
Ué?! Não estamos falando de arquitetura de 32 bits? Vale lembrar que o processador, desde o primeiro Pentium, realiza leituras e escritas usando um barramento de 64 bits de tamanho! Você até poderia pensar em forçar a barra e alinhar os tipos float de 8 em 8 bytes (64 bits boundary) assim:
typedef float __attribute__((aligned(8))) Float;
Basta trocar os tipos float por Float no código inicial. Mas, você observará que isso não mudará nada. A performance das adições em float permanecerá a mesma, corroborando a explicação sobre a conversão de tipos, pelo processador.
Outra fonte de confusão, pelo menos para mim, é a documentação do OpenGL que afirma:
We require simply that numbers’ floating-point parts contain enough bits and that their exponent fields are large enough so that individual results of floatingpoint operations are accurate to about 1 part in 105. The maximum representable magnitude of a floating-point number used to represent positional, normal, or texture coordinates must be at least 232. [p.7-8, OpenGL 4.1 Specification (Core Profile)]
Eu assumi que OpenGL fosse otimizado para atender esses requisitos e, por isso, o tipo float seria mais performático. Digo que sou uma besta quadrada porque não li a primeira frase do último parágrafo da página 7 do manual:
We do not specify how floating-point numbers are to be represented, or the details of how operations on them are performed. (o negrito é meu)
Estou batendo a cabeça na parede até agora, acreditem…
Num próximo artigo falo da comparação de performance entre SSE (que não permite o uso de doubles) e SSE2. Tenho a crença de que a discrepância mostrada ai em cima não vale para SSE, veremos…
Coolface… Bwhawhawha!
Coitada da parede, véi…
Eu sei como vc tem a cabeça dura e gosta de testar se a dos outros tb é…
Lembro bem daquela que vc já me contou sobre a prática do seu “judô” em tempos antigos!
Pega leve contigo, todos erramos. Até o William Bonner erra!