2009-07-28 6 views
4

Ich habe eine Arduino-Anwendung (naja, eigentlich eine Bibliothek), die eine Reihe von Status-Flags enthält - und ursprünglich habe ich sie einfach als Ints deklariert (gut uint8_t also 8 Bit unsigned Zeichen in diesem Fall). Aber ich hätte sie alle zu einer ganzen Zahl kombinieren und Bitmask-Operationen verwenden können, um den Status zu setzen und zu testen.Bit-Einstellung und Code-Lesbarkeit

Ein Beispiel für ersteres:

if (_shift == HIGH) 
{ 
    _shift = LOW; 
} 
else 
{ 
    _shift = HIGH; 
} 

ein Beispiel für die letztere

#define SHIFT_BIT 0 

if (bitRead(_flags, SHIFT_BIT) == HIGH) 
{ 
    bitWrite(_flags, SHIFT_BIT, LOW); 
} 
else 
{ 
    bitWrite(_flags, SHIFT_BIT, HIGH); 
} 

Erstere liest besser, aber die letztere ist effizienter (Raum und Zeit). Sollte in dieser Situation die Raum- und Zeiteffizienz immer gewinnen oder ist dies eine Art Optimierung, die nur bei Bedarf stattfinden sollte?

(Added)

Der Vollständigkeit halber hier die Verdrahtung Definition dieser bitWrite usw. Makros:

#define bitRead(value, bit) (((value) >> (bit)) & 0x01) 
#define bitSet(value, bit) ((value) |= (1UL << (bit))) 
#define bitClear(value, bit) ((value) &= ~(1UL << (bit))) 
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit)) 

Antwort

7

Check out Raymond Chen's excellent take zu diesem Thema. Zusammenfassend müssen Sie einige detaillierte Berechnungen durchführen, um herauszufinden, ob der letztere Fall tatsächlich effizienter ist, abhängig davon, wie viele Objekte vorhanden sind und wie viele Callsites diese Zustände tatsächlich setzen.

Soweit Lesbarkeit, sieht es so aus, als ob Sie dies mit Member-Variablen tun, was bedeutet, dass Sie wahrscheinlich in nette Funktionen gekapselt haben. In diesem Fall ist mir die Lesbarkeit nicht so wichtig, da zumindest der Code für die Leute, die die Klasse verwenden, gut aussieht. Sie können es jedoch immer in private Funktionen einkapseln, wenn es sich um ein Problem handelt.

+1

ich mit den meisten, dass im Fall zustimmen, wo es nur wenige Instanzen der Klasse sind. Aber (a) er spricht, als ob es unerhört ist, 1000 Fälle einer Struktur zu schaffen, und (b) die Vorteile der natürlichen Wort-Atomizität sind zweifelhaft-zu-nichts. Ein Feld, in dem Ihr Multi-Threaded-Code auf atomaren Lese- und Schreibvorgängen basiert, aber nie eine atomare Aktualisierung benötigt, ist wesentlich seltener als eine Struktur mit 1000 Instanzen. Und wird in den meisten Fällen brechen, sobald Sie es in eine Architektur verschieben, die Cache-Kohärenz fehlt. –

+0

Ich schaue mir die Referenz an, danke. In diesem Fall (es ist eine allgemeine Klasse, die eine Tasten-/LED-Kombination verwaltet) konnte ich mir nicht vorstellen, mehr als etwa 20 zu instanziieren, aber der Punkt über 1000 Instanzen ist richtig, aber nicht in diesem speziellen Fall ... –

+1

(Später nach dem Lesen) . Das ist ein guter Artikel. Das Gute daran ist, dass ich dies direkt messen kann, indem ich den Speicherbedarf der Zielanwendung und die damit verbundene Speichernutzung betrachte. In dem Chip, auf den ich ziele, stehen nur 2k Ram für "Variablen" zur Verfügung, aber 30k für das Programm. Also die Reduzierung der ersten durch die Erhöhung der letzteren kann ein guter Kompromiss sein ... –

3

Es kann klarer sein, wenn Sie die Notwendigkeit entfernen, die Konstanten HIGH und LOW zu verwenden, indem Sie in zwei Methoden aufteilen. Machen Sie einfach bitSet und bitClear Methoden. bitSet setzt das Bit auf HIGH, und bitClear setzt das Bit auf LOW. Dann wird es:

#define SHIFT_BIT 0 

if (bitRead(_flags, SHIFT_BIT) == HIGH) 
{ 
    bitClear(_flags, SHIFT_BIT); 
} 
else 
{ 
    bitSet(_flags, SHIFT_BIT); 
} 

