ใน C ++ ฉันควรกังวลกับตัวแปรแคชหรือให้คอมไพเลอร์ทำการปรับให้เหมาะสม? (นามแฝง)


114

พิจารณารหัสต่อไปนี้ ( pเป็นประเภทunsigned char*และbitmap->widthเป็นจำนวนเต็มบางประเภทซึ่งไม่ทราบแน่ชัดและขึ้นอยู่กับเวอร์ชันของไลบรารีภายนอกที่เราใช้):

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

คุ้มไหมที่จะเพิ่มประสิทธิภาพ [.. ]

มีบางกรณีไหมที่สามารถให้ผลลัพธ์ที่มีประสิทธิภาพมากขึ้นโดยการเขียน:

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

... หรือนี่เป็นเรื่องเล็กน้อยสำหรับคอมไพเลอร์ในการปรับให้เหมาะสม?

คุณคิดว่าอะไรเป็นโค้ดที่ "ดีกว่า"

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


19
หาก*pเป็นประเภทเดียวกันwidthก็ไม่สำคัญที่จะปรับให้เหมาะสมเนื่องจากpสามารถชี้widthและแก้ไขภายในลูปได้
emlai

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

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

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

4
"p" เก็บไว้ที่ไหน? ฉันขอแนะนำว่าคุณอาจได้รับชัยชนะในการแสดงที่ยิ่งใหญ่จริงๆโดยทำบางสิ่งเช่น "char * restrict p2 = p;" แล้วใช้ "p2" แทน "p" ภายในลูปของคุณ จากนั้นหากคุณต้องการให้การเปลี่ยนแปลง "p2" นำกลับไปใช้กับ p ให้ใช้ "p + = (p2-p);" โปรดทราบว่าไม่มีตัวชี้ที่เขียนภายในอายุการใช้งานของ p2 โดยตัวชี้ที่ไม่ได้คัดลอกแบบฟอร์ม p2 อาจอ่านได้โดยใช้ตัวชี้ที่คัดลอกมาจาก p2 หรือในทางกลับกันและจะไม่มีการใช้สำเนาของ p2 เพื่อวัตถุประสงค์ใด ๆ หลังจากอายุการใช้งานของ p2 แต่คอมไพเลอร์สามารถใช้สิ่งเหล่านี้ได้ ข้อเท็จจริงเพื่อเปิดใช้งานการเพิ่มประสิทธิภาพซึ่งไม่สามารถทำได้ด้วยวิธีการอื่นใด
supercat

คำตอบ:


81

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

แหล่ง unoptimized.cpp

หมายเหตุ: รหัสนี้ไม่ได้หมายถึงการเรียกใช้งาน

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    for (unsigned x = 0 ; x < static_cast<unsigned>(bitmap.width) ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

แหล่ง optimized.cpp

หมายเหตุ: รหัสนี้ไม่ได้หมายถึงการเรียกใช้งาน

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    const unsigned width = static_cast<unsigned>(bitmap.width);
    for (unsigned x = 0 ; x < width ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

การรวบรวม

  • $ g++ -s -O3 unoptimized.cpp
  • $ g++ -s -O3 optimized.cpp

แอสเซมบลี (unoptimized.s)

    .file   "unoptimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    mov %eax, %edx
    addl    $1, %eax
    movq    (%rsi,%rdx,8), %rdx
    movb    $0, (%rdx)
    cmpl    bitmap(%rip), %eax
    jb  .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

แอสเซมบลี (optimized.s)

    .file   "optimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    subl    $1, %eax
    leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    movq    (%rsi,%rax), %rdx
    addq    $8, %rax
    cmpq    %rcx, %rax
    movb    $0, (%rdx)
    jne .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

diff

$ diff -uN unoptimized.s optimized.s
--- unoptimized.s   2015-11-24 16:11:55.837922223 +0000
+++ optimized.s 2015-11-24 16:12:02.628922941 +0000
@@ -1,4 +1,4 @@
-   .file   "unoptimized.cpp"
+   .file   "optimized.cpp"
    .text
    .p2align 4,,15
 .globl main
@@ -10,16 +10,17 @@
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
+   subl    $1, %eax
+   leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
 .L3:
-   mov %eax, %edx
-   addl    $1, %eax
-   movq    (%rsi,%rdx,8), %rdx
+   movq    (%rsi,%rax), %rdx
+   addq    $8, %rax
+   cmpq    %rcx, %rax
    movb    $0, (%rdx)
-   cmpl    bitmap(%rip), %eax
-   jb  .L3
+   jne .L3
 .L2:
    xorl    %eax, %eax
    ret

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

เมื่อฉันมีเวลาในที่สุดฉันก็โพสต์เกณฑ์มาตรฐานบางอย่างเกี่ยวกับเรื่องนั้น คำถามที่ดี.


3
มันน่าสนใจที่จะดูว่าโค้ดถูกสร้างขึ้นต่างกันหรือไม่หากคุณส่งไปconst unsignedแทนที่จะเป็นunsignedในกรณีที่ไม่ได้เพิ่มประสิทธิภาพ
Mark Ransom

2
@MarkRansom ฉันคิดว่ามันไม่ควรสร้างความแตกต่าง: "คำสัญญา" ของการเป็น const เป็นเพียงการเปรียบเทียบเพียงครั้งเดียวเท่านั้นไม่ใช่สำหรับทั้งวง
Hagen von Eitzen

13
กรุณาอย่าใช้ฟังก์ชั่นmainในการทดสอบสำหรับการเพิ่มประสิทธิภาพ Gcc ตั้งใจทำเครื่องหมายว่าเย็นและปิดใช้งานการเพิ่มประสิทธิภาพบางอย่างสำหรับมัน ฉันไม่รู้ว่าเป็นอย่างนั้นหรือเปล่า แต่นั่นเป็นนิสัยสำคัญที่ต้องทำ
Marc Glisse

3
@MarcGlisse คุณถูกต้อง 100% ฉันได้เขียนไปแล้วฉันจะปรับปรุงให้ดีขึ้น
YSC

3
นี่คือลิงค์ไปยังฟังก์ชันทั้งสองในหน่วยคอมไพล์เดียวบน godboltโดยสมมติว่าbitmapเป็น global เวอร์ชันที่ไม่ใช่ CSEd ใช้ตัวถูกดำเนินการหน่วยความจำcmpซึ่งไม่ใช่ปัญหาสำหรับความสมบูรณ์แบบในกรณีนี้ หากเป็นแบบโลคัลคอมไพเลอร์อาจถือว่าพอยน์เตอร์อื่นไม่สามารถ "รู้เรื่อง" และชี้เข้าไปในนั้นได้ ไม่ใช่ความคิดที่ดีที่จะจัดเก็บนิพจน์ที่เกี่ยวข้องกับ globals ในตัวแปร temp ตราบใดที่ยังช่วยเพิ่มความสามารถในการอ่าน (หรือไม่เจ็บ) หรือหากประสิทธิภาพเป็นสิ่งสำคัญ เว้นแต่ว่าจะมีอะไรเกิดขึ้นมากมายชาวบ้านเหล่านี้มักจะอาศัยอยู่ในทะเบียนและจะไม่มีวันรั่วไหล
Peter Cordes

38

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

ซึ่งหมายความว่าในกรณีนี้หากการวนซ้ำเปลี่ยนbitmap->widthผ่านตัวชี้pจะต้องเห็นเมื่ออ่านซ้ำในbitmap->widthภายหลังซึ่งหมายความว่าการจัดเก็บไว้ในตัวแปรท้องถิ่นจะผิดกฎหมาย

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

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

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


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

3
@GuyGreer - คุณสมบัติจะไม่ใช่restrictคำตอบสำหรับปัญหานามแฝงในกรณีนี้หรือไม่?
LThode

4
จากประสบการณ์ของฉันrestrictส่วนใหญ่เป็น hit-and-miss MSVC เป็นคอมไพเลอร์ตัวเดียวที่ฉันเห็นว่าทำอย่างถูกต้อง ICC สูญเสียข้อมูลนามแฝงผ่านการเรียกใช้ฟังก์ชันแม้ว่าจะอยู่ในบรรทัดก็ตาม และ GCC มักจะไม่ได้รับประโยชน์ใด ๆ เว้นแต่คุณจะประกาศทุกพารามิเตอร์อินพุตเป็นrestrict(รวมถึงthisฟังก์ชันสมาชิก)
Mysticial

1
@Mystical: สิ่งหนึ่งที่ต้องจำคือcharนามแฝงทุกประเภทดังนั้นหากคุณมีอักขระ * คุณต้องใช้restrictกับทุกอย่าง หรือถ้าคุณบังคับให้ปิดกฎการใช้นามแฝงที่เข้มงวดของ GCC -fno-strict-aliasingทุกอย่างก็ถือว่าเป็นนามแฝงที่เป็นไปได้
Zan Lynx

1
@Ray ข้อเสนอล่าสุดrestrictเหมือนความหมายใน C ++ เป็นN4150
TC

24

โอเคฉันได้วัดด้วย GCC -O3 (ใช้ GCC 4.9 บน Linux x64)

ปรากฎว่าเวอร์ชันที่สองทำงานเร็วขึ้น 54%!

ดังนั้นฉันเดาว่านามแฝงเป็นสิ่งที่ฉันไม่ได้คิดเกี่ยวกับมัน

[แก้ไข]

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

[แก้ไข 2]

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

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

และวัดผล (ต้องใช้ "-mcmodel = large" เพื่อเชื่อมโยง) จากนั้นฉันก็ลอง:

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

ผลการวัดเหมือนกัน - ดูเหมือนว่าคอมไพเลอร์สามารถปรับให้เหมาะสมได้ด้วยตัวเอง

แล้วฉันพยายามรหัสต้นฉบับ (ที่มีตัวชี้p) คราวนี้เมื่อเป็นประเภทp std::uint16_t*อีกครั้งผลลัพธ์ก็เหมือนกัน - เนื่องจากการใช้นามแฝงที่เข้มงวด จากนั้นฉันก็ลองสร้างด้วย "-fno-เข้มงวด-aliasing" และอีกครั้งก็เห็นความแตกต่างของเวลา


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

@GuyGreer: ดู [แก้ไข 2] ของฉัน - ตอนนี้ฉันคิดว่ามันได้รับการพิสูจน์แล้ว
Yaron Cohen-Tal

2
ฉันแค่สงสัยว่าทำไมคุณถึงเริ่มใช้ตัวแปร "i" เมื่อคุณมี "x" ในลูปของคุณ?
Jesper Madsen

1
เป็นเพียงฉันที่พบว่าวลีเร็วขึ้น 54%ยากที่จะเข้าใจ? คุณหมายถึงความเร็ว 1.54 เท่าของความเร็วที่ไม่ได้เพิ่มประสิทธิภาพหรืออย่างอื่น?
Roddy

3
@ YaronCohen-Tal เร็วกว่าสองเท่า? น่าประทับใจ แต่ไม่ใช่อย่างที่ฉันเคยเข้าใจว่า "เร็วขึ้น 54%" ถึงความหมาย!
Roddy

24

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

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

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

for (unsigned x = static_cast<unsigned>(bitmap->width);x > 0;  x--)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

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

for (unsigned x = static_cast<unsigned>(bitmap->width); x-- > 0;)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

นอกจากนี้คุณยังสามารถกำจัด casts ในตัวอย่างด้านบนได้หากต้องการโดยไม่ต้องกังวลว่าจะมีผลกระทบต่อกฎการเปรียบเทียบเนื่องจากสิ่งที่คุณทำกับ bitmap-> width คือการกำหนดให้กับตัวแปรโดยตรง


2
ฉันเคยเห็นว่ากรณีที่สองถูกจัดรูปแบบเป็นx --> 0ส่งผลให้ตัวดำเนินการ "downto" ค่อนข้างตลก ปล. ฉันไม่คิดว่าการสร้างตัวแปรให้เงื่อนไขสิ้นสุดเป็นค่าลบสำหรับความสามารถในการอ่านมันอาจตรงกันข้ามก็ได้
Mark Ransom

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

1
+1 ข้อสังเกตที่ดีแม้ว่าฉันจะโต้แย้งว่าการยกขึ้นstatic_cast<unsigned>(bitmap->width)และใช้widthแทนในลูปนั้นเป็นการปรับปรุงเพื่อความสามารถในการอ่านได้จริงเพราะตอนนี้มีสิ่งต่าง ๆ ให้ผู้อ่านแยกวิเคราะห์ต่อบรรทัดน้อยลง มุมมองของผู้อื่นอาจแตกต่างกันไป
SirGuy

1
มีสถานการณ์อื่น ๆ อีกมากมายที่การนับถอยหลังเหนือกว่า (เช่นเมื่อนำรายการออกจากรายการ) ฉันไม่รู้ว่าทำไมถึงไม่ทำบ่อยขึ้น
Ian Gold ภายใน

3
ถ้าคุณต้องการเขียนลูปที่มีลักษณะเหมือน asm ที่ดีที่สุดให้ใช้do { } while()เพราะใน ASM คุณสร้างลูปที่มีสาขาเงื่อนไขที่ส่วนท้าย ปกติfor(){}และwhile(){}ลูปต้องการคำแนะนำเพิ่มเติมเพื่อทดสอบเงื่อนไขลูปหนึ่งครั้งก่อนลูปหากคอมไพเลอร์ไม่สามารถพิสูจน์ได้ว่ามันรันอย่างน้อยหนึ่งครั้งเสมอ โดยทั้งหมดใช้for()หรือwhile()เมื่อมีประโยชน์ในการตรวจสอบว่าลูปควรทำงานครั้งเดียวหรือเมื่ออ่านได้มากขึ้น
Peter Cordes

11

สิ่งเดียวที่นี่ที่สามารถป้องกันการเพิ่มประสิทธิภาพเป็นกฎ aliasing เข้มงวด ในระยะสั้น :

"นามแฝงที่เข้มงวดเป็นข้อสันนิษฐานที่สร้างขึ้นโดยคอมไพเลอร์ C (หรือ C ++) ซึ่งการลดการอ้างอิงพอยน์เตอร์ไปยังอ็อบเจ็กต์ประเภทต่างๆจะไม่อ้างถึงตำแหน่งหน่วยความจำเดียวกัน (เช่นนามแฝงซึ่งกันและกัน)"

[ ... ]

ข้อยกเว้นของกฎคือ a char*ซึ่งอนุญาตให้ชี้ไปที่ประเภทใดก็ได้

นอกจากนี้ยังใช้กับตัวชี้unsignedและข้อยกเว้นsigned char

เป็นกรณีนี้ในรหัสของคุณ: คุณกำลังปรับเปลี่ยน*pผ่านpซึ่งเป็นunsigned char*ดังนั้นคอมไพเลอร์จะต้องbitmap->widthคิดว่ามันสามารถชี้ไป ดังนั้นการแคชbitmap->widthจึงเป็นการเพิ่มประสิทธิภาพที่ไม่ถูกต้อง พฤติกรรมการเพิ่มประสิทธิภาพการป้องกันนี้จะปรากฏในคำตอบของ YSC

หากpชี้ไปที่ไม่ใช่charและไม่ใช่decltype(bitmap->width)ประเภทเท่านั้นการแคชจะเป็นการเพิ่มประสิทธิภาพที่เป็นไปได้หรือไม่


10

คำถามเดิมถาม:

ควรเพิ่มประสิทธิภาพหรือไม่

และคำตอบของฉันสำหรับสิ่งนั้น (รวบรวมคะแนนโหวตทั้งขึ้นและลง .. )

ให้คอมไพเลอร์หมดกังวล

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

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

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

แน่นอนว่าคำถามที่แตกต่างออกไปคือ:

ฉันจะทราบได้อย่างไรว่าการเพิ่มประสิทธิภาพส่วนของโค้ดนั้นคุ้มค่าหรือไม่

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

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

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

'การเพิ่มประสิทธิภาพ' หมายความว่าอย่างไร

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

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

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


12
เมื่อคุณทำซ้ำตามความกว้างของภาพบิตแมปตรรกะการวนซ้ำอาจเป็นส่วนสำคัญของเวลาที่ใช้ในลูป แทนที่จะกังวลเกี่ยวกับการเพิ่มประสิทธิภาพก่อนเวลาอันควรในกรณีนี้ควรพัฒนาแนวทางปฏิบัติที่ดีที่สุดที่มีประสิทธิภาพตั้งแต่เริ่มต้น
Mark Ransom

4
@MarkRansom เห็นด้วยบางส่วน: แต่ "แนวทางปฏิบัติที่ดีที่สุด" อาจเป็น a: ใช้ไลบรารีที่มีอยู่หรือการเรียก API สำหรับการเติมรูปภาพหรือ b: รับ GPU เพื่อทำแทนคุณ ไม่ควรเป็นการเพิ่มประสิทธิภาพไมโครที่ไม่มีการวัดค่าตามที่ OP แนะนำ และคุณจะรู้ได้อย่างไรว่าโค้ดนี้ถูกเรียกใช้งานมากกว่าหนึ่งครั้งหรือด้วยบิตแมปที่ใหญ่กว่าแล้วกว้าง 16 พิกเซล ... ?
Roddy

@Veedrac ชื่นชมเหตุผลสำหรับ -1 แรงผลักดันของคำถามเปลี่ยนไปอย่างละเอียดและชัดเจนตั้งแต่ฉันตอบ หากคุณคิดว่าคำตอบ (ขยาย) ยังไม่เป็นประโยชน์ฉันขอเวลาลบออก ... "คุ้มไหม ... " มักจะอิงตามความคิดเห็นเป็นหลักอยู่ดี
Roddy

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

6

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

for (unsigned int x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
{
  *p++ = 0xAA;
  *p++ = 0xBB;
  *p++ = 0xCC;
}

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

แก้ไข 1 : การวางการดำเนินการคงที่นอกลูปเป็นรูปแบบการเขียนโปรแกรมที่ดี แสดงความเข้าใจพื้นฐานของการทำงานของเครื่องโดยเฉพาะใน C / C ++ ฉันขอยืนยันว่าความพยายามในการพิสูจน์ตัวเองควรอยู่ที่คนที่ไม่ปฏิบัติตามแนวทางนี้ หากคอมไพเลอร์ลงโทษรูปแบบที่ดีแสดงว่าเป็นบั๊กในคอมไพเลอร์

แก้ไข 2::ฉันได้วัดข้อเสนอแนะของฉันเทียบกับรหัสเดิมใน vs2013 ได้รับการปรับปรุง% 1 เราทำได้ดีกว่านี้ไหม การเพิ่มประสิทธิภาพแบบแมนนวลอย่างง่ายช่วยให้ปรับปรุงได้ดีกว่าลูปเดิมบนเครื่อง x64 ถึง 3 เท่าโดยไม่ต้องใช้คำสั่งแปลกใหม่ โค้ดด้านล่างถือว่าเป็นระบบ endian เพียงเล็กน้อยและมีการจัดแนวบิตแมปอย่างเหมาะสม การทดสอบ 0 เป็นแบบดั้งเดิม (9 วินาที) การทดสอบ 1 เร็วขึ้น (3 วินาที) ฉันพนันได้เลยว่าใครบางคนสามารถทำให้สิ่งนี้เร็วขึ้นได้และผลของการทดสอบจะขึ้นอยู่กับขนาดของบิตแมป ในอนาคตเร็ว ๆ นี้คอมไพเลอร์จะสามารถสร้างโค้ดที่เร็วที่สุดอย่างต่อเนื่อง ฉันกลัวว่านี่จะเป็นอนาคตเมื่อคอมไพเลอร์จะเป็น AI ของโปรแกรมเมอร์ด้วยดังนั้นเราจึงต้องออกจากงาน แต่ในตอนนี้เพียงแค่เขียนโค้ดที่แสดงว่าคุณรู้ว่าไม่จำเป็นต้องใช้การดำเนินการพิเศษในลูป

#include <memory>
#include <time.h>

struct Bitmap_line
{
  int blah;
  unsigned int width;
  Bitmap_line(unsigned int w)
  {
    blah = 0;
    width = w;
  }
};

#define TEST 0 //define 1 for faster test

int main(int argc, char* argv[])
{
  unsigned int size = (4 * 1024 * 1024) / 3 * 3; //makes it divisible by 3
  unsigned char* pointer = (unsigned char*)malloc(size);
  memset(pointer, 0, size);
  std::unique_ptr<Bitmap_line> bitmap(new Bitmap_line(size / 3));
  clock_t told = clock();
#if TEST == 0
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    for (unsigned x = 0; x < static_cast<unsigned>(bitmap->width); ++x)
    //for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#else
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    unsigned x = 0;
    for (const unsigned n = static_cast<unsigned>(bitmap->width) - 4; x < n; x += 4)
    {
      *(int64_t*)p = 0xBBAACCBBAACCBBAALL;
      p += 8;
      *(int32_t*)p = 0xCCBBAACC;
      p += 4;
    }

    for (const unsigned n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#endif
  double ms = 1000.0 * double(clock() - told) / CLOCKS_PER_SEC;
  printf("time %0.3f\n", ms);

  {
    //verify
    unsigned char* p = pointer;
    for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      if ((*p++ != 0xAA) || (*p++ != 0xBB) || (*p++ != 0xCC))
      {
        printf("EEEEEEEEEEEEERRRRORRRR!!!\n");
        abort();
      }
    }
  }

  return 0;
}

คุณสามารถประหยัดได้อีก 25% สำหรับ 64 บิตหากคุณใช้ int64_t สามรายการแทน int64_t และ int32_t
Antonín Lejsek

5

มีสองสิ่งที่ต้องพิจารณา

A) การเพิ่มประสิทธิภาพจะทำงานบ่อยแค่ไหน?

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

B) สิ่งนี้จะทำให้รหัสบำรุงรักษายากขึ้น / แก้ไขปัญหาหรือไม่?

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

ที่บอกว่าฉันใช้ตัวอย่าง B เป็นการส่วนตัว


4

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


4

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

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

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

ตัวอย่างเช่นใน Windows x64 มีหน้าที่ต้องเรียกการเรียก API แบบพิเศษ__chkstkในคำนำหน้าของฟังก์ชันหากฟังก์ชันจะใช้ตัวแปรภายในมากกว่า 1 หน้า ฟังก์ชั่นนี้เปิดโอกาสให้ windows จัดการเพจป้องกันที่ใช้เพื่อขยายสแต็กเมื่อจำเป็น หากตัวแปรพิเศษของคุณดันการใช้งานสแต็กขึ้นจากด้านล่าง 1 เพจไปยังที่หรือสูงกว่า 1 เพจตอนนี้ฟังก์ชันของคุณจะต้องเรียกใช้__chkstkทุกครั้งที่ป้อน หากคุณจะเพิ่มประสิทธิภาพลูปนี้บนเส้นทางที่ช้าคุณอาจทำให้เส้นทางเร็วช้าลงมากกว่าที่คุณบันทึกไว้ในเส้นทางที่ช้า!

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


4
ฉันหวังว่า C และ C ++ จะช่วยให้สามารถระบุสิ่งที่โปรแกรมเมอร์ไม่สนใจได้มากขึ้น ไม่เพียง แต่จะให้โอกาสมากขึ้นสำหรับคอมไพเลอร์ในการปรับแต่งสิ่งต่างๆเท่านั้น แต่ยังช่วยให้โปรแกรมเมอร์คนอื่น ๆ ที่อ่านโค้ดไม่ต้องเดาว่ามันอาจจะตรวจสอบ bitmap-> width ทุกครั้งเพื่อให้แน่ใจว่าการเปลี่ยนแปลงจะส่งผลต่อลูปหรือ ไม่ว่าจะเป็นการแคชบิตแมป -> ความกว้างเพื่อให้แน่ใจว่าการเปลี่ยนแปลงจะไม่ส่งผลต่อลูป การมีวิธีพูดว่า "Cache this or not - I don't care" จะทำให้ชัดเจนถึงเหตุผลในการเลือกโปรแกรมเมอร์
supercat

@supercat ฉันเห็นด้วยสุดใจเพราะเราสามารถดูได้ว่ามีใครดูกองภาษาที่ล้มเหลวที่ฉันพยายามเขียนเพื่อแก้ไขปัญหานี้หรือไม่ ฉันพบว่ามันยากมากที่จะนิยามว่า "อะไร" ที่ใครบางคนไม่สนใจหากไม่มีไวยากรณ์ที่ผิดศีลธรรมมากจนมันไม่คุ้มค่า ฉันค้นหาต่อไปโดยเปล่าประโยชน์
Cort Ammon

ไม่สามารถกำหนดได้ในทุกกรณี แต่ฉันคิดว่ามีหลายกรณีที่ระบบประเภทสามารถช่วยได้ C เกินไปตัดสินใจที่จะสร้างประเภทอักขระเป็น "universal accessor" แทนที่จะมี type qualifier ที่ค่อนข้างหลวมกว่า "volatile" ที่สามารถใช้กับประเภทใดก็ได้โดยความหมายที่การเข้าถึงประเภทดังกล่าวจะถูกประมวลผลตามลำดับด้วย การเข้าถึงประเภทที่เทียบเท่าที่ไม่ผ่านการรับรองและการเข้าถึงตัวแปรทุกประเภทที่มีคุณสมบัติเดียวกันนั้น นั่นจะช่วยให้ชัดเจนว่าใครใช้ประเภทตัวอักษรเพราะต้องการ ...
supercat

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

1
นี่เป็นการพูดที่ชาญฉลาด แต่โดยทั่วไปถ้าคุณเลือก C สำหรับงานของคุณอยู่แล้วประสิทธิภาพอาจสำคัญมากและควรใช้กฎที่แตกต่างกัน มิฉะนั้นอาจใช้ Ruby, Java, Python หรือสิ่งที่คล้ายกันได้ดีกว่า
Audrius Meskauskas

4

การเปรียบเทียบไม่ถูกต้องเนื่องจากข้อมูลโค้ดทั้งสอง

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)

และ

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x<width ;  ++x)

ไม่เทียบเท่า

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

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

คุณคิดเกี่ยวกับมัลติเธรดหรือบางทีค่าอาจขึ้นอยู่กับภายนอกเพื่อให้ค่าของมันมีความผันผวน เราจะคาดหวังให้คอมไพเลอร์เข้าใจสิ่งเหล่านี้ได้อย่างไรถ้าคุณไม่บอก?

คอมไพเลอร์สามารถทำได้ดีเท่าที่โค้ดของคุณอนุญาตเท่านั้น


2

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


1

คอมไพเลอร์ไม่สามารถปรับให้เหมาะสมได้bitmap->widthเนื่องจากค่าของwidthสามารถเปลี่ยนแปลงได้ระหว่างการทำซ้ำ มีสาเหตุที่พบบ่อยที่สุดสองสามประการ:

  1. แบบมัลติเธรด คอมไพเลอร์ไม่สามารถคาดเดาได้ว่าเธรดอื่นกำลังจะเปลี่ยนค่าหรือไม่
  2. การปรับเปลี่ยนภายในลูปบางครั้งก็ไม่ง่ายที่จะบอกว่าตัวแปรจะถูกเปลี่ยนภายในลูปหรือไม่
  3. เป็นการเรียกใช้ฟังก์ชันเช่นiterator::end()หรือcontainer::size()ดังนั้นจึงยากที่จะคาดเดาได้ว่าจะให้ผลลัพธ์เดียวกันหรือไม่

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

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