Bewährte Methode für die Verarbeitung von 1-256 Bytes

8

Ich habe einige Funktionen, die entworfen sind, um 1-256 Bytes zu behandeln, die auf einer eingebetteten C-Plattform laufen, wo die Übergabe eines Bytes viel schneller und kompakter ist als die Übergabe eines int (eine Anweisung gegen drei), was der bevorzugte Weg ist Codierung es:

  1. Akzeptiere einen int, early-exit wenn Null und kopiere ansonsten das LSB des Zählwertes in ein unsigniertes char und benutze das in einem do {} while (- count); Schleife (ein Parameterwert von 256 wird in 0 umgewandelt, wird aber 256 Mal ausgeführt)
  2. Akzeptieren Sie ein unsigned char, early-exit wenn null, und haben Sie eine spezielle Version der Funktion für 256 Bytes (diese Fälle sind im Voraus bekannt).
  3. Akzeptieren Sie ein Zeichen ohne Vorzeichen und führen Sie 256 Mal aus, wenn es null ist.
  4. Haben Sie eine Funktion wie oben, aber rufen Sie sie über Wrapper-Funktionen auf, die sich wie (0-255) und (nur 256) verhalten.
  5. Haben Sie eine Funktion wie oben, aber rufen Sie sie über Wrapper-Makros auf, die sich wie (0-255) und (nur 256) verhalten.

Es wird erwartet, dass die innere Schleife der Funktion wahrscheinlich 15% -30% der Prozessorausführungszeit darstellt, wenn das System beschäftigt ist; Es wird manchmal für kleine Byte und manchmal auch für große Bytes verwendet. Der Speicherchip, der von der Funktion verwendet wird, hat einen pro-Transaktion-Overhead, und ich bevorzuge es, dass meine Speicherzugriffsfunktion intern die Start-Transaktions / Do-Stuff / End-Transaktions-Sequenz ausführt.

Der effizienteste Code wäre, einfach ein unsigniertes Zeichen zu akzeptieren und einen Parameterwert von 0 als eine Anforderung von 256 Bytes zu betrachten, wobei der Aufrufer angewiesen wird, zufällige Versuche, 0 Bytes zu lesen, zu vermeiden. Das scheint ein bisschen gefährlich zu sein. Haben sich andere mit solchen Problemen bei eingebetteten Systemen beschäftigt? Wie wurden sie gehandhabt?

BEARBEITEN Die Plattform ist ein PIC18Fxx (128K Coderaum; 3,5K RAM), der mit einem SPI-Flash-Chip verbunden ist; Lesen von 256 Bytes, wenn weniger erwartet wird, würde möglicherweise Lese-Puffer in dem PIC überschreiten. Schreiben von 256 Bytes anstelle von 0 würde die Daten im Flash-Chip beschädigen. Der PIC-SPI-Port ist auf alle 12 Befehlszeiten auf ein Byte begrenzt, wenn der Besetztzustand nicht überprüft wird; es wird langsamer sein, wenn man es tut. Eine typische Schreibtransaktion erfordert das Senden von 4 Bytes zusätzlich zu den zu empfangenden Daten; Ein Lesevorgang erfordert ein zusätzliches Byte für "SPI Turnaround" (der schnellste Weg zum Zugriff auf den SPI-Port besteht darin, das letzte Byte zu lesen, bevor das nächste gesendet wird).

Der Compiler ist HiTech PICC-18std.

Ich habe die HiTech PICC-16-Compiler im Allgemeinen gemocht; HiTech scheint ihre Energien vom PICC-18std-Produkt in Richtung ihrer PICC-18pro-Leitung umgeleitet zu haben, die sogar langsamere Kompilierungszeiten benötigt, die Verwendung von 3-Byte-Const-Zeigern anstelle von Zwei-Byte-Zeigern zu erfordern scheint eigene Ideen zur Speicherzuordnung. Vielleicht sollte ich mir das PICC-18pro näher ansehen, aber als ich mein Projekt auf einer Evaluierungsversion von PICC-18pro probiert habe, hat es nicht funktioniert und ich habe nicht genau herausgefunden warum - vielleicht stimmt etwas mit dem variablen Layout nicht überein meine ASM-Routinen - ich habe nur PICC-18std benutzt.

Übrigens habe ich gerade entdeckt, dass PICC-18 besonders gerne tut {} while (- bytevar); und besonders Abneigungen machen {} while (- intvar); Ich frage mich, was den "Verstand" des Compilers durchläuft, wenn er das Letztere erzeugt?

