Apakah ada keuntungan untuk manipulasi bit c-style dibandingkan std :: bitset?

16

Saya bekerja hampir secara eksklusif di C ++ 11/14, dan biasanya merasa ngeri ketika saya melihat kode seperti ini:

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

Ini hanya sebuah contoh; Saya berbicara tentang manipulasi bit-bijaksana secara umum. Dalam C ++, apakah benar-benar ada gunanya? Di atas membingungkan dan rawan kesalahan, sementara menggunakan std::bitsetmemungkinkan Anda untuk:

  1. lebih mudah memodifikasi ukuran std::bitsetsesuai kebutuhan dengan menyesuaikan parameter template dan membiarkan implementasi mengurus sisanya, dan
  2. menghabiskan lebih sedikit waktu untuk mencari tahu apa yang terjadi (dan mungkin membuat kesalahan) dan menulis std::bitsetdengan cara yang mirip dengan std::arrayatau wadah data lainnya.

Pertanyaanku adalah; adakah alasan untuk tidak menggunakan std::bitsetlebih dari tipe primitif, selain untuk keterbelakangan-mundur?

bergalah
sumber
Ukuran a std::bitsetdiperbaiki pada waktu kompilasi. Itulah satu-satunya kekurangan yang bisa saya pikirkan.
rwong
1
@ rwong saya berbicara tentang std::bitsetmanipulasi bit vs-c-style (misalnya int), yang juga diperbaiki pada waktu kompilasi.
quant
Salah satu alasannya mungkin kode warisan: Kode ditulis ketika std::bitsettidak tersedia (atau diketahui oleh penulis) dan belum ada alasan untuk menulis ulang kode yang akan digunakan std::bitset.
Bart van Ingen Schenau
Saya pribadi berpikir masalah bagaimana membuat "operasi pada set / peta / array variabel biner" mudah dipahami semua orang sebagian besar masih belum terpecahkan, karena ada banyak operasi yang digunakan dalam praktik yang tidak dapat direduksi menjadi operasi sederhana. Ada juga terlalu banyak cara untuk mewakili set tersebut, di antaranya bitsetsatu, tetapi vektor kecil atau set ints (indeks bit) juga mungkin sah. Filosofi C / C ++ tidak menyembunyikan kompleksitas pilihan ini dari programmer.
rwong

Jawaban:

12

Dari sudut pandang logis (non-teknis), tidak ada keuntungan.

Kode C / C ++ biasa dapat dibungkus dengan "pustaka konstruksi" yang sesuai. Setelah pembungkusan seperti itu, masalah "apakah ini lebih menguntungkan dari itu" menjadi pertanyaan yang bisa diperdebatkan.

Dari sudut pandang kecepatan, C / C ++ harus memungkinkan pustaka membangun untuk menghasilkan kode yang seefisien kode polos yang dibungkusnya. Namun ini tunduk pada:

  • Fungsi inlining
  • Pengecekan waktu kompilasi dan penghapusan pemeriksaan runtime yang tidak perlu
  • Penghapusan kode mati
  • Banyak optimasi kode lainnya ...

Menggunakan argumen non-teknis semacam ini, "fungsi apa pun yang hilang" dapat ditambahkan oleh siapa saja, dan karenanya tidak dianggap sebagai kerugian.

Namun, persyaratan dan batasan bawaan tidak dapat diatasi dengan kode tambahan. Di bawah ini, saya berpendapat bahwa ukuran std::bitsetadalah konstanta waktu kompilasi, dan oleh karena itu meskipun tidak dihitung sebagai kerugian, itu masih sesuatu yang mempengaruhi pilihan pengguna.


Dari sudut pandang estetika (keterbacaan, kemudahan pemeliharaan, dll), ada perbedaan.

Namun, tidak jelas bahwa std::bitsetkode langsung menang atas kode C polos. Kita harus melihat potongan kode yang lebih besar (dan bukan sampel mainan) untuk mengatakan apakah penggunaan std::bitsettelah meningkatkan kualitas manusia dari kode sumber.


Kecepatan manipulasi bit tergantung pada gaya pengkodean. Gaya pengkodean mempengaruhi manipulasi bit C / C ++, dan sama-sama berlaku std::bitsetjuga, seperti dijelaskan berikut ini.


Jika seseorang menulis kode yang menggunakan operator []membaca dan menulis satu bit pada satu waktu, seseorang harus melakukan ini beberapa kali jika ada lebih dari satu bit yang akan dimanipulasi. Hal yang sama dapat dikatakan tentang kode gaya-C.

Namun, bitsetjuga memiliki operator lain, seperti operator &=, operator <<=, dll, yang beroperasi pada lebar penuh bitset tersebut. Karena mesin yang mendasarinya sering dapat beroperasi pada 32-bit, 64-bit dan kadang-kadang 128-bit (dengan SIMD) pada suatu waktu (dalam jumlah siklus CPU yang sama), kode yang dirancang untuk mengambil keuntungan dari operasi multi-bit tersebut bisa lebih cepat dari kode manipulasi bit "loopy".

Gagasan umum disebut SWAR (SIMD dalam register) , dan merupakan subtopik dengan manipulasi bit.


Beberapa vendor C ++ mungkin menerapkan bitsetantara 64-bit dan 128-bit dengan SIMD. Beberapa vendor mungkin tidak (tetapi mungkin akhirnya melakukannya). Jika ada kebutuhan untuk mengetahui apa yang dilakukan pustaka vendor C ++, satu-satunya cara adalah dengan melihat pembongkaran.


