News Linux-News der Woche: Mesa wird Explizit, Updates für Proton, AMD auf RISC-V

mae schrieb:
Dank sehr guter Branch Prediction kann man sich heute Decoder leisten, die relativ viele Zyklen brauchen, und einfache Dekodierung bringt nicht mehr so einen grossen Vorteil wie in den 1980ern.
Jain,
dass die größeren x86 und ARM Kerne µOp Caches haben um möglichst das Decoderstufen nur einmal zu durchlaufen kommt ja nicht von ungefähr.
 
bringt diese decoder und anderen oaps mit Zyklus etwas bei 2 x die selben Programme oder werden die Befehle so zusammen gelegt und brauchen durch das dann weniger Zeit weil es ja der selbe befehl und so ist. Ich hoffe das mir die breiteren Einheiten in der hinsicht dann was bringt bei 32 bit Programm mit gewissen befehlsatz und so.
 
ETI1120 schrieb:
Aber trotz dieser Dominanz ist Intel ist daran gescheitert sich im Smartphonemarkt zu etablieren.
dazu müsste intel aber auch passende produkte haben - und diese hatten sie meines wissens nie.
die xscale reihe die die intel irgendwann anfang 2000er jahre auf die beine stellte war so "erfolgreich" dass sie innerhalb von paar jahren an damals marvell verscherbelt wurde... das müsste damals noch armv5 gewesen sein - der boom im smartphone bereich kam dann erst mit armv6.
das weiß ich weil ich meine ersten gehversuche als student damals noch mit einem arm926e (armv5) in der arm welt tat und dann geflasht war als die ersten armv6 (arm1136) eine dicke scheibe drauflegten.
dann kamen nokia, apple und samsung als lizenznehmer und haben den arm11 quasi zum default soc im mobiltelefon bereich gemacht.

ETI1120 schrieb:
Embedded ist keine Nische sondern ein riesiges Anwendungsgebiet. Zu dieser Zeit waren MC68000, X86 und andere Architekturen im Embedded-Bereich noch weit verbreitet.
ja, gerade deswegen schreibe ich das arm damals nur einer unter vielen architekturen im embedded bereich war (auch wenn der markt für sich dort riesig ist)
außerdem hat erst der smartphone boom dafür gesorgt dass etliche arm-architekturen gepusht und designt wurden - vorher lebten die armv5 wirklich in einer nische.
mit aufkommen der armv6 ging es sowohl im phone bereich steil bergauf als auch im embeddedbereich mit den cortex m0/m1 - welche z.b. stmicro augriff; heute stecken die stmicro stm32fxx (und konsorten) soc quasi überall drin.
 
Piktogramm schrieb:
Jain,
dass die größeren x86 und ARM Kerne µOp Caches haben um möglichst das Decoderstufen nur einmal zu durchlaufen kommt ja nicht von ungefähr.

