web-dev-qa-db-ja.com

1バイトのブール値。どうして?

C++では、ブール値がtrueまたはfalseを格納するために1バイトを必要とするのに、falseの場合は0、trueの場合は1のように、1ビットで十分なのはなぜですか? (なぜJavaも1バイトを必要とするのですか?)

第二に、以下を使用する方がどれだけ安全ですか?

struct Bool {
    bool trueOrFalse : 1;
};

第三に、安全だとしても、上記のフィールドテクニックは本当に役立つのでしょうか?そこでスペースを節約すると聞いたので、それらにアクセスするためにコンパイラが生成したコードは、プリミティブにアクセスするために生成されたコードよりも大きくて遅いです。

41
Sam

1ビットで十分な場合にboolがtrueまたはfalseを格納するために1バイトを必要とするのはなぜですか

C++のすべてのオブジェクトは個別にアドレス指定可能でなければならないため* (つまり、それへのポインターを持つことができなければなりません)。個々のビットをアドレス指定することはできません(少なくとも従来のハードウェアでは)。

以下を使用する方が安全ですか?

それは「安全」ですが、あまり達成されていません。

上記のフィールドテクニックは本当に役立ちますか?

いいえ、上記と同じ理由で;)

しかし、それらにアクセスするためにコンパイラーが生成したコードは、プリミティブにアクセスするために生成されたコードよりも大きくて遅いです。

ええそれはそうです。ほとんどのプラットフォームでは、これには、含まれているバイト(またはintなど)にアクセスしてから、ビットシフトおよびビットマスク操作を実行して関連するビットにアクセスする必要があります。


メモリ使用量が本当に心配な場合は、 std::bitset C++の場合、または BitSet Javaの場合、ビットをパックします。


*いくつかの例外を除いて。

90

シングルビットの使用は、割り当てがはるかに遅く、はるかに複雑です。 C/C++では、1ビットのアドレスを取得する方法がないため、ビットとして&trueOrFalseを実行することはできません。

Javaには、ビットマップを使用するBitSetとEnumSetがあります。数が非常に少ない場合は、あまり違いがない場合があります。例えばオブジェクトは少なくともバイトアラインされている必要があり、HotSpotでは8バイトアラインされています(C++ではnewオブジェクトは8〜16バイトアラインされます)。

少なくともJava)では、ビットはキャッシュにうまく収まらない限り高速ではありません。

public static void main(String... ignored) {
    BitSet bits = new BitSet(4000);
    byte[] bytes = new byte[4000];
    short[] shorts = new short[4000];
    int[] ints = new int[4000];

    for (int i = 0; i < 100; i++) {
        long bitTime = timeFlip(bits) + timeFlip(bits);
        long bytesTime = timeFlip(bytes) + timeFlip(bytes);
        long shortsTime = timeFlip(shorts) + timeFlip(shorts);
        long intsTime = timeFlip(ints) + timeFlip(ints);
        System.out.printf("Flip time bits %.1f ns, bytes %.1f, shorts %.1f, ints %.1f%n",
                bitTime / 2.0 / bits.size(), bytesTime / 2.0 / bytes.length,
                shortsTime / 2.0 / shorts.length, intsTime / 2.0 / ints.length);
    }
}

private static long timeFlip(BitSet bits) {
    long start = System.nanoTime();
    for (int i = 0, len = bits.size(); i < len; i++)
        bits.flip(i);
    return System.nanoTime() - start;
}

private static long timeFlip(short[] shorts) {
    long start = System.nanoTime();
    for (int i = 0, len = shorts.length; i < len; i++)
        shorts[i] ^= 1;
    return System.nanoTime() - start;
}

private static long timeFlip(byte[] bytes) {
    long start = System.nanoTime();
    for (int i = 0, len = bytes.length; i < len; i++)
        bytes[i] ^= 1;
    return System.nanoTime() - start;
}

private static long timeFlip(int[] ints) {
    long start = System.nanoTime();
    for (int i = 0, len = ints.length; i < len; i++)
        ints[i] ^= 1;
    return System.nanoTime() - start;
}

プリント

Flip time bits 5.0 ns, bytes 0.6, shorts 0.6, ints 0.6

40000および400Kのサイズの場合

Flip time bits 6.2 ns, bytes 0.7, shorts 0.8, ints 1.1

4M用

Flip time bits 4.1 ns, bytes 0.5, shorts 1.0, ints 2.3

および40M

Flip time bits 6.2 ns, bytes 0.7, shorts 1.1, ints 2.4
11
Peter Lawrey

1ビットの情報のみを格納する場合は、C/C++でアドレス指定可能な最小のメモリユニットであるcharほどコンパクトなものはありません。 (実装によっては、boolのサイズがcharと同じになる場合がありますが、サイズは 大きくすることができます です。)

