การวางแนววัตถุส่งผลกระทบต่อประสิทธิภาพของอัลกอริทึมจริง ๆ หรือไม่


14

การวางแนววัตถุช่วยฉันได้มากในการใช้อัลกอริทึมมากมาย อย่างไรก็ตามบางครั้งภาษาเชิงวัตถุจะแนะนำคุณในแนวทาง "ตรงไปตรงมา" และฉันสงสัยว่าวิธีการนี้เป็นสิ่งที่ดีเสมอ

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

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

กล่าวอีกนัยหนึ่งการอ้างอิงจำนวนมากระหว่างวัตถุที่แตกต่างกันหลายอย่างหรือใช้วิธีการมากมายจากหลายชั้นเรียนส่งผลให้เกิดการใช้งานที่ "หนัก"


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

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

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

Python นั้นดีสำหรับอัลกอริทึมการสร้างต้นแบบ แต่คุณก็ไม่จำเป็นต้องมีคลาสเมื่อใช้อัลกอริทึมทั่วไปในนั้น
งาน

1
+1 สำหรับการวางแนววัตถุที่เกี่ยวข้องกับอัลกอริทึมสิ่งที่ถูกมองข้ามในปัจจุบันนี้ทั้งในอุตสาหกรรมซอฟต์แวร์และสถานศึกษา ...
umlcat

คำตอบ:


16

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

ดูห้องสมุดตัวเลข ส่วนมาก (ไม่ใช่เฉพาะที่เขียนในยุค 60 หรือ 70) ไม่ใช่ OOP มีเหตุผลว่า - อัลกอริธึมเชิงตัวเลขทำงานได้ดีขึ้นเนื่องจากชุดของ decoupled modulesกว่าเป็นลำดับชั้น OO ที่มีส่วนต่อประสานและการห่อหุ้ม


2
เหตุผลหลักสำหรับการทำเช่นนี้คือมีเพียง C ++ ที่คิดว่าจะใช้เทมเพลตนิพจน์เพื่อทำให้รุ่น OO มีประสิทธิภาพ
DeadMG

4
ดูไลบรารี C ++ ที่ทันสมัย ​​(STL, Boost) - พวกมันไม่ใช่ OOP เลย และไม่ใช่เพียงเพราะประสิทธิภาพ อัลกอริทึมไม่สามารถแสดงได้ดีในสไตล์ OOP สิ่งต่าง ๆ เช่นการเขียนโปรแกรมทั่วไปเหมาะกว่าสำหรับอัลกอริธึมระดับต่ำ
SK-logic

3
วา-WHA-อะไร? ฉันเดาว่าฉันมาจากดาวเคราะห์ดวงอื่นที่ไม่ใช่ quant_dev และ SK-logic ไม่จักรวาลที่แตกต่าง ด้วยกฎของฟิสิกส์ที่แตกต่างกันและทุกสิ่ง
Mike Nakis