Das ist ein interessanter Aspekt: Im Core 2 Duo hat der loop cache viel Stromersparnis gebracht, und den haben sie dann (mit WIMRE Sandy Bridge zum uop cache erweitert, und AMD hat dann irgendwann nachgezogen. Aber offenbar haben die E-Core-Entwickler von Intel herausgefunden, wie man auch mit weniger Stromverbrauch dekodieren kann, und die dekodieren inzwischen 2x3 Befehle pro Zyklus, offenbar in stromsparender Form, und angeblich haben sie keinen uop cache, weil das Strom spart; Flaeche spart's auf jeden Fall, und der Stromverbrauch scheint nicht so schlimm zu sein.

Vielleicht sehen wir einen kuenftigen P-Core von Intel oder AMD mit Dekodern fuer 3x3 Befehlen und ohne uop cache.

ARM geht inzwischen auch wieder weg von den uop caches. Die uop caches bei ARM scheinen daher zu kommen, weil zwei recht unterschiedliche Befehlssaetze (T32 und A64) verarbeitet werden mussten, und jetzt, wo viele Kerne nur mehr A64 koennen, ist der uop cache nicht mehr noetig.
Ergänzung ()

cypeak schrieb:
dazu müsste intel aber auch passende produkte haben - und diese hatten sie meines wissens nie.

Sie hatten diverse Produkte, die ihrer Meinung nach in diesen Markt gepasst haben: MID processors/SoCs (UMPC/Smartphone/IoT) und Tablet_processors/SoCs; ok, Bonnell und Saltwell waren einfach langsam, aber beginnend mit Silvermont wurden ihre Kerne von der Performance durchaus konkurrenzfaehig; inwieweit der Stromverbrauch ein Problem war, weiss ich nicht, aber es gab durchaus Tablets mit diesen Prozessoren, und Android hat das unterstuetzt.

Aber inwieweit die Software-Hersteller das unterstuetzt haben, ist die Frage. Wir unterstuetzen bei Gforth eigentlich Intel, aber wir bekamen einmal mit, dass ein Android-User mit Intel-Prozessor eigentlich die ARM-Version verwendet hat (ARM wurde auf dem Intel-Prozessor emuliert). Wir hatten da irgendwas beim Konfigurieren des Compilierens fuer Android falsch gemacht.

Letztlich hat Intel die Produktlinien fuer Smartphones und Tablets nicht fortgesetzt, bei Smartphones war mit Silvermont Schluss, bei Tablets mit Airmont; inwiewiet das Problem nur die Kompatibilitaet war, oder ob sie auch in anderen Aspekten unterlegen waren, weiss ich nicht.
 
Zuletzt bearbeitet:
@mae
Wie man effiziente Decoder baut wissen die Leute bei Intel. Nach "Chips and Cheese" sind bei Alder Lake lustigerweise die Zugriffe µOp-Cache sowie Decode + Zugriff auf L1, L2, L3 bei den Golden Cove P-Cores effizienter als bei Gracemont Zugriffe auf L1, L2, L3 samt Decode: https://chipsandcheese.com/2022/07/07/alder-lakes-caching-and-power-efficiency/
Erst Zugriffe auf RAM kann Gracemont effizienter abwickeln.

Und Gracemont kann zwar 2x3 µOps je Takt dekodieren. Das Registerenaming kann aber "nur" 5µOps/cycle je Takt verarbeiten. Hinzu kommt die Einschränkung, dass diese Obergrenze von 5µOps/cycle "nur" bei längeren Befehlsketten ohne Sprung erreicht wird. "Instruction Fetch and Decode" von https://chipsandcheese.com/2021/12/21/gracemont-revenge-of-the-atom-cores/

Auch ist Gracemont nicht in allen Lebenslagen effizienter als GoldenCove und Zen2 steht je nach Chipkonfiguration teil gar besser aus als Gracemont.: https://chipsandcheese.com/2022/01/28/alder-lakes-power-efficiency-a-complicated-picture/

Ich sehe auf absehbare Zeit dicke Kerne nicht ohne µOp-Cache. Auch erwarte ich keine zweigeteilten Decoder bei großen Designs. Nach Sprüngen nur die Hälfte der verfügbaren Kapazität vom Decoder ins Backend zu füttern ist ein viel zu großer Nachteil. Bei GoldenCove kann das Renaming 6µOps/cycle ins Backend geben. Da direkt nach einem Sprung 50% der Kapazität zu verschenken wäre absurd und würde der Performance und Energieeffizienz massiv schaden.
 
Vielen Dank für die News. Als Umsteiger immer wieder eine sehr gute Lektüre, um auf dem Laufenden zu bleiben.
 
Piktogramm schrieb:
@mae
Wie man effiziente Decoder baut wissen die Leute bei Intel. Nach "Chips and Cheese" sind bei Alder Lake lustigerweise die Zugriffe µOp-Cache sowie Decode + Zugriff auf L1, L2, L3 bei den Golden Cove P-Cores effizienter als bei Gracemont Zugriffe auf L1, L2, L3 samt Decode: https://chipsandcheese.com/2022/07/07/alder-lakes-caching-and-power-efficiency/
Erst Zugriffe auf RAM kann Gracemont effizienter abwickeln.

Der relevante Unterschied ist zwischen dem uOp-cache auf Golden Cove und dem I-cache+decode auf Gracemont. Und da ist der uOp-cache zwar ein kleines bisschen effizienter, aber eben nur ein kleines bisschen. Und das gleicht Gracemont dadurch aus, dass der I-Cache groesser ist und dadurch teurere Zugriffe auf den L2 vermeidet. Im Vergleich dazu habe ich auf dem Xeon 5160 (= Core 2 Duo) noch einen grossen Unterschied im Stromverbrauch zwischen Code gemessen, der aus dem loop-buffer kam (dem Vorgaenger des uOp-caches) und code, der aus dem I-cache kam und decodiert werden musste. Intel hat also die Effizienz des Decoders stark verbessert. In einer der Folien steht auch etwas von 3% Stromverbrauch fuer's Decoding im Vergleich zu 10% fuer einen Cache-Zugriff.

Und Gracemont kann zwar 2x3 µOps je Takt dekodieren. Das Registerenaming kann aber "nur" 5µOps/cycle je Takt verarbeiten.

Ja, aber man kann die von mir vorgeschlagene 3x3-Dekoder-Loesung auch mit einem breiteren Renamer kombinieren. Und Intel hat im Gracemont-Nachfolger Chrestmont schon einen breiteren Renamer verwendet, dort sind es 6 uOps/cycle. Und wenn man den Geruechten glauben darf, wird im Skymont (Chrestmont-Nachfolger) tatsaechlich ein 3x3-Decoder eingesetzt und ein Renamer mit 8uops/cycle.

Hinzu kommt die Einschränkung, dass diese Obergrenze von 5µOps/cycle "nur" bei längeren Befehlsketten ohne Sprung erreicht wird. "Instruction Fetch and Decode" von https://chipsandcheese.com/2021/12/21/gracemont-revenge-of-the-atom-cores/

Habe ich mir jetzt noch einmal durchgelesen, finde dort aber so eine Einschraenkung nicht; meinst Du vielleicht die, dass nur aus dem Buffer eines Decoders auf einmal in die Rename-Unit weitergeleitet wird, sodass nicht mehr als ein taken-branch pro cycle verarbeitet werden kann?

Jedenfalls ist auch diese Einschraenkung eine, die nicht im Prinzip der mehreren parallelen Decode vorgegeben ist. Im Gegenteil, eigentlich sollte es mit 3x3 Decodern moeglich sein, in guenstigen Faellen 3 taken branches/Zyklus zu verarbeiten, waehrend ein breiter Decoder nur einen pro Zyklus schafft. Beim uOp-Cache braucht man auch Extra-Aufwand, um auf mehr als einen taken branch pro Zyklus zu kommen; Skylake schafft nur einen/Zyklus, und Zen ist WIMRE auch nicht besser. Tatsaechlich bin ich mir nicht sicher, ob's ueberhaupt schon einen AMD64-Core gibt, der mehr schafft. Auf der ARM-Seite habe ich WIMRE schon Beschreibungen von Mikroarchitekturen gesehen, die mehr schaffen, vielleicht schlaegt da die einfachere Dekodierung durch.

Ich sehe auf absehbare Zeit dicke Kerne nicht ohne µOp-Cache. Auch erwarte ich keine zweigeteilten Decoder bei großen Designs. Nach Sprüngen nur die Hälfte der verfügbaren Kapazität vom Decoder ins Backend zu füttern ist ein viel zu großer Nachteil.

Das passiert ja nicht, ausser bei mispredictions (sehr selten). Das front end laeuft der Ausfuehrung voraus, und die Decoder laufen dem Renamer voraus. Jeder Decoder fuettert seinen Buffer mit 3 uops/cycle, und wenn der Renamer dann nach einem branch uOps aus dem Buffer des anderen Decoders holt, ist der schon gut gefuellt und kann den Renamer ohne Probleme 5uops/Cycle geben. Bei Chrestmont wirds mit dem Vorauslaufen schon etwas schwieriger, weil die Decoder zusammen 6 uops/cycle in die Buffer fuellen und der Renamer 6 uops/cycle herausholt, aber die Performance wird schon nicht schlechter sein wie bei Gracemont (ist sie laut Benchmarks auch nicht).

Bei GoldenCove kann das Renaming 6µOps/cycle ins Backend geben. Da direkt nach einem Sprung 50% der Kapazität zu verschenken wäre absurd und würde der Performance und Energieeffizienz massiv schaden.

Deswegen wird das nicht gemacht, auch nicht bei Gracemont.
 
mae schrieb:
Intel hat also die Effizienz des Decoders stark verbessert. In einer der Folien steht auch etwas von 3% Stromverbrauch fuer's Decoding im Vergleich zu 10% fuer einen Cache-Zugriff.
Ich vermute, dass sich hier auch bemerkbar macht, dass SRAM-Zellen nicht so gut mit den potentiell kleiner werdenden Strukturen skalieren und daher die Differenz bei der Effizienz kleiner wird zwischen Zugriffen auf µOp Cache und Decoder.
Aber welche Folien genau, bei dem Pixelbrei zu Skymont sehe ich nichts :)

