ตกลงคุณกำลังกำหนดปัญหาที่ดูเหมือนว่าจะมีพื้นที่ไม่มากสำหรับการปรับปรุง นั่นเป็นสิ่งที่ค่อนข้างหายากในประสบการณ์ของฉัน ฉันพยายามอธิบายสิ่งนี้ในบทความของดร. ดอบส์ในเดือนพฤศจิกายน 2536 โดยเริ่มจากโปรแกรมที่ไม่ได้รับการออกแบบมาเป็นอย่างดีโดยไม่มีการสูญเสียที่เห็นได้ชัด 1.1 วินาทีและขนาดรหัสที่มาก็ลดลงโดยปัจจัยที่ 4. เครื่องมือการวินิจฉัยของฉันเป็นแบบนี้ ลำดับการเปลี่ยนแปลงคือ:
ปัญหาแรกที่พบคือการใช้ list clusters (ปัจจุบันเรียกว่า "iterators" และ "container classes") ซึ่งทำบัญชีมานานกว่าครึ่ง สิ่งเหล่านั้นถูกแทนที่ด้วยรหัสที่ค่อนข้างง่ายทำให้เวลาลดลงเหลือ 20 วินาที
ตอนนี้คนรับเวลาที่ใหญ่ที่สุดคือการสร้างรายชื่อมากขึ้น เป็นเปอร์เซ็นต์มันไม่ได้ใหญ่มากมาก่อน แต่ตอนนี้เป็นเพราะปัญหาที่ใหญ่กว่านั้นถูกลบออกไป ฉันหาวิธีเพิ่มความเร็วและเวลาลดลงถึง 17 วินาที
ตอนนี้มันยากที่จะหาผู้กระทำความผิดที่เห็นได้ชัด แต่มีน้อยกว่าที่ฉันสามารถทำบางสิ่งบางอย่างและเวลาลดลงถึง 13 วินาที
ตอนนี้ฉันดูเหมือนจะชนกำแพง ตัวอย่างกำลังบอกฉันว่ามันกำลังทำอะไรอยู่ แต่ฉันไม่สามารถหาอะไรที่ฉันสามารถปรับปรุงได้ จากนั้นฉันจะพิจารณาการออกแบบขั้นพื้นฐานของโปรแกรมโครงสร้างการทำธุรกรรมและถามว่าการค้นหารายการทั้งหมดที่ทำอยู่นั้นได้รับคำสั่งตามข้อกำหนดของปัญหาหรือไม่
จากนั้นฉันก็ออกแบบใหม่โดยที่โค้ดโปรแกรมนั้นถูกสร้างขึ้นจริง (ผ่านมาโครตัวประมวลผลล่วงหน้า) จากชุดซอร์สขนาดเล็กและโปรแกรมไม่ได้คิดหาสิ่งที่โปรแกรมเมอร์รู้อยู่เสมอ กล่าวอีกนัยหนึ่งอย่า "ตีความ" ลำดับของสิ่งต่าง ๆ ที่ต้องทำ "รวบรวม"
- การออกแบบนั้นเสร็จสิ้นแล้วลดขนาดซอร์สโค้ดลง 4 เท่าและเวลาจะลดลงเหลือ 10 วินาที
ตอนนี้เพราะว่ามันเริ่มเร็วมากมันยากที่จะสุ่มตัวอย่างดังนั้นฉันให้มันทำงานมากขึ้น 10 เท่า แต่เวลาต่อไปนี้ขึ้นอยู่กับปริมาณงานดั้งเดิม
การวินิจฉัยเพิ่มเติมพบว่ามันใช้เวลาในการจัดการคิว ซับในเหล่านี้ช่วยลดเวลา 7 วินาที
ตอนนี้คนรับใหญ่คือการพิมพ์เพื่อการวินิจฉัยที่ฉันทำอยู่ ล้างออก - 4 วินาที
ตอนนี้ที่ใหญ่ที่สุดเวลาผู้รับสายจะmallocและฟรี วัตถุรีไซเคิล - 2.6 วินาที
ฉันยังพบการปฏิบัติการที่ไม่จำเป็นอย่างเคร่งครัด - 1.1 วินาที
อัตราเร่งรวม: 43.6
ตอนนี้ไม่มีสองโปรแกรมที่เหมือนกัน แต่ในซอฟต์แวร์ที่ไม่ใช่ของเล่นฉันเห็นความก้าวหน้าเช่นนี้เสมอ ก่อนอื่นคุณจะได้ของที่ง่ายและยากขึ้นเรื่อย ๆ จนกว่าจะถึงจุดที่ผลตอบแทนลดลง จากนั้นข้อมูลเชิงลึกที่คุณได้รับอาจนำไปสู่การออกแบบใหม่เริ่มต้นการเพิ่มความเร็วรอบใหม่จนกว่าคุณจะได้รับผลตอบแทนลดลงอีกครั้ง ตอนนี้เป็นจุดที่อาจทำให้รู้สึกสงสัยว่า++i
หรือi++
หรือfor(;;)
หรือwhile(1)
จะเร็ว: ชนิดของคำถามที่ผมเห็นจึงมักจะอยู่บนกองมากเกิน
ป.ล. อาจสงสัยว่าทำไมฉันไม่ใช้ profiler คำตอบคือเกือบทุกปัญหาเหล่านี้เป็นไซต์การเรียกใช้ฟังก์ชันซึ่งระบุตัวอย่างสแต็ก ผู้สร้างโปรไฟล์แม้กระทั่งทุกวันนี้แทบจะไม่คิดว่างบและคำแนะนำการโทรมีความสำคัญต่อการค้นหาและแก้ไขได้ง่ายกว่าฟังก์ชั่นทั้งหมด
ฉันสร้าง profiler ขึ้นมาเพื่อทำสิ่งนี้ แต่สำหรับความใกล้ชิดที่แท้จริงและสกปรกกับสิ่งที่โค้ดกำลังทำอยู่ไม่มีสิ่งใดมาแทนที่การทำให้นิ้วของคุณอยู่ในนั้น ไม่ใช่ปัญหาที่จำนวนตัวอย่างน้อยเพราะไม่มีปัญหาที่พบพบว่าเล็กมากจนพลาดง่าย
เพิ่ม: jerryjvl ขอตัวอย่างบางอย่าง นี่คือปัญหาแรก ประกอบด้วยรหัสบรรทัดที่แยกจากกันจำนวนเล็กน้อยโดยใช้เวลารวมกันครึ่งหนึ่ง:
/* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)
สิ่งเหล่านี้ใช้ลิสต์ของคลัสเตอร์รายการ ILST (คล้ายกับคลาสลิสต์) พวกเขาจะดำเนินการตามปกติด้วย "การซ่อนข้อมูล" หมายความว่าผู้ใช้ในชั้นเรียนไม่ควรจะต้องสนใจว่าพวกเขาได้รับการปฏิบัติอย่างไร เมื่อบรรทัดเหล่านี้ถูกเขียน (จากโค้ดประมาณ 800 บรรทัด) คิดว่าไม่ได้ให้ความคิดที่ว่าสิ่งเหล่านี้อาจเป็น "คอขวด" (ฉันเกลียดคำนั้น) พวกเขาเป็นเพียงวิธีแนะนำในการทำสิ่งต่าง ๆ มันง่ายที่จะพูดด้วยการเข้าใจถึงปัญหาย้อนหลังที่ควรหลีกเลี่ยงสิ่งเหล่านี้ แต่ในประสบการณ์ของฉันปัญหาด้านประสิทธิภาพทั้งหมดเป็นเช่นนั้น โดยทั่วไปแล้วจะเป็นการดีหากพยายามหลีกเลี่ยงการสร้างปัญหาด้านประสิทธิภาพ มันจะดีกว่าที่จะค้นหาและแก้ไขสิ่งที่สร้างขึ้นแม้ว่าพวกเขา "ควรจะหลีกเลี่ยง" (ในการเข้าใจถึงปัญหาหลังเหตุการณ์)
นี่คือปัญหาที่สองในสองบรรทัดแยกกัน:
/* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)
นี่คือรายการสร้างโดยการต่อท้ายรายการ (การแก้ไขคือการรวบรวมรายการในอาร์เรย์และสร้างรายการทั้งหมดในครั้งเดียว) สิ่งที่น่าสนใจคืองบเหล่านี้มีค่าใช้จ่ายเท่านั้น (เช่นอยู่ใน call stack) 3/48 ของเวลาเดิมดังนั้นจึงไม่ได้อยู่ใน ความเป็นจริงเป็นปัญหาใหญ่ที่จุดเริ่มต้น อย่างไรก็ตามหลังจากลบปัญหาแรกพวกเขาเสียค่าใช้จ่าย 3/20 ของเวลาและตอนนี้ก็เป็น "ปลาที่ใหญ่กว่า" โดยทั่วไปนั่นเป็นวิธีที่มันจะไป
ฉันอาจเพิ่มว่าโครงการนี้กลั่นจากโครงการจริงที่ฉันช่วย ในโครงการนั้นปัญหาด้านประสิทธิภาพมีความน่าทึ่งมาก (เช่นการเพิ่มความเร็ว) เช่นการเรียกรูทีนการเข้าถึงฐานข้อมูลภายในลูปด้านในเพื่อดูว่างานนั้นเสร็จสิ้นหรือไม่
เพิ่มการอ้างอิง: ซอร์สโค้ดทั้งต้นฉบับและการออกแบบสามารถพบได้ในwww.ddj.comสำหรับปี 1993 ในไฟล์ 9311.zip ไฟล์ slug.asc และ slug.zip
แก้ไข 2011/11/26: ขณะนี้มีโครงการ SourceForgeที่มีซอร์สโค้ดใน Visual C ++ และคำอธิบายแบบเป่าต่อเนื่องว่ามีการปรับจูนอย่างไร มันผ่านช่วงครึ่งแรกของสถานการณ์ที่อธิบายไว้ข้างต้นเท่านั้นและไม่เป็นไปตามลำดับเดียวกันทั้งหมด แต่ยังคงได้รับการเร่งความเร็วขนาด 2-3 ลำดับ