5
@MikeNakis: ความแตกต่างของมุมมองนั้นอยู่ใน (1) ว่ารหัสการคำนวณบางส่วนสามารถได้รับประโยชน์ในแง่ของความสามารถในการอ่านของมนุษย์จาก OOP หรือไม่ (ซึ่งเป็นสูตรตัวเลข) (2) การออกแบบคลาส OOP สอดคล้องกับโครงสร้างข้อมูลและอัลกอริทึมที่เหมาะสมหรือไม่ (ดูคำตอบของฉัน); และ (3) แต่ละเลเยอร์ของการส่งผ่านทางอ้อมส่งมอบ "คุณค่า" ที่เพียงพอ (ในแง่ของการทำงานต่อการเรียกฟังก์ชั่นหรือความคมชัดทางความคิดต่อเลเยอร์) จะแสดงให้เห็นถึงค่าใช้จ่ายที่เหมาะสม (เนื่องจากการอ้อม (4) สุดท้ายความซับซ้อนของคอมไพเลอร์ / JIT / เพิ่มประสิทธิภาพเป็นปัจจัย จำกัด
ร. ว.

2
@MikeNakis คุณหมายถึงอะไร? คุณคิดว่า STL เป็นห้องสมุด OOP หรือไม่? การเขียนโปรแกรมทั่วไปไม่สามารถทำงานร่วมกับ OOP ได้ และไม่ต้องพูดถึงเลยว่า OOP นั้นเป็นกรอบที่แคบเกินไปเหมาะสำหรับงานภาคปฏิบัติที่น้อยมาก ๆ
SK-logic

9

อะไรเป็นตัวกำหนดประสิทธิภาพการทำงาน

พื้นฐาน: โครงสร้างข้อมูลอัลกอริธึมสถาปัตยกรรมคอมพิวเตอร์ฮาร์ดแวร์ บวกค่าใช้จ่าย

โปรแกรม OOP สามารถออกแบบให้สอดคล้องกับตัวเลือกของโครงสร้างข้อมูลและอัลกอริทึมที่ถือว่าเหมาะสมที่สุดโดยทฤษฎี CS มันจะมีลักษณะการทำงานเช่นเดียวกับโปรแกรมที่ดีที่สุดรวมทั้งค่าใช้จ่ายบางส่วน ค่าโสหุ้ยสามารถลดลงได้

อย่างไรก็ตามโปรแกรมที่ได้รับการออกแบบในขั้นแรกโดยมีข้อกังวลของ OOP เท่านั้นโดยไม่เกี่ยวข้องกับปัจจัยพื้นฐาน บางครั้ง sub-optimality นั้นสามารถถอดออกได้โดย refactoring บางครั้งมันไม่จำเป็นต้องมีการเขียนใหม่ทั้งหมด

Caveat: ประสิทธิภาพมีความสำคัญกับซอฟต์แวร์ธุรกิจหรือไม่?

ใช่ แต่การออกสู่ตลาด (TTM) มีความสำคัญมากขึ้นตามคำสั่งของขนาด ซอฟต์แวร์ทางธุรกิจให้ความสำคัญกับความสามารถในการปรับตัวของรหัสกับกฎเกณฑ์ทางธุรกิจที่ซับซ้อน ควรทำการวัดประสิทธิภาพตลอดวงจรชีวิตของการพัฒนา (ดูหัวข้อ: ประสิทธิภาพที่ดีที่สุดหมายถึงอะไร ) ควรทำการปรับปรุงเฉพาะตลาดและควรค่อย ๆ นำมาใช้ในรุ่นที่ใหม่กว่า

ประสิทธิภาพที่ดีที่สุดหมายถึงอะไร

โดยทั่วไปปัญหาเกี่ยวกับประสิทธิภาพของซอฟต์แวร์คือ: เพื่อที่จะพิสูจน์ว่า "มีรุ่นที่เร็วกว่า" รุ่นที่เร็วกว่านั้นจะต้องมีอยู่ก่อน (เช่นไม่มีการพิสูจน์ตัวเอง)

บางครั้งรุ่นที่เร็วกว่าจะเห็นเป็นครั้งแรกในภาษาหรือกระบวนทัศน์ที่แตกต่างกัน สิ่งนี้ควรใช้เป็นแนวทางในการปรับปรุงไม่ใช่การตัดสินความด้อยกว่าของภาษาหรือกระบวนทัศน์อื่น

เหตุใดเราจึงทำ OOP หากอาจเป็นอุปสรรคต่อการค้นหาประสิทธิภาพสูงสุด

OOP แนะนำค่าใช้จ่าย (ในพื้นที่และการดำเนินการ) เพื่อเป็นการตอบแทนสำหรับการปรับปรุง "ความสามารถทำงานได้" และด้วยเหตุนี้มูลค่าทางธุรกิจของรหัส สิ่งนี้จะช่วยลดค่าใช้จ่ายในการพัฒนาและปรับแต่งเพิ่มเติม ดู@MikeNakis

ส่วนใดของ OOP ที่อาจสนับสนุนการออกแบบขั้นต้นย่อยที่เหมาะสมที่สุด?

ชิ้นส่วนของ OOP ที่ (i) ส่งเสริมความเรียบง่าย / ความไม่เข้าใจ (ii) ใช้วิธีการออกแบบภาษาพูดแทนความรู้พื้นฐาน (iii) ไม่สนับสนุนการใช้งานที่ปรับแต่งหลายอย่างที่มีจุดประสงค์เดียวกัน

  • จูบ
  • YAGNI
  • แห้ง
  • การออกแบบวัตถุ (เช่นด้วยการ์ด CRC) โดยไม่คำนึงถึงความรู้พื้นฐานที่เท่าเทียมกัน)

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