Habe ich mir jetzt noch einmal durchgelesen, finde dort aber so eine Einschraenkung nicht; meinst Du vielleicht die, dass nur aus dem Buffer eines Decoders auf einmal in die Rename-Unit weitergeleitet wird, sodass nicht mehr als ein taken-branch pro cycle verarbeitet werden kann?

Ich hatte ein Fehler beim Verständnis, wie die Arbeitsteilung bei den Decodern realisiert ist. Die Aufteilung zwischen den Decodern erfolgt wohl aller n Instruktionen. Wenn innerhalb dieser n Instruktionen bereits ein Sprung ist (und die Branch Prediction das nicht abfängt), dann kann der zweite Decoder nichts beitragen.
Damit sind verteilte Decoder deutlich besser, als ich es erst angenommen bzw. falsch verstanden habe.

Jedenfalls ist auch diese Einschraenkung eine, die nicht im Prinzip der mehreren parallelen Decode vorgegeben ist. Im Gegenteil, eigentlich sollte es mit 3x3 Decodern moeglich sein, in guenstigen Faellen 3 taken branches/Zyklus zu verarbeiten, waehrend ein breiter Decoder nur einen pro Zyklus schafft.
Bei breiten Decodern kann die Branch Prediction und die Fetch-Stufe aber "einfach" Instruktionsketten entsprechend der vorhergesagten Sprünge zusammensetzen. Wenn da vorhergesagt ist, dass bei Instruktion 4 zu Instruktion 300 gesprungen wird, kann einem breiten Decoder ja folgendes gefüttert werden:
1 | 2 | 3 | 4 | 300 | 301 | 302 ..
Nach dem Bekanntwerden von Skymont und den Spekulationen dazu, bin ich aber auch nicht mehr all zu sehr überzeugt, ob geteilte Decoder nicht vielleicht doch eleganter sind.