charは、C標準によって少なくとも8ビットを保持することが保証されていますが、それ以上で構成することもできます。正確な数は、CHAR_BIT(Cの場合)またはclimits(C++)で定義されている limits.h マクロを介して入手できます。今日、CHAR_BIT == 8が最も一般的ですが、信頼することはできません( ここ を参照)。ただし、POSIX準拠のシステムおよび Windows では8であることが保証されています。

単一のフラグのメモリフットプリントを削減することはできませんが、もちろん複数のフラグを組み合わせることができます。すべてを実行する以外に ビット演算を手動で 、いくつかの選択肢があります:

  • コンパイル時のビット数がわかっている場合
    1. ビットフィールド (あなたの質問のように)。ただし、フィールドの順序は保証されていないため、移植性の問題が発生する可能性があることに注意してください。
    2. std::bitset
  • 実行時のみサイズがわかっている場合
    1. boost::dynamic_bitset
    2. 大きなビットベクトルを処理する必要がある場合は、 BitMagicライブラリ を参照してください。圧縮をサポートし、大幅に調整されています。

他の人がすでに指摘しているように、数ビットを節約することは必ずしも良い考えではありません。考えられる欠点は次のとおりです。

  1. 読みにくいコード
  2. 余分な抽出コードが原因で実行速度が低下しました。
  3. 同じ理由で、コードサイズの増加は、データ消費の節約を上回る可能性があります。
  4. マルチスレッドプログラムの隠れた同期の問題。たとえば、2つの異なるビットを2つの異なるスレッドで反転すると、競合状態が発生する可能性があります。対照的に、2つのスレッドがプリミティブ型の2つの異なるオブジェクト(例:char)を変更することは常に安全です。

通常、巨大なデータを処理する場合は、メモリとキャッシュへの負担が少ないというメリットがあるため、これは理にかなっています。

6
Philipp Claßen

状態をバイトに格納してみませんか?以下を実際にテストしていませんが、それはあなたにアイデアを与えるはずです。 16または32の状態でshortまたはintを使用することもできます。動作するJavaの例もあると思います。見つけたら、これを投稿します。

__int8 state = 0x0;

bool getState(int bit)
{
  return (state & (1 << bit)) != 0x0;
}

void setAllOnline(bool online)
{
  state = -online;
}

void reverseState(int bit)
{
   state ^= (1 << bit);
}

これがJavaバージョンです。それ以来、Int値に格納しています。正しく覚えていれば、バイトを使用しても4バイトが使用されます。これは明らかに使用されていません。アレイ。

public class State
{
    private int STATE;

    public State() { 
        STATE = 0x0;
    }

    public State(int previous) { 
        STATE = previous;
    }


   /*
    * @Usage - Used along side the #setMultiple(int, boolean);
    * @Returns the value of a single bit.
    */
    public static int valueOf(int bit)
    {
        return 1 << bit;
    }


   /*
    * @Usage - Used along side the #setMultiple(int, boolean);
    * @Returns the value of an array of bits.
    */
    public static int valueOf(int... bits)
    {
        int value = 0x0;
        for (int bit : bits)
            value |= (1 << bit);
        return value;
    }


   /*
    * @Returns the value currently stored or the values of all 32 bits.
    */
    public int getValue() 
    {
        return STATE;
    }

   /*
    * @Usage - Turns all bits online or offline.
    * @Return - <TRUE> if all states are online. Otherwise <FALSE>.
    */
    public boolean setAll(boolean online)
    {
        STATE = online ? -1 : 0;
        return online;
    }


   /*
    * @Usage - sets multiple bits at once to a specific state.
    * @Warning - DO NOT SET BITS TO THIS! Use setMultiple(State.valueOf(#), boolean);
    * @Return - <TRUE> if states were set to online. Otherwise <FALSE>.
    */
    public boolean setMultiple(int value, boolean online)
    {
        STATE |= value;
        if (!online)
            STATE ^= value;
        return online;
    }


   /*
    * @Usage - sets a single bit to a specific state.
    * @Return - <TRUE> if this bit was set to online. Otherwise <FALSE>.
    */
    public boolean set(int bit, boolean online)
    {
        STATE |= (1 << bit);
        if(!online)
            STATE ^= (1 << bit);
        return online;
    }


   /*
    * @return = the new current state of this bit.
    * @Usage = Good for situations that are reversed.
    */
    public boolean reverse(int bit)
    {
        return (STATE ^= (1 << bit)) == (1 << bit);
    }


