Có bất kỳ lợi thế nào đối với thao tác bit kiểu c so với std :: bitset không?


15

Tôi làm việc gần như độc quyền trong C ++ 11/14 và thường co rúm lại khi thấy mã như thế này:

std::int64_t mArray;
mArray |= someMask << 1;

Đây chỉ là một ví dụ; Tôi đang nói về thao tác bit-khôn nói chung. Trong C ++, có thực sự có điểm nào không? Ở trên là cong vênh và dễ bị lỗi, trong khi sử dụng std::bitsetcho phép bạn:

  1. dễ dàng sửa đổi kích thước của std::bitsetnhu cầu bằng cách điều chỉnh một tham số mẫu và để việc triển khai thực hiện phần còn lại và
  2. dành ít thời gian hơn để tìm hiểu những gì đang xảy ra (và có thể mắc lỗi) và viết std::bitsettheo cách tương tự std::arrayhoặc các thùng chứa dữ liệu khác.

Câu hỏi của tôi là; Có bất kỳ lý do để không sử dụng std::bitsettrên các loại nguyên thủy, ngoài khả năng tương thích ngược?


Kích thước của a std::bitsetđược cố định tại thời gian biên dịch. Đó là nhược điểm duy nhất mà tôi có thể nghĩ ra.
rwong

1
@rwong Tôi đang nói về std::bitsetthao tác bit kiểu c (ví dụ int), cũng được sửa trong thời gian biên dịch.
lượng

Một lý do có thể là mã kế thừa: Mã được viết khi std::bitsetkhông có sẵn (hoặc được tác giả biết đến) và không có lý do để viết lại mã để sử dụng std::bitset.
Bart van Ingen Schenau

Cá nhân tôi nghĩ rằng vấn đề làm thế nào để thực hiện "các thao tác trên một tập hợp / bản đồ / mảng các biến nhị phân" dễ hiểu đối với mọi người vẫn chưa được giải quyết, bởi vì có nhiều hoạt động được sử dụng trong thực tế không thể giảm xuống thành các hoạt động đơn giản. Cũng có quá nhiều cách để biểu diễn các tập như vậy, trong đó bitsetlà một, nhưng một vectơ nhỏ hoặc tập ints (chỉ số bit) cũng có thể hợp pháp. Triết lý của C / C ++ không che giấu sự phức tạp lựa chọn này từ lập trình viên.
rwong

Câu trả lời:


12

Từ quan điểm logic (phi kỹ thuật), không có lợi thế.

Bất kỳ mã C / C ++ đơn giản nào cũng có thể được gói trong "cấu trúc thư viện" phù hợp. Sau khi gói như vậy, vấn đề "liệu điều này có lợi hơn thế không" trở thành một câu hỏi tranh luận.

Từ quan điểm tốc độ, C / C ++ sẽ cho phép thư viện xây dựng để tạo mã hiệu quả như mã đơn giản mà nó bọc. Điều này tuy nhiên phải tuân theo:

  • Chức năng nội tuyến
  • Kiểm tra thời gian biên dịch và loại bỏ kiểm tra thời gian chạy không cần thiết
  • Loại bỏ mã chết
  • Nhiều tối ưu hóa mã khác ...

Sử dụng loại đối số phi kỹ thuật này, bất kỳ "chức năng bị thiếu" nào cũng có thể được thêm vào bởi bất kỳ ai và do đó không được tính là bất lợi.

Tuy nhiên, các yêu cầu và giới hạn tích hợp có thể được khắc phục bằng mã bổ sung. Dưới đây, tôi lập luận rằng kích thước của std::bitsetlà hằng số thời gian biên dịch, và do đó, mặc dù không được tính là bất lợi, nó vẫn là thứ ảnh hưởng đến sự lựa chọn của người dùng.


Từ quan điểm thẩm mỹ (dễ đọc, dễ bảo trì, v.v.), có một sự khác biệt.

Tuy nhiên, không rõ ràng rằng std::bitsetmã ngay lập tức chiến thắng mã C đơn giản. Người ta phải xem xét các đoạn mã lớn hơn (chứ không phải một số mẫu đồ chơi) để cho biết việc sử dụng std::bitsetcó cải thiện chất lượng con người của mã nguồn hay không.


