Boolean bitwise trong PHP – Stitcher.io


Trong bài viết trước của tôi, tôi đã viết về việc áp dụng các mẫu enum trong PHP mà không cần hỗ trợ enum gốc.

Trong bài đăng đó, tôi đã đưa ra ví dụ về enum “ranh giới phạm vi ngày”, một enum thể hiện ranh giới nào được bao gồm trong phạm vi và ranh giới nào không. Nó có bốn giá trị có thể:

  • Boundaries::INCLUDE_NONE();
  • Boundaries::INCLUDE_START();
  • Boundaries::INCLUDE_END();
  • Boundaries::INCLUDE_ALL();

Để thể hiện những ranh giới này, tôi đã lưu trữ hai cờ boolean trên các lớp giá trị enum: $startIncluded$endIncluded.

Trong bài đăng này, tôi muốn trình bày một cách khác để lưu trữ hai cờ boolean này bằng cách sử dụng bitmask.

Dưới đây là bản tóm tắt nhanh về (một phần) lớp enum của chúng tôi trông như thế nào:

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

    
}

Trong trường hợp này, chúng tôi đang sử dụng hai biến để lưu trữ hai các giá trị boolean.

Tuy nhiên, chúng là boolean, có nghĩa là chúng chỉ có thể có một trong hai giá trị: true hoặc false; 1 hoặc 0. Thay vì sử dụng cả byte, chúng ta chỉ cần một bit để lưu trữ giá trị này.

Đợi đã, cả một byte? – Thực tế là nhiều hơn thế: chính xác là 16 byte. PHP lưu trữ tất cả các biến trong một cấu trúc được gọi là zval, dự trữ bộ nhớ không chỉ cho tải trọng mà còn nhập thông tin, cờ bit và những thứ khác. Bạn có thể xem nó ở đây.

Trong số 16 byte đó, có 8 byte dành riêng cho mỗi zval để lưu trữ một trọng tải; đó là 64 bit!

Bây giờ, với tư cách là người đi trước, hãy làm rõ rằng có thể bạn sẽ không bao giờ cần những loại tối ưu hóa vi mô này. Mặt nạ bit Boolean thường được sử dụng trong phát triển trò chơi, trình biên dịch và những thứ tương tự vì chúng rất tiết kiệm bộ nhớ. Tuy nhiên, trong các ứng dụng web, bạn có thể yên tâm rằng có thể bạn sẽ không bao giờ cần đến chúng.

Tuy nhiên, đó là một điều thú vị, thú vị cần biết và có thể thực hiện được trong PHP.

Vì vậy, hãy lưu trữ hai cờ này trong một biến.

abstract class Boundaries
{
    protected int $inclusionMask = 0b00;
}

Chuyện gì đang xảy ra ở đây vậy? Chúng tôi đang sử dụng ký hiệu nhị phân của số nguyên để dễ dàng làm việc với từng bit riêng lẻ. Nếu bạn đã từng học về hệ nhị phân ở trường hoặc ở nơi nào khác, bạn biết rằng 0b00 bằng 0, 0b01 bằng 1, 0b10 bằng 2 và 0b11 bằng 3. 0b là tiền tố mà PHP sử dụng để biết bạn đang viết nhị phân và 00 là hai bit thực tế.

Bây giờ chúng ta đã có hai bit để làm việc, thật dễ dàng để lưu trữ hai giá trị boolean trong đó. Giả sử rằng bit ngoài cùng bên phải đại diện endIncludedvà bit ngoài cùng bên trái đại diện cho startIncluded.

Vì thế 0b01 có nghĩa là ranh giới đầu không được bao gồm, trong khi ranh giới cuối thì có; 0b11 có nghĩa là cả hai đều được bao gồm – bạn hiểu ý chính.

Bây giờ chúng ta đã biết cách lưu trữ dữ liệu theo bit, chúng ta vẫn cần một cách đọc thông tin trong startIncluded()endIncluded() phương pháp: chúng tôi không muốn lập trình mọi thứ ở dạng nhị phân.

Đây là lúc các toán tử bitwise phát huy tác dụng, cụ thể hơn là and nhà điều hành.

