GCC 11.2.0, unterschiedliche Optimierung size_t/uint32_t

#6795533
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...
#6795549
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]
Gast #6795566
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).
#6795567
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
Gast #6795584
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).

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren