คุณใช้เทคนิคง่ายๆอะไรในการปรับปรุงประสิทธิภาพ


21

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

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

แต่ฉันมักจะทำสิ่งนี้เมื่อforeachไม่สามารถใช้ได้:

for(int i = 0, j = collection.length(); i < j; i++ ){
   // stuff here
}

ฉันคิดว่านี่เป็นวิธีที่ดีกว่าเพราะจะเรียกlengthวิธีการนี้เพียงครั้งเดียว ... แฟนของฉันบอกว่ามันเป็นความลับ มีเคล็ดลับง่าย ๆ ที่คุณใช้ในการพัฒนาของคุณเอง?


34
+1 เพียงมีแฟนที่จะบอกคุณเมื่อรหัสของคุณไม่ชัดเจน
Kristo

76
คุณเพิ่งโพสต์สิ่งนี้เพื่อบอกเราว่าคุณมีแฟน
Josh K

11
@Christian: อย่าลืมว่ามีการเพิ่มประสิทธิภาพของคอมไพเลอร์ซึ่งอาจทำเพื่อคุณดังนั้นคุณอาจจะส่งผลกระทบต่อความสามารถในการอ่านและไม่ส่งผลต่อประสิทธิภาพเลย การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นสาเหตุของความชั่วร้ายทั้งหมด ... พยายามหลีกเลี่ยงการประกาศหรือมอบหมายมากกว่าหนึ่งครั้งในบรรทัดเดียวกันอย่าทำให้คนอ่านสองครั้ง ... คุณควรใช้วิธีปกติ (ตัวอย่างแรก) หรือใส่ การประกาศครั้งที่สองนอก for for loop (แม้ว่าจะลดความสามารถในการอ่านได้เช่นเดียวกับที่คุณต้องอ่านกลับเพื่อดูว่า j หมายถึงอะไร)
Tamara Wijsman

5
@TomWij: คำพูดที่ถูกต้อง (และครบถ้วน): "เราควรลืมเกี่ยวกับประสิทธิภาพเล็กน้อยพูดถึง 97% ของเวลา: การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด แต่เราไม่ควรผ่านโอกาสของเราในการวิจารณ์ 3% "
Robert Harvey

3
@tomwij: ถ้าคุณใช้เงินสามเปอร์เซ็นต์จากนั้นตามคำนิยามคุณควรจะทำมันในรหัสที่มีความสำคัญต่อเวลาและไม่ต้องเสียเวลาไปกับ 97% อีก
Robert Harvey

คำตอบ:


28

แทรกการบรรยายก่อนเวลาอันควรคือรากเหง้าแห่งความชั่วร้ายทั้งหมด

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

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

รู้ว่าใหญ่ของคุณ

สิ่งนี้อาจจะถูกรวมเข้ากับการสนทนาที่มีความยาวด้านบน เป็นเรื่องธรรมดามากที่การวนรอบด้านในของวงวนที่วงด้านในทำการคำนวณซ้ำจะช้าลง ตัวอย่างเช่น:

for (i = 0; i < strlen(str); i++) {
    ...
}

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

เมื่อเรียงลำดับล้านจำนวนเต็ม 32 บิตฟองเรียงลำดับจะเป็นวิธีที่ผิดไป โดยทั่วไปการเรียงลำดับสามารถทำได้ในเวลา O (n * log n) (หรือดีกว่าในกรณีของการจัดเรียง radix) ดังนั้นถ้าคุณรู้ว่าข้อมูลของคุณจะมีขนาดเล็กให้มองหาอัลกอริทึมที่อย่างน้อย O (n * log n)

ในทำนองเดียวกันเมื่อต้องจัดการกับฐานข้อมูลระวังดัชนี หากคุณSELECT * FROM people WHERE age = 20และคุณไม่มีดัชนีของบุคคล (อายุ) มันจะต้องมีการสแกนตามลำดับ O (n) มากกว่าการสแกนดัชนี O (log n) ที่เร็วกว่ามาก

