วิธีการต่อต้านรหัสฐานช้าลงอย่างสม่ำเสมอ


11

เรากำลังทำงานอยู่ในระดับปานกลาง C ขนาด ++ รหัสฐาน (10Mloc) ซึ่งผ่านความพยายามเพิ่มประสิทธิภาพของเราจะกลายเป็นเหมือนกันช้า

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

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

แก้ไข

หากต้องการตอบคำถามว่าเราโปรไฟล์อย่างไร:

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


10
10Mloc เป็นโครงการขนาดใหญ่จริงๆ
B --овић

1
มันเป็น 10 ล้าน loc (SI คำนำหน้า) slocขณะที่นับ ฉันเรียกมันว่า "ขนาดปานกลาง" เพราะฉันไม่รู้ว่าสิ่งใดที่ถือว่า "ใหญ่" ที่นี่
Benjamin Bannier

5
ค่อนข้างแน่ใจว่า 10 ล้านเป็นอย่างน้อยใหญ่ทุกที่และอาจใหญ่ที่สุด
Ryathal

1
เยี่ยมมากขอบคุณ @honk สำหรับ 10M LOC ดูเหมือนว่าคุณกำลังปรับให้เหมาะสมที่ต่ำมากเกือบจะถึงระดับฮาร์ดแวร์หรือไม่ OOP แบบดั้งเดิม (AOS "อาร์เรย์ของโครงสร้าง") ไม่มีประสิทธิภาพอย่างน่ากลัวสำหรับแคชคุณลองจัดเรียงคลาสของคุณใหม่เป็น SOA (โครงสร้างของอาร์เรย์) ดังนั้นจุดข้อมูลที่รหัสของคุณทำงานนั้นเชื่อมโยงกันในหน่วยความจำหรือไม่? เมื่อคุณใช้เครื่องจักรหลายเครื่องในการปิดกั้นการสื่อสารหรือการประสานเวลากินหมด? คำถามสุดท้ายคุณจัดการกับสตรีมข้อมูลจำนวนมากหรือเป็นปัญหาของการดำเนินการที่ซับซ้อนในชุดข้อมูลของคุณหรือไม่?
Patrick Hughes

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

คำตอบ:


9

ผมไม่ทราบว่าของวิธีการใช้งานทั่วไปในการแก้ไขปัญหานี้ แต่ทั้งสองวิธีที่เกี่ยวข้องค่อนข้างทำงานได้ดีสำหรับฉันในอดีตที่ผ่านมา: สำหรับการขาดเงื่อนไขที่ดีกว่าผมเรียกว่าพวกเขารวบและการเพิ่มประสิทธิภาพในแนวนอน

วิธีการเจาะเป็นความพยายามในการแทนที่การดำเนินการระยะสั้นและรวดเร็วจำนวนมากด้วยการดำเนินการแบบเดี่ยวช้าลงและทำงานพิเศษซึ่งจะให้ผลลัพธ์ที่เหมือนกันในที่สุด

ตัวอย่าง: หลังจากการทำโปรไฟล์อย่างช้าๆโดยเฉพาะอย่างยิ่งการแก้ไขกฎการมองเห็นของเราเราพบว่าไม่มี "ผลไม้แขวนลอยต่ำ": ไม่มีการดำเนินการเดียวที่ใช้เวลาดำเนินการมากกว่า 2% แต่การดำเนินการโดยรวมรู้สึกเฉื่อย อย่างไรก็ตามเราค้นพบว่าผู้แก้ไขกำลังส่งคำขอเล็ก ๆ จำนวนมากไปยังเซิร์ฟเวอร์ แม้ว่าตัวแก้ไขจะประมวลผลการตอบกลับแต่ละรายการอย่างรวดเร็วจำนวนของการโต้ตอบการร้องขอ / การตอบกลับมีผลหลายทางดังนั้นเวลาโดยรวมในการดำเนินการจึงใช้เวลาหลายวินาที หลังจากแคตตาล็อกการโต้ตอบของตัวแก้ไขอย่างระมัดระวังในระหว่างการดำเนินการที่ยาวนานนั้นเราได้เพิ่มคำสั่งใหม่ไปยังเซิร์ฟเวอร์อินเตอร์เฟส คำสั่งเพิ่มเติมมีความเฉพาะเจาะจงมากขึ้นเนื่องจากได้รับข้อมูลที่ต้องการเพื่อดำเนินการชุดย่อยของการดำเนินการแบบสั้น สำรวจการพึ่งพาข้อมูลเพื่อค้นหาชุดสุดท้ายของข้อมูลที่จะส่งคืนและให้การตอบสนองที่มีข้อมูลที่จำเป็นในการดำเนินการเล็ก ๆ ทั้งหมดในการเดินทางไปยังเซิร์ฟเวอร์ สิ่งนี้ไม่ได้ลดเวลาในการประมวลผลในรหัสของเรา แต่มันลดจำนวนเวลาในการตอบสนองอย่างมากเนื่องจากการลบการเรียกคืนแบบไปกลับระหว่างไคลเอนต์ - เซิร์ฟเวอร์