   /*
    * @return = <TRUE> if this bit is online. Otherwise <FALSE>.
    */
    public boolean online(int bit)
    {
        int value = 1 << bit;
        return (STATE & value) == value;        
    }


   /*
    * @return = a String contains full debug information.
    */
    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("TOTAL VALUE: ");
        sb.append(STATE);
        for (int i = 0; i < 0x20; i++)
        {
            sb.append("\nState(");
            sb.append(i);
            sb.append("): ");
            sb.append(online(i));
            sb.append(", ValueOf: ");
            sb.append(State.valueOf(i));
        }
        return sb.toString();
    }
}

また、これに特別なクラスを使用するのではなく、使用する可能性が最も高いクラス内に変数を格納する必要があることも指摘しておく必要があります。数百または数千のブール値を計画している場合は、バイトの配列を検討してください。

例えば。以下の例。

boolean[] states = new boolean[4096];

以下に変換できます。

int[] states = new int[128];

ここで、128配列からインデックス4095にアクセスする方法について疑問に思われるかもしれません。つまり、これが行っているのは、単純化した場合です。 4095は5ビット右にシフトされます。これは、技術的には32で除算するのと同じです。したがって、4095/32 =切り捨てられます(127)。したがって、配列のインデックス127にいます。次に、4095と31を実行して、0から31までの値にキャストします。これは、2の累乗から1を引いた値でのみ機能します。 0、1、3、7、15、31、63、127、255、511、1023など..

これで、その位置のビットにアクセスできます。ご覧のとおり、これは非常にコンパクトで、ファイルに4096ブール値があるビートです:)これにより、バイナリファイルへの読み取り/書き込みがはるかに高速になります。このBitSetのものが何であるかはわかりませんが、完全なゴミのように見えます。byte、short、int、longは技術的にはすでにビット形式になっているため、そのまま使用することもできます。次に、メモリから個々のビットにアクセスするための複雑なクラスを作成します。これは、いくつかの投稿を読んで理解できることです。

boolean getState(int index)
{
    return (states[index >> 5] & 1 << (index & 0x1F)) != 0x0;
}

さらに詳しい情報...

基本的に、上記が少し混乱した場合は、ここで起こっていることの簡略版を示します。

タイプ "byte"、 "short"、 "int"、 「long」はすべて、範囲が異なるデータ型です。

このリンクを表示できます: http://msdn.Microsoft.com/en-us/library/s3f49ktz(v = vs.80).aspx

それぞれのデータ範囲を確認します。

したがって、1バイトは8ビットに相当します。したがって、4バイトのintは32ビットになります。

