Cập nhật
Đó là lứa tuổi kể từ khi tôi đăng một này, nhưng:
Tôi đã biết rằng đó là dễ dàng hơn và rõ ràng hơn so với bit không quan trọng trên một số nguyên , nhưng nó như là Nhanh?
Nếu bạn đang sử dụng bitset
theo một cách mà không thực sự làm cho nó rõ ràng hơn và sạch hơn so với bit không quan trọng, như kiểm tra một chút tại một thời điểm thay vì sử dụng một mặt nạ bit, sau đó chắc chắn bạn sẽ mất tất cả những lợi ích mà các hoạt động bitwise cung cấp, như có thể kiểm tra xem 64 bit được đặt cùng một lúc với một mặt nạ hay sử dụng hướng dẫn FFS để nhanh chóng xác định bit nào được đặt trong 64 bit.
Tôi không chắc rằng bitset
gánh chịu một hình phạt để sử dụng trong tất cả các cách có thể (ví dụ: sử dụng Bitwise nó operator&
), nhưng nếu bạn sử dụng nó như một kích thước cố định mảng boolean đó là khá nhiều cách tôi luôn thấy mọi người sử dụng nó, sau đó bạn thường mất tất cả những lợi ích được mô tả ở trên. Thật không may, chúng tôi không thể có được mức độ biểu đạt của việc chỉ truy cập một bit tại một thời điểm với operator[]
và có trình tối ưu hóa tất cả các thao tác bitwise và FFS và FFZ và tiếp tục cho chúng tôi, ít nhất là không kể từ lần cuối cùng tôi được kiểm tra (nếu không, bitset
sẽ là một trong những cấu trúc yêu thích của tôi).
Bây giờ nếu bạn định sử dụng bitset<N> bits
thay thế cho nhau, ví dụ như, uint64_t bits[N/64]
như truy cập cả hai cách tương tự bằng cách sử dụng bitwise, nó có thể là ngang hàng (chưa kiểm tra từ bài đăng cũ này). Nhưng sau đó bạn mất nhiều lợi ích khi sử dụng bitset
ngay từ đầu.
for_each
phương pháp
Trong quá khứ tôi đã vào một số hiểu lầm, tôi nghĩ rằng, khi tôi đề xuất một phương pháp for_each
để lặp qua những thứ như vector<bool>
, deque
, và bitset
. Điểm của phương thức như vậy là sử dụng kiến thức nội bộ của container để lặp qua các phần tử hiệu quả hơn khi gọi một hàm, giống như một số thùng chứa liên kết cung cấp phương thức của riêng mình thay vì sử dụng std::find
để làm tốt hơn thời gian tuyến tính. Tìm kiếm. Ví dụ, bạn có thể lặp qua tất cả các bit thiết lập của vector<bool>
hoặc bitset
nếu bạn có kiến thức nội bộ về các vùng chứa này bằng cách kiểm tra 64 phần tử cùng một lúc bằng cách sử dụng mặt nạ 64 bit khi 64 chỉ mục liền kề bị chiếm đóng và tương tự như vậy sử dụng hướng dẫn FFS khi không phải như vậy.
Nhưng một thiết kế vòng lặp phải làm loại logic vô hướng trong operator++
chắc chắn sẽ phải làm một cái gì đó đáng kể tốn kém hơn, chỉ bởi bản chất mà các vòng lặp được thiết kế trong những trường hợp đặc biệt này. bitset
thiếu các trình vòng lặp hoàn toàn và thường làm cho những người muốn sử dụng nó để tránh xử lý bitwise logic để sử dụng operator[]
để kiểm tra từng bit riêng lẻ trong một vòng tuần tự chỉ muốn tìm ra các bit nào được đặt. Điều đó cũng không hiệu quả như những gì thực hiện phương pháp for_each
có thể thực hiện.
Double/Nested Vòng lặp
Một thay thế cho phương pháp for_each
container cụ thể đề xuất trên sẽ được sử dụng lặp kép/lồng nhau: đó là, một iterator bên ngoài mà chỉ vào một tiểu phạm vi của một khác nhau loại trình lặp. Ví dụ về mã khách hàng:
for (auto outer_it = bitset.nbegin(); outer_it != bitset.nend(); ++outer_it)
{
for (auto inner_it = outer_it->first; inner_it != outer_it->last; ++inner_it)
// do something with *inner_it (bit index)
}
Mặc dù không phù hợp với loại thiết kế lặp có sẵn trong thùng chứa tiêu chuẩn, điều này có thể cho phép một số tối ưu hóa rất thú vị. Như một ví dụ, hãy tưởng tượng một trường hợp như thế này:
bitset<64> bits = 0x1fbf; // 0b1111110111111;
Trong trường hợp đó, các iterator bên ngoài có thể, chỉ với một vài lần lặp Bitwise ((FFZ/hoặc/bổ sung), suy ra rằng phạm vi đầu tiên của các bit để xử lý sẽ là bit [0, 6), tại thời điểm đó chúng ta có thể lặp qua tiểu phạm vi đó rất rẻ thông qua trình vòng lặp bên trong/lồng nhau (nó sẽ tăng số nguyên, làm cho ++inner_it
tương đương với chỉ ++int
). Sau đó, khi chúng ta tăng vòng lặp bên ngoài, nó có thể sau đó rất nhanh chóng, và một lần nữa với một vài hướng dẫn bitwise, xác định rằng phạm vi tiếp theo sẽ là [7, 13]. Sau khi chúng tôi lặp qua tiểu phạm vi đó, chúng tôi đã hoàn tất. Thực hiện việc này như một ví dụ khác:
bitset<16> bits = 0xffff;
Trong trường hợp này, các tiểu phạm vi đầu tiên và cuối cùng sẽ là [0, 16)
, và bitset có thể xác định rằng với một hướng dẫn Bitwise duy nhất tại thời điểm đó chúng tôi có thể lặp qua tất cả các bit bộ và sau đó chúng ta đã xong.
Loại thiết kế vòng lặp lồng nhau này sẽ ánh xạ đặc biệt tốt đến vector<bool>
, deque
và bitset
cũng như các cấu trúc dữ liệu khác mà mọi người có thể tạo như danh sách chưa được kiểm soát.
Tôi nói rằng theo cách vượt xa chỉ là suy đoán về ghế bành, vì tôi có một tập hợp các cấu trúc dữ liệu giống như số deque
thực sự ngang bằng với lặp tuần tự vector
(vẫn chậm hơn một cách ngẫu nhiên để truy cập ngẫu nhiên, đặc biệt là nếu chúng ta chỉ lưu trữ một loạt các nguyên thủy và xử lý tầm thường). Tuy nhiên, để đạt được thời gian tương đương với vector
cho lặp tuần tự, tôi phải sử dụng các loại kỹ thuật này (phương pháp for_each
và các trình vòng lặp đôi/lồng nhau) để giảm số lượng xử lý và phân nhánh đang diễn ra trong mỗi lần lặp. Tôi không thể cạnh tranh thời gian bằng cách khác chỉ sử dụng thiết kế lặp vòng lặp phẳng và/hoặc operator[]
. Và tôi chắc chắn không thông minh hơn những người triển khai thư viện chuẩn nhưng đã đưa ra một vùng chứa deque
giống như có thể được lặp tuần tự nhanh hơn nhiều và điều đó gợi ý cho tôi rằng đó là vấn đề với thiết kế giao diện chuẩn của trình lặp trong trường hợp này đi kèm với một số chi phí trong những trường hợp đặc biệt mà trình tối ưu hóa không thể tối ưu hóa.
Old trả lời
Tôi là một trong những người sẽ cung cấp cho bạn một câu trả lời thực hiện tương tự, nhưng tôi sẽ cố gắng cung cấp cho bạn một cái gì đó nhiều hơn một chút sâu hơn "just because"
. Đó là điều tôi đã trải qua qua việc định hình và định thời gian thực tế, không chỉ là sự ngờ vực và hoang tưởng.
Một trong những vấn đề lớn nhất với bitset
và vector<bool>
là thiết kế giao diện của họ "quá tiện lợi" nếu bạn muốn sử dụng chúng như một mảng các boolean. Tối ưu hóa tất cả cấu trúc mà bạn thiết lập để cung cấp an toàn, giảm chi phí bảo trì, thay đổi ít xâm nhập, v.v. Họ làm một công việc đặc biệt tốt với việc chọn hướng dẫn và phân bổ số lượng đăng ký tối thiểu để làm cho mã chạy nhanh như các lựa chọn thay thế không an toàn, không dễ bảo trì/thay đổi.
Phần làm giao diện bitet "quá tiện lợi" với chi phí hiệu quả là truy cập ngẫu nhiên operator[]
cũng như thiết kế trình lặp cho vector<bool>
. Khi bạn truy cập vào một trong các chỉ mục này tại chỉ mục n
, trước tiên, mã phải tìm ra byte thứ n thuộc về, và sau đó chỉ mục phụ tới bit trong đó. Giai đoạn đầu tiên thường liên quan đến một bộ phận/rshifts chống lại một lvalue cùng với modulo/bitwise và đó là tốn kém hơn so với hoạt động bit thực tế bạn đang cố gắng thực hiện.
Thiết kế iterator cho vector<bool>
phải đối mặt với tình trạng khó xử vụng về tương tự nơi nó hoặc có chi nhánh thành mã khác nhau mỗi 8 + lần bạn lặp qua nó hoặc trả tiền rằng loại chi phí lập chỉ mục mô tả ở trên. Nếu trước đây được thực hiện, nó làm cho logic không đối xứng trên lặp đi lặp lại, và thiết kế lặp có xu hướng để có một hit hiệu suất trong những trường hợp hiếm hoi. Để minh họa, nếu vector
có phương thức for_each
của riêng nó, bạn có thể lặp qua, bao gồm 64 phần tử cùng một lúc bằng cách chỉ che các bit dựa vào mặt nạ 64 bit cho vector<bool>
nếu tất cả các bit được đặt mà không kiểm tra từng bit riêng. Nó thậm chí có thể sử dụng FFS để tìm ra phạm vi tất cả cùng một lúc. Một thiết kế iterator sẽ có xu hướng chắc chắn phải làm điều đó trong một thời trang vô hướng hoặc lưu trữ nhiều tiểu bang mà phải được redundantly kiểm tra mỗi iteration. Để truy cập ngẫu nhiên, trình tối ưu hóa dường như không thể tối ưu hóa chi phí lập chỉ mục này để tìm ra byte và bit tương đối nào cần truy cập (có lẽ hơi phụ thuộc vào thời gian chạy) khi không cần thiết và bạn có xu hướng thấy hiệu suất đáng kể đạt được với các bit xử lý mã thủ công hơn theo tuần tự với kiến thức nâng cao về byte/từ/dword/qword mà nó đang làm việc.Đó là một sự so sánh không công bằng, nhưng khó khăn với std::bitset
là không có cách nào để so sánh công bằng trong những trường hợp như vậy, nơi mã biết byte nào nó muốn truy cập trước, và thường xuyên hơn không, bạn có xu hướng có thông tin này trước. Đó là một quả táo để so sánh màu cam trong trường hợp truy cập ngẫu nhiên, nhưng bạn thường chỉ cần cam.
Có lẽ sẽ không xảy ra nếu thiết kế giao diện liên quan đến bitset
trong đó operator[]
trả về proxy, yêu cầu mẫu truy cập hai chỉ mục để sử dụng. Ví dụ, trong trường hợp này, bạn sẽ truy cập bit 8 bằng cách viết bitset[0][6] = true; bitset[0][7] = true;
với tham số mẫu để biểu thị kích thước của proxy (64 bit, ví dụ). Một ưu tốt có thể mất một thiết kế như vậy và làm cho nó cạnh tranh với, loại trường cũ tay cách để thực hiện các thao tác bit bằng tay bằng cách dịch đó vào: bitset |= 0x60;
Một thiết kế có thể giúp là nếu bitsets
cung cấp một for_each_bit
loại phương thức, chuyển một proxy bit tới hàm functor mà bạn cung cấp. Điều đó thực sự có thể có khả năng cạnh tranh với phương pháp thủ công.
std::deque
có vấn đề về giao diện tương tự. Hiệu suất của nó không được là mà chậm hơn nhiều so với std::vector
để truy cập tuần tự. Tuy nhiên, thật không may, chúng tôi truy cập nó theo tuần tự sử dụng operator[]
được thiết kế để truy cập ngẫu nhiên hoặc thông qua một trình lặp, và đại diện nội bộ của deques chỉ đơn giản là không ánh xạ rất hiệu quả cho một thiết kế dựa trên vòng lặp. Nếu deque cung cấp một phương thức riêng của mình là for_each
thì có khả năng nó sẽ có khả năng bắt đầu gần hơn với hiệu suất truy cập tuần tự std::vector's
. Đây là một số trường hợp hiếm hoi mà thiết kế giao diện trình tự đi kèm với một số chi phí hiệu quả mà các trình tối ưu hóa thường không thể xóa bỏ. Thường thì các trình tối ưu hóa tốt có thể làm cho tiện ích trở nên miễn phí với chi phí thời gian chạy trong một bản dựng sản xuất, nhưng tiếc là không phải trong mọi trường hợp.
Xin lỗi!
Cũng xin lỗi, khi nhìn lại tôi lang thang một chút với bài đăng này nói về vector<bool>
và deque
ngoài bitset
. Đó là bởi vì chúng tôi đã có một codebase, nơi sử dụng ba, và đặc biệt là lặp đi lặp lại thông qua họ hoặc sử dụng chúng với truy cập ngẫu nhiên, thường là các điểm nóng.
táo để cam
Như đã nhấn mạnh trong câu trả lời cũ, so sánh việc sử dụng đơn giản của bitset
để loại nguyên thủy với logic bitwise ở mức độ thấp được so sánh táo để cam. Nó không giống như bitset
được thực hiện rất không hiệu quả cho những gì nó làm. Nếu bạn thực sự cần truy cập một loạt các bit với một mẫu truy cập ngẫu nhiên, vì một lý do nào đó, cần kiểm tra và thiết lập một chút thời gian, thì nó có thể được thực hiện lý tưởng cho một mục đích như vậy. Nhưng quan điểm của tôi là hầu như tất cả các trường hợp sử dụng tôi gặp phải không yêu cầu điều đó, và khi nó không được yêu cầu, cách học cũ liên quan đến hoạt động bitwise có xu hướng hiệu quả hơn đáng kể.
http://stackoverflow.com/questions/11712479/which-bitset-implementation-should-i-use-for-maximum-performance –
Không phải mất nhiều thời gian để chuẩn như nó đã viết câu hỏi của bạn...? –
@TonyD Sẽ mất khoảng một ngày để đưa ra một bộ thử nghiệm toàn diện trên các kiến trúc khác nhau mà có thể sử dụng trong một ý nghĩa chung, và thậm chí sau đó quá trình sẽ dễ bị lỗi vì tôi không phải là một chuyên gia. Tôi không nghĩ rằng nó không hợp lý để hỏi nếu nghiên cứu về điều này đã tồn tại ở nơi khác. – arman