Beim uOp-Cache braucht man auch Extra-Aufwand, um auf mehr als einen taken branch pro Zyklus zu kommen; Skylake schafft nur einen/Zyklus, und Zen ist WIMRE auch nicht besser. Tatsaechlich bin ich mir nicht sicher, ob's ueberhaupt schon einen AMD64-Core gibt, der mehr schafft. Auf der ARM-Seite habe ich WIMRE schon Beschreibungen von Mikroarchitekturen gesehen, die mehr schaffen, vielleicht schlaegt da die einfachere Dekodierung durch.
Aus Interesse, wo wäre eine entsprechende ARM Architektur beschrieben?
Und bei Superskalaren Prozessoren, die gerade bei x86 würde ich jetzt auch nicht den großen Bedarf sehen, dass man da vorsieht für einen Takt mehr als einen Branch einzuplanen. GoldenCove und Zen4 haben ja "nur" die Möglichkeit 6 µOps/Takt im Backend zu verarbeiten. Da lohnt es kaum Transistoren damit zu verplanen, dass man im Schnitt aller 2µOps verzweigen kann.
Edit: Gut, ich bin nicht all zu flüssig in Assembler, aber Code der zufällig aller zwei RISC Instruktionen springt und dabei nicht bei Zeiten den L1-Cache verlässt und vom "lahmen" L2 oder Schlimmeren landet kann ich mir nicht vorstellen.


Naja aber hauptsächlich lag ich falsch, mit meinem Missverständnis, wie die Arbeitsteilung zwischen den Decodern organisiert ist. Vielleicht gibt es doch bald dicke Kerne mit geteilten Decodern.
 
Zuletzt bearbeitet:
@mae sind diese decode Vorteil bei 2 x die gleiche Anwendung weil du was von 3x3 decode und so und was von 1 decode. Weil ich ja 2 x die selbe Anwendung verwende. Wie verhält es sich denn da so dann?
 
Piktogramm schrieb:
Ich vermute, dass sich hier auch bemerkbar macht, dass SRAM-Zellen nicht so gut mit den potentiell kleiner werdenden Strukturen skalieren

Spielt wohl auch eine Rolle. Ich glaube aber, dass sie vor allem frueher sehr energiehungrige Decoder hatten (also der Energiehunger fuer die Performance noch kein Problem war), und sie seither sparsame entwickelt haben.

Aber welche Folien genau, bei dem Pixelbrei zu Skymont sehe ich nichts :)

Ich auch nicht. "Folien" war das falsche Wort, ich meinte diese Grafik, die in dem Gracemont-Artikel verlinkt wird.

