Schieben vs. Tabelle

Gast #7070653
Lesenswert?

Ich würde gerne zeitkritische sachen in C auf einem wirklich kleinen uC 
wie Attiny10 programmieren. Da mein PC gerade Lieferprobleme hat, muss 
ich "trocken-programmieren"

uint_8t maske,n;

/* ein paar Berechnungen */

maske = 0xFF >> n;

Die Frage, nach 8 mal schieben ist alles raus, maske ist dann 0. Würde, 
falls n jetzt zum Beispiel 255 ist, der Compiler den Armen uC jetzt 
tatsächlich 255 x schieben lassen? Oder limitiert der Compiler die 
Schieberei auf 8? Oder legt der Compiler netterweise eine für den User 
unsichtbare Tabelle mit 10 Einträgen an?
Sorry for that, aber ich habe in Noment nur Handy.
Ich sollte einen C Compiler für Handy suchen.
Gast #7070661
Lesenswert?

Bertram Bügler schrieb:
> der Compiler den Armen uC jetzt
> tatsächlich 255 x schieben lassen? Oder limitiert der Compiler die
> Schieberei auf 8? Oder legt der Compiler netterweise eine für den User
> unsichtbare Tabelle mit 10 Einträgen an?
Wie er will. Er kann auch intern ein paar Floating-Point Berechnungen 
machen, wenn er meint, dass das notwendig ist und am Ende alles richtig 
ist.

> Ich sollte einen C Compiler für Handy suchen.
Ein Compiler liefert Dir ein Compilat. Das sagt nichts aus über das 
Compilat eines anderen Compilers oder das beim nächsten Start.
Gast #7070666
Lesenswert?

Bertram Bügler schrieb:
> Die Frage, nach 8 mal schieben ist alles raus, maske ist dann 0. Würde,
> falls n jetzt zum Beispiel 255 ist, der Compiler den Armen uC jetzt
> tatsächlich 255 x schieben lassen? Oder limitiert der Compiler die
> Schieberei auf 8? Oder legt der Compiler netterweise eine für den User
> unsichtbare Tabelle mit 10 Einträgen an?

Ein Shift der Breite des Typs oder mehr ist undefined behavior
Gast #7070751
Lesenswert?

Bertram Bügler schrieb:
> Die Frage, nach 8 mal schieben ist alles raus, maske ist dann 0. Würde,
> falls n jetzt zum Beispiel 255 ist, der Compiler den Armen uC jetzt
> tatsächlich 255 x schieben lassen? Oder limitiert der Compiler die
> Schieberei auf 8? Oder legt der Compiler netterweise eine für den User
> unsichtbare Tabelle mit 10 Einträgen an?

Servus,

keine der genannten Variablen ist volatile und falls in "ein paar 
Berechnungen" nirgends ein volatile-Wert genutzt wird, von dem "n" 
abhängt, dann sollte der Compiler das Ergebnis von "maske = 0xFF >> n;" 
bereits zur compile-zeit berechnen können und dann (bei höherer 
Optimierungsstufe) einfach entsprechend zuweisen.

Gruß,
Michael
Gast #7070793
Lesenswert?

Michael F. schrieb:
> keine der genannten Variablen ist volatile und falls in "ein paar
> Berechnungen" nirgends ein volatile-Wert genutzt wird, von dem "n"
> abhängt, dann sollte der Compiler das Ergebnis von "maske = 0xFF >> n;"
> bereits zur compile-zeit berechnen können und dann (bei höherer
> Optimierungsstufe) einfach entsprechend zuweisen.
> Gruß,
> Michael

Natürlich wird n berechnet und maske wird weiterverwendet. Und

maske = 0xFF >> n;
seriell_out(maske);
/* seriell_out(0xFF >> n); */
Liegt in einer zeitkritischen Schleife und wird ganz häufig aufgerufen!
Ich könnte natürlich schreiben :

if (n > 8) n = 8; // nötig?!

Aber da ich NICHT "volatile" verwendet habe ist meine Frage ob C 
Compiler (GCC) in hoher Optimierungsstufe mir automatisch die 
Limitierung der Schiebevorgänge auf 8 abnehmen?
#7070831
Lesenswert?

Bertram Bügler schrieb:
> Aber da ich NICHT "volatile" verwendet habe ist meine Frage ob C
> Compiler (GCC) in hoher Optimierungsstufe mir automatisch die
> Limitierung der Schiebevorgänge auf 8 abnehmen?

