ฉันควรใช้โครงสร้างข้อมูลใดสำหรับกลยุทธ์การแคชนี้


11

ฉันกำลังทำงานกับแอปพลิเคชั่น. NET 4.0 ซึ่งทำการคำนวณค่อนข้างแพงในสองครั้งที่ส่งคืนเป็นสองเท่า การคำนวณนี้จะดำเนินการอย่างใดอย่างหนึ่งหลายพันในแต่ละรายการ การคำนวณเหล่านี้ดำเนินการในTaskเธรดพูลเธรด

การทดสอบเบื้องต้นบางอย่างแสดงให้เห็นว่ามีการคำนวณแบบเดียวกันซ้ำแล้วซ้ำอีกดังนั้นฉันต้องการแคชผลลัพธ์n เมื่อแคชเต็มฉันต้องการที่จะโยนรายการที่ใช้บ่อยที่สุด ( แก้ไข:ฉันรู้น้อยที่สุด - ไม่สมเหตุสมผลเพราะเมื่อแคชเต็มและฉันจะแทนที่ผลลัพธ์ด้วยการคำนวณใหม่หนึ่งรายการนั้นจะใช้อย่างน้อยบ่อยครั้งและแทนที่ทันทีในครั้งถัดไปที่มีการคำนวณผลลัพธ์ใหม่ และเพิ่มลงในแคช)

เพื่อที่จะนำสิ่งนี้ไปใช้ฉันคิดว่าจะใช้ a Dictionary<Input, double>(ซึ่งInputจะเป็น mini-class ที่เก็บค่าสองค่าสองอินพุต) เพื่อเก็บอินพุตและผลลัพธ์ที่แคชไว้ อย่างไรก็ตามฉันจะต้องติดตามเมื่อมีการใช้ผลลัพธ์ครั้งล่าสุด สำหรับสิ่งนี้ฉันคิดว่าฉันต้องการคอลเล็กชันที่สองที่จัดเก็บข้อมูลฉันจะต้องลบผลลัพธ์ออกจาก dictonary เมื่อแคชเริ่มเต็ม ฉันกังวลว่าการเรียงลำดับรายการนี้อย่างสม่ำเสมอจะส่งผลเสีย

มีวิธีที่ดีกว่า (เช่นมีประสิทธิภาพมากกว่า) ในการทำเช่นนี้หรืออาจเป็นโครงสร้างข้อมูลทั่วไปที่ฉันไม่รู้จัก? ฉันควรทำโปรไฟล์ / การวัดประเภทใดเพื่อกำหนดประสิทธิภาพสูงสุดของการแก้ปัญหาของฉัน

คำตอบ:


12

หากคุณต้องการใช้ LRU eviction cache (การใช้อย่างน้อยเมื่อเร็ว ๆ นี้) นั่นอาจเป็นการผสมผสานที่ดีของโครงสร้างข้อมูลที่จะใช้คือ:

  • รายการที่เชื่อมโยงแบบวงกลม (เป็นลำดับความสำคัญคิว)
  • พจนานุกรม

นี่คือเหตุผล:

  • รายการที่เชื่อมโยงมีเวลาการแทรกและการลบ O (1)
  • โหนดรายการสามารถนำกลับมาใช้ใหม่ได้เมื่อรายการเต็มและไม่จำเป็นต้องทำการจัดสรรเพิ่มเติม

นี่คือวิธีที่อัลกอริทึมพื้นฐานควรทำงาน:

โครงสร้างข้อมูล

LinkedList<Node<KeyValuePair<Input,Double>>> list; Dictionary<Input,Node<KeyValuePair<Input,Double>>> dict;

  1. ได้รับข้อมูลเข้า
  2. หากพจนานุกรมมีรหัส
    • ส่งคืนค่าที่เก็บในโหนดและย้ายโหนดไปยังจุดเริ่มต้นของรายการ
  3. หากพจนานุกรมไม่มีคีย์
    • คำนวณค่า
    • เก็บค่าในโหนดสุดท้ายของรายการ
    • หากอันสุดท้ายไม่มีค่าให้ลบคีย์ก่อนหน้าออกจากพจนานุกรม
    • ย้ายโหนดสุดท้ายไปยังตำแหน่งแรก
    • เก็บในพจนานุกรมค่าคู่ (อินพุต, โหนด) ในคีย์

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


จุดดีความคิดที่ดีที่สุด IMHO ฉันติดตั้งแคชตามวันนี้และจะต้องทำการโพรไฟล์และดูว่ามันทำงานได้ดีในวันพรุ่งนี้
PersonalNexus

3

ดูเหมือนจะเป็นความพยายามอย่างมากในการคำนวณเพียงครั้งเดียวเนื่องจากกำลังการประมวลผลที่คุณมีในพีซีโดยเฉลี่ย นอกจากนี้คุณยังคงมีค่าใช้จ่ายในการโทรครั้งแรกในการคำนวณของคุณสำหรับค่าที่ไม่ซ้ำกันแต่ละคู่ดังนั้น 100,000 ค่าที่ไม่ซ้ำกันจะยังคงใช้เวลาของคุณn * 100,000 ขั้นต่ำ พิจารณาว่าการเข้าถึงค่าในพจนานุกรมของคุณอาจช้าลงเมื่อพจนานุกรมมีขนาดใหญ่ขึ้น คุณสามารถรับประกันความเร็วในการเข้าถึงพจนานุกรมของคุณจะชดเชยเพียงพอที่จะให้ผลตอบแทนที่สมเหตุสมผลเทียบกับความเร็วในการคำนวณของคุณหรือไม่?

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


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

