Hi,
Folgender Code soll perfomant sein. Es geht um einen Ringpuffer bei der
seriellen Kommunikation (die Arraygröße ist eine Zweierpotenz): Ich
finde es ist sehr schlecht lesbar:
1
if(((r_in-r_out)&~(RBUF_SIZE-1))==0)
2
{
3
rbuf[r_in&(RBUF_SIZE-1)]=SBUF;
4
r_in++;
5
}
Ich hingegen hätte einfach nur geschrieben:
1
if(r_in>=RBUF_SIZE)
2
r_in=0;
3
4
rbuf[r_in++]=SBUF;
Ist doch das gleiche oder? Mir ist auch klar, dass man bei solchen
Anwendungen möglichst viel effizienten Code schreiben muss, um eine
geschmeidige Kommunikation zu ermöglichen.
Die beiden Codestücke sind nicht gleichwertig in der Funktion.
Im ersten wird mit der if Abfrage der Bufferzustand überprüft, während
im 2. Codesegment nur die Bufferinkrementierung behandelt wird.
Man muß als nur die 2 Zeilen innerhald des if des 1. Codebeispiel mit
den 2. Codebeispiel vergleichen.
Vor- und Nachteile:
Codestück 1: Wird i.A. auf den schnellsten Code hinauslaufen. Wird ein
bitweises AND und ein INCREMENT im Assemblercode ergeben (also 2
Assemblerbefehle).
Nachteil: funktioniert nur mit 2-er Potenz langen Buffern.
Codestück 2: funktioniert mit jeder Buffergröße. Erfordert im
Assemblercode (je nach Architektur) einen bedingten Sprung oder bedingte
Assembleranweisungen, was nur wenige Assembler-Architekturen können. Der
Bedingte Sprung hat auch so manche Nachteile weil er (je nach Processor)
ein Pipeline leeren und neu füllen erfordern kann. Aber auch mit den
besten Architekturen wird man kaum schneller als Variante 1 sein.
Mag auf den 1. Blick in C besser verständlich sein.
Moin,
erstmal danke für die Antwort. Ich hätt eher gedacht, dass es mehr als
ein "AND" und ein "INCREMENT" ist in Variante-1. Man hat ja schließlich
ein "AND", "NOT" und dann auch noch ein Vergleich mit 0 in "if" und dann
nochmal die Indexberechnung mit "AND" und dann nochmal Inkrement.
Das ist ja auch nicht wenig. Aber wahrscheinlich sind dann halt diese
Operationen auf Binärebene schneller.
Die beiden Häppchen haben völlig unterschiedliches Verhalten beim
Überlauf des Puffers, also bei mehr Daten als der Puffer fassen kann.
Das erste verwirft die überschüssigen Daten, beim zweiten gehen
komplette Pufferinhalte verloren, weil die entscheidende Relation
zwischen r_in und r_out nicht beachtet wird.
Lens schrieb:> erstmal danke für die Antwort. Ich hätt eher gedacht, dass es mehr als> ein "AND" und ein "INCREMENT" ist in Variante-1. Man hat ja schließlich> ein "AND", "NOT" und dann auch noch ein Vergleich mit 0 in "if" und dann> nochmal die Indexberechnung mit "AND" und dann nochmal Inkrement.
Nochmal: das if hat mit der Bufferincrementierung nichts zu tun!!
Das ist eine Überprüfung (soviel ich verstanden habe) ob der Buffer frei
ist mit den beiden Variablen r_in, r_out!!!!!!!!!
Das oder ähnliches brauchst du auch noch in Variant 2!!!!!
Nur die 2 Zeilen inkrementieren den Buffer:
rbuf [r_in & (RBUF_SIZE-1)] = SBUF;
r_in++;
und die sind definitiv schneller als als Variante 2
Zur Performance einzelner Assembler-Befehle:
- Bei einfachen Implementierungen mit sehr kurzer Pipeline, wie AVR,
PIC8/16, 8051, kann man in sehr grober Näherung arithmetische Befehle
mit bedingten Sprungbefehlen gleichsetzen. Das mögen dann 1 vs. 2-3
Takte sein, aber dieser Unterschied ist nicht gross, verglichen mit...
- Bei High-End Implementierungen wie in aktuellen PC-Prozessoren sind
bedingte Sprungbefehle im Regelfall ebenfalls billig, können aber in
Einzelfällen auch über 100 Takte verbraten. Daher sind bei solchen
Prozessoren bedingte Operationen/Zuweisungen immer wichtiger. Weshalb
sowas wie
i = (i + 1) % N; // Bedingung komplett vermieden
if (i < N) ++i; // bedingte Zuweisung (x86) oder Operation (ARM)
sehr viel billiger sein kann, als ein komplexer if-Block mit
Sprungbefehl
if (i < 0) { ...; ...; ...; }
Hi,
Ja das stimmt. Ich hab nicht drann gedacht, dass der Mikrokontroller
schnell genug ist, um die Daten im Empfangspuffer alle rechtzeitig
fertig zu verarbeiten.
Danke für den Hinweis!
Noch eine Frage: Der Mikrokontroller gibt einiges der empfangenen
Zeichen aus, einiges geht aber verloren, wird nicht ausgegeben, weil der
Mikrokontroller bei manchen neu eingegeben Zeichen noch einen vollen
Puffer hat (obwohl ich Variante-1 verwende). Verwendet wird eine
Baudrate von 1200. Wie kann man dafür sorgen, dass alle Zeichen
empfangen werden? Höhere Baudrate?
Wenn der Controller die Zeichen langsamer verarbeitet, als er sie
empfangen kann, dann hilft eines von:
- Übertragungsrate/Baudrate entsprechend reduzieren(!),
- Verarbeitung der Zeichen beschleunigen,
- Dem Sender per Handshake mitteilen, dass grad nix mehr geht.
Lens schrieb:> Wie kann man dafür sorgen, dass alle Zeichen empfangen werden?
Entweder indem man dafür garantiert, dass die Daten schnell genug
verarbeitet werden oder indem man dem anderen Sender mitteilt, dass
erstmal nix mehr geht (Hardware-Handshake, Software-Handshake oder
Protokoll mit Empfangsbestätigung). Mit schnellerem Empfang kann noch
mehr rein kommen, d.h. das bringt den Prozessor noch eher in Bedrängnis.
Ok. Super. Aber regeln muss man die Geschwindigkeit nicht nur Seitens
Mikrokontroller, sondern auch Seitens der Terminalsoftware. Auch wenn
ich die richtige Baudrate in der Terminalsoftware einstelle kommt der
Mikrokontroller an vielen Stellen nicht mit.
Erst wenn ich in der Terminalsoftware eine zeitliche Verzögerung von
"260ms" einstelle bekomm ich auch alle Ausgaben.
Der Mikrokontroller wird ganz schön langsam:)
Dass man die Baudrate auf beiden Seiten gleich einstellt hielt ich
ursprünglich für selbstverständlich. ;-)
Ein Handshake-Verfahren hat gegenüber einer inhaltsunabhägig niedrigen
Übertragungsrate dann einen Vorteil, wenn nur wenige Zeichen langsam
verarbeitet werden, die meisten aber schnell.
Ah ja noch eine Frage hätte ich da:
Wieso setzt man in der Anwendung die Priorität vom "Serial Channel" auf
0? Es gibt ja nichts anderes außer die serielle Schnittstelle, die in
der Anwendung einen Interrupt auslösen kann
Lens schrieb:> Erst wenn ich in der Terminalsoftware eine zeitliche Verzögerung von> "260ms" einstelle bekomm ich auch alle Ausgaben.> Der Mikrokontroller wird ganz schön langsam:)
Dann hast du in deiner Software ein Problem. Bei 1200Bd hat der µC sooh
viel Zeit für die Verarbeitung der empfangenen Daten, genaugenommen 8
Millisekunden pro Zeichen.
Lens schrieb:> Wieso setzt man in der Anwendung die Priorität vom "Serial Channel"
Nicht jeder Code ist spezifisch für die Anwendung geschrieben. Ein
Programmteil für die serielle Schnittstelle wird gerne in anderem Code
weiterverwendet. So stammt dieser möglicherweise aus einer Anwendung, in
der Prioritäten wichtig waren.
Lens schrieb:> Wieso setzt man in der Anwendung die Priorität vom "Serial Channel" auf> 0?
Vielleich erzählst du mal, von was für einer Anwendung du sprichst, um
was für einen Prozessor es sich handelt und welches Betriebssystem
darauf läuft?
Also es geht um ein "Keil-Projektbeispiel", bei dem ich an der ein oder
anderen Stelle auch das Gefühl hab, dass manche Sachen aus einem anderen
Projekt kopiert wurden. So wird z. B.
- 2x die "com_baudrate()"-Funktion aufgerufen.
- 2x TI auf 0 gesetzt in "com_baudrate()" und "com_initialize()"
--> Sind diese Redundanzen wirklich notwendig?
liebe Grüße
fußnagel schrieb:> So wird es noch geschmeidiger:> http://www.duden.de/rechtschreibung/performant
Mal im ernst hier geht es Inhalte der Elektrotechnik nicht um
Rechtschreibung. Diese ist für Behörden und Schule bindend, ansonsten
kann jeder in Deutschland schreiben wie er will.
Bitte informiere dich mal über die deutsche Rechtschreibung, bevor du
hier versuchst Menschen ohne einen Grund du diskreditieren.
Rechtschreiber schrieb:> fußnagel schrieb:>> So wird es noch geschmeidiger:>> http://www.duden.de/rechtschreibung/performant>> Mal im ernst hier geht es Inhalte der Elektrotechnik nicht um> Rechtschreibung. Diese ist für Behörden und Schule bindend, ansonsten> kann jeder in Deutschland schreiben wie er will.>> Bitte informiere dich mal über die deutsche Rechtschreibung, bevor du> hier versuchst Menschen ohne einen Grund du diskreditieren.
@Rechtschreiber: Ich sehe, daß du weniger zum Thema beizutragen hast als
ich. Ich habe nicht diskreditiert, sondern versucht in der
"geschliffenen" Ausdrucksweise noch Bugs beseitigen zu helfen :) Ich hab
mich jetzt zwar auch nicht dran gehalten, aber: Do not feed the trolls
:-)
Kann mir einer sagen wieso:
1. Die Redundanzen, die ich oben beschrieben habe gemacht wurden.
2. Wieso wird vor z. B. "void com_baudrate(unsigned baudrate)" das
geschrieben: #pragma disabled
--> Ist doch garnicht notwendig, da EAL=1 sowieso erst nach Ausführung
von
"com_baudrate()" gesetzt wird
Lens schrieb:> Folgender Code soll perfomant sein...
"This example works with any 8051-compatible device."
Du solltest dir nicht blindlings irgendwelche Codeschnipsel an Land
ziehen, sondern dir selbst vorab Gedanken über die Struktur deines
Programmes machen.
Zunächst sind irgendwelche #pragma xyz meistens nicht portabel, du
solltest sowas also im Zweifelsfall einfach weglassen.
Als nächstes sollte dir klar sein, daß das Programm in deinem uC
normalerweise wesentlich schneller ist, als die Zeichen zum Rx-Eingang
herein kommen - es sei denn, du hast einen saftigen Fehler in der
Struktur gemacht. Über einen oder zwei Befehle beim Empfangs-Ringpuffer
sooltest du dich daher nicht aufregen.
Ganz anders sieht es beim Senden aus: Da ist wiederum das Programm viel
schneller, als die Zeichen zum Tx hinausgehen. Also solltest du dir
einen deutlich größeren Sendepuffer gönnen, als der Empfangspuffer groß
ist und das Senden natürlich per Interrupt erledigen.
Die einzige Ausnahme von dem Gesagten sehe ich dann, wenn der
betreffende uC aus Stromspargründen nur mit 32 kHz läuft.
Ich komme mir zwar vor wie ne tibetanische Gebetsmühle, aber guck dir in
der Codesammlung mal die "Lernbetty" an. Die dortigen I/O-Routinen für
den seriellen Port funktionieren, sind m.E. auch gut verständlich
geschrieben und lassen sich gut auf andere Systeme portieren. (Ähnliches
gilt auch für die dortigen Kommando-Routinen und Konvertierungen)
W.S.
Wenn du ein HD44780-LCD als Ausgabedevice an die serielle Schnittstelle
eines PCs hängst, dann kanns auch ohne Programmierfehler und bei 100MHz
Takt passieren, dass die Schnittstelle den Code für ClearScreen
schneller überträgt, als das Display ihn ausführt.