ลำดับชั้นทางคณิตศาสตร์จำนวนเต็ม

เมื่อเขียนโปรแกรมใน C โปรดทราบว่าการดำเนินการทางคณิตศาสตร์บางอย่างมีราคาแพงกว่าการดำเนินการอื่น สำหรับจำนวนเต็มลำดับชั้นจะเป็นดังนี้ (อย่างน้อยแพงก่อน):

  • + - ~ & | ^
  • << >>
  • *
  • /

จริงอยู่ที่คอมไพเลอร์จะสิ่งที่มักจะเพิ่มประสิทธิภาพเช่นn / 2การn >> 1โดยอัตโนมัติหากคุณกำลังกำหนดเป้าหมายคอมพิวเตอร์หลัก แต่ถ้าคุณกำหนดเป้าหมายอุปกรณ์ฝังตัวที่คุณอาจไม่ได้รับความหรูหราที่

นอกจากนี้% 2และ& 1มีความหมายที่แตกต่างกัน การหารและโมดูลัสมักจะปัดเศษเป็นศูนย์ แต่จะมีการกำหนดการใช้งาน ดีเฒ่า>>และ&รอบไปทางลบอนันต์ซึ่ง (ในความคิดของฉัน) ทำให้รู้สึกมากขึ้น ตัวอย่างเช่นบนคอมพิวเตอร์ของฉัน:

printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1

ดังนั้นใช้สิ่งที่เหมาะสม อย่าคิดว่าคุณเป็นเด็กดีโดยใช้เมื่อคุณได้เดิมจะเขียน% 2& 1

การดำเนินการจุดลอยตัวที่แพง

หลีกเลี่ยงการดำเนินการจุดลอยตัวที่หนักหน่วงเช่นpow()และlog()ในรหัสที่ไม่ต้องการโดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับจำนวนเต็ม ยกตัวอย่างเช่นการอ่านตัวเลข:

int parseInt(const char *str)
{
    const char *p;
    int         digits;
    int         number;
    int         position;

    // Count the number of digits
    for (p = str; isdigit(*p); p++)
        {}
    digits = p - str;

    // Sum the digits, multiplying them by their respective power of 10.
    number = 0;
    position = digits - 1;
    for (p = str; isdigit(*p); p++, position--)
        number += (*p - '0') * pow(10, position);

    return number;
}

ไม่เพียง แต่การใช้pow()(และการแปลงint<-> ที่doubleจำเป็นต้องใช้) ค่อนข้างแพง แต่มันสร้างโอกาสในการสูญเสียความแม่นยำ (โดยบังเอิญรหัสข้างต้นไม่มีปัญหาความแม่นยำ) นั่นเป็นเหตุผลที่ฉันสะดุ้งเมื่อฉันเห็นฟังก์ชั่นประเภทนี้ที่ใช้ในบริบทที่ไม่ใช่ทางคณิตศาสตร์

นอกจากนี้ให้สังเกตว่าอัลกอริทึม "ฉลาด" ด้านล่างซึ่งคูณด้วย 10 ในการทำซ้ำแต่ละครั้งนั้นจริง ๆ แล้วรัดกุมกว่ารหัสด้านบน:

int parseInt(const char *str)
{
    const char *p;
    int         number;

    number = 0;
    for (p = str; isdigit(*p); p++) {
        number *= 10;
        number += *p - '0';
    }

    return number;
}

คำตอบอย่างละเอียดมาก
Paddyslacker

1
หมายเหตุการสนทนาการปรับให้เหมาะสมก่อนกำหนดไม่ได้ใช้กับรหัสขยะ คุณควรใช้การนำไปใช้งานได้ดีตั้งแต่แรก

โปรดทราบว่า GCC เพิ่มประสิทธิภาพกรณีนี้จริงเพราะ strlen () ถูกทำเครื่องหมายเป็นฟังก์ชั่นบริสุทธิ์ ฉันคิดว่าคุณหมายถึงว่ามันเป็นฟังก์ชั่น const ไม่บริสุทธิ์
Andy Lester