Nein.
Da das aber, wie schon geschrieben wurde, undefined behaviour wird, kann 
der Compiler da dann andere, für dich unerwartete Dinge tun.

Das es dazu auf einem AVR viel schneller wäre, die Maske jedes mal um 1 
zu shiften, an statt den Ausgangswert um n, sei hier nur angemerkt.

Alles in allem solltest du aber zunächst laufen lernen, bevor du zu 
sprinten beginnst.

Oliver
Gast #7070836
Lesenswert?

Oliver S. schrieb:
> Alles in allem solltest du aber zunächst laufen lernen, bevor du zu
> sprinten beginnst.

Dummer Spruch.


> Das es dazu auf einem AVR viel schneller wäre, die Maske jedes mal um 1
> zu shiften, an statt den Ausgangswert um n,

Wertvoller Hinweis.


Bei Programmieren geht es darum, es gleich von Anfang an richtig zu 
lernen und sich nicht widersinnige Beispiele einfallen zu lassen, nur um 
irgendwelchen Akadämikern was zu beweisen.
Moderator #7070837
Lesenswert?

Bertram Bügler schrieb:
> Aber da ich NICHT "volatile" verwendet habe ist meine Frage ob C
> Compiler (GCC) in hoher Optimierungsstufe mir automatisch die
> Limitierung der Schiebevorgänge auf 8 abnehmen?

Hoffentlich nicht, denn das wäre ja für alle n<8 (was der Normalfall
sein sollte) keine Optimierung, sondern das Gegenteil davon. Sinnvolle
Optimierungen kann der Compiler nur dann vornehmen, wenn er bereits zu
Compilezeit Schlüsse über mögliche Werte von n ziehen kann. Ob dies der
Fall ist, hängt davon ab, was sich hinter
1
/* ein paar Berechnungen */

verbirgt.
#7070844
Lesenswert?

Mit uint8_t hast Du einen logical-right-shift. Bei uint8_t bekommst Du 
das auch mit Division durch 2 statt >>. Natürlich ist der Compiler so 
intelligent, und ersetzt die Division durch `lsr` (AVR). Dann hast Du 
das Problem mit dem UB nicht, wenn mehr als die Anzahl der Bits 
geschoben wird. Nichts desto trotz machen die meisten Compiler dann aus 
dem Ausdruck eine `0` ...
#7070846
Lesenswert?

Bertram Bügler schrieb:
> Natürlich wird n berechnet

Dann kennt der Compiler den Wert von n nicht und darf davon ausgehen, 
dass n nie größer als 8 wird, weil:

Jemand2 schrieb:
> Ein Shift der Breite des Typs oder mehr ist undefined behavior

Ich als Compiler würde 8 shift-right Befehle hintereinander schreiben 
und je nach n die ersten 0 bis 7 davon überspringen. Die CPU kann 
wahrscheinlich sinngemäß etwas wie "add pc,(8-n)" -- optimal schnell, 
korrekt übersetzt, aber mit eingebauter Überraschung ;)
Gast #7071150
Lesenswert?

Michael F. schrieb:
> keine der genannten Variablen ist volatile und falls in "ein paar
> Berechnungen" nirgends ein volatile-Wert genutzt wird, von dem "n"
> abhängt, dann sollte der Compiler das Ergebnis von "maske = 0xFF >> n;"
> bereits zur compile-zeit berechnen können

Nee, falls da irgend ein Argument der Funktion an den ominösen 
Berechnungen beteiligt ist oder ein HW-Register oder irgend eine globale 
Variable, dann kann das eben nicht zur Compilezeit berechnet werden. Ein 
'volatile' ist da nicht nötig.

W.S.
Gast #7071195
Lesenswert?

Oliver S. schrieb:
> Das es dazu auf einem AVR viel schneller wäre, die Maske jedes mal um 1
> zu shiften, an statt den Ausgangswert um n, sei hier nur angemerkt.

Nein n ist nicht etwa der Schleifenzähler, n ergibt sich aus wilden 
Berechnungen mit dem Cordic-Algorithmus. (Der Attiny10 ist 
wahrscheinlich zu klein für float Berechnungen und so mache ich 
Sinus/Cosinus mit dem Cordic-Algorithmus)

Als Tabelle würde das so aussehen