Lấy hai giá trị nhị phân sau:

0b0100101;
0b1010101;

Điều gì xảy ra khi chúng ta áp dụng một and hoạt động trên cả hai giá trị này? Kết quả sẽ có tất cả các bit được đặt thành 1 cả hai bit ở đâu 1 ở hai giá trị ban đầu:

0b0100101;
0b1010101;

Đây là kết quả cuối cùng:

0b0000101;

Quay lại ví dụ về ranh giới của chúng tôi. Làm thế nào chúng ta có thể biết liệu phần bắt đầu có được bao gồm hay không? Vì ranh giới bắt đầu được biểu thị bằng bit ngoài cùng bên trái nên chúng ta có thể áp dụng mặt nạ bit cho biến bao gồm của mình. Nếu chúng ta muốn biết liệu bit bắt đầu có được đặt hay không, chúng ta chỉ cần thực hiện một thao tác and hoạt động giữa mặt nạ bao gồm và giá trị nhị phân 0b10.

Làm sao vậy? Vì chúng tôi chỉ quan tâm đến việc biết giá trị của ranh giới bắt đầu nên chúng tôi sẽ chỉ tạo mặt nạ cho bit đó. Nếu chúng ta áp dụng một and hoạt động giữa hai giá trị này, kết quả sẽ luôn là 0b00trừ khi bit bắt đầu thực sự được đặt.

Đây là một ví dụ trong đó bit bắt đầu là 0:

0b10; // The mask we're applying
0b01; // The inclusion mask

0b00; // The result

Và đây là nơi bắt đầu 1:

0b10; // The mask we're applying
0b10; // The inclusion mask

0b10; // The result

Bit kết thúc sẽ luôn là 0 trong trường hợp này, vì mặt nạ chúng ta đang áp dụng được đặt thành 0. Do đó, bất kỳ giá trị nào được lưu trữ cho ranh giới cuối trong mặt nạ bao hàm sẽ luôn dẫn đến 0.

Vậy làm thế nào để thực hiện điều này trong PHP? Bằng cách sử dụng hệ nhị phân and toán tử, là một toán tử đơn &:

public function startIncluded(): bool 
{
    return $this->inclusionMask & 0b10;
}

public function endIncluded(): bool 
{
    return $this->inclusionMask & 0b01;
}

Hệ thống kiểu động của PHP sẽ tự động đưa ra kết quả, 0 hoặc một giá trị số, thành một boolean. Tuy nhiên, nếu bạn muốn rõ ràng hơn, bạn có thể viết nó như sau:

public function startIncluded(): bool 
{
    return ($this->inclusionMask & 0b10) !== 0;
}

public function endIncluded(): bool 
{
    return ($this->inclusionMask & 0b01) !== 0;
}

Nhận thấy một tpyo? Bạn có thể gửi PR để sửa nó. Nếu bạn muốn cập nhật về những gì đang diễn ra trên blog này, bạn có thể Theo dõi danh sách gửi thư của tôi: gửi email đến [email protected] và tôi sẽ thêm bạn vào danh sách.

Hãy làm rõ rằng bạn không nên làm điều này vì mục đích thúc đẩy hiệu suất trong PHP. Thậm chí có thể có những trường hợp đặc biệt mà cách tiếp cận này sẽ kém tối ưu hơn vì mặt nạ bao gồm của chúng tôi không thể được thu thập rác trừ khi không còn tham chiếu nào đến bất kì của các cờ boolean.

Tuy nhiên, nếu bạn đang làm việc với nhiều cờ boolean cùng một lúc, việc lưu trữ chúng trong một biến thay vì nhiều biến có thể hữu ích để giảm tải nhận thức. Bạn có thể coi việc “lưu trữ các giá trị boolean” như một chi tiết triển khai hậu trường, trong khi API công khai của một lớp vẫn cung cấp cách làm việc rõ ràng với chúng.

Vì vậy, ai biết được, có thể có những trường hợp kỹ thuật này hữu ích. Nếu bạn có một số trường hợp sử dụng thực tế, hãy nhớ cho tôi biết trên Twitter hoặc qua e-mail.



Leave a Comment

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Scroll to Top