Aus Interesse, wo wäre eine entsprechende ARM Architektur beschrieben?

Als Andrei Frumusanu noch fuer Anandtech schrieb, hat er regelmaessig gute Artikel ueber ARM-Cores von ARM, Apple, und Samsung geschrieben, mit Mikroarchitektur-Information. Bei chipsncheese gibt's inzwischen auch ein paar Artikel in die Richtung. In welchem Artikel das vorkam, falls ueberhaupt, weiss ich nicht. Ich wuerde als erstes die Artikel uber die Apple-chips und da ueber die grossen Cores anschauen, und als naechstes die Artikel ueber Cortex-X-Kerne. Ich habe jetzt einmal Cortex X2: Arm Aims High angeschaut, und dort nichts davon gefunden; und an einigen anderen Stellen auch nicht:-(, noch nicht einmal irgendwelche Darstellungen von den execution ports von Cortex-X und Apple-Kernen (und da bin ich mir ziehmlich sicher, dass ich das gesehen habe).

Edit: Gut, ich bin nicht all zu flüssig in Assembler, aber Code der zufällig aller zwei RISC Instruktionen springt und dabei nicht bei Zeiten den L1-Cache verlässt und vom "lahmen" L2 oder Schlimmeren landet kann ich mir nicht vorstellen.

Stell dir Code vor wie

Code:
for (i=0; i<n; i++)
  if (a[i]<0)
    ca++;

Und stell Dir vor, dass die meisten a>=0 sind. Da hast Du ganz schnell einen taken branch alle 2 uops.

Sicher, sowas kommt nicht dauernd vor, und dadurch, dass das Front end in vielen Faellen ohnehin vorauseilt, ist es wohl nicht so schlimm, wenn es ab und zu einmal weniger produziert, weil da gerade ein taken branch zu knapp an dem davor war. Wenn man die Maschine dann immer breiter macht, und es Code gibt, der auch genuegend instruction-level parallelism hat, um das auszunutzen, wird 1 taken-branch/cycle immer mehr zu einem messbaren Bottleneck. Dann wird man sich irgendwann ueberlegen, ob man die Kosten fuer mehr taken branches/cycle nicht doch tragen will.
Ergänzung ()

latiose88 schrieb:
@mae sind diese decode Vorteil bei 2 x die gleiche Anwendung weil du was von 3x3 decode und so und was von 1 decode. Weil ich ja 2 x die selbe Anwendung verwende. Wie verhält es sich denn da so dann?

Es geht hier um single-threaded code. Die parallelen Decoder von Tremont ff. dekodieren unterschiedliche Teile des gleichen Befehlsstroms.
 
Ich freue mich ja, das hier mal gehaltvolle Diskussionen vorkommen. Das ist 'ne wohltuende Abwechslung zu dem üblichen "Welche Linux-Distri unterstützt am besten Games" etc.
 
  • Gefällt mir
Reaktionen: Piktogramm
mae schrieb:
Es geht hier um single-threaded code. Die parallelen Decoder von Tremont ff. dekodieren unterschiedliche Teile des gleichen Befehlsstroms.
achso beschleunigt das dann weil ja unterschiedliche bereiche des selben codes berechnen dann das ganze oder ergänzen sie das nur?
 
mae schrieb:
Stell dir Code vor wie

Code:
for (i=0; i<n; i++)
if (a[i]<0)
ca++;

