(jp) =
前回の投稿では、ネイティブの列挙型サポートなしで PHP に列挙型パターンを適用する方法について書きました。
その投稿では、「日付範囲の境界」列挙型の例を示しました。これは、範囲に含まれる境界と含まれない境界を表すものです。 可能な値は次の 4 つです。
-
Boundaries::INCLUDE_NONE(); -
Boundaries::INCLUDE_START(); -
Boundaries::INCLUDE_END(); -
Boundaries::INCLUDE_ALL();
これらの境界を表すために、enum 値クラスに 2 つのブール値フラグを格納しました。 $startIncluded と $endIncluded.
この投稿では、ビットマスクを使用して、これら 2 つのブール値フラグを格納する別の方法を示したいと思います。

列挙型クラス (の一部) がどのように見えるかを簡単に要約すると、次のようになります。
abstract class Boundaries
private bool $startIncluded = false;
private bool $endIncluded = false;
public function startIncluded(): bool
return $this->startIncluded;
public function endIncluded(): bool
return $this->endIncluded;
この場合、使用しています 2 格納する変数 2 ブール値。
ただし、これらはブール値であるため、次の 2 つの値のいずれかしか持てないことを意味します。 true また false; 1 また 0. バイト全体を使用する代わりに、この値を格納するために必要なのは 1 ビットだけです。
ちょっと待って、1 バイト? — 実際にはもっと多く、正確には 16 バイトです。 PHP はすべての変数を a と呼ばれる構造体に格納します。 zval、ペイロードだけでなく、型情報、ビットフラグなどのためにメモリを予約します。 こちらでご覧いただけます。
これらの 16 バイトのうち、1 つにつき 8 バイトが予約されています。 zval ペイロードを格納します。 それは64ビットです!
さて、前兆として、この種のマイクロ最適化はおそらく必要ないことを明確にしましょう。 Boolean ビットマスクは、メモリ効率が非常に高いため、ゲーム開発やコンパイラなどでよく使用されます。 ただし、Web アプリケーションでは、それらが必要になることはおそらくないでしょう。
それにもかかわらず、それは知っておくべきクールでマニアックなことであり、PHP で可能です。
では、これら 2 つのフラグを 1 つの変数に格納しましょう。
abstract class Boundaries
protected int $inclusionMask = 0b00;
ここで何が起こっているのですか? 個々のビットを簡単に操作できるように、整数のバイナリ表記を利用しています。 学校などでバイナリ システムについて学んだことがあれば、 0b00 0に等しい、 0b01 1に等しい、 0b10 は 2 に等しく、 0b11 3に等しい。 0b バイナリを書いていることを知るために PHP が使用する接頭辞です。 00 2 つの実際のビットです。
作業する 2 つのビットがあるので、2 つのブール値を簡単に格納できます。 一番右のビットが表すとしましょう endIncluded、および左端のビットが表す startIncluded.
そう 0b01 開始境界は含まれませんが、終了境界は含まれます。 0b11 両方が含まれていることを意味します—要点がわかります。
データをビット単位で保存する方法がわかったので、まだ情報を読み取る方法が必要です。 startIncluded() と endIncluded() メソッド: すべてをバイナリでプログラムする必要はありません。
ここでビットごとの演算子が登場します。より具体的には、 and オペレーター。
次の 2 つのバイナリ値を取得します。
0b0100101;
0b1010101;
適用するとどうなるか and これらの値の両方で操作しますか? 結果は、すべてのビットが に設定されます。 1 両方のビットがあった場所 1 2 つの元の値:
0b0100101;
0b1010101;
これが最終結果です。
0b0000101;
境界の例に戻ります。 スタートが含まれているかどうかはどうすればわかりますか? 開始境界は左端のビットで表されるため、包含変数にビットマスクを適用できます。 開始ビットが設定されているかどうかを知りたい場合は、単純に次のことを行う必要があります。 and 包含マスクとバイナリ値の間の演算 0b10.
どうして? 開始境界の値を知りたいだけなので、そのビットのみのマスクを作成します。 適用する場合 and これら 2 つの値の間の演算では、結果は常に次のようになります。 0b00、開始ビットが実際に設定されていない限り。
スタートビットが 0:
0b10; // The mask we're applying
0b01; // The inclusion mask
0b00; // The result
そして、ここに開始ビットがある場所があります 1:
0b10; // The mask we're applying
0b10; // The inclusion mask
0b10; // The result
エンドビットは常に 0 この場合、適用しているマスクが次のように設定されているためです 0. したがって、包含マスクの終了境界に格納されている値が何であれ、常に結果は次のようになります。 0.
では、PHP でこれを行うにはどうすればよいでしょうか。 バイナリーを使うことで and 単一の演算子 &:
public function startIncluded(): bool
return $this->inclusionMask & 0b10;
public function endIncluded(): bool
return $this->inclusionMask & 0b01;
PHP の動的型システムは自動的に結果をキャストします。 0 または数値をブール値に変換します。 より明確にしたい場合は、次のように記述できます。
public function startIncluded(): bool
return ($this->inclusionMask & 0b10) !== 0;
public function endIncluded(): bool
return ($this->inclusionMask & 0b01) !== 0;
PHP のパフォーマンス向上のためにこれを行うべきではないことを明確にしましょう。 への参照がなくなっていない限り、包含マスクをガベージ コレクションできないため、このアプローチが最適ではないエッジ ケースさえあるかもしれません。 どれか ブールフラグの。
でも、一度に複数のブール値フラグを使用している場合は、認知負荷を軽減するために、それらを複数ではなく 1 つの変数に格納すると便利な場合があります。 「ブール値の保存」は舞台裏の実装の詳細と考えることができますが、クラスのパブリック API は依然としてそれらを操作する明確な方法を提供します。
したがって、この手法が役立つ場合があるかもしれません。 実際のユースケースがある場合は、必ず私に知らせてください ツイッター または電子メールで。
//platform.twitter.com/widgets.js