อะไรคือการบรรเทาร่วมกันของค่าใช้จ่าย OOP?

ตามที่ระบุไว้ก่อนหน้านี้การใช้โครงสร้างข้อมูลที่เหมาะสมกับการออกแบบ

บางภาษารองรับโค้ดอินไลน์ซึ่งสามารถกู้คืนประสิทธิภาพรันไทม์บางอย่างได้

เราจะใช้ OOP ได้อย่างไรโดยไม่ทำให้ประสิทธิภาพลดลง

เรียนรู้และใช้ทั้ง OOP และพื้นฐาน

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

มีรหัสประเภทที่ไม่ได้รับประโยชน์จาก OOP หรือไม่?

(ขยายจากการสนทนาระหว่าง [@quant_dev], [@ SK-logic] และ [@MikeNakis])

  1. สูตรตัวเลขซึ่งมาจากคณิตศาสตร์
    • สมการทางคณิตศาสตร์และการแปลงตัวเองสามารถเข้าใจได้ว่าเป็นวัตถุ
    • จำเป็นต้องใช้เทคนิคการแปลงรหัสที่ซับซ้อนมากเพื่อสร้างรหัสที่มีประสิทธิภาพ การใช้งานไร้เดียงสา ("ไวท์บอร์ด") จะมีประสิทธิภาพสุดซึ้ง
    • อย่างไรก็ตามคอมไพเลอร์หลักในปัจจุบันไม่สามารถทำได้
    • ซอฟต์แวร์เฉพาะทาง (MATLAB และ Mathematica ฯลฯ ) มีทั้ง JIT และตัวแก้สัญลักษณ์ที่สามารถสร้างรหัสที่มีประสิทธิภาพสำหรับปัญหาย่อยบางอย่าง นักแก้ปัญหาพิเศษเหล่านี้สามารถถูกมองว่าเป็นคอมไพเลอร์เพื่อจุดประสงค์พิเศษ (ผู้ไกล่เกลี่ยระหว่างรหัสที่มนุษย์อ่านได้และรหัสที่ใช้งานกับเครื่อง) ซึ่งจะได้รับประโยชน์จากการออกแบบ OOP
    • แต่ละปัญหาย่อยต้องการ "คอมไพเลอร์" และ "การแปลงรหัส" ของตัวเอง ดังนั้นนี่เป็นพื้นที่เปิดการวิจัยที่มีการใช้งานมากโดยมีผลลัพธ์ใหม่ปรากฏขึ้นทุกปี
    • เนื่องจากการวิจัยใช้เวลานานนักเขียนซอฟต์แวร์จึงต้องทำการปรับให้เหมาะสมบนกระดาษและคัดลอกรหัสที่ได้รับการปรับปรุงให้เป็นซอฟต์แวร์ รหัสที่ถอดความอาจไม่สามารถเข้าใจได้
  2. รหัสระดับต่ำมาก
      * * * *

8

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

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


1
เนื่องจากคอมไพเลอร์เป็นแบบย่อยที่ดีที่สุด (หรือกฎของภาษาการเขียนโปรแกรมห้ามการใช้ประโยชน์จากสมมติฐานหรือการปรับให้เหมาะสม) จึงมีค่าใช้จ่ายที่แน่นอนซึ่งไม่สามารถลบออกได้ นอกจากนี้การปรับให้เหมาะสมบางอย่างเช่น vectorization นั้นมีข้อกำหนดขององค์กรข้อมูล (เช่นโครงสร้างของอาร์เรย์แทนที่จะเป็นโครงสร้างของอาร์เรย์) ซึ่ง OOP สามารถปรับปรุงหรือขัดขวางได้ (ฉันเพิ่งทำงาน std :: vector optimization task.)
rwong

5

เห็นได้ชัดว่าเป็นความคิดที่ดี OOP และเช่นเดียวกับความคิดที่ดีมันสามารถใช้งานได้มากเกินไป จากประสบการณ์ของฉันมันเป็นวิธีที่ใช้มากเกินไป ประสิทธิภาพแย่และผลลัพธ์การบำรุงรักษาแย่

มันไม่มีส่วนเกี่ยวข้องกับการเรียกฟังก์ชั่นเสมือนจริงและไม่เกี่ยวข้องกับสิ่งที่ optimizer / jitter ทำ

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

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

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

