Perfomancevorteil - Algorithmus

Gast #3067926
Lesenswert?

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.
Gast #3067957
Lesenswert?

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.
Gast #3067959
Lesenswert?

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.
#3067967
Lesenswert?

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.
Gast #3067970
Lesenswert?

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
#3067979
Lesenswert?

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) { ...; ...; ...; }
Gast #3067982
Lesenswert?

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?
Gast #3067990
Lesenswert?

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.
Gast #3067994
Lesenswert?

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:)
#3067998
Lesenswert?

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.
Gast #3067999
Lesenswert?

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
Gast #3068001
Lesenswert?

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.
Gast #3068013
Lesenswert?

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
Gast #3068042
Lesenswert?

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.
Gast #3068113
Lesenswert?

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 
:-)
Gast #3068121
Lesenswert?

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
Gast #3069467
Lesenswert?

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.

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