 
Dieser Tweet machte gerade die Runde.
Die Funktion bestimmt, ob ein Buchstabe Whitespace ist (Leerzeichen, Tab, Zeilenumbruch, Seitenumbruch etc).
Der Standardweg steckt hinter 
#if BORING. Der andere Weg ist deutlich aufregender: Da die Zahlenwerte von Whitespace im ASCII-Code keine 32 Stellen auseinanderliegen, funktioniert man kurzerhand eine 32-Bit-Zahl zu einem 32-Bit-Lookup um. Ist ein Bit gesetzt, ist das ASCII-Zeichen an dieser Position (plus 1) Whitespace, sonst nicht.
Aufregender ist das, aber ist es auch 
schneller?
Eine wörtliche Übersetzung vorausgesetzt: Absolut!
- In vielen textbasierten Formaten können Token durch Whitespace getrennt werden. Also wird so eine Prüfung hinter jedem Token benötigt!
- Gerade weil sich in textbasierten Formaten Leerzeichen und Tabs mit Zeilenumbrüchen abwechseln, ist das naive if Gift für die Sprungvorhersage.
- Anstelle einer langen Befehlskette haben wir eine kurze Kette aus Basisarithmetik und Shifts, die jeweils in einem Takt oder weniger abschließen.
Aber wir haben die Rechnung ohne den Compiler gemacht.
Schauen wir uns das Disassembly der naiven Funktion an – vereinfacht auf Leerzeichen, Tab, Carriage Return und Line Feed (damit ich nicht so viel tippen muss) und für x86-64.
Code: Alles auswählen
bool isSpace(char c) {
    return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
Hier GCC -O2:
Code: Alles auswählen
isSpace(char):
        cmp     dil, 32
        sete    al
        cmp     dil, 9
        sete    dl
        or      al, dl
        jne     .L1
        cmp     dil, 10
        sete    al
        cmp     dil, 13
        sete    dl
        or      eax, edx
.L1:
        ret
… da kommt überhaupt nur ein einziger Sprung vor! GCC führt zwar Vergleiche aus, kombiniert die Ergebnisse dann aber mit OR. Das ist 
verdammt schnell. Messt selber.
Dann kommt Clang -O2:
Code: Alles auswählen
isSpace(char):
        add     dil, -9
        cmp     dil, 23
        ja      .LBB0_2
        mov     eax, 8388627
        mov     ecx, edi
        shr     eax, cl
        and     al, 1
        ret
.LBB0_2:
        xor     eax, eax
        ret
Wenn man das nach C++ rückübersetzt, sieht es so aus:
Code: Alles auswählen
	auto c2 = static_cast<unsigned int>(c) - 23;
	if(c2 > 23)
		return fasle;
	return static_cast<bool>((0x800013 >> c2) & 1);
Überflüssig zu sagen: Clang hat die langweilige Variante 
automatisch in die aufregende Verwandelt! Nur mit leicht anderen Zahlen, weil es 9 abzieht statt 1 (so sind die Konstanten kleiner, was andere Vorteile mit sich bringt).
	
	Nun Visual C++ /O2:
Code: Alles auswählen
bool isSpace(char) PROC
        cmp     cl, 32
        ja      SHORT $LN5@isSpace
        mov     rax, 0x100002600
        bt      rax, rcx
        jae     SHORT $LN5@isSpace
        mov     al, 1
        ret     0
$LN5@isSpace:
        xor     al, al
        ret     0
Das sieht aus wie die aufregende Variante (zumal die Konstante wieder darin vorkommt), aber dahinter steckt mehr. 
Diese Version zieht allen anderen – auch dem „aufregenden“ Code – die Socken aus. Sie verwendet nämlich den 
BT Befehl (
Bit Test), der Shift + AND + Vergleich in einen einzigen Befehl kombiniert. Der heiße Pfad ist hier – insbesondere in einer Schleife – zwei Takte kürzer. Gegen das hier hat auch der handgeschriebene Code keine Chance.
Zusammenfassung:
- GCC: ★☆☆
 Bringt naiven Code fast auf das Niveau von handoptimiertem Code.
- Clang: ★★☆
 Bringt naiven Code auf das Niveau von handoptimiertem Code.
- Visual C++: ★★★
 Übertrifft sogar handoptimierten Code.
An dieser Stelle könnten wir aufhören.
Aber jetzt beginnt das Lernen erst so richtig!
Ich hatte die Funktion ja vereinfacht, so dass sie nicht auf Vertical Tab und Form Feed testet. Damit ich nicht so viel tippen muss. Wenn ich diese Prüfungen hinzufüge, dann müssten Clang und Visual C++ ja bloß zwei weitere Bits zu ihren Konstanten hinzufügen. Sonst bleibt alles gleich.
Oder?
Code: Alles auswählen
bool isSpace(char c) {
    return c == ' ' || c == '\t' || c == '\v' || c == '\f' || c == '\n' || c == '\r';
}
Der olle GCC -O2:
Code: Alles auswählen
        cmp     dil, 32
        ja      .L2
        movabs  rdx, 4294973952
        mov     eax, 1
        bt      rdx, rdi
        jnc     .L2
        ret
.L2:
        cmp     dil, 10
        sete    al
        cmp     dil, 13
        sete    dl
        or      eax, edx
        ret
Was ist denn 
da passiert?! GCC nutzt nun ebenfalls den heißen 
BT-Befehl – aber verkorkst alles drumherum so dermaßen, dass ich gar nicht weiß, wo ich anfangen soll.
		
Clang -O2:
Code: Alles auswählen
sSpace(char):
        add     dil, -9
        cmp     dil, 23
        ja      .LBB0_2
        mov     eax, 8388639
        mov     ecx, edi
        shr     eax, cl
        and     al, 1
        ret
.LBB0_2:
        xor     eax, eax
        ret
Clang hat 
tatsächlich nur zwei neue Bits hinzugefügt. Aber sein Code bleibt weiterhin um ein Vielfaches langsamer als Visual C++’s vorheriger.
Apropos Visual C++:
Code: Alles auswählen
bool isSpace(char) PROC
        cmp     cl, 32
        je      SHORT $LN3@isSpace
        sub     cl, 9
        cmp     cl, 4
        jbe     SHORT $LN3@isSpace
        xor     al, al
        ret     0
$LN3@isSpace:
        mov     al, 1
        ret     0
Visual C++ ist plötzlich dumm geworden und optimiert zu
… keine Spur mehr von 
BT.
Quintessenz
Compiler sind nicht „schlau“. Sie führen keine „schlauen“ Optimierungen durch. Was sie tun ist lediglich Mustererkennung: Ein Programmierer sieht, dass der Compiler schlechten Code für 
isSpace erzeugt. Also programmiert er in den Compiler ein: 
„Wenn das Muster auftritt, dass eine Variable gegen vier Werte verglichen wird, und diese vier Werte sind Leerzeichen/Tab/Zeilenumbruch/Zeilenvorschub, dann lösche den Ausdruck und ersetze ihn durch <cleverste Befehlsfolge, die dem Entwickler gerade einfällt>.“
Die Tabellen für Mustererkennung nehmen absurde Ausmaße an. Durchsucht den Quelltext eures Compilers mal nach 
cos – da müsstet ihr auf Hunderte Muster à „
ersetze cos(0) durch 1.0“ treffen.
Darum würde ich die Bewertung nun eher so vergeben:
- GCC: ☆☆☆
 Hat ein paar Optimierungen einprogrammiert, aber sobald man eine Kleinigkeit ändert, geht der Code richtig in die Brüche.
- Visual C++: ★☆☆
 Wie GCC, nur dass hier der Entwickler schlau genug war, direkt BT hardzucoden.
- Clang: ★★☆
 Produziert zwar nie optimalen Code, aber zumindest scheint hier ein halbwegs variables Regelwerk einprogrammiert worden sein anstelle starrer Muster. … oder doch nicht?
Nun wäre noch zwei Fragen zu klären:
Ist die handoptimierte Version besser als die automatisch optimierte?
Sie liefert auf allen Compilern vergleichbare Leistung. Sie ist nie optimal, aber auch nie katastrophal langsam.
Kann man auf allen Compilern händisch die optimale Leistung erreichen?
Auf GCC nicht. Das erzeugt kein 
BT.
Auf allen anderen führt das hier zu quasi-optimalem Code:
Code: Alles auswählen
bool isSpace(char c) {
    auto const u = static_cast<unsigned>(c);
    return (u <= 63) & (0 != (0x100003E00 & (uint64_t(1) << u)));
}
Code: Alles auswählen
        cmp     dil, 64
        setb    cl
        movabs  rax, 4294983168
        bt      rax, rdi
        setb    al
        and     al, cl
        ret
(
https://godbolt.org/z/TfssZ8)
… aber stattdessen solltet ihr den Compiler-Entwickler eures Vertrauens fragen, ob er das nicht direkt in den Compiler hardcoden möchte.