ฉันควรใช้คอนเทนเนอร์ STL ใดสำหรับ FIFO


93

คอนเทนเนอร์ STL ใดที่เหมาะกับความต้องการของฉันมากที่สุด โดยพื้นฐานแล้วฉันมีคอนเทนเนอร์กว้าง 10 องค์ประกอบซึ่งฉันมีpush_backองค์ประกอบใหม่อย่างต่อเนื่องในขณะที่ใช้องค์ประกอบที่pop_frontเก่าแก่ที่สุด (ประมาณหนึ่งล้านครั้ง)

ฉันกำลังใช้ a std::dequeสำหรับงานนี้ แต่สงสัยว่า a std::listจะมีประสิทธิภาพมากกว่านี้หรือไม่เพราะฉันไม่จำเป็นต้องจัดสรรตัวเองใหม่ (หรือบางทีฉันอาจเข้าใจผิดว่าเป็นstd::dequea std::vector?) หรือมีคอนเทนเนอร์ที่มีประสิทธิภาพมากกว่าสำหรับความต้องการของฉันหรือไม่?

ปล. ฉันไม่ต้องการการเข้าถึงแบบสุ่ม


5
ทำไมไม่ลองใช้ทั้งสองอย่างและเวลาเพื่อดูว่าอันไหนเร็วกว่าที่คุณต้องการ?
เคทีซี

5
ฉันกำลังจะทำสิ่งนี้ แต่ฉันก็กำลังมองหาคำตอบทางทฤษฎีเช่นกัน
Gab Royer

2
std::dequeจะไม่จัดสรร มันเป็นลูกผสมของ a std::listและstd::vectorที่มันจัดสรรชิ้นส่วนที่ใหญ่กว่า a std::listแต่จะไม่จัดสรรใหม่เหมือน a std::vector.
Matt Price

2
ไม่นี่คือการรับประกันที่เกี่ยวข้องจากมาตรฐาน: "การแทรกองค์ประกอบเดียวที่จุดเริ่มต้นหรือจุดสิ้นสุดของ deque จะใช้เวลาคงที่เสมอและทำให้เกิดการเรียกเพียงครั้งเดียวไปยังตัวสร้างสำเนาของ T. "
Matt Price

1
@ จอห์น: ไม่มันจัดสรรอีกแล้ว บางทีเราอาจจะแค่ผสมเงื่อนไข ฉันคิดว่าการจัดสรรใหม่หมายถึงการนำการจัดสรรเก่าคัดลอกไปยังการจัดสรรใหม่และทิ้งการจัดสรรเก่า
GManNickG

คำตอบ:


198

เนื่องจากมีคำตอบมากมายคุณจึงอาจสับสน แต่สรุป:

ใช้ไฟล์std::queue. เหตุผลง่ายๆก็คือโครงสร้าง FIFO คุณต้องการ FIFO คุณใช้ไฟล์std::queue.

มันทำให้เจตนาของคุณชัดเจนสำหรับคนอื่นและแม้แต่ตัวคุณเอง A std::listหรือstd::dequeไม่ รายการสามารถแทรกและลบได้ทุกที่ซึ่งไม่ใช่สิ่งที่โครงสร้าง FIFO ควรทำและdequeสามารถเพิ่มและลบจากปลายด้านใดด้านหนึ่งซึ่งเป็นสิ่งที่โครงสร้าง FIFO ไม่สามารถทำได้

นี่คือเหตุผลที่คุณควรใช้ไฟล์queue.

ตอนนี้คุณถามเกี่ยวกับประสิทธิภาพ ประการแรกจำกฎง่ายๆนี้ไว้เสมอ: รหัสที่ดีก่อนประสิทธิภาพสุดท้าย

เหตุผลง่ายๆก็คือคนที่มุ่งมั่นเพื่อประสิทธิภาพก่อนที่ความสะอาดและความสง่างามมักจะเสร็จสิ้นเป็นอันดับสุดท้าย รหัสของพวกเขากลายเป็นเรื่องเหลวไหลเพราะพวกเขาละทิ้งทุกสิ่งที่ดีเพื่อที่จะไม่ได้อะไรจากมัน

