เรากำลังพัฒนาซอฟต์แวร์สำคัญที่มีประสิทธิภาพสูงใน C ++ ที่นั่นเราต้องมีแผนที่แฮชพร้อมกันและนำไปใช้ std::unordered_map
ดังนั้นเราจึงเขียนมาตรฐานที่จะคิดออกเท่าไหร่ช้ากว่าแผนที่กัญชาพร้อมกันของเราจะถูกเมื่อเทียบกับ
แต่std::unordered_map
ดูเหมือนว่าจะไม่น่าเชื่อช้า ... ดังนั้นนี้เป็นของเราไมโครมาตรฐาน (สำหรับแผนที่พร้อมกันเรากลับกลายเป็นหัวข้อใหม่เพื่อให้แน่ใจว่าการล็อคไม่ได้รับการปรับให้เหมาะสมออกไปและทราบว่าฉันไม่เคย Inser 0 เพราะผมยังมาตรฐานด้วยgoogle::dense_hash_map
, ซึ่งต้องการค่า null):
boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> dist(std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max());
std::vector<uint64_t> vec(SIZE);
for (int i = 0; i < SIZE; ++i) {
uint64_t val = 0;
while (val == 0) {
val = dist(rng);
}
vec[i] = val;
}
std::unordered_map<int, long double> map;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < SIZE; ++i) {
map[vec[i]] = 0.0;
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "inserts: " << elapsed.count() << std::endl;
std::random_shuffle(vec.begin(), vec.end());
begin = std::chrono::high_resolution_clock::now();
long double val;
for (int i = 0; i < SIZE; ++i) {
val = map[vec[i]];
}
end = std::chrono::high_resolution_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "get: " << elapsed.count() << std::endl;
(แก้ไข: ซอร์สโค้ดทั้งหมดสามารถพบได้ที่นี่: http://pastebin.com/vPqf7eya )
ผลลัพธ์สำหรับstd::unordered_map
คือ:
inserts: 35126
get : 2959
สำหรับgoogle::dense_map
:
inserts: 3653
get : 816
สำหรับแผนที่พร้อมกันที่สำรองไว้ด้วยมือของเรา (ซึ่งทำการล็อคแม้ว่าเกณฑ์มาตรฐานจะเป็นเธรดเดียว - แต่อยู่ในเธรดการวางไข่ที่แยกจากกัน):
inserts: 5213
get : 2594
หากฉันรวบรวมโปรแกรมเปรียบเทียบโดยไม่รองรับ pthread และเรียกใช้ทุกอย่างในเธรดหลักฉันจะได้ผลลัพธ์ต่อไปนี้สำหรับแผนที่พร้อมกันที่สำรองไว้ด้วยมือของเรา:
inserts: 4441
get : 1180
ฉันรวบรวมด้วยคำสั่งต่อไปนี้:
g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc
ดังนั้นโดยเฉพาะอย่างยิ่งการแทรกบนstd::unordered_map
ดูเหมือนจะมีราคาแพงมาก - 35 วินาทีเทียบกับ 3-5 วินาทีสำหรับแผนที่อื่น ๆ นอกจากนี้เวลาในการค้นหาดูเหมือนจะค่อนข้างสูง
คำถามของฉัน: ทำไมถึงเป็นเช่นนี้? ฉันอ่านคำถามอื่นเกี่ยวกับ stackoverflow ที่มีคนถามทำไมถึงstd::tr1::unordered_map
ช้ากว่าการใช้งานของเขาเอง มีคำตอบที่ได้รับคะแนนสูงสุดระบุว่าstd::tr1::unordered_map
จำเป็นต้องใช้อินเทอร์เฟซที่ซับซ้อนมากขึ้น แต่ฉันไม่เห็นอาร์กิวเมนต์นี้: เราใช้วิธีการฝากข้อมูลใน concurrent_map ของเราstd::unordered_map
ใช้วิธีการฝากข้อมูลด้วย ( google::dense_hash_map
ไม่ แต่std::unordered_map
อย่างน้อยควรเร็วกว่าเวอร์ชันที่ปลอดภัยพร้อมกันที่สำรองไว้ในมือของเราหรือไม่) นอกเหนือจากนั้นฉันไม่เห็นอะไรในอินเทอร์เฟซที่บังคับใช้คุณลักษณะที่ทำให้แฮชแมปทำงานได้ไม่ดี ...
ดังนั้นคำถามของฉัน: เป็นเรื่องจริงที่std::unordered_map
ดูเหมือนว่าจะช้ามาก? ถ้าไม่: มีอะไรผิดปกติ? ถ้าใช่: อะไรคือสาเหตุของสิ่งนั้น
และคำถามหลักของฉัน: ทำไมการใส่ค่าเข้าไปในstd::unordered_map
ราคาแพงมาก (แม้ว่าเราจะจองพื้นที่ไว้เพียงพอในตอนเริ่มต้น แต่ก็ไม่ได้ผลดีกว่ามากนัก - ดังนั้นการเปลี่ยนใหม่จึงดูเหมือนจะไม่ใช่ปัญหา)
แก้ไข:
ก่อนอื่น: ใช่เกณฑ์มาตรฐานที่นำเสนอนั้นไม่มีที่ติ - นี่เป็นเพราะเราเล่นกับมันมากและมันเป็นเพียงการแฮ็ก (เช่นการuint64
กระจายเพื่อสร้าง ints ในทางปฏิบัติจะไม่ใช่ความคิดที่ดียกเว้น 0 ในลูป เป็นคนโง่ ฯลฯ ... )
ในขณะที่ความคิดเห็นส่วนใหญ่อธิบายว่าฉันสามารถทำให้ unordered_map เร็วขึ้นได้โดยการจัดสรรพื้นที่ให้เพียงพอ ในแอปพลิเคชันของเราสิ่งนี้เป็นไปไม่ได้: เรากำลังพัฒนาระบบจัดการฐานข้อมูลและต้องการแผนที่แฮชเพื่อจัดเก็บข้อมูลบางส่วนระหว่างการทำธุรกรรม (เช่นการล็อกข้อมูล) ดังนั้นแผนที่นี้สามารถเป็นได้ทุกอย่างตั้งแต่ 1 (ผู้ใช้เพียงแค่แทรกและคอมมิต) ไปจนถึงหลายพันล้านรายการ (หากเกิดการสแกนแบบเต็มตาราง) เป็นไปไม่ได้เลยที่จะจัดสรรพื้นที่ให้เพียงพอที่นี่ (และการจัดสรรจำนวนมากในตอนแรกจะใช้หน่วยความจำมากเกินไป)
นอกจากนี้ฉันขออภัยที่ฉันไม่ได้ระบุคำถามของฉันให้ชัดเจนเพียงพอ: ฉันไม่สนใจที่จะทำให้ unordered_map เร็วขึ้น (การใช้แผนที่แฮชที่หนาแน่นของ googles ทำงานได้ดีสำหรับเรา) ฉันไม่เข้าใจจริงๆว่าความแตกต่างของประสิทธิภาพขนาดใหญ่นี้มาจากไหน . ไม่สามารถเป็นเพียงการจัดสรรล่วงหน้า (แม้จะมีหน่วยความจำที่จัดสรรไว้ล่วงหน้าเพียงพอ แต่แผนที่ที่หนาแน่นจะมีลำดับความสำคัญเร็วกว่า unordered_map แผนที่พร้อมกันที่สำรองไว้ด้วยมือของเราเริ่มต้นด้วยอาร์เรย์ขนาด 64 - ดังนั้นจึงมีขนาดเล็กกว่า unordered_map)
แล้วสาเหตุที่ทำให้ผลงานแย่ขนาดนี้std::unordered_map
คืออะไร? หรือถามแตกต่างกัน: เราสามารถเขียนการใช้งานstd::unordered_map
อินเทอร์เฟซที่เป็นไปตามมาตรฐานและ (เกือบ) เร็วเท่าแผนที่แฮชที่หนาแน่นของ googles ได้หรือไม่? หรือมีบางอย่างในมาตรฐานที่บังคับให้ผู้ปฏิบัติงานเลือกวิธีที่ไม่มีประสิทธิภาพในการนำไปใช้?
แก้ไข 2:
โดยการทำโปรไฟล์ฉันเห็นว่ามีการใช้เวลาเป็นจำนวนมากสำหรับการหารจำนวนเต็ม std::unordered_map
ใช้จำนวนเฉพาะสำหรับขนาดอาร์เรย์ในขณะที่การใช้งานอื่น ๆ ใช้พาวเวอร์ของสอง เหตุใดจึงstd::unordered_map
ใช้จำนวนเฉพาะ เพื่อให้ทำงานได้ดีขึ้นหากแฮชไม่ดี? สำหรับแฮชที่ดี imho ไม่สร้างความแตกต่าง
แก้ไข 3:
นี่คือตัวเลขสำหรับstd::map
:
inserts: 16462
get : 16978
Sooooooo: ทำไมแทรกในstd::map
เร็วกว่าแทรกในstd::unordered_map
... ฉันหมายถึง WAT? std::map
มีตำแหน่งที่แย่กว่า (ต้นไม้เทียบกับอาร์เรย์) จำเป็นต้องทำการจัดสรรเพิ่มเติม (ต่อการแทรกเทียบกับการรีแฮช + บวก ~ 1 สำหรับการชนแต่ละครั้ง) และที่สำคัญที่สุด: มีความซับซ้อนของอัลกอริทึมอื่น (O (บันทึก) เทียบกับ O (1))!
SIZE
และเพิ่มการคาดคะเนที่ดีกว่าสำหรับคุณ