@ Andy Lester: จริง ๆ แล้วฉันหมายถึงบริสุทธิ์ ในเอกสาร GCCนั้นระบุว่า const นั้นเข้มงวดกว่าบริสุทธิ์เล็กน้อยซึ่งฟังก์ชั่น const ไม่สามารถอ่านหน่วยความจำระดับโลกได้ strlen()ตรวจสอบสตริงที่ชี้ไปตามอาร์กิวเมนต์ตัวชี้ซึ่งหมายความว่าไม่สามารถเป็น const นอกจากนี้ยังstrlen()ถูกระบุว่าบริสุทธิ์ใน glibc แน่นอนstring.h
Joey Adams

คุณพูดถูกความผิดของฉันและฉันควรตรวจสอบอีกครั้ง ฉันได้ทำงานกับโปรเจ็กต์ Parrot ในการทำหน้าที่ฟังก์ชั่นอย่างใดอย่างหนึ่งpureหรือconstแม้กระทั่งและจัดทำเป็นเอกสารไว้ในไฟล์ส่วนหัวเนื่องจากความแตกต่างเล็กน้อยระหว่างทั้งสอง docs.parrot.org/parrot/1.3.0/html/docs/dev/c_functions.pod.html
Andy Lester

13

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

ฉันเป็นแฟนตัวยงของปรัชญาของKent Beck :

"ทำงานให้ถูกต้องทำให้เร็ว"

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

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

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

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


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

ไม่จำเป็นต้องมีการทดสอบหน่วย แต่ก็คุ้มค่าที่จะใช้เวลา 20 นาทีนี้เพื่อตรวจสอบว่าบางตำนานประสิทธิภาพเป็นจริงหรือไม่โดยเฉพาะอย่างยิ่งเพราะคำตอบมักขึ้นอยู่กับคอมไพเลอร์และสถานะของแฟล็ก -O และ -g (หรือ Debug / ปล่อยในกรณีของ VS)
mbq

+1 คำตอบนี้เสริมความคิดเห็นที่เกี่ยวข้องกับคำถาม
Tamara Wijsman

1
@Ashelly: ถ้าเรากำลังพูดถึงการปฏิรูปอย่างง่ายของวากยสัมพันธ์วนรอบการเปลี่ยนแปลงหลังจากความจริงมันเป็นเรื่องง่ายมากที่จะทำ นอกจากนี้สิ่งที่คุณพบว่าสามารถอ่านได้อย่างเท่าเทียมกันอาจไม่เป็นเช่นนั้นสำหรับโปรแกรมเมอร์คนอื่น ๆ ดีที่สุดในการใช้ไวยากรณ์ "มาตรฐาน" มากที่สุดเท่าที่จะเป็นไปได้และจะเปลี่ยนแปลงได้ก็ต่อเมื่อจำเป็นต้องพิสูจน์แล้วเท่านั้น
Joeri Sebrechts

@ แน่นอนว่าถ้าคุณคิดวิธีการอ่านที่เท่าเทียมกันสองวิธีและคุณเลือกวิธีที่มีประสิทธิภาพน้อยกว่าที่คุณไม่ได้ทำงานอยู่ใช่ไหม? ใครบ้างที่จะทำเช่นนั้น?
glenatron

11

โปรดทราบว่าคอมไพเลอร์ของคุณอาจเปลี่ยนเป็น:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

เป็น:

int j = collection.length();
for(int i = 0; i < j; i++ ){
   // stuff here
}

หรือสิ่งที่คล้ายกันถ้า collectionไม่ได้เปลี่ยนแปลงวง

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

สิ่งนี้จะรักษาความสามารถในการอ่านของรหัส (ในอดีตคือสิ่งที่คนส่วนใหญ่คาดหวังที่จะเห็น) ในขณะที่คุณเพิ่มรอบเครื่องเหล่านั้นไม่กี่ จากนั้นคุณสามารถจดจ่อกับส่วนอื่น ๆ ที่คอมไพเลอร์ไม่สามารถช่วยคุณได้

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