การเขียนโค้ดที่ดีและอ่านได้ก่อนอื่นปัญหาด้านประสิทธิภาพส่วนใหญ่ของคุณจะคลี่คลายเอง และหากในภายหลังคุณพบว่าประสิทธิภาพของคุณขาดหายไปตอนนี้คุณสามารถเพิ่ม profiler ลงในโค้ดที่ดีและสะอาดของคุณได้อย่างง่ายดายและค้นหาว่าปัญหาอยู่ที่ใด

ที่กล่าวมาทั้งหมดstd::queueเป็นเพียงอะแดปเตอร์เท่านั้น มีอินเทอร์เฟซที่ปลอดภัย แต่ใช้คอนเทนเนอร์อื่นที่อยู่ด้านใน คุณสามารถเลือกคอนเทนเนอร์พื้นฐานนี้และช่วยให้มีความยืดหยุ่นได้ดี

คุณควรใช้คอนเทนเนอร์ใด เรารู้ว่าstd::listและstd::dequeทั้งสองให้ฟังก์ชั่นที่จำเป็น ( push_back(), pop_front()และfront()) ดังนั้นวิธีที่เราจะตัดสินใจ?

ก่อนอื่นให้ทำความเข้าใจว่าการจัดสรรหน่วยความจำ (และการจัดสรร) ไม่ใช่สิ่งที่ต้องทำโดยทั่วไปเนื่องจากเกี่ยวข้องกับการออกไปที่ระบบปฏิบัติการและขอให้ทำอะไรบางอย่าง A listต้องจัดสรรหน่วยความจำทุกครั้งที่มีการเพิ่มบางสิ่งและยกเลิกการจัดสรรหน่วยความจำเมื่อมันหายไป

A dequeในทางกลับกันจัดสรรเป็นชิ้น ๆ จะจัดสรรน้อยกว่า a list. คิดว่ามันเป็นรายการ แต่แต่ละหน่วยความจำสามารถเก็บได้หลายโหนด (แน่นอนฉันขอแนะนำให้คุณเรียนรู้วิธีการทำงานจริงๆ)

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

สิ่งที่สองที่จะเข้าใจได้ประสิทธิภาพแคช การออกไปที่ RAM นั้นช้าดังนั้นเมื่อจำเป็นต้องใช้ CPU จริงๆมันจะทำให้ดีที่สุดในช่วงเวลานี้โดยการดึงหน่วยความจำกลับไปในแคช เนื่องจากมีการdequeจัดสรรในหน่วยความจำจึงมีแนวโน้มว่าการเข้าถึงองค์ประกอบในคอนเทนเนอร์นี้จะทำให้ CPU นำส่วนที่เหลือของคอนเทนเนอร์กลับมาด้วย ตอนนี้การเข้าถึงเพิ่มเติมdequeจะเป็นไปอย่างรวดเร็วเนื่องจากข้อมูลอยู่ในแคช

ซึ่งแตกต่างจากรายการที่ข้อมูลจะถูกจัดสรรทีละรายการ ซึ่งหมายความว่าข้อมูลอาจกระจายออกไปทั่วทุกที่ในหน่วยความจำและประสิทธิภาพของแคชจะไม่ดี

ดังนั้นเมื่อพิจารณาแล้วdequeควรเป็นทางเลือกที่ดีกว่า ด้วยเหตุนี้จึงเป็นคอนเทนเนอร์เริ่มต้นเมื่อใช้ไฟล์queue. ที่กล่าวมาทั้งหมดนี้ยังคงเป็นเพียงการคาดเดาที่มีการศึกษา (มาก): คุณจะต้องกำหนดโปรไฟล์รหัสนี้โดยใช้การdequeทดสอบในครั้งเดียวและlistอีกรายการหนึ่งเพื่อให้ทราบอย่างแน่นอน

แต่อย่าลืมว่า: รับโค้ดที่ทำงานร่วมกับอินเทอร์เฟซที่สะอาดหมดจดแล้วกังวลเรื่องประสิทธิภาพ

จอห์นทำให้เกิดความกังวลว่าการห่อlistหรือdequeจะทำให้ประสิทธิภาพลดลง อีกครั้งเขาหรือฉันไม่สามารถพูดได้อย่างแน่นอนโดยไม่ต้องทำโปรไฟล์ด้วยตัวเอง แต่มีโอกาสที่คอมไพเลอร์จะอินไลน์การเรียกที่queueทำให้ นั่นคือเมื่อคุณพูดqueue.push()มันจะพูดจริงๆqueue.container.push_back()โดยข้ามการเรียกฟังก์ชันไปโดยสิ้นเชิง