Und natürlich, wenn Sie nur HIGH == 1 und LOW == 0, dann brauchen Sie nicht die == überprüfen.

+1

In der Arduino/Wiring-Implementierung HIGH ist gleich 0x01 und LOW ist gleich 0x00 ... –

1

Zu meinem Auge ist sogar Ihr letzter Code immer noch gut lesbar. Indem Sie jedem Ihrer Flaggen einen Namen geben, kann der Code ohne viel Aufwand gelesen werden.

Ein schlechter Weg, es zu tun wäre, „Magie“ Zahlen zu verwenden:

if(_flags | 0x20) { // What does this number mean? 
    do_something(); 
} 
0

Für Bitfelder, es besser ist, logische Operationen zu verwenden, so dass Sie tun können:

if (flags & FLAG_SHIFT) { 
    flags &= ~FLAG_SHIFT; 
} else { 
    flags |= FLAG_SHIFT; 
} 

Diese jetzt hat das Aussehen der ersteren mit der Effizienz der letzteren. Nun könnte man Makros anstatt Funktionen, so (wenn ich das richtig haben - wäre es so etwas wie):

#define bitIsSet(flags,bit) flags | bit 
#define bitSet(flags,bit) flags |= bit 
#define bitClear(flags,bit) flags &= ~bit 

Sie nicht den Aufwand der Aufruf von Funktionen haben, und der Code wird besser lesbar nochmal.

Ich habe nicht mit Arduino (noch) gespielt, aber es könnte schon Makros für diese Art von Sache sein, ich weiß es nicht.

+1

Ja, tut mir leid über die Verwendung der Arduino/Verdrahtung definiert - BitSet/BitWrite usw. ist durch Makros, die genau oder ähnlich sind, was Sie haben oben geschrieben. Die Herausforderung besteht in der Lesbarkeit von fünf verschiedenen Ebenen von Makros, die die wahre Sache verbergen, die Sie erreichen möchten ... –

+0

Ich denke, die Sache ist Bit-Manipulation ist in C ziemlich trivial und jeder, der an eingebetteten Systemen arbeitet (die was Arduino eigentlich ist) würde oder sollte mit den Konstrukten vertraut sein. Ich denke, das Problem mit Ihrem zweiten Bit Code könnte einfach nur die Namen der Makros sein ... bitRead() und bitWrite() schien nur ungerade Konventionen im Vergleich zur Verwendung von Set, Clear oder IsSet. Aber das ist nicht zu verstehen, warum :-) –

+0

Ja, es gibt ein BitWrite, mit dem man definieren kann, ob * oder * dann gibt es auch BitSet und BitClear, die dasselbe mit einem weniger Parameter tun (dh Variable + Bit statt Variable + Bit + 0 oder 1). Ich hatte gehofft (bevor ich auf die Header-Datei schaute), dass es eine magische Optimierung gab, die mit diesen Unterschieden weiterging, aber es ist nur jemandes Konvention ... :-) –

5

Abhängig von der Kompatibilität des AVR-GCC-Compilers, über den ich mich nicht sicher bin, können Sie so etwas machen und die Dinge schön und sauber halten.

struct flags { 
    unsigned int flag1 : 1; //1 sets the length of the field in bits 
    unsigned int flag2 : 4; 
}; 

flags data; 

data.flag1 = 0; 
data.flag2 = 12; 

if (data.flag1 == 1) 
{ 
    data.flag1 = 0; 
} 
else 
{ 
    data.flag1 = 1; 
} 

Wenn Sie auch sofort Zugriff auf die gesamte Flagge int wollen, dann:

union 
{ 
    struct { 
     unsigned int flag1 : 1; //1 sets the length of the field in bits 
     unsigned int flag2 : 4; 
    } bits; 
    unsigned int val; 
} flags; 

Sie können dann ein wenig mit 2 Dereferenzierungsebenen Zugang: variable.bits.flag1 < --returns einzelnes Bit-Flag oder mit einer einzigen Ebene, um den gesamten int-Wert von Flags zu erhalten: variable.val < --returns int

+0

Shoot, ja, würde ich diese Technik in meinem Ausflug zurück zu C++ –

+1

bizarrerweise vergessen Zusätzlich zu den oben genannten, könnten Sie auch definieren, für jede Flagge: #define f_EndOfFrameReached (data.endOfFrameReached) und das erhöht die Lesbarkeit. Ich habe an Embedded-Projekten gearbeitet, die diese Methode konsistent verwendeten, und sie war sowohl wartbar als auch ressourcenschonend. Die Programmierer müssen sich nur der Konvention und ihrer Fehler bewusst sein (wie zB Vorsicht vor dem nicht-atomaren Lesen-Modifizieren-Schreiben, was bedeutet, dass Sie diese wahrscheinlich nicht mit Interrupt-Routinen verwenden sollten). –

