มีข้อได้เปรียบในการจัดการบิตของ c-style มากกว่า std :: bitset หรือไม่?


16

ฉันทำงานเฉพาะใน C ++ 11/14 และมักจะประจบประแจงเมื่อฉันเห็นรหัสเช่นนี้

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

นี่เป็นเพียงตัวอย่างเท่านั้น ฉันกำลังพูดถึงการจัดการบิตที่ชาญฉลาดโดยทั่วไป ใน C ++ มีจุดใดจริง ๆ หรือไม่ ข้างต้นคือการแปรปรวนของจิตใจและข้อผิดพลาดได้ง่ายในขณะที่ใช้std::bitsetช่วยให้คุณ:

  1. ปรับเปลี่ยนขนาดของstd::bitsetสิ่งที่ต้องการได้ง่ายขึ้นโดยการปรับพารามิเตอร์เทมเพลตและให้การใช้งานสามารถจัดการส่วนที่เหลือและ
  2. ใช้เวลาน้อยลงในการหาว่าเกิดอะไรขึ้น (และอาจทำผิดพลาด) และเขียนstd::bitsetในลักษณะที่คล้ายกับstd::arrayหรือบรรจุข้อมูลอื่น ๆ

คำถามของฉันคือ; มีเหตุผลที่จะไม่ใช้std::bitsetกับประเภทดั้งเดิมอื่น ๆ นอกเหนือจากความเข้ากันได้ย้อนหลัง?


ขนาดของ a std::bitsetได้รับการแก้ไข ณ เวลารวบรวม นั่นเป็นข้อเสียเปรียบเพียงอย่างเดียวที่ฉันคิดได้

1
@rwong ฉันกำลังพูดถึงstd::bitsetvs การจัดการบิตสไตล์ c (เช่นint) ซึ่งได้รับการแก้ไขในเวลารวบรวม
quant

เหตุผลหนึ่งอาจเป็นรหัสเดิม: รหัสที่เขียนขึ้นเมื่อstd::bitsetไม่สามารถใช้ได้ (หรือที่รู้จักกันที่ผู้เขียน) std::bitsetและไม่เคยมีเหตุผลที่จะเขียนรหัสเพื่อใช้งาน
Bart van Ingen Schenau

ฉันเองคิดว่าปัญหาของวิธีการ "ดำเนินการกับชุด / แผนที่ / อาเรย์ของตัวแปรไบนารี" ง่ายต่อการเข้าใจกับทุกคนยังคงไม่ได้แก้ปัญหาส่วนใหญ่เพราะมีการดำเนินงานจำนวนมากที่ใช้ในการปฏิบัติที่ไม่สามารถลดลง นอกจากนี้ยังมีวิธีมากเกินไปในการแสดงชุดดังกล่าวซึ่งbitsetเป็นหนึ่ง แต่เวกเตอร์ขนาดเล็กหรือชุดints (ของดัชนีบิต) อาจถูกต้องตามกฎหมายเช่นกัน ปรัชญาของ C / C ++ ไม่ได้ซ่อนความซับซ้อนของตัวเลือกเหล่านี้จากโปรแกรมเมอร์

คำตอบ:


12

จากมุมมองเชิงตรรกะ (ไม่ใช่ด้านเทคนิค) จะไม่มีประโยชน์

รหัส C / C ++ ธรรมดาใด ๆ สามารถห่อหุ้มด้วย "การสร้างห้องสมุด" ที่เหมาะสม หลังจากการห่อเช่นนี้เรื่องของ "ไม่ว่าจะเป็นข้อได้เปรียบกว่านั้น" กลายเป็นคำถามที่สงสัย

จากมุมมองของความเร็ว C / C ++ ควรอนุญาตให้ไลบรารีสร้างเพื่อสร้างโค้ดที่มีประสิทธิภาพเท่ากับโค้ดธรรมดาที่ล้อมรอบ อย่างไรก็ตามเรื่องนี้ขึ้นอยู่กับ:

  • ฟังก์ชั่นอินไลน์
  • การตรวจสอบเวลาแบบคอมไพล์และการกำจัดการตรวจสอบรันไทม์ที่ไม่จำเป็น
  • การกำจัดรหัสที่ตายแล้ว
  • การปรับแต่งโค้ดอื่น ๆ อีกมากมาย ...

การใช้อาร์กิวเมนต์ที่ไม่ใช่ด้านเทคนิคประเภทนี้ทุกคนสามารถเพิ่ม "ฟังก์ชั่นที่หายไป" ได้ดังนั้นจึงไม่ถือว่าเป็นข้อเสีย

อย่างไรก็ตามข้อกำหนดและข้อ จำกัด ในตัวไม่สามารถเอาชนะได้ด้วยรหัสเพิ่มเติม ด้านล่างฉันยืนยันว่าขนาดของstd::bitsetค่าคงที่เวลารวบรวมและดังนั้นในขณะที่ไม่นับเป็นข้อเสียก็ยังคงเป็นสิ่งที่มีผลต่อการเลือกของผู้ใช้