Seperti apakah std::bitsetmemiliki keterbatasan, saya bisa memberikan dua contoh.

  1. Ukuran std::bitsetharus diketahui pada waktu kompilasi. Untuk membuat array bit dengan ukuran yang dipilih secara dinamis, kita harus menggunakannya std::vector<bool>.
  2. Spesifikasi C ++ saat ini untuk std::bitsettidak menyediakan cara untuk mengekstrak sepotong berturut-turut N bit dari bitsetM bit yang lebih besar .

Yang pertama adalah fundamental, artinya bagi orang yang membutuhkan bitet berukuran dinamis, mereka harus memilih opsi lain.

Yang kedua dapat diatasi, karena seseorang dapat menulis semacam adapter untuk melakukan tugas, bahkan jika standarnya bitsettidak dapat diperluas.


Ada beberapa jenis operasi SWAR lanjutan yang tidak disediakan di luar kotak std::bitset. Orang dapat membaca tentang operasi ini di situs web ini tentang permutasi bit . Seperti biasa, seseorang dapat mengimplementasikan ini sendiri, beroperasi di atas std::bitset.


Mengenai diskusi tentang kinerja.

Satu peringatan: banyak orang bertanya mengapa (sesuatu) dari perpustakaan standar jauh lebih lambat daripada beberapa kode gaya C sederhana. Saya tidak akan mengulangi pengetahuan prasyarat tentang microbenchmarking di sini, tetapi saya hanya memiliki saran ini: pastikan untuk melakukan benchmark dalam "mode rilis" (dengan optimisasi diaktifkan), dan pastikan kode tidak dihilangkan (penghilangan kode mati) atau sedang diangkat dari loop (gerakan kode invarian loop) .

Karena secara umum kita tidak dapat mengatakan apakah seseorang (di internet) melakukan microbenchmarks dengan benar, satu-satunya cara kita bisa mendapatkan kesimpulan yang dapat diandalkan adalah dengan melakukan microbenchmarks kita sendiri, dan mendokumentasikan detailnya, dan menyerahkan kepada publik tinjauan dan kritik. Tidak ada salahnya untuk melakukan microbenchmark yang telah dilakukan orang lain sebelumnya.

rwong
sumber
Masalah # 2 juga berarti bahwa bitset tidak dapat digunakan dalam pengaturan paralel di mana setiap thread harus bekerja pada subset dari bitset.
user239558
@ user239558 Saya ragu ada orang yang ingin memparalelkan pada yang sama std::bitset. Tidak ada jaminan konsistensi memori (dalam std::bitset), yang berarti tidak seharusnya dibagi antara inti. Orang yang perlu membagikannya di seluruh inti akan cenderung untuk membangun implementasi mereka sendiri. Ketika data dibagi di antara inti yang berbeda, itu adalah kebiasaan untuk menyelaraskannya ke batas garis cache. Tidak melakukan hal itu mengurangi kinerja, dan mengekspos lebih banyak perangkap non-atomisitas. Saya tidak memiliki pengetahuan yang cukup untuk memberikan gambaran tentang bagaimana membangun implementasi yang paralel dari std::bitset.
rwong
pemrograman paralel data biasanya tidak memerlukan konsistensi memori. Anda hanya menyinkronkan antara fase. Saya benar-benar ingin memproses bitset secara paralel, saya pikir siapa pun dengan bitsetkemauan besar .
user239558
@ user239558 yang kedengarannya seperti menyiratkan penyalinan (kisaran bitet yang relevan untuk diproses oleh setiap inti harus disalin sebelum pemrosesan dimulai). Saya setuju dengan itu, meskipun saya pikir siapa pun yang berpikir tentang paralelisasi akan berpikir untuk meluncurkan implementasi mereka sendiri. Secara umum, banyak fasilitas perpustakaan standar C ++ disediakan sebagai implementasi dasar; siapa pun yang memiliki kebutuhan yang lebih serius akan mengimplementasikannya sendiri.
rwong
tidak ada penyalinan. itu hanya mengakses berbagai bagian struktur data statis. tidak diperlukan sinkronisasi.
user239558
2

Ini tentu saja tidak berlaku dalam semua kasus, tetapi kadang-kadang suatu algoritma mungkin tergantung pada efisiensi tw-bit-style C untuk memberikan keuntungan kinerja yang signifikan. Contoh pertama yang muncul di benak saya adalah penggunaan bitboard , pengkodean integer yang pintar dari posisi permainan papan, untuk mempercepat mesin catur dan sejenisnya. Di sini, ukuran tetap dari tipe integer tidak ada masalah, karena papan catur selalu 8 * 8.

Untuk contoh sederhana, pertimbangkan fungsi berikut (diambil dari jawaban ini oleh Ben Jackson ) yang menguji posisi Connect Four untuk menang:

// 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 |
}
David Zhang
sumber
2
Apakah Anda pikir std::bitsetakan lebih lambat?
quant
Nah, dari pandangan sekilas ke sumbernya, libc ++ bitset didasarkan pada satu size_t atau array dari mereka, jadi mungkin akan dikompilasi ke sesuatu yang pada dasarnya setara / identik, terutama pada sistem di mana sizeof (size_t) == 8 - jadi tidak, itu mungkin tidak akan lebih lambat.
Ryan Pavlik