บางทีประสิทธิภาพของแอพที่ระบุอาจเป็นเรื่องที่เขียนได้

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


3

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

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

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


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

@rwong ไม่สามารถอธิบายได้ดีขึ้น ;-)
umlcat

3

แต่ OOP นี้อาจเป็นข้อเสียของซอฟต์แวร์ที่ขึ้นอยู่กับประสิทธิภาพนั่นคือโปรแกรมรันเร็วแค่ไหน?

บ่อยครั้งที่ !!! แต่...

กล่าวอีกนัยหนึ่งการอ้างอิงจำนวนมากระหว่างวัตถุที่แตกต่างกันหลายอย่างหรือใช้วิธีการมากมายจากหลายชั้นเรียนส่งผลให้เกิดการใช้งานที่ "หนัก"

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

ในภาษาอื่น ๆ เช่น Java มีค่าใช้จ่ายเล็กน้อยสำหรับวัตถุ (มักจะค่อนข้างเล็กในหลายกรณี แต่ดาราศาสตร์ในบางกรณีที่หายากที่มีวัตถุเล็กจริงๆ) ตัวอย่างเช่นIntegerมีประสิทธิภาพน้อยกว่ามากint(ใช้ 16 ไบต์เมื่อเทียบกับ 4 ใน 64- บิต) แต่นี่ไม่ใช่แค่ของเสียที่เห็นได้ชัดหรืออะไรทำนองนั้น ในการแลกเปลี่ยนข้อเสนอ Java สิ่งที่ต้องการสะท้อนให้เห็นในทุกประเภทที่ผู้ใช้กำหนดเดียวเหมือนกันเช่นเดียวกับความสามารถในการแทนที่ฟังก์ชันใด ๆ finalที่ไม่ทำเครื่องหมายเป็น

ทีนี้มาลองดูสถานการณ์ที่ดีที่สุด: คอมไพเลอร์ที่ปรับค่า C ++ ให้เหมาะสมซึ่งสามารถปรับอินเทอร์เฟซของวัตถุให้เป็นค่าใช้จ่ายให้เป็นศูนย์ได้ ถึงแม้ว่า OOP จะลดประสิทธิภาพลงและป้องกันไม่ให้ถึงจุดสูงสุด นั่นอาจฟังดูเป็นเส้นขนานที่สมบูรณ์: เป็นไปได้อย่างไร? ปัญหาอยู่ใน:

การออกแบบส่วนต่อประสานและ Encapsulation

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

ใช้ตัวอย่างนี้:

class Particle
{
public:
    ...

private:
    double birth;                // 8 bytes
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
    /*padding*/                  // 4 bytes of padding
};
Particle particles[1000000];     // 1mil particles (~24 megs)

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

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

สิ่งนี้อาจจะดูน่าสงสัยเพราะเรามีกิกะไบต์ของ DRAM ในปัจจุบัน แม้กระทั่งเครื่องจักรที่ร้ายกาจที่สุดที่เรามีอยู่ในปัจจุบันมักจะมีเพียง 8 เมกะไบต์เท่านั้นเมื่อมันมาถึงขอบเขตที่ช้าที่สุดและใหญ่ที่สุดของ CPU แคช (L3) ยิ่งเราใส่ได้น้อยเท่าไหร่เราก็ยิ่งจ่ายมากขึ้นในแง่ของการเข้าถึง DRAM ซ้ำ ๆ และสิ่งที่ช้าลงก็จะได้รับ ทันใดนั้นการสูญเสียความทรงจำ 16.7% ดูเหมือนว่าจะไม่ยุ่งยากอีกต่อไป

เราสามารถกำจัดค่าใช้จ่ายนี้ได้อย่างง่ายดายโดยไม่มีผลกระทบใด ๆ กับการจัดแนวสนาม:

class Particle
{
public:
    ...

private:
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
};
Particle particles[1000000];     // 1mil particles (~12 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

ตอนนี้เราได้ลดหน่วยความจำจาก 24 megs เป็น 20 megs ด้วยรูปแบบการเข้าถึงแบบลำดับเครื่องจะใช้ข้อมูลนี้เร็วขึ้นเล็กน้อย

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

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

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

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

