Forum: Compiler & IDEs STM32: Arrays und Performance


von Nop (Gast)


Lesenswert?

Hallo,

ich habe neulich eine Beobachtung gemacht, die vielleicht den einen oder 
anderen interessieren könnte.

also, auf Cortex-M sind 32bit-Operationen schneller als 8bit, weil die 
Maskierung entfällt. Das gilt auch beim Laden aus dem Speicher. Etwa 
lokale Zähler und Schleifenvariablen sind daher als 32bit-Integer 
schneller als in 8bit, auch wenn man nur den 8bit-Wertebereich braucht.

Daher dachte ich nun, daß bei einer Lookup-Tabelle ebenfalls 32bit 
sinnvoll sind, auch wenn als Datentyp der Array-Elemente ein (u)int8_t 
ausreichen würde.

Pustekuchen. Benchmarks haben gezeigt (C99, ARM-GCC 5.4.1 mit -O2), daß 
die Tabellen in 8bit schneller sind, jedenfalls wenn man sie abhängig 
von dynamischen Variablen indexiert anspricht, mit "table[foo*(bar+1)]" 
oder so und nicht via "*table++".

Der Grund ist, daß beim Berechnen des Offsets der Index im Falle von 
8bit-Tabelleninhalten direkt schon in Bytes ist, während er bei 32bit 
erst noch geshiftet werden muß, was nicht umsonst ist.

Das Nette ist, daß man mit 8bit Speicher spart und zugleich ein bißchen 
Performance gewinnt.

von Andreas R. (daybyter)


Lesenswert?

@Nop: Vielen Dank für Dein hilfreiches Posting!

von Carl D. (jcw2)


Lesenswert?

Cortex M kann Laden/Speicher mit Angabe der Speicheradresse über ein 
"Basis-Register" + "Index-Register", wobei letzteres mit (1/)2/4/8 
skaliert werden kann, d.h. es können Arrays aus 8-/16-/32-/64-Bit Werten 
direkt über eine Laufvariable (i) angesprochen werden.
Dauert immer gleich lang.
Falls der Compiler das weis.

Bytes legt man aber schon aus Platzgründen "gepackt" an.

BTW, Intel Maschinen können das auch, Mainframes (gefühlt heute nicht 
mehr im Einsatz, aber Gefühle täuschen manchmal ;-) können das, ...

AVR's leider nicht ;-(

von Nop (Gast)


Lesenswert?

Carl D. schrieb:

> Falls der Compiler das weis.

Dann scheint es, daß GCC das zumindest in 5.4.1 nicht weiß.

Oder aber das Einrichten der Skalierung kostet selber erstmal Zeit. 
Einen eigenen Assembler-Befehl habe ich so auf Anhieb im instruction set 
dafür nicht gesehen - gibt's den?

> Bytes legt man aber schon aus Platzgründen "gepackt" an.

Jein; mitunter ist der Platzbedarf egal, weil nur Geschwindigkeit zählt. 
Die Lookup-Tabellen sind übrigens genau deswegen, obwohl statisch, 
trotzdem ins RAM gelinkt.

von Carl D. (jcw2)


Lesenswert?

Nop schrieb:
> Carl D. schrieb:
>
>> Falls der Compiler das weis.
>
> Dann scheint es, daß GCC das zumindest in 5.4.1 nicht weiß.
>
> Oder aber das Einrichten der Skalierung kostet selber erstmal Zeit.
> Einen eigenen Assembler-Befehl habe ich so auf Anhieb im instruction set
> dafür nicht gesehen - gibt's den?

LDR und STR und das ARM Programmierhandbuch für Cortex M(3).

"Base Scaled Index" ist eine Adressierungsart. (man darf auch noch eine 
Konstante bis 255 dazu tun).

Wobei natürlich table[foo*(Bar+1)] (2-dimensionaler wahlfreier Zugriff) 
damit nicht machbar ist, nur das
> Der Grund ist, daß beim Berechnen des Offsets der Index im Falle von
> 8bit-Tabelleninhalten direkt schon in Bytes ist, während er bei 32bit
> erst noch geshiftet werden muß, was nicht umsonst ist.
das Skalieren von (foo*(bar+1)) auf die Größe der Arrayelemente von 
2/4/8 Bytes, das kann die Maschine selbst und umsonst.

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

Hi Nop,
interessantes Thema, um das Ganze greifbarer zu machen, fehlen jedoch 
ein paar Infos, z.B.:

