staging.inyokaproject.org

C/C++ double vs float (reloaded)

Status: Gelöst | Ubuntu-Version: Ubuntu 14.04 (Trusty Tahr)
Antworten |

Dakuan

Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6532

Das Thema ist ja schon öfters an diversen Orten diskutiert worden, und endete meist mit leichten Vorteilen für double Variablen (zumindest auf halbwegs moderner PC Hardware).

Bei meinem aktuellen Projekt konnte ich das weitgehend bestätigen. Es geht dabei um die Erzeugung kleiner Vorschaubilder für eine Anwendung, die das betreffende Bild nur in einem kleinen Fenster-Bereich anzeigen soll. Mein Toolkit (FLTK) verwendet dafür standardmäßig den Nearest-Neighbour Algorithmus, der zwar der absolut schnellste ist, allerdings bei Bildern mit technischen Zeichnungen oder schrägen Kanten, sehr schlechte Ergebnisse bring. Daher habe ich versucht etwas eigenes zu machen, was jetzt im Prinzip auch funktioniert.

Was mich dabei überrascht hat, ist die Tatsache, das ich trotzdem bei Verwendung von float's für die Bildung der Pixel Summen im Array sum[] (RGBA) einen leichten Geschwindigkeitsvorteil erzielen konnte, den ich mir noch nicht erklären kann (wir reden hier von 20 ms bei einer Gesamtzeit von ca. 600 ms). Bei der Verwendung von int's für das sum[] Array ist das Ergebnis übrigens nochmal um ca. 20 ms schlechter (bezogen immer auf das selbe Digitalfoto).

Woran liegt das?

Zum Verständnis hier mal ein (nicht ganz) kurzer Code Abschnitt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//      Downscale an image by averaging
Fl_Image *
MainWindow::down_scale( Fl_Shared_Image * simg, int w, int h ) {
    Fl_RGB_Image * new_image;
    const unsigned char * sp;       // source pixel pointer
    unsigned char * dp;             // destination pixel pointer
    unsigned char * t_array;        // temporary pixel data, first run
    unsigned char * n_array;        // new image pixel data array, second run
    //int     sum[4];                 // pixel sum for up to 4 channels
    float   sum[4];                 // pixel sum for up to 4 channels
    int     depth = simg->d();      // must be 1 to 4
    int     s_width = simg->w();    // source image width
    int     s_hight = simg->h();    // source image hight
    double  x_size = (double)s_width / (double)w;
    double  x_wight = 1.0 / x_size;
    double  y_size = (double)s_hight / (double)h;
    double  y_wight = 1.0 / y_size;
    double  delta;
    double  pxfract;            // fractional pixel factor
    int     i, l;

    if( depth < 1 || depth > 4 || simg->ld() != 0 || simg->count() != 1 ) {
        ...
        return NULL;
    }
    // horizontal scaling
    t_array = new unsigned char [w * s_hight * depth];
    dp = t_array;
    sp = (const unsigned char *)simg->data()[0]; // pointer to pixel array
    for( l = 0; l < s_hight; l++ ) {
        delta = 0.0;
        sum[0] = sum[1] = sum[2] = sum[3] = 0;
        for( i = 0; i < s_width; i++ ) {
            delta += 1.0;
            if( delta < x_size  &&  i < s_width-1 ) {
                for( int c = 0; c < depth; c++ )
                    sum[c] += *sp++;        // add whole pixel to scaled pixel
            } else {                        // split the pixel
                delta -= x_size;
                pxfract = 1.0 - delta;
                for( int c = 0; c < depth; c++) {   // for all cannels ... <<<
                    sum[c] += *sp * pxfract;        // left fraction       <<<
                    *dp++ = (unsigned char)((sum[c] * x_wight) + 0.499);   <<<
                    sum[c] = *sp++ * delta;         // right fraction      <<<
                }                                                          <<<
            }
        }
    }
    // vertical scaling
    ...
    new_image = new Fl_RGB_Image( n_array, w, h, depth );
    delete [] t_array;
    return new_image;
}

Der hierfür relevante Bereich ist mit "<<<" gekennzeichnet.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

Dakuan schrieb:

Woran liegt das?

Spekulation: bei so etwas denke ich immer zuerst an die Pipelines im Prozessor, die Speicherschnittstelle und die FPU als Faktoren, die die Geschwindigkeit beeinflussen. Die Abweichung ist ja mit ~3% auch nicht wirklich dramatisch.

Gewisse Hinweise könntest Du erhalten, wenn Du den Compiler nur die Assemblerausgabe für die verschiedenen Varianten produzieren lässt. Ggf. kannst Du da schon etwas sehen.

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11278

Dakuan schrieb:

Woran liegt das?

Läuft man da nicht in eine (implizite) truncating conversion, wenn man mit float bzw. double Werten rechnet und das dann in einen Array aus int schreibt (https://www.cs.tut.fi/~jkorpela/round.html)? https://stackoverflow.com/questions/9344709/fast-float-to-int-conversion-truncate

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6532

Die Abweichung ist ja mit ~3% auch nicht wirklich dramatisch.

Das nicht, aber ich hätte gerne die Ursache gewusst. Die genannten Zahlen bezogen sich jetzt auf ein großes Bild, das ich zufällig zum Testen ausgewählt hatte. Ich vermute, das der Unterschied bei kleineren Bildern größer ist, da dann der obere Schleifenteil weniger oft durchlaufen wird.

Gewisse Hinweise könntest Du erhalten, wenn Du den Compiler nur die Assemblerausgabe für die verschiedenen Varianten produzieren lässt.

Ich habe das mal versucht. war gar nicht so einfach, bei ca. 30000 Zeilen die 50 richtigen zu finden. Aber viel Unterschied kann ich da nicht erkennen, wobei ich die meisten Befehle auch nicht kenne. Aufgefallen ist mir nur, das bei float an einigen Stellen eine 4 auftaucht, wo in der double Version eine 8 steht. Aber das war ja zu erwarten.

Läuft man da nicht in eine (implizite) truncating conversion, ...

Da hast Du mich nicht richtig verstanden, es geht ja um den umgekehrten Weg. Aber wahrscheinlich ist das dasselbe in grün.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6532

Ich habe mir die Assemblerbefehle mal etwas näher angesehen. Ich bin zwar noch weit davon entfernt das alles zu verstehen, glaube aber die Ursache gefunden zu haben. Erstmal habe ich festgestellt, dass g++ immer den Befehl fmull verwendet, die FPU also offenbar immer mit der vollen Breite arbeitet. Da bleiben dann noch 2 Verdächtige:

  • flds / fldl zum laden der Werte in die FPU

  • fsts / fstl zum abspeichern

Es werden also einmal 4 und einmal 8 Bytes bewegt. Damit sieht es für mich so aus, das nur die Anzahl der Bytes den Unterschied macht. Zur Veranschaulichung hier mal die float-Version (die einige Zeilen länger ist!).

.LBB42:
	.loc 16 1002 0
	movl	$0, -60(%ebp)
	jmp	.L556
.L557:
	.loc 16 1003 0
	movl	-60(%ebp), %edx
	movl	-60(%ebp), %eax
	flds	-136(%ebp,%eax,4)
	movl	-16(%ebp), %eax
	movzbl	(%eax), %eax
	movzbl	%al, %eax
	pushl	%eax
	fildl	(%esp)
	leal	4(%esp), %esp
	fmull	-120(%ebp)
	faddp	%st, %st(1)
	fstps	-140(%ebp)
	flds	-140(%ebp)
	fstps	-136(%ebp,%edx,4)
	.loc 16 1004 0
	movl	-60(%ebp), %eax
	flds	-136(%ebp,%eax,4)
	fmull	-88(%ebp)
	fldl	.LC30
	faddp	%st, %st(1)
	fnstcw	-142(%ebp)
	movzwl	-142(%ebp), %eax
	movb	$12, %ah
	movw	%ax, -144(%ebp)
	fldcw	-144(%ebp)
	fistps	-146(%ebp)
	fldcw	-142(%ebp)
	movzwl	-146(%ebp), %eax
	movl	%eax, %edx
	movl	-20(%ebp), %eax
	movb	%dl, (%eax)
	addl	$1, -20(%ebp)
	.loc 16 1005 0
	movl	-60(%ebp), %edx
	movl	-16(%ebp), %eax
	movzbl	(%eax), %eax
	movzbl	%al, %eax
	pushl	%eax
	fildl	(%esp)
	leal	4(%esp), %esp
	fmull	-112(%ebp)
	fstps	-140(%ebp)
	flds	-140(%ebp)
	fstps	-136(%ebp,%edx,4)
	addl	$1, -16(%ebp)
	.loc 16 1002 0
	addl	$1, -60(%ebp)
.L556:
	movl	-60(%ebp), %eax
	cmpl	-32(%ebp), %eax
	jl	.L557
.L555:

Dabei entspricht ".loc 16 1002 0" der Zeile 41 im oben gezeigten Listing.

Antworten |