การปรับให้เหมาะสมที่สุดในแนวนอนเป็นเทคนิคที่เกี่ยวข้องเมื่อคุณกำจัด "ความช้า" ที่มีการกระจายบาง ๆ ระหว่างองค์ประกอบหลายส่วนของระบบของคุณโดยใช้คุณสมบัติเฉพาะของสภาพแวดล้อมการทำงานของคุณ

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


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

3

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

เมื่อคุณเริ่มเขียนใหม่นี้คุณต้องทำหลายสิ่งที่แตกต่าง

เป็นครั้งแรก และที่สำคัญที่สุด หยุด "การเพิ่มประสิทธิภาพ" "การเพิ่มประสิทธิภาพ" ไม่สำคัญมากนัก อย่างที่คุณเห็นมีเพียงเรื่องการเขียนใหม่เท่านั้น

ดังนั้น.

ที่สอง ทำความเข้าใจกับความหมายของโครงสร้างข้อมูลและตัวเลือกอัลกอริทึม

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

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


1
ขอบคุณสำหรับคำตอบ. ในขณะที่เรายังคงต้องปรับให้เหมาะสม (ซึ่งอยู่ภายใต้ 1 และ 2 สำหรับฉัน) ฉันชอบความคิดที่เป็นระบบอยู่เบื้องหลัง 3. ด้วยการสร้างโครงสร้างข้อมูลอัลกอริทึม & การเข้าถึงที่กำหนดล่าช้าและชัดเจนเราควรจะสามารถจัดการได้ ปัญหาที่เรากำลังเผชิญ ขอบคุณที่ทำให้มันเป็นภาษาที่สอดคล้องกัน
Benjamin Bannier

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

1
อัลกอริธึมที่ไม่ดีทั้งหมดสามารถพบได้โดยการใช้เหตุผลที่บริสุทธิ์ นาน ๆ ครั้งหนึ่งต้องนั่งลงและโปรไฟล์ นี่เป็นวิธีเดียวที่จะตรวจสอบว่าการเดาเริ่มต้นถูกต้องหรือไม่ นั่นเป็นวิธีเดียวที่จะประเมินค่าใช้จ่ายจริง (BigO + const)
Benjamin Bannier

การทำโปรไฟล์จะเปิดเผยอัลกอริทึมที่ไม่ดี ถูกต้องทั้งหมด ที่ยังไม่ "เพิ่มประสิทธิภาพ" นั่นยังคงเป็นการแก้ไขข้อบกพร่องพื้นฐานในการออกแบบของฉันการเปลี่ยนแปลงการออกแบบ การปรับให้เหมาะสม (การปรับแต่งการปรับจูน ฯลฯ ) แทบจะมองไม่เห็นการทำโปรไฟล์
S.Lott

3

เคล็ดลับการปฏิบัติที่ดีคือการใช้ชุดทดสอบหน่วยของคุณเป็นชุดทดสอบประสิทธิภาพการทำงาน

วิธีการต่อไปนี้ทำงานได้ดีในฐานรหัสของฉัน:

  1. ตรวจสอบให้แน่ใจว่าครอบคลุมการทดสอบหน่วยของคุณดี (คุณทำสิ่งนี้แล้วใช่ไหม?)
  2. ตรวจสอบให้แน่ใจว่ากรอบการทำงานของคุณรันรายงานรันไทม์ในการทดสอบแต่ละรายการ นี้เป็นสิ่งสำคัญเพราะคุณต้องการที่จะหาที่ประสิทธิภาพการทำงานช้าเป็นสิ่งที่เกิดขึ้น
  3. หากการทดสอบทำงานช้าให้ใช้วิธีนี้เป็นวิธีในการดำน้ำและปรับให้เหมาะสมเป้าหมายในพื้นที่นี้ การปรับให้เหมาะสมก่อนระบุปัญหาด้านประสิทธิภาพอาจได้รับการพิจารณาก่อนกำหนดดังนั้นสิ่งที่ดีเกี่ยวกับวิธีนี้คือคุณได้รับหลักฐานที่ชัดเจนเกี่ยวกับประสิทธิภาพที่ไม่ดีก่อน หากจำเป็นให้แบ่งการทดสอบออกเป็นแบบทดสอบขนาดเล็กเพื่อกำหนดมาตรฐานในด้านต่าง ๆ เพื่อให้คุณสามารถระบุได้ว่าปัญหารากอยู่ที่ใด
  4. หากการทดสอบทำงานเร็วมากซึ่งปกติแล้วจะดีแม้ว่าคุณอาจลองใช้การทดสอบแบบวนซ้ำกับพารามิเตอร์อื่น สิ่งนี้ทำให้เป็นการทดสอบประสิทธิภาพที่ดีขึ้นและยังเพิ่มความครอบคลุมการทดสอบของคุณเกี่ยวกับพื้นที่พารามิเตอร์
  5. เขียนการทดสอบเพิ่มเติมเล็กน้อยที่กำหนดเป้าหมายประสิทธิภาพโดยเฉพาะเช่นเวลาการทำธุรกรรมแบบ end-to-end หรือเวลาเพื่อให้แอปพลิเคชัน 1,000 กฎเสร็จสมบูรณ์ หากคุณมีข้อกำหนดด้านประสิทธิภาพที่ไม่สามารถใช้งานได้เฉพาะ (เช่นเวลาตอบสนอง <300ms) ทำให้การทดสอบล้มเหลวหากใช้เวลานานเกินไป