เพื่อให้ได้สิ่งต่อไปสมมติว่าเนื่องจากเราเพิ่งเคลื่อนที่อนุภาคไปรอบ ๆ เราสามารถเข้าถึงเขตข้อมูล x / y / z ของพวกเขาในวงแยกสามวง ในกรณีนี้เราสามารถได้รับประโยชน์จากรูปแบบ SOA SIMD ที่แท้จริงด้วย AVX รีจิสเตอร์ที่สามารถแปลงการดำเนินการ SPFP 8 แบบขนาน แต่เมื่อต้องการทำเช่นนี้เราจะต้องใช้การเป็นตัวแทนนี้:

float particle_x[1000000];       // 1mil particle X positions (~4 megs)
float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

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

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

วิธีการแก้

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

แต่เราสามารถนำเสนอสุดท้ายที่เราตัดสินและยังคงจำลองอินเทอร์เฟซเชิงวัตถุ:

// Represents a collection of particles.
class ParticleSystem
{
public:
    ...

private:
    double particle_birth[1000000];  // 1mil particle births (~8 bytes)
    float particle_x[1000000];       // 1mil particle X positions (~4 megs)
    float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
    float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
};

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

ParticleSystemอาจเป็นนามธรรมและใช้ฟังก์ชันเสมือนได้ มันสงสัยตอนนี้เรากำลังจ่ายเงินสำหรับค่าใช้จ่ายในคอลเลกชันของอนุภาคระดับแทนการที่ต่ออนุภาคระดับ ค่าโสหุ้ยคือ 1 / 1,000,000 ในสิ่งที่มันจะเป็นอย่างอื่นถ้าเราจำลองวัตถุในระดับอนุภาคของแต่ละบุคคล

นั่นคือคำตอบในพื้นที่ที่มีความสำคัญต่อประสิทธิภาพอย่างแท้จริงซึ่งรองรับภาระงานหนักและสำหรับภาษาการเขียนโปรแกรมทุกประเภท (เทคนิคนี้มีประโยชน์กับ C, C ++, Python, Java, JavaScript, Lua, Swift และอื่น ๆ ) และมันก็ไม่สามารถจะระบุว่าเป็น "การเพิ่มประสิทธิภาพก่อนวัยอันควร" ตั้งแต่นี้เกี่ยวข้องกับการออกแบบอินเตอร์เฟซและสถาปัตยกรรม เราไม่สามารถเขียน codebase แบบจำลองของอนุภาคเดี่ยวเป็นวัตถุที่มีจำนวนเรือบรรทุกของการพึ่งพาลูกค้าไปยังParticle'sส่วนต่อประสานสาธารณะแล้วเปลี่ยนใจในภายหลัง ฉันได้ทำสิ่งนั้นมาหลายครั้งเมื่อถูกเรียกให้ปรับแต่งรหัสฐานแบบดั้งเดิมและนั่นอาจใช้เวลาหลายเดือนในการเขียนบรรทัดใหม่อีกนับหมื่นบรรทัดอย่างระมัดระวังเพื่อใช้การออกแบบที่มีขนาดใหญ่ขึ้น สิ่งนี้มีผลต่อวิธีที่เราออกแบบสิ่งต่าง ๆ ล่วงหน้าโดยที่เราสามารถคาดการณ์ภาระหนักได้

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


น่าอัศจรรย์ นี่คือสิ่งที่ฉันกำลังมองหาในแง่ของการรวม OOP กับความต้องการประสิทธิภาพสูง ฉันไม่สามารถเข้าใจได้ว่าทำไมมันถึงไม่ลงคะแนนมากขึ้น
pbx

2

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

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

ในระดับอัลกอริทึมมันมักจะคิดเกี่ยวกับภาพที่ใหญ่ขึ้นและข้อ จำกัด หรือความสัมพันธ์ระหว่างค่าที่นำไปสู่ผลกำไร Big O ตัวอย่างอาจเป็นไปได้ว่าไม่มีสิ่งใดใน OOP ความคิดที่จะนำคุณไปสู่การแปลง "ผลรวมของช่วงจำนวนเต็มต่อเนื่อง" จากลูปเป็น(max + min) * n/2

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