Und stell Dir vor, dass die meisten a>=0 sind. Da hast Du ganz schnell einen taken branch alle 2 uops.
Wieder der Disclaimer, dass meine Kenntnisse in Assembler nicht all zu belastbar sind. Aber godbolt.org hilft: https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYTStJg1AB9U8lJL6yAngGVG6AMKpaAVxYM9DgDJ4GmADl3ACNMYgkAVlIAB1QFQlsGZzcPPVj4mwFffyCWUPCuKMtMa0ShAiZiAmT3Ty4LTCtMhnLKgmzAkLDIiwqqmtT6hT72v0687sKASgtUV2Jkdg4AegAqAGoAFQBPaMx17bnidbQsdYQwzFJ1knXaVCZ0dcN1zFVWaPoAOnXV5YBSDQAQT8BBOgggoPWDHcU3W/wA7AAhQFA9bo9ZQpj/CJIgBMCJxABF/gBmEnI+p40ik0jSKIANlICNIAA5SABOUhcDTc6lcWkAWik3KiXCZvKpNLppEZzLZnO5vI0iJJpJRwIxmME0LJRLxDLJGrRGKhoj1KvVqNRWv4x0hOrwFqNmLJjgYLqdeJR3qmiONWq1eCo6wg2NxToiascGj9yJtgcT5u9/xTVs1ifhCJJGfRqutufWxEwBHmDBO2PTQPzwI4M1onAivE83F4qE4jnWCiOi3heNJPFIBE0dZmAGsQHiog2OJJmyPSO2OLwFCBecOOFoZnBYEg0CxonQwuRKPvD/RwshgFJ6jRaAQwquIMEF8E/JVtpxB2/mMRtgB5YJtBKDdB33NhBH/BhaE/TdeCwYJXGARwxFoVdW1ILAWEMYBxDgzC8GLUoADdMHQrRSDeEpXAfL9eFBRoF1oPBgmID9nCwBcCGIPAWDo0hSOIYI4kwIlMGwoxmKMEcZioAxgAUAA1PBMAAd3/PYW0HfhBBEMR2BFHT5CUNQF10eoDGk0xjHMZjglXSAZlQaJmnQttBJ4rAHIgGZilKOwIAcAZPDxXkfDGXJ8hASQuXSBIBGCydeTi5oOki7oYoaJoyhGRKhkaECcraNKunCSRpGGNo8t6YqItK6LJF8nsDPrRt53wpd1lUVkGUFBlJHWYBkGQdYpC+LhQ1wQhblTAcpl4Dct3HEBSVJL4CUkftxQRVlJB6jkuRnOdSBbCilxXNchxk0gd0QEA5gIaIaJPCAzyPYgAlYJZut6/rBuG0bJHG3hMHwIhPL0Iy9PEQzZEUFR1Hw8zSFUtjojo1qOCbE6FyXf8aKesFUBDH6+oGoaRrGiaIGcA93r7OaFpk5bxS+BEpwRBlWVJPFCi4PEDvZI72rOzgLvXa7bogPdUDpi8Xrei8QGIYiecFK9htZUK+DoB9iCfF98J/D9+ONv9AOA6x+PAxgCCgmCFwQpCUNoND+KwnC8Io/AiJsUjyJB1RqNojCGJnCi7LYv8OKWCjuN4/jBOEpQxIk3C/FAODZPkpSVPUzT+Kh0QYekIz4dMpHJ30HCQGs2yWO8pyXMSNzFw8vAvPgXyCv8zxAoYJwXFqLwB5KiYChiOJ4qSIfBknjJEjHqL8uygRWn6We6iywq15GJfJhqjeUi3yqqn3gomvmRYJEx7HTrbTh1gAJQASSERxBUU0M1bfAgFDhCm6xWTrS+BoSaYMZpbXmldLOy0pz6E4Mde+i4xYWEuotUcCCOB4hFg/ZcMCloCUfIkaKQA==
:)

C:
int cnt(int num) {
    int a[27]={1,2,3,4,5,6,7,8,9,10,12,13,-14,15,16,0,1,2,3,4,5,6,7,8,9,10,0};
    int n=26;
    int ca=0;
    for (int i=0; i<n; i++){
        if (a[i]<0){
            ca++;
        }
    }
    return ca;
}
wird zu
Code:
cnt:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 24
        mov     DWORD PTR [rbp-132], edi
        mov     DWORD PTR [rbp-128], 1
        mov     DWORD PTR [rbp-124], 2
        mov     DWORD PTR [rbp-120], 3
        mov     DWORD PTR [rbp-116], 4
        mov     DWORD PTR [rbp-112], 5
        mov     DWORD PTR [rbp-108], 6
        mov     DWORD PTR [rbp-104], 7
        mov     DWORD PTR [rbp-100], 8
        mov     DWORD PTR [rbp-96], 9
        mov     DWORD PTR [rbp-92], 10
        mov     DWORD PTR [rbp-88], 12
        mov     DWORD PTR [rbp-84], 13
        mov     DWORD PTR [rbp-80], -14
        mov     DWORD PTR [rbp-76], 15
        mov     DWORD PTR [rbp-72], 16
        mov     DWORD PTR [rbp-68], 0
        mov     DWORD PTR [rbp-64], 1
        mov     DWORD PTR [rbp-60], 2
        mov     DWORD PTR [rbp-56], 3
        mov     DWORD PTR [rbp-52], 4
        mov     DWORD PTR [rbp-48], 5
        mov     DWORD PTR [rbp-44], 6
        mov     DWORD PTR [rbp-40], 7
        mov     DWORD PTR [rbp-36], 8
        mov     DWORD PTR [rbp-32], 9
        mov     DWORD PTR [rbp-28], 10
        mov     DWORD PTR [rbp-24], 0
        mov     DWORD PTR [rbp-12], 26
        mov     DWORD PTR [rbp-4], 0
        mov     DWORD PTR [rbp-8], 0
        jmp     .L2