1
ทำไมไม่ทำอย่างชัดเจน?

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

1
นี่คือเหตุผลที่ฉันพูดว่า "มันจะคุ้มค่าในการค้นหา"
ChrisF

คอมไพเลอร์ C # จะรู้ได้อย่างไรว่า collection.length () ไม่ปรับเปลี่ยนคอลเล็กชันในลักษณะที่ stack.pop () ทำหน้าที่อย่างไร ฉันคิดว่ามันเป็นการดีที่สุดที่จะตรวจสอบ IL แทนที่จะคิดว่าคอมไพเลอร์เพิ่มประสิทธิภาพนี้ ใน C ++ คุณสามารถทำเครื่องหมายวิธีเป็น const ('ไม่เปลี่ยนวัตถุ') เพื่อให้คอมไพเลอร์สามารถเพิ่มประสิทธิภาพนี้ได้อย่างปลอดภัย
JBRWilkinson

1
@JBRW เครื่องมือเพิ่มประสิทธิภาพที่ทำสิ่งนี้ยังตระหนักถึงวิธีการที่เรียกกันว่า ok-Let's-it-constness-even-This-not-C-++ ท้ายที่สุดคุณสามารถตรวจสอบขอบเขตได้เฉพาะในกรณีที่คุณสังเกตเห็นว่ามีบางสิ่งที่เป็นคอลเล็กชั่นและรู้ว่าจะรับความยาวได้อย่างไร
Kate Gregory

9

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


1
มอบหนังสือเกี่ยวกับการเขียนโปรแกรมให้เธอ;)
Joeri Sebrechts

1
+1 เนื่องจากแฟนของเราส่วนใหญ่มีความสนใจเลดี้กาก้ามากกว่าความชัดเจนของรหัส
haploid

คุณช่วยอธิบายได้ไหมว่าทำไมถึงไม่แนะนำ
Macneil

@macneil ดี ... เคล็ดลับที่ทำให้รหัสไม่เหมือนกันและทั้งหมดไม่ทำงานชิ้นส่วนของการเพิ่มประสิทธิภาพนั้นควรจะทำโดยคอมไพเลอร์
ชั้นเชิง

@macneil หากคุณกำลังทำงานในภาษาระดับสูงกว่าคิดในระดับเดียวกัน
ชั้นเชิง

3

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

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

เป็นตัวอย่างที่เฉพาะเจาะจงสำหรับหน่วยประมวลผลในปัจจุบันของเราสาขาล้างท่อไปยังหน่วยประมวลผลเป็นเวลา 8 รอบ คอมไพเลอร์ใช้รหัสนี้:

 bool isReady = (value > TriggerLevel);

และเปลี่ยนมันให้เทียบเท่าการชุมนุมของ

isReady = 0
if (value > TriggerLevel)
{
  isReady = 1;
}

นี้อาจจะใช้เวลา 3 รอบหรือ 10 isReady=1;ถ้ามันกระโดดมากกว่า แต่โปรเซสเซอร์มีmaxคำสั่งรอบเดียวดังนั้นจึงเป็นการดีกว่ามากที่จะเขียนโค้ดเพื่อสร้างลำดับนี้ซึ่งรับประกันว่าจะใช้เวลา 3 รอบเสมอ:

diff = value-TriggerLevel;
diff = max(diff, 0);
isReady = min(1,diff);

เห็นได้ชัดว่าความตั้งใจที่นี่ชัดเจนน้อยกว่าต้นฉบับ ดังนั้นเราจึงสร้างมาโครที่เราใช้เมื่อใดก็ตามที่เราต้องการเปรียบเทียบแบบบูลมากกว่า - มากกว่า:

#define BOOL_GT(a,b) min(max((a)-(b),0),1)

//isReady = value > TriggerLevel;
isReady = BOOL_GT(value, TriggerLevel);

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


3

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

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

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


3

ฉันมีเทคนิคง่าย ๆ

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

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


2