unsigned_8t[9]={0xFF,0x7F,0x3F,0x1F,0x0F,0x07,0x03,0x01,0x00};
#7071349
Lesenswert?

Im folgenden mal ein paar Varianten, die mir noch so eingefallen sind:

* test1() führt nie zu UB
* test2() führt zu UB wenn die n_mask größer als 0x0f ist, also der 
Shift >= 16, leider wird das UB vom Compiler hier nicht angewarnt (NDR).
* test3() deckt UB ggf. zur Compiler-Zeit auf (UB in constexpr ist 
zwingend ein error)
* test4() führt nie zu UB

Wegen test3() und der LUT habe ich einfach mal angenommen, dass es eine 
sinnvolle Maske für das `n` gibt, aber trotzdem ein n >= 8 zugelassen.
1
#include <array>
2

3
volatile uint8_t x;
4
volatile uint8_t n;
5

6
static constexpr uint8_t n_mask{0x0f};
7
//static constexpr uint8_t n_mask{0x1f}; // führt zu UB (s.u.)
8

9
void test1() {
10
    uint8_t m{0xff};
11
    for(uint8_t i{}; i < (n & n_mask); ++i) {
12
        m >>= 1; // 8-Bit Shift
13
    }
14
    x = m;    
15
}
16

17
void test2() {
18
    uint8_t m2{0xff};
19
    x = m2 >> (n & n_mask);  // no UB, wenn rhs < 16: Int-Promo 
20
}
21

22
void test3() {
23
    static constexpr auto lut = []{
24
        std::array<uint8_t, (n_mask + 1)> d;
25
        for(uint8_t i{0}; auto& v : d) {
26
            v = 0xffu >> i++; // falls UB vorhanden, wird es aufgedeckt
27
        }
28
        return d;
29
    }();
30
    x = lut[n & n_mask];
31
}
32

33
void test4() {
34
    switch(n) {
35
    case 0:
36
        x = 0xff;
37
        break;
38
    case 1:
39
        x = 0x7f;
40
        break;
41
    case 2:
42
        x = 0x3f;
43
        break;
44
    case 3:
45
        x = 0x1f;
46
        break;
47
    case 4:
48
        x = 0x0f;
49
        break;
50
    case 5:
51
        x = 0x07;
52
        break;
53
    case 6:
54
        x = 0x03;
55
        break;
56
    case 7:
57
        x = 0x01;
58
        break;
59
    default:
60
        x = 0;
61
        break;
62
    }
63
}

Im Anhang das Kompilat.
Angehängte Dateien:
Gast #7075172
Lesenswert?

Bertram Bügler schrieb:
> maske = 0xFF >> n;
> seriell_out(maske);
> /* seriell_out(0xFF >> n); */
> Liegt in einer zeitkritischen Schleife und wird ganz häufig
> aufgerufen!
> Ich könnte natürlich schreiben :
>
> if (n > 8) n = 8; // nötig?!

Wenn n größer als 8 sein kann, dann ist es absolut nötig, sonst darf der 
Compiler auch Code erzeugen der in dem Fall Deine HDD formatiert, alle 
GPIOs auf wechselnde Zufallswerte setzt oder was ihm sonst lustiges 
einfällt. Wird er zwar wahrscheinlich nicht machen, wäre ja auch 
unnötiger Aufwand, aber falls er es macht, hättest Du keinen Grund Dich 
zu beschweren.

Besser wäre allerdings (weil es den - zumindest bei breiteren Datentypen 
ebenfalls problematischen - Fall n=Bitbreite des zu schiebenden Typs 
auch korrekt abfängt):
1
  maske = (n>7) ? 0 : 0xFF >> n
#7075218
Lesenswert?

(prx) A. K. schrieb:
> mIstA schrieb:
>> Wenn n größer als 8 sein kann,
>
> C rechnet mindestens in int.

Wie oben

Beitrag "Re: Schieben vs. Tabelle"

schon gesagt, haben wir Integerpromotion. Daher tauchen Probleme erst 
mit
n >= 16 auf. Wie man im Assembler-Code auch schön sehen kann, macht der 
avr-gcc einen 16-Bit Shift daraus.

Nur bei einem einfachen Shift um eine Position optimiert der Compiler 
die Integerpromotion wieder weg und macht 8-Bit Shift. Daher ist test1() 
effektiver als test2() im obigen Beispiel.

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