อีกครั้งนี่เป็นเพียงการคาดเดาที่มีการศึกษา แต่การใช้queueจะไม่ลดประสิทธิภาพเมื่อเทียบกับการใช้คอนเทนเนอร์ที่อยู่เบื้องหลัง อย่างที่ฉันเคยพูดไปแล้วให้ใช้queueเพราะมันสะอาดใช้งานง่ายและปลอดภัยและถ้ามันกลายเป็นโปรไฟล์และการทดสอบที่มีปัญหาจริงๆ


10
+1 - และหากปรากฎว่า boost :: circular_buffer <> มีประสิทธิภาพที่ดีที่สุดให้ใช้สิ่งนั้นเป็นคอนเทนเนอร์พื้นฐาน (นอกจากนี้ยังมี push_back (), pop_front (), front () และ back () ที่จำเป็น ).
Michael Burr

2
ยอมรับสำหรับการอธิบายรายละเอียด (ซึ่งเป็นสิ่งที่ฉันต้องการขอบคุณที่สละเวลา) สำหรับการทำงานของโค้ดที่ดีเป็นอันดับแรกฉันต้องยอมรับว่านั่นเป็นหนึ่งในค่าเริ่มต้นที่ใหญ่ที่สุดของฉันฉันมักจะพยายามทำสิ่งต่าง ๆ อย่างสมบูรณ์แบบในการรันครั้งแรก ... ฉันเขียนโค้ดโดยใช้ deque แรกที่ยาก แต่เนื่องจากสิ่งนั้นไม่เป็น ' t แสดงได้ดีอย่างที่คิดไว้ (น่าจะเกือบเรียลไทม์) ฉันเดาว่าควรปรับปรุงสักหน่อย อย่างที่นีลบอกฉันควรจะใช้โปรไฟล์จริงๆแม้ว่าฉันจะมีความสุขที่ได้ทำผิดพลาดเหล่านี้ในขณะที่มันไม่สำคัญจริงๆ ขอบคุณมากทุกท่าน
Gab Royer

4
-1 สำหรับการไม่แก้ปัญหาและคำตอบที่ไร้ประโยชน์ คำตอบที่ถูกต้องนี่คือสั้น ๆ และมันคือ boost :: circular_buffer <>
Dmitry Chichkov

1
"รหัสที่ดีก่อนประสิทธิภาพสุดท้าย" นั่นเป็นคำพูดที่ยอดเยี่ยม ถ้าทุกคนเข้าใจสิ่งนี้ :)
thegreendroid

ฉันรู้สึกซาบซึ้งกับความเครียดในการทำโปรไฟล์ การให้หลักการง่ายๆเป็นสิ่งหนึ่งแล้วการพิสูจน์ด้วยการทำโปรไฟล์เป็นสิ่งที่ดีกว่า
talekeDskobeDa

28

std::queueตรวจสอบ มัน wraps std::dequeประเภทภาชนะพื้นฐานและภาชนะเริ่มต้นคือ


3
ทุกเลเยอร์พิเศษจะถูกกำจัดโดยคอมไพเลอร์ ตามตรรกะของคุณเราทุกคนควรตั้งโปรแกรมในแอสเซมบลีเนื่องจากภาษาเป็นเพียงเปลือกที่ขวาง ประเด็นคือการใช้ประเภทที่ถูกต้องสำหรับงาน และqueueเป็นประเภทที่ รหัสที่ดีก่อนประสิทธิภาพในภายหลัง นรกประสิทธิภาพส่วนใหญ่มาจากการใช้รหัสที่ดีในตอนแรก
GManNickG

2
ขออภัยที่คลุมเครือ - ประเด็นของฉันคือคิวคือสิ่งที่คำถามถูกถามและนักออกแบบ C ++ คิดว่า deque เป็นคอนเทนเนอร์พื้นฐานที่ดีสำหรับกรณีการใช้งานนี้
Mark Ransom

2
ไม่มีอะไรในคำถามนี้ที่บ่งชี้ว่าเขาพบว่าขาดประสิทธิภาพ ผู้เริ่มต้นหลายคนมักจะถามเกี่ยวกับวิธีแก้ปัญหาที่มีประสิทธิภาพที่สุดสำหรับปัญหาใด ๆ โดยไม่คำนึงว่าโซลูชันปัจจุบันของพวกเขาทำงานได้ดีหรือไม่ก็ตาม
jalf