現在、[〜#〜] n [〜#〜]乗の値を実行する簡単な方法はありません。ただし、ビットシフトのおかげで、ある程度シミュレートできます。 1 << Nを実行すると、これは1 * 2 ^ Nに相当します。したがって、2 << 2 ^ Nを実行した場合、2 * 2 ^ Nを実行することになります。したがって、2の累乗を実行するには、常に「1 << N」を実行します。

これで、intには32ビットがあることがわかったので、各ビットを使用できるので、単純にインデックスを付けることができます。

簡単にするために、「&」演算子は、値に別の値のビットが含まれているかどうかを確認する方法と考えてください。したがって、31の値があったとしましょう。31に到達するには、次のビット0から4を追加する必要があります。1、2、4、8、および16です。これらはすべて合計31になります。 31&16は、2 ^ 4 = 16であるビット4がこの値にあるため、16を返します。ここで、ビット2と4がこの値にあるかどうかをチェックする31と20を実行したとしましょう。ビット2と4の両方がここにあるため、これは20を返します。2^ 2 = 4 + 2 ^ 4 = 16 = 20。ここで、31と48を実行したとします。これはビット4と5をチェックしています。 31にビット5があります。したがって、これは16のみを返します。0は返しません。したがって、複数のチェックを実行するときは、物理的にその値と等しいことを確認する必要があります。 0に等しいかどうかをチェックする代わりに。

以下は、個々のビットが0または1にあるかどうかを確認します。0は偽であり、1は真です。

bool getState(int bit)
{
    return (state & (1 << bit)) != 0x0;
}

以下は、2つの値にそれらのビットが含まれているかどうかを確認する例です。各ビットが2 ^ BITとして表されているように考えてください。そうすると、

いくつかの演算子について簡単に説明します。最近、「&」演算子について少し説明しました。さて、「|」オペレーター。

以下を行う場合

int value = 31;
value |= 16;
value |= 16;
value |= 16;
value |= 16;

値は31のままです。これは、ビット4または2 ^ 4 = 16がすでにオンになっているか、1に設定されているためです。したがって、「|」を実行します。そのビットをオンにしてその値を返します。すでにオンになっている場合、変更は行われません。 「| =」を使用して、変数をその戻り値に実際に設定します。

-> "value = value | 16;"を実行する代わりに。 「value | = 16;」を実行するだけです。

ここで、「」と「|」の使用方法についてもう少し詳しく見ていきましょう。

/*
 * This contains bits 0,1,2,3,4,8,9 turned on.
 */
const int CHECK = 1 | 2 | 4 | 8 | 16 | 256 | 512;

/*
 * This is some value were we add bits 0 through 9, but we skip 0 and 8.
 */
int value = 2 | 4 | 8 | 16 | 32 | 64 | 128 | 512;

したがって、以下のコードを実行すると。

int return_code = value & CHECK;

戻りコードは2+ 4 + 8 + 16 + 512 = 542になります

したがって、799をチェックしていましたが、542を受け取りました。これは、ビットoと8がオフラインであるため、256 + 1 = 257と799-257 = 542に等しくなります。

上記は、ビデオゲームを作成していて、ボタンが押された場合にボタンが押されたかどうかを確認するための優れた方法です。これらの各ビットを1回のチェックで簡単にチェックでき、すべての状態に対してブールチェックを実行するよりも何倍も効率的です。

ここで、常に反転されるブール値があるとしましょう。

通常、あなたは次のようなことをします

bool state = false;

state = !state;

これは、「^」演算子を使用してビットでも実行できます。

「1 << N」を実行して、そのビットの値全体を選択したのと同じです。逆でも同じことができます。したがって、「| =」がリターンを格納する方法を示したように、「^ =」でも同じことを行います。つまり、そのビットがオンの場合はオフにします。オフの場合はオンにします。

void reverseState(int bit)
{
   state ^= (1 << bit);
}

現在の状態を返すようにすることもできます。以前の状態に戻したい場合は、「!=」を「==」に交換してください。つまり、これは反転を実行してから現在の状態をチェックします。

bool reverseAndGet(int bit)
{
    return ((state ^= (1 << bit)) & (1 << bit)) != 0x0;
}

複数の非シングルビット別名bool値をintに格納することもできます。通常、以下のように座標位置を書き出すとしましょう。

int posX = 0;
int posY = 0;
int posZ = 0;

ここで、これらが1023を通過しなかったとしましょう。したがって、0から1023がこれらすべての最大距離でした。前述のように、他の目的で1023を選択します。これは、0〜2 ^ N-1の値を強制する方法として「&」変数を操作できます。したがって、範囲が0から1023であったとしましょう。「value&1023」を実行でき、インデックスパラメータチェックなしで常に0から1023の間の値になります。前述のように、これは2から1の累乗でのみ機能することに注意してください。 2 ^ 10 = 1024-1 = 1023。

例えば。 if(value> = 0 && value <= 1023)の場合はこれ以上ありません。

したがって、2 ^ 10 = 1024であり、0から1023までの数値を保持するには10ビットが必要です。

したがって、10x3 = 30であり、それでも32以下です。これらすべての値をintに保持するには十分です。

したがって、次のことを実行できます。したがって、使用したビット数を確認します。 0 + 10 + 20を実行します。0を配置する理由は、2 ^ 0 = 1であるため、#* 1 =#であることを視覚的に示すためです。 y << 10が必要な理由は、xが0から1023までの10ビットを使用するためです。したがって、それぞれに一意の値を設定するには、yに1024を掛ける必要があります。次に、Zに2 ^ 20、つまり1,048,576を掛ける必要があります。

int position = (x << 0) | (y << 10) | (z << 20);

これにより、比較が高速になります。

今できる

return this.position == position;

に並置

return this.x == x && this.y == y && this.z == z;

では、それぞれの実際の位置が必要な場合はどうでしょうか。

Xの場合、次のようにします。

int getX()
{ 
   return position & 1023;
}

次に、yについて、左ビットシフトを実行してからANDを実行する必要があります。

int getY()
{ 
   return (position >> 10) & 1023;
}

ご想像のとおり、ZはYと同じですが、10ではなく20を使用します。

int getZ()
{ 
   return (position >> 20) & 1023;
}

これを見た人は誰でも情報を得る価値があると思うでしょう:)。

5
Jeremy Trifilo

本当に1ビットを使用したい場合は、charを使用して8つのブール値を格納し、bitshiftを使用して必要な値を取得できます。それが速くなるとは思えませんし、おそらくそのように作業することで多くの頭痛の種になるでしょうが、技術的には可能です。

ちなみに、このような試みは、変数に使用できるメモリがあまりないが、必要以上の処理能力を備えているシステムに役立つ可能性があります。しかし、あなたがそれを必要とすることはないだろうと私は強く疑っています。

4
Kevin