- Hardware (M0..7)
- Systemsetup (Speichermodell, Waitstates, ...)
- Beispielcode ggf. Disassembly
- quantitative Ergebnisse, auch bei anderen Optimierungsstufen, Zeiten 
und und Speicherverbrauch
- Vergleich mit anderen Compilern insbesondere Keil und IAR

Grüße,
 marcus

von Nop (Gast)


Lesenswert?

Carl D. schrieb:

> LDR und STR und das ARM Programmierhandbuch für Cortex M(3).

Ah ich habs für den M4 gefunden, Kapitel 3.4.3 "LDR and STR, register 
offset":

op{type}{cond} Rt, [Rn, Rm {, LSL #n}]

"The offset is specified by the register Rm and can be shifted left by 
up to 3 bits using  LSL".

> Wobei natürlich table[foo*(Bar+1)] (2-dimensionaler wahlfreier Zugriff)
> damit nicht machbar ist

Ist klar - was ich damit nur ausschließen wollte, ist ein Zugriff wie 
"table[3]", wo man den Shift auch schon zur Compilezeit machen könnte.

> das Skalieren von (foo*(bar+1)) auf die Größe der Arrayelemente von
> 2/4/8 Bytes, das kann die Maschine selbst und umsonst.

Skurril, wenn ich was messe, wo ich nichts messen sollte. Muß ich mal am 
WE gründlicher nachforschen.

von Dr. Sommer (Gast)


Lesenswert?

Nop schrieb:
> Die Lookup-Tabellen sind übrigens genau deswegen, obwohl statisch,
> trotzdem ins RAM gelinkt.

Das ist gar nicht unbedingt besser, mit Pech sogar langsamer (aber dafür 
deterministisch), denn manche STM32 haben beim Flash ja einen Cache und 
der ist ja auch über einen extra Bus angebunden.

Nop schrieb:
> Skurril, wenn ich was messe, wo ich nichts messen sollte. Muß ich mal am
> WE gründlicher nachforschen.
Assembly Listing ist hier wie üblich sehr hilfreich...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Nop schrieb:
> also, auf Cortex-M sind 32bit-Operationen schneller als 8bit, weil die
> Maskierung entfällt. Das gilt auch beim Laden aus dem Speicher. Etwa
> lokale Zähler und Schleifenvariablen sind daher als 32bit-Integer
> schneller als in 8bit, auch wenn man nur den 8bit-Wertebereich braucht.

Ja, diese Beobachtung habe ich für STM32 auch bereits vor ein paar 
Jahren gemacht. Seitdem benutze ich u.a. für Schleifenvariablen, für die 
ein uint8_t vom Wertebereich ausreicht, immer den uint_fast8_t Typ.

Ich könnte da zwar aus Bequemlichkeit auch einfach uint32_t (oder auch 
unsigned int) hinschreiben, aber es gibt 2 Gründe, warum ich lieber 
uint_fast8_t benutze:

  1. Portabilität: Wenn der Code sowohl auf STM32 als auch auf AVR
     laufen soll, empfiehlt sich ein uint_fast8_t, um den AVR
     nicht durch Verwendung eines uint32_t ins Schwitzen zu bringen.
     Konkrete Anwendung ist hier IRMP, welche auf vielen
     verschiedenen µCs zum Einsatz kommt. So läuft der Code auf
     allen Prozessoren mit optimaler Geschwindigkeit - egal ob
     8- oder 32-Bitter.

  2. Codeverständnis: Mit der Verwendung von uint_fast8_t deute ich
     an, dass der Wertebereich der Variablen von 0 bis 255 geht, auch
     wenn dann konkret auf STM32 letztendlich eine 32-Bit-Variable
     verwendet wird.

: Bearbeitet durch Moderator
von Nop (Gast)


Lesenswert?

Marcus H. schrieb:

> - Hardware (M0..7)

Cortex-M4, genauer gesagt STM32F405.

> - Systemsetup (Speichermodell, Waitstates, ...)

Kein externer Speicher, Waitstates nach Datenblatt - 5 bei 168MHz. 
Dcache/Icache enabled. Also die übliche Konfiguration.

> - Beispielcode ggf. Disassembly

Habe ich derzeit nicht, weil das nur Teil der Applikation ist und kein 
eigentlicher Benchmark. Allerdings konnte ich bei einem längeren 
Durchlauf eine Reduktion von 605s auf 600s messen, also der Effekt ist 
merklich. Meßschwankungen bei den Einzelläufen sind übrigens nicht 
vorhanden bzw. unter einer Sekunde, also der Unterschied ist kein 
Rauschen.

Muß ich mal ein Minimalbeispiel zusammenstellen, vielleicht ein simpel 
runtergehacktes "Game of Life" oder so, mal mit einem Array aus uint8_t, 
mal mit uint32_t.

> - quantitative Ergebnisse, auch bei anderen Optimierungsstufen, Zeiten

Ließe sich machen. Wobei meine Erfahrung ist, daß man -O3 gar nicht erst 
testen braucht, weil es sowieso langsamer als -O2 ist.

> und Speicherverbrauch

Ist für mich nicht relevant, mich interessiert nur die Geschwindigkeit.

> - Vergleich mit anderen Compilern insbesondere Keil und IAR

Fällt aus, weil ich die nicht habe - bei Keil ist die Gratisversion zu 
begrenzt, und bei IAR weiß ich nichtmal, ob es überhaupt eine gibt. Nur 
für einen Test setze ich aber keine Toolchain von Compilern auf, die ich 
sonst ohnehin nicht einsetzen kann.

Andererseits, mit dem (noch zu erstellenden) Minimalbeispiel könnten das 
ja auch andere machen, die die Toolchains ohnehin schon benutzen.

von Nop (Gast)


Lesenswert?

Dr. Sommer schrieb:

> denn manche STM32 haben beim Flash ja einen Cache

Der Dache, ja, aber der wirkt nicht bei scattered access, und selbst 
wenn, wäre das jedesmal ein Cache Miss, weil bis zum nächsten Lookup 
noch einiges passiert.

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

@Nop
Danke für Dein Feedback. Es tut gut, wenn die Bemühung um objektive 
Berichterstattung honoriert wird.

Ich hatte vor Jahren mal einen Artikel für eine Zeitschrift geschrieben.
Es ging um CoreMark 1.0 auf STM32F1 / STM32F4.
Wir sind zwar mittlerweile zig Compilerversionen weiter, aber vielleicht 
vermittelt der Text immer noch ein bisserl Gefühl für die verschiedenen 
Konfigurationen.

Ich verwende aktuell meist Atollic/GCC. Dadurch kann ich dem Kunden für 
Wartungszwecke ein komplettes, kostenloses Entwicklungssystem in die 
Hand geben.
Allerdings würde ich, wenn's bei großen Stückzahlen auf der MCU eng 
wird, Testläufe mit IAR bzw. Keil machen. Und ja, die bieten 
Teststellungen.

http://www.harerod.de/links_ger.html ->
CoreMark_STM32 - Testbericht über CoreMark 1.0 Läufe auf STM32 mit 
verschiedenen Compilereinstellungen.

von Nop (Gast)


Angehängte Dateien:

Lesenswert?

So, hier mal Ergebnisse. Interessant ist, daß signed und unsigned 
unterschiedlich schnell gehen, sobald man mit O2 optimiert.

Da ich ursprünglich nur signed hatte, ist mir auch nur die 
Beschleunigung beim Übergang auf 8 bit aufgefallen; bei unsigned wäre es 
aber umgedreht gewesen.

Wer das noch mit anderen Compilern oder so probieren will: Quelltext 
liegt bei, kann man in jedes Framework recht einfach reinsetzen. Eine 
Demo-main() für PC liegt auch bei. Die erzeugten Assembler-Listings für 
alle 4 Varianten (signed/unsigned, O2/O0) ebenfalls.

GCC: arm-none-eabi 5.4.1

Optionen: -Wall -std=c99 -mcpu=cortex-m4 -mtune=cortex-m4 -g -mthumb

STM32F405, dCache/iCache enabled, ART enabled, 168 MHz, 5 flash 
waitstates

NUM_GENS 100000ul

-O02
unsigned:
 8 bit: 28.04 s
32 bit: 27.43 s

signed:
 8 bit: 26.82 s
32 bit: 28.04 s


-O00
unsigned:
 8 bit:  97.50 s
32 bit: 100.55 s

signed:
 8 bit:  97.50 s
32 bit: 100.55 s

Und für die, die nur eben den Code angucken wollen, ohne das 
runterzuladen:
1
/*************************************************************************
2
"Conway's Game of Life" benchmark for 8 bit and 32 bit data types.
3
The calculations start with a pseudo-random, but reproducible world. The
4
calculations are identical in every benchmark call with the same number
5
of generations.
6
7
resource requirements:
8
needs about 8 kB RAM for the "game of life" worlds at 32x32 cells.
9
10
external requirements:
11
needs the following get-time function:
12
int32_t Get_Time(void);
13
14
benchmark functions:
15
int32_t Gol_Benchmark_8(uint32_t num_generations);
16
int32_t Gol_Benchmark_32(uint32_t num_generations);
17
18
SIGNED_TEST:  if this define is enabled, signed integers are used.
19
20
input:        desired number of generations.
21
22
output:       difference between Get_Time() at start and end, in whatever
23
              unit the Get_Time() function is using. The initialization
24
              is not part of the benchmark.
25
26
side effects: the volatile global variable "prevent_compiler_opt" is set
27
              to the final state of the middle "game of life" cell. This
28
              keeps the compiler from optimizing the benchmark away.
29
30
limitations:  cell 0 is not updated, and the border wrapping is strange,
31
              but it doesn't matter for benchmarking.
32
*************************************************************************/
33
34
#include <stdint.h>
35
36
#define SIGNED_TEST
37
38
#ifdef SIGNED_TEST
39
    #define INT8 int8_t
40
    #define INT32 int32_t
41
#else
42
    #define INT8 uint8_t
43
    #define INT32 uint32_t
44
#endif
45
46
#define DEAD      0
47
#define ALIVE     1U
48
49
/*must be powers of 2*/
50
#define ROWS      32UL
51
#define COLS      32UL
52
53
#define WRAP_MASK (ROWS * COLS - 1UL)
54
55
extern int32_t Get_Time(void);
56
57
volatile INT32 prevent_compiler_opt;
58
59
/*reuse the worlds for the 8 bit test via pointer casting*/
60
static INT32 world_0[ROWS * COLS];
61
static INT32 world_1[ROWS * COLS];
62
63
static uint32_t rand_state;
64
65
static uint32_t Random(uint32_t max_val) {
66
    rand_state *= 1103515245UL;
67
    rand_state += 12345UL;
68
    return((rand_state >> 16U) % max_val);
69
}
70
71
static void Gol_Init_8(INT8 *world_8) {
72
    uint32_t cnt;
73
    rand_state = 42UL; /*pseudo-random, reproducible init*/
74
    for (cnt = 0; cnt < ROWS * COLS; cnt++)
75
        world_8[cnt] = (INT8) Random(ALIVE+1UL);
76
}
77
78
static void Gol_Init_32(INT32 *world_32) {
79
    uint32_t cnt;
80
    rand_state = 42UL; /*pseudo-random, reproducible init*/
81
    for (cnt = 0; cnt < ROWS * COLS; cnt++)
82
        world_32[cnt] = (INT32) Random(ALIVE+1UL);
83
}
84
85
static void Gol_Iteration_8(const INT8 *old_world, INT8 *new_world) {
86
    uint32_t base_cell;
87
    /*some simplifications here to avoid benchmarking the loop overhead
88
      or the wrapping.
89
      cell 0 is not updated, and the wrapping is strange, but it doesn't
90
      matter for benchmarking.*/
91
    for (base_cell = ROWS * COLS - 1UL; base_cell != 0; base_cell--) {
92
        INT32 cell_state, living_adjacents;
93
94
        /*counting from upper left adjacent, clockwise*/
95
        living_adjacents  = old_world[(base_cell - COLS - 1UL) & WRAP_MASK];
96
        living_adjacents += old_world[(base_cell - COLS      ) & WRAP_MASK];
97
        living_adjacents += old_world[(base_cell - COLS + 1UL) & WRAP_MASK];
98
        living_adjacents += old_world[(base_cell        + 1UL) & WRAP_MASK];
99
        living_adjacents += old_world[(base_cell + COLS + 1UL) & WRAP_MASK];
100
        living_adjacents += old_world[(base_cell + COLS      ) & WRAP_MASK];
101
        living_adjacents += old_world[(base_cell + COLS - 1UL) & WRAP_MASK];
102
        living_adjacents += old_world[(base_cell        - 1UL) & WRAP_MASK];
103
104
        cell_state = old_world[base_cell];
105
        if (cell_state == DEAD) {
106
            if (living_adjacents == 3) /*cell born*/
107
                new_world[base_cell] = ALIVE;
108
            else
109
                new_world[base_cell] = DEAD;
110
        } else { /*cell has been alive*/
111
            if ((living_adjacents == 2) || (living_adjacents == 3))
112
                new_world[base_cell] = ALIVE; /*cell survives*/
113
            else
114
                new_world[base_cell] = DEAD; /*cell dies*/
115
        }
116
    }
117
}
118
119
static void Gol_Iteration_32(const INT32 *old_world, INT32 *new_world) {
120
    uint32_t base_cell;
121
    /*some simplifications here to avoid benchmarking the loop overhead
122
      or the wrapping.
123
      cell 0 is not updated, and the wrapping is strange, but it doesn't
124
      matter for benchmarking.*/
125
    for (base_cell = ROWS * COLS - 1UL; base_cell != 0; base_cell--) {
126
        INT32 living_adjacents, cell_state;
127
128
        /*counting from upper left adjacent, clockwise*/
129
        living_adjacents  = old_world[(base_cell - COLS - 1UL) & WRAP_MASK];
130
        living_adjacents += old_world[(base_cell - COLS      ) & WRAP_MASK];
131
        living_adjacents += old_world[(base_cell - COLS + 1UL) & WRAP_MASK];
132
        living_adjacents += old_world[(base_cell        + 1UL) & WRAP_MASK];
133
        living_adjacents += old_world[(base_cell + COLS + 1UL) & WRAP_MASK];
134
        living_adjacents += old_world[(base_cell + COLS      ) & WRAP_MASK];
135
        living_adjacents += old_world[(base_cell + COLS - 1UL) & WRAP_MASK];
136
        living_adjacents += old_world[(base_cell        - 1UL) & WRAP_MASK];
137
138
        cell_state = old_world[base_cell];
139
        if (cell_state == DEAD) {
140
            if (living_adjacents == 3) /*cell born*/
141
                new_world[base_cell] = ALIVE;
142
            else
143
                new_world[base_cell] = DEAD;
144
        } else { /*cell has been alive*/
145
            if ((living_adjacents == 2) || (living_adjacents == 3))
146
                new_world[base_cell] = ALIVE; /*cell survives*/
147
            else
148
                new_world[base_cell] = DEAD; /*cell dies*/
149
        }
150
    }
151
}
152
153
/*the 8 bit benchmark API function*/
154
int32_t Gol_Benchmark_8(uint32_t num_generations) {
155
    INT8 *old_world, *new_world;
156
    int32_t start_time, end_time;
157
    uint32_t cnt;
158
    /*set up buffers*/
159
    old_world = (INT8 *) world_0;
160
    new_world = (INT8 *) world_1;
161
    /*init time does not count*/
162
    Gol_Init_8(old_world);
163
    start_time = Get_Time();
164
165
    for (cnt = num_generations; cnt != 0; cnt--) {
166
        INT8 *tmp_ptr;
167
        Gol_Iteration_8(old_world, new_world);
168
        /*switch buffers*/
169
        tmp_ptr = old_world;
170
        old_world = new_world;
171
        new_world = tmp_ptr;
172
    }
173
174
    end_time = Get_Time();
175
    prevent_compiler_opt = old_world[ROWS * COLS / 2UL];
176
    return(end_time - start_time);
177
}
178
179
/*the 32 bit benchmark API function*/
180
int32_t Gol_Benchmark_32(uint32_t num_generations) {
181
    INT32 *old_world, *new_world;
182
    int32_t start_time, end_time;
183
    uint32_t cnt;
184
    /*set up buffers*/
185
    old_world = world_0;
186
    new_world = world_1;
187
    /*init time does not count*/
188
    Gol_Init_32(old_world);
189
    start_time = Get_Time();
190
191
    for (cnt = num_generations; cnt != 0; cnt--) {
192
        INT32 *tmp_ptr;
193
        Gol_Iteration_32(old_world, new_world);
194
        /*switch buffers*/
195
        tmp_ptr = old_world;
196
        old_world = new_world;
197
        new_world = tmp_ptr;
198
    }
199
200
    end_time = Get_Time();
201
    prevent_compiler_opt = old_world[ROWS * COLS / 2UL];
202
    return(end_time - start_time);
203
}

von Nico W. (nico_w)


Lesenswert?

Könntest du das ganze noch mit -Os machen?

von Nop (Gast)


Lesenswert?

Nico W. schrieb:
> Könntest du das ganze noch mit -Os machen?

Os und O03 funktionieren leider nicht, weil GCC dann anfängt 
herumzuspinnen und irgendwelche Library-Funktionen einfügen möchte, die 
ich gar nicht aufrufe. Das führt dann zu unresolved symbols und 
Linkerfehlern.

von Nico W. (nico_w)


Lesenswert?

Gut, dann kau ich das einmal neu durch. Das liegt sicher nicht am GCC.
Ich habe hier nen STM32F411 auf 100MHz. GCC 6.2.1
1
signed:
2
3
      | O0     | Os     | O1     | O2     | O3
4
------|--------|--------|--------|--------|-------
5
8bit  | 88.10s | 28.82s | 29.64s | 23.73s | 22.67s
6
32bit | 90.12s | 33.97s | 32.79s | 25.69s | 26.78s
7
8
unsigned:
9
10
      | O0     | Os     | O1     | O2     | O3
11
------|--------|--------|--------|--------|-------
12
8bit  | 88.12s | 30.88s | 32.84s | 21.73s | 21.71s
13
32bit | 87.06s | 29.86s | 33.79s | 28.76s | 23.72s

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Nico W. schrieb:
> Gut, dann kau ich das einmal neu durch. Das liegt sicher nicht am GCC.

Vermutlich doch, weil ich ohne C-Standard-Bibliothek linke, GCC aber bei 
Os und O3 selber einfach unaufgefordert Funktionen davon reinnimmt. Das 
fällt natürlich auf die Nase. Linkst Du die Standardbibliothek mit rein?

Deine Ergebnisse sind relativ ähnlich, nur bei O0/unsigned hat 6.2.1 
wohl aufgeholt, dafür aber bei O2/unsigned ganz stark nachgelassen. Das 
sieht nach einer Regression aus, denn da ist 8bit ja sagenhafte 32% 
schneller.

Da, wie oben im Thread dargelegt, die Adressierung eigentlich gratis 
sein sollte, ist das schon bemerkenswert.

von Nico W. (nico_w)


Lesenswert?

Nop schrieb:
> Linkst Du die Standardbibliothek mit rein?

Wieder was gelernt. Ich hab hier zumindest kein -nostdinc im Makefile, 
falls es das ist was du meinst.

Ansonsten darfst du nicht vergessen, das mein Prozessor nur mit 100MHz 
läuft. Deiner aber mit 168MHz.

Als Basis hab ich jetzt einfach meinen Port der Teacup-Firmware 
genommen. Falls du genau sehen willst, wie das Makefile aussieht: 
https://github.com/Traumflug/Teacup_Firmware/blob/arm-stm32f411-port/Makefile-ARM

von Nop (Gast)


Lesenswert?

Nico W. schrieb:

> Wieder was gelernt. Ich hab hier zumindest kein -nostdinc im Makefile,
> falls es das ist was du meinst.

Ich habe "-nostartfiles -nodefaultlibs -ffreestanding" mit im 
Linkeraufruf, das dürfte der verantwortliche Unterschied sein.

> Ansonsten darfst du nicht vergessen, das mein Prozessor nur mit 100MHz
> läuft. Deiner aber mit 168MHz.

Das ist klar, aber mit den 32% meinte ich bei Dir den Unterschied 
innerhalb Deiner Meßergebnisse: 8bit/21.7s, 32bit/28.7s. Das ist 
wesentlich mehr Unterschied, als ich mit GCC 5.4.1 bei O2/unsigned 
zwischen 8bit/32bit messe.

von Jim M. (turboj)


Lesenswert?

Nop schrieb:
> Ich habe "-nostartfiles -nodefaultlibs -ffreestanding" mit im

Irks: IIRC deaktiviert -ffreestanding etliche interne Builtins und 
Optimierungen, z.B. wird bei strlen("Hallo") das strlen() aufgerufen und 
nicht durch eine Konstante ersetzt. Nimm das mal raus...

von Nop (Gast)


Lesenswert?

Jim M. schrieb:

> Irks: IIRC deaktiviert -ffreestanding etliche interne Builtins und
> Optimierungen,

Laut GCC-Doku ist ffreestanding dafür gedacht: "A freestanding 
environment is one in which the standard library may not exist, and 
program startup may not necessarily be at main." Genau deswegen habe ich 
das drin. Das Problem ist auf Os/O3 eher, daß GCC -ffreestanding 
gepflegt ignoriert und trotzdem versucht, mir Funktionen aus der 
Runtime-Lib reinzudrücken.

> z.B. wird bei strlen("Hallo") das strlen() aufgerufen und
> nicht durch eine Konstante ersetzt.

Bei den wenigen Gelegenheiten, wo ich die Länge eines Strings brauche, 
ist der sowieso von unbekannter Länge.

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.