หากคุณทำสิ่งนี้ต่อไปประสิทธิภาพการทำงานโดยเฉลี่ยของฐานรหัสของคุณจะดีขึ้น

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


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

@jasonk - คุณพูดถูก แม้ว่าฉันจะเพิ่มว่าบางครั้งมันสามารถให้หลักฐานที่คุณต้องแสดงให้เห็นว่าทำไมการเปลี่ยนแปลงทางสถาปัตยกรรมโดยเฉพาะนั้นมีเหตุผล .....
mikera

1

คำตอบโดย @dasblinkenlight ชี้ให้เห็นปัญหาที่พบบ่อยมากโดยเฉพาะอย่างยิ่งกับรหัสฐานขนาดใหญ่ (จากประสบการณ์ของฉัน) อาจจะมีปัญหาในการใช้อย่างจริงจัง แต่พวกเขาจะไม่ได้มีการแปล ถ้าคุณเรียกใช้ตัวสร้างโปรไฟล์ไม่มีรูทีนที่ใช้เวลาในการดึงดูดความสนใจ (สมมติว่าคุณดูเปอร์เซ็นต์เวลารวมที่มี callees อย่าไปสนใจกับ "self time")

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

ฉันได้รับกรณีศึกษาซึ่งมีการนำเสนอภาพนิ่งแบบ PDF สั้น ๆ แสดงให้เห็นถึงปัญหานี้โดยละเอียดและวิธีการจัดการ จุดพื้นฐานคือเนื่องจากรหัสช้ากว่าที่ควรจะเป็นนั่นหมายถึง (ตามคำนิยาม) เวลาส่วนเกินที่ใช้ไปทำสิ่งที่สามารถลบออกได้

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

มันไม่ใช่ "ฮอตสปอต"

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

(มีการวิพากษ์วิจารณ์บ่อยครั้งจากวิธีนี้ว่าจำนวนตัวอย่างน้อยเกินไปสำหรับความถูกต้องทางสถิตินั่นคือคำตอบในสไลด์ที่ 13 ของ PDF สั้น ๆ - ใช่มีความไม่แน่นอนสูงใน "การวัด" ของการประหยัดที่มีศักยภาพ แต่ 1) มูลค่าที่คาดหวังของการประหยัดที่อาจเกิดขึ้นนั้นไม่ได้รับผลกระทบใด ๆ และ 2) เมื่อการประหยัดที่มีศักยภาพ $ x $ ถูกแปลเป็นอัตราส่วนสปีดอัปโดย $ 1 / (1-x) $ มันบิดเบือนไปอย่างมาก


ขอบคุณสำหรับคำตอบ. เราไม่เชื่อในการสุ่มตัวอย่างเชิงสถิติและใช้เครื่องมือวัดกับ valgrind สิ่งนี้ทำให้เราประเมินได้ดีทั้งค่าใช้จ่าย "ตนเอง" และ "รวม" ของสิ่งที่เราทำ
Benjamin Bannier

@honk: ใช่ แต่น่าเศร้าที่การใช้เครื่องมือยังคงนำความคิดที่ว่าปัญหาประสิทธิภาพการทำงานเป็นภาษาท้องถิ่นและสามารถพบได้โดยการวัดเศษส่วนของเวลาที่ใช้ในกิจวัตร ฯลฯ คุณสามารถเรียกใช้ valgrind และอื่น ๆ ได้อย่างแน่นอน แต่ถ้าคุณต้องการข้อมูลเชิงลึกจริง ๆ .
Mike Dunlavey
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.