Bitmanipulation, verschieben von Bits, optimierung

OP #3208619
Lesenswert?

Hallo Leute,
hier gehts darum den vorhandenen Code zu Optimieren oder andere 
möglichkeiten aufzugreifen. Der Code funktioniert!
Ich habe ein Atmega 8 bei dem ich den Status der Portregister zu einem 
Char zusammenfügen möchte um es dann mit einem PC Programm auszuwerten.

dazu verwende ich momentan

  char B2 = 0b00000000;
  B2 |= (((PIND & (1<<5))>>5) | ((PIND & (1<<6))>>5));

hier wird das Bit PIND5 um 5 stellen nach rechtsverschoben und liegt 
dann in der var B2 genauso bei PIND6

aus 0b00100000 und 0b01000000
wird nun 0b00000011

Kann man das auch anders machen?

Gruß
(Firma: guloshop.de) #3208731
Lesenswert?

Mathias Feld schrieb:
>   char B2 = 0b00000000;

Hallo!

Bei Bitmanipulationen verwende ich zur Sicherheit normalerweise 
"unsigned char", damit es bei negativen Werten (gesetztes Bit 7) keine 
Überraschungen gibt.

>   B2 |= (((PIND & (1<<5))>>5) | ((PIND & (1<<6))>>5));

Gleichbedeutend mit
1
B2 |= (PIND & 1<<5)>>5 | (PIND & 1<<6)>>5;

falls ich mich nicht irre. Manchmal sind weniger Klammern besser 
(zumindest übersichtlicher) als mehr. Zuerst könntest du das ">>5" 
rausziehen:
1
B2 |= ( (PIND & 1<<5) | (PIND & 1<<6) )>>5;

Dann wird einmal weniger geschoben. Die beiden zu lesenden Bits lassen 
sich natürlich auch auf einmal lesen:
1
B2 |= ( PIND & (1<<5 | 1<<6) )>>5;

Ein guter Compiler optimiert das aber möglcherweise selbst. Den Rest 
hast du bei C nicht mehr in der Hand, du bist auf den Compiler 
angewiesen. Wenn der Compiler schlau ist, schaut der erzeugte Code 
ungefähr so aus:
1
in temp,PIND
2
andi temp,0b01100000
3
swap temp
4
lsr temp
5
or B2,temp

Braucht 5 CPU-Takte. Es gibt noch einen universelleren, komplett anderen 
Weg, der aber ebenfalls 5 Takte braucht.
#3208740
Lesenswert?

Markus Weber schrieb:

> Die beiden zu lesenden Bits lassen
> sich natürlich auch auf einmal lesen:
>
>
1
B2 |= ( PIND & (1<<5 | 1<<6) )>>5;
>
> Ein guter Compiler optimiert das aber möglcherweise selbst.

Um da einzuhaken:
Nein das darf er nicht. (Also die Zusammenfassung der beiden 
Maskieroperationen)
Der Grund dafür ist, dass *PIND als volatile markiert ist, d.h. jeder 
Zugriff muss genau so ausgeführt werden, wie er da steht.

Und da macht es nun mal einen Unterschied, ob man 2 mal kurz 
hintereinander auf den PIND zugreift (in der Zwischenzeit könnten sich 
ja auch Bits verändert haben) oder ob man nur einen einzigen Zugriff 
hat.

Es geht nur um die Anzahl der Zugriffe! Denn: Die Reihenfolge, in der 
die Zugriffe auf das ausmaskieren von Bit 5 bzw. Bit 6 erfolgen, hat man 
damit nicht in der Hand. Die kann der Compiler wieder machen wie er 
will. Nur weil man schreibt

  i =  a() + b()

bedeutet das nicht, dass die zuerst die Funktion a aufgerufen wird und 
dann b. Genau das ist gemeint, wenn man sagt: Operatoren Reihenfolge ist 
etwas anderes wie Auswertungsreihenfolge.
Gast #3208741
Lesenswert?

Markus Weber schrieb:
> B2 |= ( (PIND & 1<<5) | (PIND & 1<<6) )>>5;
> Dann wird einmal weniger geschoben. Die beiden zu lesenden Bits lassen
> sich natürlich auch auf einmal lesen:
> B2 |= ( PIND & (1<<5 | 1<<6) )>>5;

was aber nicht mehr das gleiche ist, denn PIND ist volatile und muss bei 
jedem Zugriff gelesen werden. Hier darf er nicht weiter optimieren.
(Firma: guloshop.de) #3208755
Lesenswert?

Markus Weber schrieb:
> Ein guter Compiler optimiert das aber möglcherweise selbst.

Karl Heinz und Peter: Ihr habt natürlich Recht, PIND ist keine "normale" 
Variable, der Compiler hat in diesem Fall weniger Freiheiten bei der 
Optimierung. Mein Fehler. Bleibt also nur die Vor-Optimierung von Hand.