+0

Bitfield-Strukturen sind berüchtigt dafür, dass sie Probleme verursachen, da der Compiler die Strukturen auf unerwartete Weise packen kann. Dieser Ansatz sollte nur verwendet werden, wenn Sie (1) positiv auf das Verhalten Ihres Compilers reagieren und (2) niemals Compiler ändern (z. B. Spezialprozessor mit nur einem Compiler). –

1

Wenn Sie nicht optimieren müssen, tun Sie es nicht und verwenden Sie die einfachste Lösung.

Wenn Sie optimieren müssen, sollten Sie wissen, was:

  • Die erste Version minimal schneller sein würde, wenn Sie nur das Bit setzen oder löschen, anstatt sie von Makeln, weil dann nicht Sie tun muss die Erinnerung lesen.

  • Die erste Version ist besser w.r.t. Nebenläufigkeit. In der zweiten haben Sie lesen-ändern-schreiben, so müssen Sie sicherstellen, dass das Speicherbyte nicht gleichzeitig zugegriffen wird. Normalerweise würden Sie Interrupts deaktivieren, was Ihre Interrupt-Latenz etwas erhöht. Außerdem kann das Vergessen von Interrupts zu sehr unangenehmen und schwer zu findenden Fehlern führen (der bösartigste Fehler, dem ich bisher begegnet bin, war genau dieser Art).

  • Die erste Version ist minimal besser w.r.t. Code-Größe (weniger Flash-Nutzung), da jeder Zugriff ein einzelner Lade- oder Speichervorgang ist. Der zweite Ansatz benötigt zusätzliche Bitoperationen.

  • Die zweite Version verwendet weniger RAM, besonders wenn Sie viele dieser Bits haben.

  • Die zweite Version ist auch schneller, wenn Sie mehrere Bits gleichzeitig testen möchten (z. B. eines der gesetzten Bits).

1

Wenn Sie die Lesbarkeit im Gespräch sind, Bit-Sets und C++, warum ich etwas nicht finden, auf std::bitset da drin? Ich verstehe, dass das Embedded-Programmierer-Rennen mit Bitmasken ziemlich vertraut ist und eine Blindheit für seine schiere Hässlichkeit entwickelt hat (die Masken, nicht die Rennen :), aber abgesehen von Masken und Bitfeldern hat die Standard-Bibliothek auch eine ziemlich elegante Lösung.

Ein Beispiel:

#include <bitset> 

enum tFlags { c_firstflag, c_secondflag, c_NumberOfFlags }; 

... 

std::bitset<c_NumberOfFlags> bits; 

bits.set(c_firstflag); 
if(bits.test(c_secondflag)) { 
    bits.clear(); 
} 

// even has a pretty print function! 
std::cout << bits << std::endl;// does a "100101" representation. 
+0

Nicht sicher, ob die Std-Bibliothek auf diesem Compiler unterstützt wird (oder zu viel Speicher belegen würde, um unbrauchbar zu sein). Und standardmäßig gibt es keinen Standard. Es gibt keinen Bildschirm, nur vielleicht eine serielle Schnittstelle, wenn Sie es aktivieren ... –

+0

@Alan Moore: das Cout-Bit war nur zu zeigen, was es tun kann. Sehr nützlich zum Testen! – xtofl

0

Ich würde sagen, das erste, was ich besorgt bin mit ist: „#define SHIFT 0“ Warum nicht die Verwendung eines konstanten machen eher als ein Makro? Soweit es die Effizienz betrifft, erlaubt die Konstante, den Typ zu bestimmen, wodurch sichergestellt wird, dass danach keine Umwandlung mehr erforderlich ist.

Für die Effizienz Ihrer Technik: - zuerst, loswerden der else-Klausel (warum setzen Sie das Bit auf HIGH, wenn sein Wert bereits HIGH ist?) - Zweitens, lieber etwas lesbares zuerst haben, inline-Setter/Getter mit Bit-Maskierung intern würde die Arbeit tun, effizient und lesbar sein.

Wie für den Speicher, für C++ würde ich dazu neigen, ein Bitset (kombiniert mit einem Enum) zu verwenden.

0

Ist es zu einfach zu sagen:

flags ^= bit; 
Verwandte Themen