1
@ จอห์นถ้าเขาพบว่าประสิทธิภาพขาดการกำจัดเปลือกแห่งความปลอดภัยออกไปqueueจะไม่เพิ่มประสิทธิภาพอย่างที่ฉันเคยพูด คุณแนะนำ a listซึ่งอาจจะทำงานได้แย่ลง
GManNickG

3
สิ่งที่เกี่ยวกับ std :: que <> ก็คือถ้า deque <> ไม่ใช่สิ่งที่คุณต้องการ (เพื่อความสมบูรณ์แบบหรือเหตุผลใดก็ตาม) มันเป็นซับเดียวที่จะเปลี่ยนให้ใช้ std :: list เป็นที่เก็บสำรอง - as จีแมนพูดกลับไป และถ้าคุณต้องการใช้แหวนบัฟเฟอร์แทนรายการจริงๆ boost :: circular_buffer <> จะลดลงใน ... std :: que <> เกือบจะเป็น 'อินเทอร์เฟซ' ที่ควรใช้ ที่เก็บสำรองสำหรับมันสามารถเปลี่ยนแปลงได้ตามต้องการ
Michael Burr


7

ฉันpush_backสร้างองค์ประกอบใหม่อย่างต่อเนื่องในขณะที่ใช้องค์ประกอบที่pop_frontเก่าแก่ที่สุด (ประมาณล้านครั้ง)

ล้านไม่ใช่ตัวเลขใหญ่ในการคำนวณ ตามที่คนอื่นแนะนำให้ใช้ a std::queueเป็นวิธีแก้ปัญหาแรกของคุณ ในกรณีที่ไม่น่าจะเกิดขึ้นช้าเกินไปให้ระบุคอขวดโดยใช้ profiler (อย่าเดา!) และนำไปใช้ใหม่โดยใช้คอนเทนเนอร์อื่นที่มีอินเทอร์เฟซเดียวกัน


1
ที่จริงก็คือมันเป็นจำนวนมากเพราะสิ่งที่ฉันต้องการทำควรเป็นเวลาจริง แม้ว่าคุณจะถูกต้องที่ฉันควรใช้โปรไฟล์เพื่อระบุสาเหตุ ...
Gab Royer

สิ่งนี้คือฉันไม่คุ้นเคยกับการใช้โปรไฟล์ (เราเคยใช้ gprof เล็กน้อยในชั้นเรียนของเรา แต่เราไม่ได้ลงลึกจริงๆ ... ) หากคุณสามารถชี้ให้ฉันเห็นแหล่งข้อมูลได้ฉันจะขอบคุณมาก! ปล. ฉันใช้ VS2008
Gab Royer

@Gab: คุณมี VS2008 รุ่นไหน (Express, Pro ... )? บางห้องมาพร้อมกับโปรไฟล์
sbi

@Gab ขออภัยฉันไม่ได้ใช้ VS อีกต่อไปจึงให้คำแนะนำไม่ได้จริงๆ

@Sbi จากสิ่งที่ฉันเห็นมันมีเฉพาะในรุ่นระบบทีมเท่านั้น (ซึ่งฉันสามารถเข้าถึงได้) ฉันจะตรวจสอบสิ่งนี้
Gab Royer


3

คิวน่าจะเป็นอินเตอร์เฟซที่เรียบง่ายกว่าdequeแต่สำหรับรายชื่อดังกล่าวมีขนาดเล็กแตกต่างกันในการทำงานอาจจะเป็นเล็กน้อย

กันไปสำหรับรายการ มันเป็นเพียงตัวเลือกของ API ที่คุณต้องการ


แต่ฉันสงสัยว่า push_back คงที่กำลังสร้างคิวหรือจัดสรรตัวเองใหม่
Gab Royer

std :: que เป็นเครื่องห่อหุ้มรอบคอนเทนเนอร์อื่นดังนั้นการตัดคิวแบบ deque จะมีประสิทธิภาพน้อยกว่า raw deque
John Millikin

1
สำหรับ 10 รายการประสิทธิภาพมักจะเป็นปัญหาเล็ก ๆ น้อย ๆ นั่นคือ "ประสิทธิภาพ" อาจวัดได้ดีกว่าเวลาของโปรแกรมเมอร์มากกว่าเวลาใช้รหัส และการเรียกจากคิวไปยัง deque โดยการปรับแต่งคอมไพเลอร์ที่เหมาะสมจะไม่เหลืออะไรเลย
lavinio