จากมุมมองที่สวยงาม (ความสามารถในการอ่าน, ความสะดวกในการบำรุงรักษา ฯลฯ ) มีความแตกต่าง

อย่างไรก็ตามจะไม่ปรากฏว่าstd::bitsetรหัสชนะทันทีเหนือรหัส C ธรรมดา เราต้องดูโค้ดขนาดใหญ่กว่า (และไม่ใช่ตัวอย่างของเล่น) เพื่อบอกว่าการใช้งานstd::bitsetได้ปรับปรุงคุณภาพของซอร์สโค้ดของมนุษย์หรือไม่


ความเร็วของการจัดการบิตขึ้นอยู่กับรูปแบบการเข้ารหัส รูปแบบการเข้ารหัสมีผลต่อการจัดการบิตทั้ง C / C ++ และใช้ได้กับstd::bitsetเช่นกันตามที่อธิบายไว้ด้านล่าง


หากมีการเขียนรหัสที่ใช้ในoperator []การอ่านและเขียนทีละบิตหนึ่งจะต้องทำเช่นนี้หลายครั้งหากมีมากกว่าหนึ่งบิตที่จะจัดการ รหัสเดียวกันสามารถพูดได้ของ C-style

อย่างไรก็ตามbitsetยังมีผู้ประกอบการอื่น ๆ เช่นoperator &=, operator <<=เป็นต้นซึ่งทำงานบนเต็มความกว้างของ bitset เนื่องจากเครื่องจักรพื้นฐานสามารถทำงานกับ 32- บิต, 64- บิตและบางครั้ง 128- บิต (กับ SIMD) ในเวลา (ในจำนวนรอบของ CPU เดียวกัน), รหัสที่ถูกออกแบบมาเพื่อใช้ประโยชน์จากการดำเนินงานหลายบิตดังกล่าว สามารถเร็วกว่าโค้ด "การจัดการบิต" แบบวนซ้ำ

แนวคิดทั่วไปเรียกว่าSWAR (SIMD ภายในการลงทะเบียน)และเป็นหัวข้อย่อยภายใต้การจัดการบิต


ผู้ขาย C ++ บางรายอาจใช้งานbitsetระหว่าง 64- บิตและ 128- บิตด้วย SIMD ผู้ขายบางรายอาจไม่ (แต่อาจทำในที่สุด) หากจำเป็นต้องทราบว่าไลบรารีของผู้ขาย C ++ กำลังทำอะไรวิธีเดียวคือดูที่การถอดแยกชิ้นส่วน


สำหรับstd::bitsetข้อ จำกัด นั้นฉันสามารถยกตัวอย่างได้สองแบบ

  1. ขนาดของstd::bitsetต้องเป็นที่รู้จักในเวลารวบรวม std::vector<bool>เพื่อให้อาร์เรย์ของบิตที่มีขนาดที่ได้รับการแต่งตั้งแบบไดนามิกที่หนึ่งจะต้องใช้
  2. ข้อมูลจำเพาะ C ++ ปัจจุบันสำหรับstd::bitsetไม่ได้ให้วิธีการแยกชิ้นส่วนต่อเนื่องของ N บิตจากbitsetM บิตขนาดใหญ่

สิ่งแรกคือพื้นฐานซึ่งหมายความว่าสำหรับผู้ที่ต้องการบิตเซ็ตที่มีขนาดแบบไดนามิกพวกเขาจะต้องเลือกตัวเลือกอื่น ๆ

อันที่สองสามารถเอาชนะได้เพราะเราสามารถเขียนอะแดปเตอร์บางชนิดเพื่อทำงานได้แม้ว่ามาตรฐานbitsetจะไม่สามารถขยายได้


มีบางประเภทของการดำเนินงาน SWAR std::bitsetขั้นสูงที่จะไม่ให้ออกจากกล่องจากมี หนึ่งสามารถอ่านข้อมูลเกี่ยวกับการดำเนินงานเหล่านี้บนเว็บไซต์นี้เกี่ยวกับพีชคณิตบิต std::bitsetตามปกติหนึ่งสามารถดำเนินการเหล่านี้ได้ด้วยตัวเองในการดำเนินงานด้านบนของ


เกี่ยวกับการอภิปรายเกี่ยวกับประสิทธิภาพ

การตักเตือนครั้งเดียว: ผู้คนมากมายถามว่าทำไม(บางอย่าง)จากไลบรารี่มาตรฐานนั้นช้ากว่าโค้ดสไตล์ C ง่ายๆ ฉันจะไม่ทำซ้ำความรู้ที่จำเป็นของ microbenchmarking นี่ แต่ฉันเพียงแค่มีคำแนะนำนี้: ให้แน่ใจว่ามาตรฐานในโหมด "ปล่อย" (ด้วยการเพิ่มประสิทธิภาพเปิดใช้งาน) และให้แน่ใจว่ารหัสไม่ได้ถูกตัดออก (กำจัดรหัสตาย)หรือความเป็นอยู่ยกออกจากวง (วงคงเคลื่อนไหว code)