ใช้ประโยชน์จากการลัดวงจร:

if(someVar || SomeMethod())

ใช้เวลานานในการเขียนโค้ดและสามารถอ่านได้ดัง:

if(someMethod() || someVar)

แต่มันจะประเมินได้เร็วขึ้นเมื่อเวลาผ่านไป


1

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


6
เอ่อ ... ลูกค้าของคุณเห็นว่าเป็นอย่างไร คุณร่ำรวยพอที่จะซื้อคอมพิวเตอร์ใหม่สำหรับพวกเขาด้วยหรือไม่
Robert Harvey

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

+1 คำตอบนี้เสริมความคิดเห็นที่เกี่ยวข้องกับคำถาม
Tamara Wijsman

3
เวลาในการเขียนโปรแกรมไม่แพงกว่าฮาร์ดแวร์เมื่อคุณมีผู้ใช้เป็นพันหรือเป็นล้าน ๆ เวลาโปรแกรมเมอร์ไม่สำคัญกว่าเวลาของผู้ใช้รับสิ่งนี้ผ่านหัวของคุณโดยเร็วที่สุด
HLGEM

1
เข้าสู่นิสัยที่ดีจากนั้นไม่ต้องใช้เวลาเขียนโปรแกรมใด ๆ เพราะเป็นสิ่งที่คุณทำตลอดเวลา
Dominique McDonnell

1

พยายามอย่าปรับเวลาให้เหมาะสมล่วงหน้าก่อนแล้วเมื่อคุณปรับความกังวลให้น้อยลงเกี่ยวกับความสามารถในการอ่าน

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

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

แม้ว่าโดยเฉพาะกับความหมายของคุณฉันพบว่าหลายครั้งที่ทำบูลีนตรงข้ามกับวิธีการเริ่มต้นบางครั้งช่วย:

for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}

สามารถกลายเป็น

for(int i = collection.length(); i > 0; i-=1 ){
// stuff here
}

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

ใน c # เช่น:

        string[] collection = {"a","b"};

        string result = "";

        for (int i = 0, j = collection.Count() - 1; i < j; i++)
        {
            result += collection[i] + "~";
        }

สามารถเขียนเป็น:

        for (int i = collection.Count() - 1; i > 0; i -= 1)
        {
            result = collection[i] + "~" + result;
        }

(และใช่คุณควร o ด้วยการเข้าร่วมหรือ stringbuilder แต่ฉันพยายามทำตัวอย่างง่ายๆ)

มีเทคนิคอื่น ๆ อีกมากมายที่เราสามารถใช้ที่ไม่ยากที่จะติดตาม แต่หลายคนใช้ไม่ได้กับทุกภาษาเช่นการใช้ mid ที่ด้านซ้ายของการมอบหมายใน vb เก่าเพื่อหลีกเลี่ยงการกำหนดค่าสตริงใหม่หรือการอ่านไฟล์ข้อความในโหมดไบนารี ใน. net เพื่อรับโทษการบัฟเฟอร์เมื่อไฟล์มีขนาดใหญ่เกินไปสำหรับการอ่านซ้ำ

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


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

แต่ด้วยการย้อนกลับดัชนีการทำซ้ำตัวเองอาจมีความซับซ้อนมากขึ้น
Tamara Wijsman

@stijn ฉันกำลังคิดถึง c # เมื่อฉันเขียนมัน แต่บางทีคำแนะนำนี้อาจอยู่ในหมวดหมู่ภาษาเฉพาะด้วยเหตุผลนั้น - ดูการแก้ไข ... @ ToWij แน่นอนฉันไม่คิดว่าจะมีคำแนะนำมากมายในลักษณะนี้ ที่ไม่เสี่ยงต่อสิ่งนั้น หาก // สิ่งของคุณเป็นการจัดการสแต็กบางชนิดอาจไม่สามารถย้อนกลับตรรกะได้อย่างถูกต้อง แต่ในหลายกรณีมันเป็นและไม่สับสนเกินไปหากทำอย่างระมัดระวังในกรณีส่วนใหญ่ IMHO
Bill

