FPU / SSE: verschiedene Genauigkeit?



  • Moin!
    Ich spiele gerade ein wenig mit SSE und inline assembler in gcc rum. Ich habe hier zwei Programme, die eigentlich genau dasselbe Ergebnis produzieren sollten.

    Nichtoptimierte Variante

    #include <stdio.h>
    
    int main(int argc, char** argv)
    {
    	float inp[4] __attribute__((aligned(16))) = {2.0, 2.0, 2.0, 2.0};
    	float outp[4] __attribute__((aligned(16))) = {1.0, 1.0, 1.0, 1.0};
    
    	for(int i = 0; i < 20000; ++i)
    		for(int k = 0; k < 4; ++k)
    		{
    			inp[k] += 1;
    			outp[k] += inp[k];
    		}
    
    	printf("Ergebnis: %f %f %f %f\n", outp[0], outp[1], outp[2], outp[3]);
    
    	return 0;
    }
    

    Optimierte Variante

    #include <stdio.h>
    
    int main(int argc, char** argv)
    {
    	float inp[4] __attribute__((aligned(16))) = {2.0, 2.0, 2.0, 2.0};
    	float ones[4] __attribute__((aligned(16))) = {1.0, 1.0, 1.0, 1.0};
    	float outp[4] __attribute__((aligned(16)));
    
    	__asm__ volatile (
    		"movaps %0, %%xmm0\n\t"
    		"movaps %1, %%xmm1\n\t"
    		"movaps %0, %%xmm2\n\t"
    		:: "m"(ones[0]),"m"(inp[0])
    	);
    
    	for(int i = 0; i < 20000; ++i)
    	{
    		__asm__ volatile (
    			"addps %%xmm0, %%xmm1\n\t"
    			"addps %%xmm1, %%xmm2\n\t"
    			::
    		);
    	}
    
    	__asm__ volatile ("movaps %%xmm2, %0\n\t"
    		: "=m"(outp[0]));
    
    	printf("Ergebnis: %f %f %f %f\n", outp[0], outp[1], outp[2], outp[3]);
    
    	return 0;
    }
    

    Das Programm tut nichts sinnvolles. Ist nur ein Test-Objekt. Bis in etwa 10.000 Schleifendurchläufen der äußeren Schleifen erzielt man dasselbe Ergebnis. Aber schon bei 20.000 (wie im Code angegeben) erhält man verschiedene Werte.

    Ergebnis nichtoptimiert:
    Ergebnis: 200042914.000000 200042914.000000 200042912.000000 200042914.000000

    Ergebnis optimiert:
    Ergebnis: 200042912.000000 200042912.000000 200042912.000000 200042912.000000

    Ich finde das höchst merkwürdig. Ich kann mir höchstens vorstellen, dass mit verschiedener Genauigkeit gerechnet wird.

    Kompiliert wurde das ganze für einen Athlon XP 2500+ mit GCC 4.1.2. Wer's ausprobieren möchte: hier ist das Makefile.

    OPT=-O3
    
    all: normal optimized
    
    normal:
    	gcc -O3 -std=gnu99 $(OPT) -S main_normal.c
    	gcc -o test_normal -lm main_normal.s
    
    optimized:
    	gcc -O3 -std=gnu99 $(OPT) -S main_optimized.c
    	gcc -o test_optimized -lm main_optimized.s
    

    Weiß jemand von euch mehr bzw. was Genaues?
    EDIT: Auf AMD64 scheint es zu funktionieren...


  • Mod

    Wie sieht denn der generierte Assemblercode aus? Da float nur 23 signifikante Stellen hat, ist klar, dass hier Rundungsfehler ins Spiel kommen - ein unterschiedliches Verhalten verschiedener Prozessoren sollte allerdings nicht auftreten.



  • slightly OT: Ich kann dir zwar nicht helfen. Aber schau dir vielleicht mal die GCC SSE Builtin-Funktionen an, dann musst du nicht mit __asm__ rumfummeln 🙂



  • ist nur so ne vermutung:

    auf x87 wird intern anders gerechnet als der datentyp ist. es gibt also kein float/double/long double. wenn du also zwei zahlen addierst die eigentlich ein genauigkeitsproblem bei float haetten, bekommt man das mit fpu dank 80bit noch hin, nach der addition passt das mit float wieder.

    mit fpcontrol und PC_CW_24 oder so kann man die fpu auf floatgenauigkeit stellen, meines wissens nach beinflusst das aber nur die division und wurzel instruktion (jedenfalls was die geschwindigkeit angeht).


Log in to reply