Tốc độ của thao tác bit phụ thuộc vào phong cách mã hóa. Kiểu mã hóa ảnh hưởng đến cả thao tác bit C / C ++ và cũng có thể áp dụng std::bitsetnhư nhau, như được giải thích sau đây.


Nếu một người viết mã sử dụng operator []để đọc và ghi một bit tại một thời điểm, người ta sẽ phải thực hiện việc này nhiều lần nếu có nhiều hơn một bit được thao tác. Điều tương tự cũng có thể nói về mã kiểu C.

Tuy nhiên, bitsetcũng có nhà khai thác khác, chẳng hạn như operator &=, operator <<=vv, mà hoạt động trên toàn bộ chiều rộng của bitset. Bởi vì máy móc cơ bản thường có thể hoạt động trên 32 bit, 64 bit và đôi khi là 128 bit (với SIMD) tại một thời điểm (trong cùng một số chu kỳ CPU), mã được thiết kế để tận dụng các hoạt động đa bit như vậy có thể nhanh hơn mã thao tác bit "loopy".

Ý tưởng chung được gọi là SWAR (SIMD trong một thanh ghi) và là một chủ đề con dưới các thao tác bit.


Một số nhà cung cấp C ++ có thể triển khai bitsetgiữa 64 bit và 128 bit với SIMD. Một số nhà cung cấp có thể không (nhưng cuối cùng có thể làm). Nếu có nhu cầu biết thư viện của nhà cung cấp C ++ đang làm gì, cách duy nhất là xem xét việc tháo gỡ.


Về việc std::bitsetcó những hạn chế, tôi có thể đưa ra hai ví dụ.

  1. Kích thước của std::bitsetphải được biết tại thời gian biên dịch. Để tạo ra một mảng các bit có kích thước được chọn động, người ta sẽ phải sử dụng std::vector<bool>.
  2. Đặc tả C ++ std::bitsethiện tại không cung cấp cách trích xuất một lát N bit liên tiếp từ một bitsetbit M lớn hơn .

Đầu tiên là cơ bản, có nghĩa là đối với những người cần các bit có kích thước động, họ phải chọn các tùy chọn khác.

Cái thứ hai có thể được khắc phục, bởi vì người ta có thể viết một số loại bộ điều hợp để thực hiện nhiệm vụ, ngay cả khi tiêu chuẩn bitsetkhông thể mở rộng.


Có một số loại hoạt động SWAR tiên tiến không được cung cấp ngoài hộp std::bitset. Người ta có thể đọc về các hoạt động này trên trang web này về hoán vị bit . Như thường lệ, người ta có thể tự thực hiện những điều này, hoạt động trên đầu trang std::bitset.


Về các cuộc thảo luận về hiệu suất.

Một lời khuyên: rất nhiều người hỏi về lý do tại sao (một cái gì đó) từ thư viện tiêu chuẩn chậm hơn nhiều so với một số mã kiểu C đơn giản. Tôi sẽ không lặp lại những kiến thức điều kiện tiên quyết của microbenchmarking ở đây, nhưng tôi chỉ có lời khuyên này: hãy chắc chắn để chuẩn trong "chế độ phát hành" (với tối ưu hóa được kích hoạt), và chắc chắn rằng mã không được loại bỏ (loại bỏ mã chết) hoặc phúc kéo ra khỏi một vòng lặp (chuyển động mã vòng lặp bất biến) .

Vì nói chung, chúng tôi không thể biết liệu ai đó (trên internet) đã thực hiện các điểm chuẩn vi mô một cách chính xác hay không, cách duy nhất chúng tôi có thể đưa ra kết luận đáng tin cậy là thực hiện các dấu hiệu vi mô của riêng mình, và ghi lại các chi tiết, và gửi đánh giá và phê bình công khai. Sẽ không hại gì khi làm lại các dấu hiệu vi mô mà những người khác đã làm trước đây.