.L4:
        mov     eax, DWORD PTR [rbp-8]
        cdqe
        mov     eax, DWORD PTR [rbp-128+rax*4]
        test    eax, eax
        jns     .L3
        add     DWORD PTR [rbp-4], 1
.L3:
        add     DWORD PTR [rbp-8], 1
.L2:
        mov     eax, DWORD PTR [rbp-8]
        cmp     eax, DWORD PTR [rbp-12]
        jl      .L4
        mov     eax, DWORD PTR [rbp-4]
        leave
        ret
Interessant für die Schleife ist da alles von .L4: bis jl .L4

Für den Fall, dass ca++; nicht ausgeführt wird, wird folgendes durchlaufen:
Code:
.L4:
        mov     eax, DWORD PTR [rbp-8]
        cdqe
        mov     eax, DWORD PTR [rbp-128+rax*4]
        test    eax, eax
        jns     .L3
# Hier wird das ADD nach dem jns    .L3 übersprungen
.L3:
        add     DWORD PTR [rbp-8], 1
.L2:
        mov     eax, DWORD PTR [rbp-8]
        cmp     eax, DWORD PTR [rbp-12]
        jl      .L4
Es sind also mit Adressgenerierung und dem Laden von Adressen in Register 9 Assemblerbefehle für zwei bedingte Sprünge. Jetzt habe ich keine Ahnung wie Intel x86 in µOps übersetzt und kann es nur mit RISC-V ASM von Godbolt vergleichen, aber es werden tendenziell mehr Befehle je Durchlauf der Schleife.
Nur nach x86 Assembler komme ich aber auf 3,5 Befehle je bedingtem Sprung, bei Prozessoren wo das Frontend irgendwo zwischen 6..8 µOps/Takt schafft sehe ich da keinen unmittelbaren Bedarf beim Decode mehr als einen Branch zu berücksichtigen.

Edit:
Und bei Fällen von großen Arrays a[] bin ich mir recht sicher, dass man da in der x86-Welt Schandtaten mit AVX2 oder neuer anrichten kann. Grob würde mir da Vorschweben so ein Array in Vektoren zu zerlegen, die Vektoren dann mit right Shift zu bearbeiten, sodass nur noch das Vorzeichenbit stehenbleibt. Danach kann man die Vektoren addieren und hat das gewünsche Ergebnis ganz ohne Branches. Zumindest für den Fall a[i]<0.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: andy_m4
uff ganz schön vielfältig der code.Ich verstehe zwar das meiste nicht.aber nicht schlecht.
 
@latiose88: Die Frage ist, womit man vergleicht. Goldmont+, der Vorgaenger des Tremont hatte einen Decoder fuer 4 Befehle. Die 2x3 des Tremont koennen natuerlich mehr Befehle pro Zyklus dekodieren, naemlich 6. Bei der Vorstellung des Tremont wurde gesagt, dass das so gewaehlt wurde, um Strom zu sparen, auch im Vergleich zum uOp-cache der P-cores. Und wir haben drueber diskutiert, ob das so stimmt, und was die Vor- und Nachteile davon sind.
Ergänzung ()

Piktogramm schrieb:
C:
int cnt(int num) {
    int a[27]={1,2,3,4,5,6,7,8,9,10,12,13,-14,15,16,0,1,2,3,4,5,6,7,8,9,10,0};
    int n=26;
    int ca=0;
    for (int i=0; i<n; i++){
        if (a[i]<0){
            ca++;
        }
    }
    return ca;
}
wird zu
Code:
[...]
.L4:
        mov     eax, DWORD PTR [rbp-8]
        cdqe
        mov     eax, DWORD PTR [rbp-128+rax*4]
        test    eax, eax
        jns     .L3
        add     DWORD PTR [rbp-4], 1
.L3:
        add     DWORD PTR [rbp-8], 1