2
@ จอห์น: ฉันอยากให้คุณแสดงชุดของเกณฑ์มาตรฐานที่แสดงให้เห็นถึงความแตกต่างของประสิทธิภาพดังกล่าว มันมีประสิทธิภาพไม่น้อยไปกว่า raw deque คอมไพเลอร์ C ++ แบบอินไลน์ก้าวร้าวมาก
jalf

3
ผมลองมาแล้ว : คอนเทนเนอร์ DA 10 องค์ประกอบที่รวดเร็วและสกปรกพร้อม 100,000,000 pop_front () & push_back () rand () หมายเลข int บน Release build สำหรับความเร็วบน VC9 ให้: list (27), que (6), deque (6), array (8) .
KTC

0

ใช้ a std::queueแต่ระวังการแลกเปลี่ยนประสิทธิภาพของสองContainerคลาสมาตรฐาน

ตามค่าเริ่มต้นstd::queueคืออะแดปเตอร์ที่อยู่ด้านบนของstd::deque. โดยทั่วไปแล้วจะให้ประสิทธิภาพที่ดีเมื่อคุณมีคิวจำนวนน้อยที่มีรายการจำนวนมากซึ่งเป็นเนื้อหาทั่วไป

แต่ไม่ได้เป็นคนตาบอดที่จะดำเนินการตามมาตรฐาน :: deque โดยเฉพาะ:

"... โดยทั่วไป deques จะมีต้นทุนหน่วยความจำน้อยที่สุด deque ที่ถือองค์ประกอบเพียงตัวเดียวจะต้องจัดสรรอาร์เรย์ภายในเต็มรูปแบบ (เช่นขนาดวัตถุ 8 เท่าบน libstdc ++ 64 บิต; 16 เท่าของขนาดวัตถุหรือ 4096 ไบต์แล้วแต่จำนวนใดจะใหญ่กว่า บน libc ++ 64 บิต) "

หากต้องการสรุปให้สันนิษฐานว่ารายการคิวเป็นสิ่งที่คุณต้องการจัดคิวกล่าวคือมีขนาดเล็กพอสมควรหากคุณมี 4 คิวแต่ละคิวมี 30,000 รายการการstd::dequeติดตั้งจะเป็นตัวเลือกที่คุณเลือก ในทางกลับกันหากคุณมี 30,000 คิวแต่ละรายการมี 4 รายการแสดงว่าการstd::listติดตั้งใช้งานจะเหมาะสมกว่าปกติเนื่องจากคุณจะไม่ตัดstd::dequeค่าใช้จ่ายในสถานการณ์นั้น

คุณจะได้อ่านความคิดเห็นมากมายเกี่ยวกับวิธีที่แคชเป็นราชาวิธีที่ Stroustrup เกลียดรายการที่เชื่อมโยง ฯลฯ และทั้งหมดนี้เป็นจริงภายใต้เงื่อนไขบางประการ อย่ายอมรับมันด้วยความเชื่อที่มืดบอดเพราะในสถานการณ์ที่สองของเรามีไม่น่าเป็นไปได้ที่การstd::dequeใช้งานเริ่มต้นจะดำเนินการ ประเมินการใช้งานและวัดผล


-1

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

// FIFO with circular buffer
#define fifo_size 4

class Fifo {
  uint8_t buff[fifo_size];
  int writePtr = 0;
  int readPtr = 0;
  
public:  
  void put(uint8_t val) {
    buff[writePtr%fifo_size] = val;
    writePtr++;
  }
  uint8_t get() {
    uint8_t val = NULL;
    if(readPtr < writePtr) {
      val = buff[readPtr%fifo_size];
      readPtr++;
      
      // reset pointers to avoid overflow
      if(readPtr > fifo_size) {
        writePtr = writePtr%fifo_size;
        readPtr = readPtr%fifo_size;
      }
    }
    return val;
  }
  int count() { return (writePtr - readPtr);}
};

แต่จะเกิดขึ้นได้อย่างไร / เมื่อไร?
user10658782

โอ้ฉันคิดว่ามันอาจจะมีเหตุผลบางอย่าง ไม่เป็นไร!
Ry-
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.