Vấn đề # 2 cũng có nghĩa là bitet không thể được sử dụng trong bất kỳ thiết lập song song nào trong đó mỗi luồng sẽ hoạt động trên một tập hợp con của bitet.
dùng239558

@ user239558 Tôi nghi ngờ bất cứ ai cũng muốn song song như vậy std::bitset. Không có đảm bảo tính nhất quán của bộ nhớ (in std::bitset), có nghĩa là nó không được chia sẻ giữa các lõi. Những người cần chia sẻ nó trên các lõi sẽ có xu hướng xây dựng triển khai của riêng họ. Khi dữ liệu được chia sẻ giữa các lõi khác nhau, theo thông lệ, việc căn chỉnh chúng theo ranh giới dòng bộ đệm. Không làm như vậy làm giảm hiệu suất, và phơi bày nhiều cạm bẫy phi nguyên tử hơn. Tôi không có đủ kiến ​​thức để đưa ra một cái nhìn tổng quan về cách xây dựng một triển khai song song std::bitset.
rwong

lập trình song song dữ liệu thường không yêu cầu bất kỳ sự nhất quán bộ nhớ. bạn chỉ đồng bộ hóa giữa các giai đoạn. Tôi hoàn toàn muốn xử lý song song một bitet, tôi nghĩ bất cứ ai có bitsetý chí lớn .
user239558

@ user239558 nghe có vẻ như ngụ ý sao chép (phạm vi bitet có liên quan được xử lý bởi mỗi lõi sẽ được sao chép trước khi bắt đầu xử lý). Tôi đồng ý với điều đó, mặc dù tôi nghĩ rằng bất cứ ai nghĩ về song song hóa cũng sẽ nghĩ về việc triển khai thực hiện của riêng họ. Nói chung, rất nhiều cơ sở thư viện tiêu chuẩn C ++ được cung cấp dưới dạng triển khai cơ sở; Bất cứ ai có nhu cầu nghiêm trọng hơn sẽ thực hiện riêng của họ.
rwong

không có không có bản sao. nó chỉ đơn giản là truy cập các phần khác nhau của cấu trúc dữ liệu tĩnh. không cần đồng bộ hóa sau đó.
user239558

2

Điều này chắc chắn không áp dụng trong mọi trường hợp, nhưng đôi khi một thuật toán có thể phụ thuộc vào hiệu quả của việc xoay bit theo kiểu C để mang lại hiệu suất đáng kể. Ví dụ đầu tiên tôi nghĩ đến là việc sử dụng bảng bit , mã hóa số nguyên thông minh của các vị trí trò chơi trên bàn cờ, để tăng tốc các động cơ cờ vua và những thứ tương tự. Ở đây, kích thước cố định của các loại số nguyên không có vấn đề gì, vì dù sao bàn cờ luôn luôn là 8 * 8.

Đối với một ví dụ đơn giản, hãy xem xét chức năng sau (được lấy từ câu trả lời này của Ben Jackson ) để kiểm tra vị trí Connect Four để giành chiến thắng:

// return whether newboard includes a win
bool haswon2(uint64_t newboard)
{
    uint64_t y = newboard & (newboard >> 6);
    uint64_t z = newboard & (newboard >> 7);
    uint64_t w = newboard & (newboard >> 8);
    uint64_t x = newboard & (newboard >> 1);
    return (y & (y >> 2 * 6)) | // check \ diagonal
           (z & (z >> 2 * 7)) | // check horizontal -
           (w & (w >> 2 * 8)) | // check / diagonal
           (x & (x >> 2));      // check vertical |
}

2
Bạn có nghĩ rằng std::bitsetsẽ chậm hơn không?
lượng

Chà, từ một cái nhìn nhanh về nguồn, bitet libc ++ dựa trên một size_t duy nhất hoặc một mảng của chúng, do đó có thể sẽ biên dịch thành một cái gì đó về cơ bản tương đương / giống hệt nhau, đặc biệt là trên một hệ thống có sizeof (size_t) == 8 - Vì vậy, nó có thể sẽ không chậm hơn.
Ryan Pavlik
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.