Würde mich trotzdem interessieren, welchen Assembler-Code der Compiler 
dann draus macht. Mathias, falls du das Programm übersetzt, magst du mal 
nachschauen?
Gast #3208891
Lesenswert?

>PIND ist keine "normale" Variable
>Bleibt also nur die Vor-Optimierung von Hand.
Du musst eben selbst erst überlegen, wann (wie oft) diese 
Spezialregister explizit gelesen bzw. geschrieben werden soll oder muß.
Einzelne "rd" bzw. "wr" Zugriffe sollten zwecks Optimierung 
grundsätzlich in eine oder ggf. mehrere "temp" Zwischenvariable(n) 
erfolgen.
Die Zwischenvariable(n) kannst du dann ruhig beliegig weiterverwursteln, 
dann kann der Compiler diese tatsächlich optimieren.
Bei schrittweiser Bearbeitung ruhig mehrere Zwischenvariablen temp1, 
temp2 usw. verwenden, auf jede nur einmalig zuweisen. Das macht den Code 
lesbarer und letztendlich optimiert der Compiler alles Zwischenvariablen 
weg (bei geeigneter Optimierungseinstellung und bei einem uC der 
ausreichend Arbeitsregister hat).
Und mit mehreren dann eher kurzen Anweisungszeilen kann man auch noch 
ordentliche Kommentare hinschreiben.
Gruss
#3209655
Lesenswert?

@Εrnst B✶ (ernst)

>Wenn's mehrere Bits zum umsortieren sind:

>uint8_t  vermischt=__builtin_avr_insert_bits (0x04217563, abc, 0);

>http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/AVR-Bu...

Ich hab hier die letzte offizielle Version von WinAVR von 2010. Läuft 
für meine Zwecke problemlos. Aber da ist nur GCC 4.3.3 drin. Dieses 
Makro ist dort noch nicht enthalten. Gibt es irgendwo eine fertig 
nutzbare, aktuelle GCC Version, die ich einfach mit WinAVR 2010 nutzen 
kann? Nein, ich möchte den Compiler und das Ganze Geraffel nicht selber 
bauen!
#3209785
Lesenswert?

Anhänge mit folgendem Compiler:
1
leo@cb:~/src/avr/tmp$ avr-gcc -v
2
Using built-in specs.
3
COLLECT_GCC=avr-gcc
4
COLLECT_LTO_WRAPPER=/usr/lib/gcc/avr/4.7.2/lto-wrapper
5
Target: avr
6
Configured with: ../src/configure -v --enable-languages=c,c++ --prefix=/usr/lib --infodir=/usr/share/info --mandir=/usr/share/man --bindir=/usr/bin --libexecdir=/usr/lib --libdir=/usr/lib --enable-shared --with-system-zlib --enable-long-long --enable-nls --without-included-gettext --disable-libssp --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=avr
7
Thread model: single
8
gcc version 4.7.2 (GCC)

Edit: (was ich sagen wollte)
__builtin_avr_insert_bits ist ja mal was interessantes, benutzt "load 
bit" und "store bit" Befehle.
In diesem Spezialfall ist aber "herkömmliches" Bitschieben effektiver, 
insbesondere die Variante: erst schieben, dann maskieren.
Angehängte Dateien:
Persönliche Seite #3209898
Lesenswert?

*ARGL* was soll das denn???

Jörg Esser schrieb:

> Nachfolgend das Howto:
> [...]
> Nun alle Verzeichnisse vom alten WinAVR2010 sichern.
> Bei mir sind das folgende:
> avr
> bin
> lib
> share
> Ich habe sie einfach umbenannt in:
> avr.2010
> bin.2010
> lib.2010
> share.2010
>
> Jetz alle Unterverzeichnisse aus avr-gcc-4.8_2013-03-06_mingw32
> in das WinAVR-20100110 kopieren.
> Dann noch avrdude.conf,avrdude.exe undlibusb0.dll
> von bin.2010 nach bin kopieren
>
> Damit sollte nun alles wieder funktionieren allerdings mit
> neuestem avr-gcc

Es genügt doch PATH zu setzen!

Bitte reduzieren Sie die Anzahl der Zitatzeilen.
Bitte reduzieren Sie die Anzahl der Zitatzeilen.
Bitte reduzieren Sie die Anzahl der Zitatzeilen.
#3210275
Lesenswert?

@Johann L. (gjlayde) Benutzerseite

>Es genügt doch PATH zu setzen!

Und wie genau? Ihc hab die beiden Einträhe in Path verändert, geht aber 
nicht, im AVR Studio sind noch die alten EInträge vom Pfad. Wenn man die 
mauell verbiegt (und vorher das Verzeichnis util, wo make drin ist 
kopiert), läuft es scheinbar, aber es fehlt am Ende die Auflistung des 
Speicherverbrauchs.

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