Forum: Compiler & IDEs GCC 11.2.0, unterschiedliche Optimierung size_t/uint32_t


von Vincent H. (vinci)


Lesenswert?

Ich bin gerade dabei kritschen Interrupt-Code für einen Cortex M4 zu 
optimieren und da fiel mir auf, dass GCC 11.2.0 (-Os) unterschiedlichen 
Code für size_t/uint32_t erzeugt.

Das Disassembly im IRQ ist ein Pattern wo Loop-Unrolling forciert wird 
sieht folgendermaßen aus:
1
// static size_t cnt_
2
// uint16_t ccr_
3
    if (cnt_ < ccr_) {
4
 8003b16:  8812        ldrh  r2, [r2, #0]
5
 8003b18:  4293        cmp  r3, r2
6
 8003b1a:  f04f 4290   mov.w  r2, #1207959552  ; 0x48000000
7
 8003b1e:  bf34        ite  cc
8
 8003b20:  2010        movcc  r0, #16
9
 8003b22:  f44f 1080   movcs.w  r0, #1048576  ; 0x100000
10
 8003b26:  f8c2 0c18   str.w  r0, [r2, #3096]  ; 0xc18

Dieser Teil wird dabei etwa ein dutzend Mal wiederholt.

Ersetze ich den size_t Zähler durch einen uint32_t Zähler, dann erzeugt 
der Compiler plötzlich ein zusätzliches Load für jeden 
"Schleifendurchlauf".
1
// static uint32_t cnt_
2
// uint16_t ccr_
3
    if (cnt_ < ccr_) {
4
 8003b28:  6819        ldr  r1, [r3, #0]
5
 8003b2a:  8812        ldrh  r2, [r2, #0]
6
 8003b2c:  4291        cmp  r1, r2
7
 8003b2e:  f04f 4290   mov.w  r2, #1207959552  ; 0x48000000
8
 8003b32:  bf34        ite  cc
9
 8003b34:  2110        movcc  r1, #16
10
 8003b36:  f44f 1180   movcs.w  r1, #1048576  ; 0x100000
11
 8003b3a:  f8c2 1c18   str.w  r1, [r2, #3096]  ; 0xc18

Ich versteh nicht so ganz welche Optimierung für size_t in Frage kommt 
die für uint32_t nicht greift? Beide Typen sind unsigned integer mit 32 
Bit Breite. Irgendwer eine Idee woran das liegen könnte?

Ich werf in der Zwischenzeit mal Clang an...

von Programmierer (Gast)


Lesenswert?

Hat es vielleicht was mit Aliasing zu tun (dann hilft restrict)? Zeig 
doch mal den Code...

von Vincent H. (vinci)


Lesenswert?

Der gesamte Code besteht aus einer Methode die unrolled wird. cnt_ und 
ccr_ sind Member der Klasse.
1
  void setClear() const {
2
    if (cnt_ < ccr_)
3
      // Pin setzen
4
    else
5
      // Pin löschen
6
  }

Clang fügt sowohl das 1. load ein, als auch ein weiteres am Schluss. 
Sehr spannend.
1
;     if (cnt_ < ccr_) {
2
 800d4d0: dc f8 00 10    ldr.w  r1, [r12]
3
 800d4d4: be f8 14 20    ldrh.w  r2, [lr, #20]
4
 800d4d8: 4f f0 00 43    mov.w  r3, #2147483648
5
 800d4dc: 91 42          cmp  r1, r2
6
 800d4de: 38 bf          it  lo
7
 800d4e0: 4f f4 00 43    movlo.w  r3, #32768
8
 800d4e4: c0 f8 00 3c    str.w  r3, [r0, #3072]
9
 800d4e8: d0 f8 00 1c    ldr.w  r1, [r0, #3072]

von Programmierer (Gast)


Lesenswert?

Vincent H. schrieb:
> void setClear() const {
>     if (cnt_ < ccr_)

Ohne den ganzen Code und Definition von cnt_ und ccr_ kann man da nichts 
zu sagen...

von Vincent H. (vinci)


Lesenswert?

Programmierer schrieb:
> Vincent H. schrieb:
>> void setClear() const {
>>     if (cnt_ < ccr_)
>
> Ohne den ganzen Code und Definition von cnt_ und ccr_ kann man da nichts
> zu sagen...

Was cnt_ und ccr_ is steht bereits oben.
1
struct S {
2
  static inline uint32_t cnt_{};
3
  uint16_t ccr_{};
4
};

von Oliver S. (oliverso)


Lesenswert?

Vincent H. schrieb:
> Beide Typen sind unsigned integer mit 32
> Bit Breite.

Es müssten sogar beide typedefs auf unsigned sein, also nicht nur das 
gleiche, sondern das selbe.

Oliver

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

Oliver S. schrieb:
> Vincent H. schrieb:
>> Beide Typen sind unsigned integer mit 32
>> Bit Breite.
>
> Es müssten sogar beide typedefs auf unsigned sein, also nicht nur das
> gleiche, sondern das selbe.
>
> Oliver

Folgendes schlägt allerdings fehl
1
static_assert(std::same_as<uint32_t, size_t>);

von Programmierer (Gast)


Lesenswert?

IMO riecht das stark nach Aliasing. Wenn "cnt_" und dein Counter beide 
den selben Typ haben (size_t), wird der Compiler annehmen dass beide auf 
der selben Speicherstelle liegen können. Versuche es mal mit
1
__restrict__
 (ist eine GCC/Clang-Erweiterung).

von Oliver S. (oliverso)


Lesenswert?

Vincent H. schrieb:
> Oliver S. schrieb:
>> Vincent H. schrieb:
>>> Beide Typen sind unsigned integer mit 32
>>> Bit Breite.
>>
>> Es müssten sogar beide typedefs auf unsigned sein, also nicht nur das
>> gleiche, sondern das selbe.
>>
>> Oliver
>
> Folgendes schlägt allerdings fehl
> static_assert(std::same_as<uint32_t, size_t>);

Dann drück halt auf die Taste deiner IDE, die dich zur jeweiligen 
typedef- Deklaration bringt, und schau nach, was da steht.

Oliver

von Vincent H. (vinci)


Lesenswert?

uint32_t entspricht long unsigned int
size_t entspricht unsigned int

_restrict_ brachte keinerlei Veränderung

Ich glaub eher dass für einen Typ irgendeine Optimierung im Register 
Allocator greift und für den andern eben nicht... oder irgendwie sowas?

von Programmierer (Gast)


Lesenswert?

Ohne kompletten Code (reduziertes Minimalbeispiel) ist das alles 
Kaffeesatzlesen. restrict ist etwas diffizil und muss richtig eingesetzt 
werden.

Vincent H. schrieb:
> Ich glaub eher dass für einen Typ irgendeine Optimierung im Register
> Allocator greift und für den andern eben nicht... oder irgendwie sowas?

Nein, die Compiler behandeln "effektiv gleiche" Typen (wie long und int 
wenn beide 32bit sind) im Backend (Optimierung) identisch (Pointer sind 
aber natürlich nicht kompatibel).

von Oliver S. (oliverso)


Lesenswert?

Vincent H. schrieb:
> Folgendes schlägt allerdings fehl
> static_assert(std::same_as<uint32_t, size_t>);

arm-gcc 11.2 kennt godbolt noch nicht, aber mit arm-gcc 11.1 im 32-Bit 
Mode hält das static_assert, im 64-Bit Mode nicht. Was allerdings so zu 
erwarten war.

Oliver

von Oliver S. (oliverso)


Lesenswert?

Vincent H. schrieb:
> uint32_t entspricht long unsigned int
> size_t entspricht unsigned int

Selbst wenn das bei deinem gcc beides 32 bit unsigned Typen sind und 
auch sein sollen (was man nachschauen müsste), sieht das doch seltsam 
aus. Ich hätte das, wenn schon mit long, umgekehrt erwartet.

Oliver

von Vincent H. (vinci)


Lesenswert?

Ja find ich auch, sieht fast so aus als hätten sich die Package Manager 
(Arch Repo) bei den Predefines vertan oder so...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.