%Vor%

Der Compiler lädt einen Zeiger auf die Variable, nicht einmal mit dem LFSR-Befehl (der zwei Wörter benötigt), sondern eine Kombination von MOVLW / MOVWF (unter vier). Dann verwendet es diesen Zeiger, um das Dekrementieren und Vergleichen durchzuführen. Während ich zugeben werde, dass {} while (- wordvar); kann nicht so schönen Code ergeben wie {} while (wordvar--); Der Code ist besser als das, was das letzte Format tatsächlich erzeugt. Das Ausführen eines separaten Dekrements und While-Tests (z. B. while (-lpw, lpw)) ergibt einen vernünftigen Code, der jedoch etwas hässlich wirkt. Der Post-Dekrementierungsoperator könnte den besten Code für eine Abwärtszählschleife ergeben:

%Vor%

aber stattdessen erzeugt es schlechteren Code als --lpw. Der beste Code wäre für eine aufwärtszählende Schleife:

%Vor%

aber der Compiler erzeugt das nicht.

EDIT 2 Ein anderer Ansatz, den ich verwenden könnte: Zuweisen einer globalen 16-Bit-Variablen für die Anzahl der Bytes und Schreiben der Funktionen, sodass der Zähler immer vor dem Beenden auf Null gesetzt wird. Wenn dann nur ein 8-Bit-Wert benötigt wird, müssen nur 8 Bits geladen werden. Ich würde Makros für Dinge verwenden, damit sie für die beste Effizienz optimiert werden können. Auf dem PIC ist die Verwendung von | = für eine Variable, von der bekannt ist, dass sie null ist, niemals langsamer als die Verwendung von = und ist manchmal schneller. Zum Beispiel wäre intvar | = 15 oder intvar | = 0x300 zwei Anweisungen (jeder Fall muss sich nur mit einem Byte des Ergebnisses befassen und kann das andere ignorieren); intvar | = 4 (oder eine Potenz von 2) ist eine Anweisung. Offensichtlich ist intvar = 0x300 auf einigen anderen Prozessoren schneller als intvar | = 0x300; Wenn ich ein Makro verwende, kann es entsprechend angepasst werden.

    
supercat 19.08.2010, 16:00
quelle

3 Antworten

0

FWIW, ich würde eine Variante der Option # 1 wählen. Die Schnittstelle der Funktion bleibt vernünftig, intuitiv und scheint weniger wahrscheinlich falsch aufgerufen zu werden (Sie könnten darüber nachdenken, was Sie tun möchten, wenn ein Wert größer als 256 übergeben wird - eine Debug-Build-Only-Assertion könnte angebracht sein).

Ich glaube nicht, dass die geringfügige "Hack" / Mikro-Optimierung, um die richtige Anzahl von Malen mit einem 8-Bit-Zähler zu durchlaufen, wirklich ein Wartungsproblem wäre, und es scheint, dass Sie erhebliche Analysen durchgeführt haben, um es zu rechtfertigen.

Ich würde nicht gegen Wrapper argumentieren, wenn jemand sie bevorzugen würde, aber ich würde mich persönlich immer leicht an Option 1 halten.

Ich würde jedoch dagegen sprechen, dass die öffentliche Schnittstelle erfordert, dass der Aufrufer einen Wert um weniger als den Wert eingibt, den sie lesen wollten.

    
Michael Burr 19.08.2010, 18:20
quelle
2

Ihre innere Funktion sollte count + 1 bytes kopieren, z. B.

%Vor%

Wenn das Nach-Dekrementieren langsam ist, sind andere Alternativen:

%Vor%

oder

%Vor%

Der Aufrufer / Wrapper kann folgendes tun:

if (count > 0 && count <= 256) inner((uint8_t)(count-1))

oder

if (((unsigned )(count - 1)) < 256u) inner((uint8_t)(count-1))

wenn es in Ihrem Compiler schneller ist.

    
Doug Currie 19.08.2010 16:28
quelle
0

Wenn ein int-Parameter 3 Anweisungen kostet und ein char-Parameter 1 kostet, könnten Sie einen zusätzlichen char-Parameter für das zusätzliche 1-Bit übergeben, das Ihnen fehlt. Es scheint ziemlich albern, dass Ihr (vermutlich 16-Bit) int mehr als doppelt so viele Anweisungen wie ein 8-Bit-Zeichen braucht.

    
nmichaels 19.08.2010 17:49
quelle

Tags und Links