2009-07-28 16 views
4

私はArduinoアプリケーション(実際にはライブラリです)に数多くのステータスフラグを持っていますが、もともと私はintとして宣言していました(この場合uint8_tなので8ビットのunsigned charsです)。しかし、それらをすべて1つの整数に結合し、ビットマスク操作を使用してステータスを設定およびテストすることができました。ビット設定とコードの可読性

前者の例:

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

後者

#define SHIFT_BIT 0 

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

の例前者は、より良好な読み出しが、後者は(空間及び時間)より効率的です。この状況では、時間と空間の効率が常に勝つべきか、これは一種の最適化であり、必要な時にのみ起こるべきですか?

(追加)

完全性のために、ここでそれらのbitWriteなどの配線定義はマクロです:

#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)) 

答えて

7

は、この問題に関するRaymond Chen's excellent takeをチェックしてください。要約すると、いくつのオブジェクトが存在しているか、実際にこれらの状態をいくつ設定しているかによって、後者のケースが実際より効率的かどうかを調べるためには、詳細な計算を行う必要があります。

可読性の面では、メンバ変数でこれを行うように見えます。これはおそらく素敵な関数にカプセル化されたことを意味します。その場合、少なくともクラスを使用する人々のコードはすばらしく見えるので、私は読みやすさに心配していません。しかし、それが懸念されている場合は、常にプライベート関数にカプセル化することができます。

+1

私はクラスのいくつかのインスタンスがある場合にはそのほとんどに同意します。しかし、(a)構造のインスタンスを1000個作成することはあまり知られていないかのように話します。(b)自然な単語 - 原子性の利点は疑いの余地がありません。あなたのマルチスレッドコードが原子の読み書きに依存しているが原子レベルの更新は必要ないフィールドは、1000個のインスタンスを持つ構造体よりもかなり稀です。ほとんどの場合、キャッシュ一貫性のないアーキテクチャに移動するとすぐに中断します。 –

+0

私はリファレンスを見て、感謝します。この場合(それはボタン/ ledの組み合わせを管理する一般的なクラスです)、私は約20以上のインスタンス化を想像することはできませんでしたが、約1000インスタンスのポイントは正しいですが、この特定のケースではありません... –

+1

(それを読んだ後、 。それは良い記事です。良いことは、ターゲットアプリケーションのメモリーフットプリントと関連するメモリー使用量を調べることでこれを直接測定できることです。私が目標としているチップでは、 "変数"では2k、プログラムでは30kしか使用できません。したがって、後者を増やすことによって前者を減らすことは良いトレードオフになる可能性があります。 –

3

定数HIGHLOWを使用する必要性を取り除くと、2つの方法に分割すると、より明確になります。ちょうどbitSetbitClearの方法を作る。 bitSetはビットをHIGHに設定し、bitClearはビットをLOWに設定します。そして、それは次のようになります。

#define SHIFT_BIT 0 

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

そしてもちろん、あなただけのHIGH == 1LOW == 0を持っている場合、その後、あなたは==チェックは必要ありません。

+1

Arduino/Wiringの実装では、HIGHは0x01に等しく、LOWは0x00 ... –

1

私の目には、後者のコードでさえもかなり読みやすいです。あなたのフラグのそれぞれに名前を付けることによって、多くの労力を要することなくコードを読むことができます。

それは「魔法」の数字を使用することです行うには貧弱な方法:あなたができるので、ビットフィールドの場合

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

が、それは論理演算を使用することをお勧めします:

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

これを今は後者の効率性で前者の見た目を持っています。今、あなたは(私はこの権利を持っている場合 - それはようなものになるだろう)ので、むしろ機能よりもマクロを持つことができます:

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

あなたは、関数を呼び出すのオーバーヘッドを持っていない、とコードが読みやすくなり、再び。

私はArduinoと遊んでいませんが(まだ)、この種のマクロはすでに存在するかもしれませんが、わかりません。

+1

になります。Arduino /配線定義の使用についてはごめんね。bitSet/bitWriteなどはすべて、あなたのものと正確に似たマクロ上記に書かれている。課題は、あなたが達成しようとしている実際のものを隠すことに終わる5つの異なるレベルのマクロの可読性です。 –

+0

私は、ビット操作はCや組込みシステムで作業している人ではかなり些細なことだと思います効果的にArduinoが何であるか)、構造に精通している必要があります。私はあなたの2番目のビットの問題は単にマクロの名前かもしれないと思う... bitRead()とbitWrite()はSet、ClearまたはIsSetを使用するのと比べて奇妙な慣習に思えた。しかし、私たちは理由を論ずるものではありません: –