0

หนึ่งความคิดคือเหตุผลว่าทำไมแคชเฉพาะผลลัพธ์ n? แม้ว่า n คือ 300,000 คุณจะใช้หน่วยความจำ 7.2MB เท่านั้น (รวมถึงสิ่งพิเศษสำหรับโครงสร้างตาราง) นั่นถือว่าแน่นอน 64 บิตสองเท่า คุณสามารถใช้บันทึกช่วยจำกับรูทีนการคำนวณที่ซับซ้อนได้เองหากคุณไม่กังวลว่าพื้นที่หน่วยความจำไม่เพียงพอ


จะไม่มีแคชเพียงหนึ่ง แต่หนึ่งต่อ "รายการ" ที่ฉันวิเคราะห์และอาจมีหลายแสนรายการ
PersonalNexus

การป้อนข้อมูลมาจาก 'รายการ' ในทางใด มีผลข้างเคียงหรือไม่
jk

@jk รายการที่แตกต่างกันจะสร้างอินพุตที่แตกต่างกันมากสำหรับการคำนวณ เนื่องจากนี่หมายความว่าจะมีการทับซ้อนกันเล็กน้อยฉันไม่คิดว่าจะเก็บไว้ในแคชเดียวจึงสมเหตุสมผล นอกจากนี้ไอเท็มต่าง ๆ สามารถมีชีวิตอยู่ในเธรดที่แตกต่างกันดังนั้นเพื่อหลีกเลี่ยงสถานะที่แชร์ฉันต้องการแยกแคชออกจากกัน
PersonalNexus

@ ส่วนบุคคล Nexus ฉันใช้สิ่งนี้เพื่อบ่งบอกว่ามีมากกว่า 2 พารามิเตอร์ที่เกี่ยวข้องในการคำนวณหรือไม่ ถ้าอย่างนั้นคุณยังมี f (x, y) = ทำบางสิ่งอยู่ รวมถึงสถานะที่ใช้ร่วมกันดูเหมือนว่าจะช่วยให้ประสิทธิภาพมากกว่าขัดขวาง
ปีเตอร์สมิ ธ

@PeterSmith พารามิเตอร์ทั้งสองเป็นอินพุตหลัก มีคนอื่น แต่ไม่ค่อยเปลี่ยน ถ้าเป็นเช่นนั้นฉันจะทิ้งแคชทั้งหมดไป โดย "สถานะที่ใช้ร่วมกัน" ฉันหมายถึงแคชที่ใช้ร่วมกันสำหรับรายการทั้งหมดหรือกลุ่ม เนื่องจากสิ่งนี้จะต้องถูกล็อคหรือประสานวิธีอื่นมันจะขัดขวางประสิทธิภาพ เพิ่มเติมเกี่ยวกับผลกระทบการดำเนินงานของรัฐที่ใช้ร่วมกัน
PersonalNexus

0

วิธีการที่มีคอลเลกชันที่สองเป็นเรื่องปกติ มันควรจะเป็นลำดับความสำคัญของคิวที่อนุญาตให้ค้นหา / ลบค่าขั้นต่ำได้อย่างรวดเร็วและยังเปลี่ยนลำดับความสำคัญ (เพิ่มขึ้น) ภายในคิว ห้องสมุด C5IntervalHeapมีคอลเลกชันดังกล่าวจะเรียกว่า

หรือแน่นอนคุณสามารถลองสร้างคอลเลกชันของคุณเองเช่น a SortedDictionary<int, List<InputCount>>. ( InputCountต้องเป็นคลาสที่รวมInputข้อมูลของคุณเข้ากับCountค่าของคุณ)

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


0

เป็นแหลมออกในคำตอบของปีเตอร์สมิ ธ , รูปแบบที่คุณกำลังพยายามที่จะใช้เรียกว่าmemoization ใน C # เป็นการยากที่จะใช้การบันทึกในลักษณะโปร่งใสโดยไม่มีผลข้างเคียง หนังสือของ Oliver Sturm ในการเขียนโปรแกรมใช้งานได้ใน C #มอบวิธีแก้ปัญหา (มีรหัสให้ดาวน์โหลดตอนที่ 10)

ใน F # มันจะง่ายขึ้นมาก แน่นอนว่ามันเป็นการตัดสินใจที่ยิ่งใหญ่ในการเริ่มต้นใช้ภาษาการเขียนโปรแกรมอื่น แต่มันอาจคุ้มค่าที่จะพิจารณา โดยเฉพาะอย่างยิ่งในการคำนวณที่ซับซ้อนจะทำให้โปรแกรมง่ายขึ้นกว่าการบันทึกข้อมูล

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