คุณถูก; ใน C ++ ฉันยังคงต้องการวง 'ปกติ' แต่ด้วยความยาว () โทรออกจากการวนซ้ำ (เช่นใน const size_t len ​​= collection.length (); สำหรับ (size_t i = 0; i <len; ++ i) {}) ด้วยเหตุผลสองประการ: ฉันพบว่าลูปการนับไปข้างหน้า 'ปกติ' จะอ่าน / เข้าใจได้ง่ายขึ้น (แต่อาจเป็นเพราะมันเป็นเรื่องธรรมดามากกว่า) และใช้ความยาวของลูปคงที่ () โทรออกจากลูป
stijn

1
  1. ข้อมูลส่วนตัว. พวกเรามีปัญหาหรือเปล่า? ที่ไหน?
  2. ใน 90% กรณีที่เกี่ยวข้องกับ IO ใช้แคช (และอาจได้รับหน่วยความจำเพิ่มเติม)
  3. หากเกี่ยวข้องกับ CPU ให้ทำการแคช
  4. หากประสิทธิภาพยังคงเป็นปัญหาอยู่เราได้ทิ้งขอบเขตของเทคนิคง่ายๆ - ทำคณิตศาสตร์

1

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


1

สิ่งที่ง่ายที่สุดสำหรับฉันคือการใช้สแต็กเมื่อเป็นไปได้เมื่อใดก็ตามที่รูปแบบการใช้เคสทั่วไปเหมาะกับช่วงของการพูด, [0, 64) แต่มีกรณีที่หายากที่ไม่มีขอบเขตบนเล็ก

ตัวอย่าง C แบบง่าย (ก่อน):

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int* values = calloc(n, sizeof(int));

    // do stuff with values
    ...
    free(values);
}

และหลังจากนั้น:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int values_mem[64] = {0}
    int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int));

    // do stuff with values
    ...
    if (values != values_mem)
        free(values);
}

ฉันได้สรุปลักษณะนี้ไว้แล้วเนื่องจากฮอตสปอตประเภทนี้ปลูกฝังการทำโปรไฟล์มากมาย:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    MemFast values_mem;
    int* values = mf_calloc(&values_mem, n, sizeof(int));

    // do stuff with values
    ...

    mf_free(&values_mem);
}

ด้านบนใช้สแต็กเมื่อข้อมูลที่ถูกจัดสรรมีขนาดเล็กพอในกรณี 99.9% และใช้ฮีปอื่น

ใน C ++ ฉันได้สรุปสิ่งนี้ด้วยลำดับเล็ก ๆ ที่ได้มาตรฐาน (คล้ายกับSmallVectorการใช้งานที่นั่น) ซึ่งหมุนรอบแนวคิดเดียวกัน

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


0

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

เป็นเพียงตัวอย่างของการเพิ่มประสิทธิภาพอย่างรวดเร็วสำหรับ SQL Server: ใช้ดัชนีที่เหมาะสมหลีกเลี่ยงเคอร์เซอร์ - ใช้ตรรกะแบบ set-based ใช้ sargable ในกรณีที่ส่วนคำสั่งไม่ใช้มุมมองซ้อนด้านบนของมุมมองไม่ส่งคืนข้อมูลมากกว่าที่คุณต้องการ คอลัมน์มากกว่าที่คุณต้องการอย่าใช้แบบสอบถามย่อยที่มีความเกี่ยวข้อง


0

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

มันไม่คุ้มที่จะเปลี่ยนรหัสที่มีอยู่ในโอกาสที่มันจะช่วย แต่มันเป็นนิสัยที่ดีในการเข้าใช้


0

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

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

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

คุณอาจสามารถแก้ไขปัญหาเหล่านี้ได้โดยไม่มีการเปลี่ยนแปลงที่สำคัญกับรหัส

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

วิธีที่คุณเขียนลูปไม่มีอะไรเกี่ยวข้องกับมันจริงๆ

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