เนื่องจากโดยทั่วไปเราไม่สามารถบอกได้ว่ามีใครบางคน (บนอินเทอร์เน็ต) ทำไมโครบุ๊กมาร์กอย่างถูกต้องวิธีเดียวที่เราจะได้ข้อสรุปที่เชื่อถือได้ก็คือการทำไมโครบุ๊กมาร์คของเราเองและจัดทำเอกสารรายละเอียด ไม่เจ็บที่จะทำ microbenchmarks ที่คนอื่นทำมาก่อน


ปัญหา # 2 ยังหมายความว่าไม่สามารถใช้บิตเซ็ตในการตั้งค่าแบบขนานใด ๆ ที่แต่ละเธรดควรทำงานกับเซ็ตย่อยของบิตเซ็ต
user239558

@ user239558 std::bitsetผมมีข้อสงสัยทุกคนต้องการจะคู่ขนานบนเดียวกัน ไม่มีการรับประกันความสอดคล้องของหน่วยความจำ (ในstd::bitset) ซึ่งหมายความว่าไม่ควรแชร์ระหว่างคอร์ ผู้ที่ต้องการแชร์ข้ามแกนจะสร้างการใช้งานของตนเอง เมื่อข้อมูลถูกแชร์ระหว่างคอร์ที่แตกต่างกันมันเป็นธรรมเนียมในการจัดแนวให้กับขอบเขตของแคช การไม่ทำเช่นนั้นจะช่วยลดประสิทธิภาพและทำให้เกิดข้อผิดพลาดที่ไม่เกี่ยวกับอะตอมมิกมากขึ้น std::bitsetผมไม่ได้มีความรู้พอที่จะให้ภาพรวมเกี่ยวกับวิธีการสร้างการดำเนินงานของ parallelizable
ร.

การเขียนโปรแกรมข้อมูลแบบขนานมักไม่ต้องการความสอดคล้องของหน่วยความจำใด ๆ คุณซิงโครไนซ์ระหว่างเฟสเท่านั้น ฉันต้องการประมวลผลบิตเซ็ตแบบขนานอย่างแน่นอนฉันคิดว่าทุกคนที่มีความbitsetตั้งใจที่ยิ่งใหญ่
user239558

@ user239558 ที่ฟังดูเหมือนหมายถึงการทำสำเนา (ช่วงของบิตเซ็ตที่เกี่ยวข้องที่จะประมวลผลโดยแต่ละคอร์จะต้องคัดลอกก่อนที่การประมวลผลจะเริ่มต้น) ฉันเห็นด้วยกับสิ่งนั้นถึงแม้ว่าฉันคิดว่าใครก็ตามที่คิดเกี่ยวกับการทำคู่ขนานจะคิดถึงการนำไปใช้ของตนเอง โดยทั่วไปสิ่งอำนวยความสะดวกไลบรารีมาตรฐาน C ++ จำนวนมากถูกจัดเตรียมเป็นการใช้งานพื้นฐาน ใครก็ตามที่มีความต้องการที่จริงจังกว่านี้ก็จะนำไปใช้เอง
ร.

ไม่มีการคัดลอก มันเป็นเพียงการเข้าถึงส่วนต่าง ๆ ของโครงสร้างข้อมูลแบบคงที่ ไม่จำเป็นต้องทำข้อมูลให้ตรงกัน
user239558

2

สิ่งนี้ไม่ได้นำไปใช้ในทุกกรณี แต่บางครั้งอัลกอริทึมอาจขึ้นอยู่กับประสิทธิภาพของ bit-twiddling แบบ C เพื่อให้ได้รับประสิทธิภาพที่สำคัญ ตัวอย่างแรกที่อยู่ในใจของฉันคือการใช้บิตบอร์ดการเข้ารหัสจำนวนเต็มอย่างชาญฉลาดของตำแหน่งเกมกระดานเพื่อเร่งเครื่องมือหมากรุกและสิ่งที่คล้ายกัน ที่นี่ขนาดคงที่ของจำนวนเต็มชนิดไม่มีปัญหาเนื่องจากกระดานหมากรุกมักจะมีขนาด 8 * 8 อยู่ดี

สำหรับตัวอย่างง่ายๆลองพิจารณาฟังก์ชั่นต่อไปนี้ (นำมาจากคำตอบของ Ben Jackson ) ซึ่งทดสอบตำแหน่ง Connect Four เพื่อชัยชนะ:

// 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
คุณคิดว่าstd::bitsetจะช้ากว่านี้ไหม?
quant

ดีจากแหล่งที่มาอย่างรวดเร็ว libc ++ บิตเซ็ตจะขึ้นอยู่กับ size_t เดียวหรืออาร์เรย์ของพวกเขาดังนั้นอาจจะรวบรวมลงไปเป็นสิ่งที่เทียบเท่า / เหมือนกันโดยเฉพาะอย่างยิ่งในระบบที่ sizeof (size_t) == 8 - ดังนั้นไม่มันอาจจะไม่ช้ากว่านี้
Ryan Pavlik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.