เมื่อคุณดูบางอย่างเช่น Performance Primitives ของ Intel คุณมีการใช้งานอย่างรวดเร็วนับพันของการแปลงฟูริเยร์อย่างรวดเร็วแต่ละอันปรับแต่งเพื่อให้ทำงานได้ดีขึ้นสำหรับขนาดข้อมูลและสถาปัตยกรรมเครื่องที่เฉพาะเจาะจง (น่าสนใจปรากฎว่าส่วนใหญ่ของการใช้งานเหล่านี้สร้างขึ้นจากเครื่อง: Markus Püschelการเขียนโปรแกรมประสิทธิภาพอัตโนมัติ )

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


0

มันเกี่ยวข้องและมักจะมองข้าม

มันไม่ใช่คำตอบที่ง่ายมันขึ้นอยู่กับว่าคุณต้องการทำอะไร

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

ก่อนที่การปฐมนิเทศวัตถุโรงเรียนหลายแห่งสอนการออกแบบอัลกอริธึมด้วยการเขียนโปรแกรมแบบมีโครงสร้าง ทุกวันนี้โรงเรียนหลายแห่งสอนการเขียนโปรแกรมเชิงวัตถุโดยไม่สนใจการออกแบบและประสิทธิภาพอัลกอริธึม ..

แน่นอนว่ามีโรงเรียนที่สอนการเขียนโปรแกรมเชิงโครงสร้างซึ่งไม่สนใจเรื่องอัลกอริทึมเลย


0

ประสิทธิภาพทั้งหมดมาถึงซีพียูและรอบหน่วยความจำในที่สุด แต่ความแตกต่างของเปอร์เซ็นต์ระหว่างโอเวอร์เฮดของการส่งข้อความ OOP และการห่อหุ้มและความหมายของการเขียนโปรแกรมแบบเปิดที่กว้างขึ้นอาจหรือไม่อาจเป็นเปอร์เซ็นต์ที่สำคัญพอที่จะสร้างความแตกต่างที่ชัดเจนในประสิทธิภาพของแอปพลิเคชันของคุณ หากแอพพลิเคชั่นเป็นดิสก์หรือ data-cache-miss bound ค่าใช้จ่าย OOP ใด ๆ อาจหายไปจากเสียงรบกวน

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

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


0

การออกแบบเชิงวัตถุที่ดีช่วยให้ฉันเร่งความเร็วแอพพลิเคชันได้อย่างมาก A ต้องสร้างกราฟิกที่ซับซ้อนในวิธีอัลกอริทึม ฉันทำผ่าน Microsoft Visio automation ฉันทำงาน แต่ก็ช้าอย่างไม่น่าเชื่อ โชคดีที่ฉันได้แทรกระดับพิเศษของสิ่งที่เป็นนามธรรมระหว่างตรรกะ (อัลกอริทึม) และเนื้อหาของ Visio องค์ประกอบ Visio ของฉันเปิดเผยการทำงานผ่านส่วนต่อประสาน สิ่งนี้ทำให้ฉันสามารถแทนที่ส่วนประกอบที่ช้าด้วยการสร้างไฟล์ SVG อื่นที่เร็วกว่าอย่างน้อย 50 ครั้ง! หากไม่มีวิธีการที่ชัดเจนในเชิงวัตถุรหัสสำหรับอัลกอริทึมและการควบคุมการมองเห็นจะเข้าไปยุ่งเกี่ยวซึ่งจะทำให้การเปลี่ยนแปลงกลายเป็นฝันร้าย


คุณหมายถึง OO Design ที่ใช้กับภาษาเชิงโพรซีเดอร์หรือ OO Design & OO หรือไม่
umlcat

ฉันกำลังพูดถึงแอปพลิเคชัน C # ทั้งการออกแบบและภาษาคือ OO เนื่องจาก OO-iness ของภาษาจะแนะนำประสิทธิภาพการทำงานขนาดเล็ก (การเรียกใช้เมธอดเสมือนการสร้างวัตถุการเข้าถึงสมาชิกผ่านทางอินเตอร์เฟส) การออกแบบ OO ช่วยให้ฉันสร้างแอปพลิเคชันที่เร็วขึ้น สิ่งที่ฉันต้องการจะพูดคือ: ลืมประสิทธิภาพการทำงานเนื่องจาก OO (ภาษาและการออกแบบ) นอกจากว่าคุณกำลังทำการคำนวณจำนวนมากด้วยการวนซ้ำนับล้าน OO จะไม่เป็นอันตรายต่อคุณ โดยปกติคุณจะเสียเวลาไปมากคือ I / O
Olivier Jacot-Descombes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.