+0

はい、そこには、設定するか、クリアするかを定義するbitWriteがあります。*さらに、1つ少ないパラメータ(つまり変数の代わりに変数+ビット)で同じことをするbitSetとbitClearもあります。 +ビット+0または1)。私は(ヘッダーファイルを見る前に)それらの違いで進んでいる魔法の最適化があることを期待していましたが、それはちょうど誰かの大会です:-) –

5

私が確信しているAVR-GCCコンパイラのコンプライアンスに応じて、このようなことができ、きれいできれいな状態を保つことができます。

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; 
} 

あなたも、その後、一度に全体フラグint型にアクセスしたい場合:

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

あなたはその後、間接の2つのレベルのビットにアクセスすることができます。variable.bits.flag1 < --returns単一ビットフラグかvariable.val < --returns int

+0

C++への私の取り組みで、このテクニックについて奇妙に忘れてしまいました –

+1

上記に加えて、各フラグに対して以下を定義することもできます: #define f_EndOfFrameReached(data.endOfFrameReached) これは可読性を向上させます。私は、そのメソッドを一貫して使用していた組み込みプロジェクトに取り組んできました。これは、メンテナンス可能でRAMを節約していました。プログラマーは、規則とその問題点(例えば、非アトミックリードモディファイライトに注意してください。これはおそらく割り込みルーチンでこれらを使用すべきではありません)を知っておく必要があります。 –

+0

Bitfield構造体は、予期しない方法で構造体をパックすることができるため、問題を引き起こすことで有名です。このアプローチは、(1)コンパイラの動作が肯定的であり、(2)コンパイラをこれまでどおりに変更しない場合(たとえば、コンパイラが1つしかない特殊プロセッサなど)にのみ使用してください。 –

1

最適化する必要がない場合は、最も単純な解決方法を使用しないでください。

あなたが最適化する必要がある場合、あなたは何のために知っておく必要があります。そして、あなたがいないので、あなただけではなく、それをトグルのビットを設定するか、クリアした場合、最初のバージョンは最小限に速くなり

  • メモリを読み取る必要があります。

  • 最初のバージョンが優れています。並行性。後者では、リード・モディファイ・ライトがあるので、メモリ・バイトが同時にアクセスされないようにする必要があります。通常、割り込みを無効にすると、割り込みのレイテンシがいくらか増加します。また、割り込みを無効にするのを忘れると、非常に厄介で、見つけにくいバグが発生する可能性があります(これまで遭遇した最も厄介なバグはまさにこの種のものでした)。

  • 最初のバージョンは最小限に抑えられています。各アクセスが単一のロードまたはストア操作であるため、コードサイズ(フラッシュ使用量が少ない)が必要です。第2のアプローチは、追加のビット操作を必要とする。

  • 2番目のバージョンは、特にこれらのビットが多い場合は、RAMの使用量が少なくなります。

  • 複数のビットを一度にテストする場合(たとえば、設定されているビットの1つなど)、2番目のバージョンも高速です。

1

あなたが読みやすさ、ビットセットとC++を話している場合は、なぜ私はそこにstd::bitsetに何かを見つけていませんか?私は組み込みプログラマーのレースはビットマスクにはとても慣れ親しんでいることを理解しています。マスクやビットフィールドを除いて、醜い醜さ(マスクではなく、レースではありません)を失ってしまったことを理解しています。

例:定数ではなく、マクロの使用をしないのはなぜ 「の#define SHIFT 0」 :

#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

stdライブラリがこのコンパイラでサポートされているかどうかは不明です(使用できないようにメモリを使いすぎるかもしれません)。デフォルトでは標準出力はありません。あなたがそれを可能にすれば、おそらくシリアルポートインターフェースだけの画面はありません。 –

+0

@Alan Moore:刃ビットはそれが何をするかを誇示するだけでした。テストに非常に便利です! – xtofl

0

私はと心配しています最初のものがあると思いますか?効率が得られる限り、定数は型を決定することを許すので、後で変換が必要になることはありません。

あなたの技術の効率について: - まず、else節を取り除いてください(値がすでにHIGHの場合、ビットをHIGHに設定するのはなぜですか?) - 第二に、内部でビットマスキングを使用してインライン設定されたセッター/ゲッターが効率的に読み込み可能なものを読みやすくすることを好みます。

ストレージに関しては、C++ではビートセット(enumと組み合わせて)を使用する傾向があります。

0

はそれがあまりにもシンプルなだけと言うことです:

flags ^= bit; 
関連する問題