.L2:
        mov     eax, DWORD PTR [rbp-8]
        cmp     eax, DWORD PTR [rbp-12]
        jl      .L4
[...]

godbolt bietet uebrigens auch short links an.

Wenn ich als Compiler-Option -O waehle, kommt als Code fuer die Schleife folgendes heraus:

Code:
.L3:
        cmp     DWORD PTR [rax], -2147483648
        sbb     edx, -1
        add     rax, 4
        cmp     rax, rcx
        jne     .L3

Die Optimierung hat es in diesem Fall geschafft, den Sprung des if wegzuoptimieren. Ist aber ein Sprung alle 5 Befehle; die uops duerften wie folgt sein: [rax], cmp, sbb, add, cmp+jne; also 5 uops/Iteration.

Wenn ich statt ca++ ca^=1 schreibe, damit diese Optimierung nicht geht, kommt folgendes heraus:

Code:
.L6:
        xor     ecx, 1
.L2:
        add     rax, 4
        cmp     rax, rdx
        je      .L5
.L3:
        cmp     DWORD PTR [rax], 0
        jns     .L2
        jmp     .L6
.L5:

Hier hat der Compiler die Schleife umarrangiert, um nur einen taken branch pro iteration zu haben. Sehr gefinkelt. Die Anzahl der uops ist im >0-Fall (Schleife von L2: bis jns L2) 4 uops: add, cmp+je, [rax] laden, cmp+jns. Also mehr als einen taken branch pro Zyklus machen zu koennen, wuerde sich schon auszahlen. Die Frage ist halt, wie teuer das in der Hardware ist.

Edit:
Und bei Fällen von großen Arrays a[] bin ich mir recht sicher, dass man da in der x86-Welt Schandtaten mit AVX2 oder neuer anrichten kann.

Ja, SIMD-Optimierung waere in diesem Beispiel moeglich. Aber es gibt genug Faelle, wo man eben weniger als 6 uops pro taken branch hat, das war nur ein einfaches Beispiel. Schon wenn statt i++ z.B. i+=20 stehen wuerde, wirds mit SIMD schwierig.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: andy_m4 und Piktogramm
@mae
Beim Link war ich eigentlich der Meinung, dass die Forensoftware das eh einkürzt. Naja es werden alle überleben :)

Und ja, bei -O werden Sprünge optimiert, bei -O2 werden die SSE (xmm) Register angesprochen und bei -O3 wird die ganze Funktion vorberechnet und meinem Beispiel enstsprechend einfach eine "1" in eax geschrieben und zurückgegeben. Die folgenden Beispiele sind daher auch angepasst, indem das Array der Funktion übergeben wird und n=2.000.000 gesetzt wurde, damit die Compiler die Schleifen nicht auflösen.

Und auch bei i+20 können Compiler vektorisieren. Spätestens -O2 nutzt fröhlich xmm und mit -O3 und -march=haswell ymm: https://godbolt.org/z/8Y61hzcMT

Für ca^=1: https://godbolt.org/z/hahKorTvE
-O springt fröhlich
-O2 Ein bedingter Sprung, SSE
-O3 -march haswell -> ymm und xmm


Ich bleibe dabei, beim bisheriger Parallelität der Prozessoren ist es nicht notwendig bis verschmerzbar, wenn "nur" ein Branch berücksichtigt wird. Bei >=8 Ops/Takt sehe ich es als sinnvolle Möglichkeit, darunter aber nicht.
mae schrieb:
Aber es gibt genug Faelle, wo man eben weniger als 6 uops pro taken branch hat,
Entsprechenden Code mag es geben. Aber sagen wir, wir haben ein Branch aller 5 Ops bei einer CPU die 6Ops/Takt kann. (In -> Instruktionen nach bedingtem Sprung; B -> Branch)
Code:
I1; I1; I1; I1; B; I2;
I2; I2; I2; B; I3; I3;
I3; I3; B; I4; I4; I4;
I4; B; I5; I5; I5; I5;
B; I6; I6; I6; I6; B;
I7; I7; i7; i7; B; I8;
Erst bei jedem 5. Takt wird eine spekulative Operation verschenkt. Also 96,6666% Effizienz und das auch nur im Fall, dass die Trefferquote der Branchprediction perfekt ist und jeder Zugriff vom L1-Cache abgefangen werden kann.
 
  • Gefällt mir
Reaktionen: andy_m4
Zurück
Oben