Câu lệnh if so với câu lệnh if-else, cái nào nhanh hơn? [đóng cửa]


83

Hôm trước tôi đã tranh luận với một người bạn về hai đoạn trích đó. Cái nào nhanh hơn và tại sao?

value = 5;
if (condition) {
    value = 6;
}

và:

if (condition) {
    value = 6;
} else {
    value = 5;
}

Nếu valuelà một ma trận thì sao?

Lưu ý: Tôi biết điều đó value = condition ? 6 : 5;tồn tại và tôi hy vọng nó sẽ nhanh hơn, nhưng đó không phải là một lựa chọn.

Chỉnh sửa (do nhân viên yêu cầu vì hiện tại câu hỏi đang bị treo):

  • vui lòng trả lời bằng cách xem xét hợp ngữ x86 được tạo bởi trình biên dịch chính thống ( giả sử g ++, clang ++, vc, mingw ) trong cả phiên bản được tối ưu hóa và không được tối ưu hóa hoặc hợp ngữ MIPS .
  • khi lắp ráp khác nhau, hãy giải thích tại sao một phiên bản nhanh hơn và khi nào ( ví dụ: "tốt hơn vì không phân nhánh và phân nhánh có vấn đề sau blahblah" )

173
Việc tối ưu hóa sẽ giết chết tất cả những điều đó ... điều đó không quan trọng ...
Nhà vật lý lượng tử

21
Bạn có thể lập hồ sơ cho nó, cá nhân tôi nghi ngờ bạn sẽ thấy bất kỳ sự khác biệt nào khi sử dụng trình biên dịch hiện đại.
George

25
Sử dụng value = condition ? 6 : 5;thay vì if/elserất có thể sẽ dẫn đến cùng một mã được tạo. Nhìn vào kết quả lắp ráp nếu bạn muốn biết thêm.
Jabberwocky

8
điều quan trọng nhất trong trường hợp này là tránh chi nhánh, đó là thứ đắt nhất ở đây. (Hướng dẫn đường ống nạp, loại bỏ trước khi lấy, vv)
Tommylee2k

11
Lần duy nhất có ý nghĩa để tối ưu hóa vi mô cho tốc độ như thế này là bên trong một vòng lặp sẽ chạy nhiều lần và hoặc trình tối ưu hóa có thể tối ưu hóa tất cả các hướng dẫn nhánh, như gcc có thể đối với ví dụ nhỏ này hoặc trong thế giới thực hiệu suất sẽ phụ thuộc nhiều vào dự đoán nhánh chính xác (liên kết bắt buộc đến stackoverflow.com/questions/11227809/… ). Nếu bạn không thể tránh khỏi phân nhánh bên trong một vòng lặp, bạn có thể trợ giúp bộ dự đoán nhánh bằng cách tạo một cấu hình và biên dịch lại với nó.
Davislor

Câu trả lời:


281

TL; DR: Trong mã được tối ưu hóa, ifmà không elsedường như irrelevantly hiệu quả hơn nhưng với ngay cả những mức cơ bản nhất của tối ưu hóa kích hoạt các mã được về cơ bản được viết lại để value = condition + 5.


Tôi đã thử và tạo lắp ráp cho đoạn mã sau:

int ifonly(bool condition, int value)
{
    value = 5;
    if (condition) {
        value = 6;
    }
    return value;
}

int ifelse(bool condition, int value)
{
    if (condition) {
        value = 6;
    } else {
        value = 5;
    }
    return value;
}

Trên gcc 6.3 đã tắt tối ưu hóa ( -O0), sự khác biệt có liên quan là:

 mov     DWORD PTR [rbp-8], 5
 cmp     BYTE PTR [rbp-4], 0
 je      .L2
 mov     DWORD PTR [rbp-8], 6
.L2:
 mov     eax, DWORD PTR [rbp-8]

cho ifonly, trong khi ifelse

 cmp     BYTE PTR [rbp-4], 0
 je      .L5
 mov     DWORD PTR [rbp-8], 6
 jmp     .L6
.L5:
 mov     DWORD PTR [rbp-8], 5
.L6:
 mov     eax, DWORD PTR [rbp-8]

Cái thứ hai trông kém hiệu quả hơn một chút vì nó có thêm một bước nhảy nhưng cả hai đều có ít nhất hai và nhiều nhất ba nhiệm vụ, vì vậy trừ khi bạn thực sự cần phải vắt kiệt từng giọt hiệu suất cuối cùng (gợi ý: trừ khi bạn đang làm việc trên tàu con thoi thì không và thậm chí sau đó bạn có thể không) sự khác biệt sẽ không đáng chú ý.

Tuy nhiên, ngay cả với mức tối ưu hóa thấp nhất ( -O1) cả hai hàm đều giảm xuống như nhau:

test    dil, dil
setne   al
movzx   eax, al
add     eax, 5

về cơ bản tương đương với

return 5 + condition;

giả sử conditionlà không hoặc một. Các mức tối ưu hóa cao hơn không thực sự thay đổi kết quả đầu ra, ngoại trừ việc họ quản lý để tránh movzxbằng cách loại bỏ thanh EAXghi một cách hiệu quả ngay từ đầu.


Tuyên bố từ chối trách nhiệm: Bạn có thể không nên 5 + conditiontự viết (mặc dù tiêu chuẩn đảm bảo rằng việc chuyển đổi truesang kiểu số nguyên mang lại 1) vì ý định của bạn có thể không rõ ràng ngay lập tức đối với những người đọc mã của bạn (có thể bao gồm cả bản thân bạn trong tương lai). Điểm của đoạn mã này là chỉ ra rằng những gì trình biên dịch tạo ra trong cả hai trường hợp là (thực tế) giống hệt nhau. Ciprian Tomoiaga nói rõ điều đó trong các bình luận:

một con người công việc 's là viết mã cho con người và để cho các trình biên dịch ghi mã cho máy .


50
Đây là một câu trả lời tuyệt vời và nên được chấp nhận.
dtell

10
Tôi sẽ chưa bao giờ sử dụng phép bổ sung (<- những gì con trăn làm với bạn.)
Ciprian Tomoiagă

26
@CiprianTomoiaga và trừ khi bạn đang viết một người tối ưu hóa, bạn không nên! Trong hầu hết các trường hợp, bạn nên để trình biên dịch tối ưu hóa như thế này, đặc biệt là khi chúng làm giảm nghiêm trọng khả năng đọc mã. Chỉ khi kiểm tra hiệu suất cho thấy các vấn đề với một đoạn mã nhất định, bạn mới nên bắt đầu cố gắng tối ưu hóa nó, và thậm chí sau đó giữ cho nó sạch sẽ và được nhận xét tốt và chỉ thực hiện những tối ưu tạo ra sự khác biệt có thể đo lường được.
Muzer

22
Tôi muốn trả lời Muzer, nhưng nó sẽ không thêm bất cứ điều gì vào chuỗi. Tuy nhiên, tôi chỉ muốn nói lại rằng công việc của con người là viết mã cho con người và để trình biên dịch viết mã cho máy . Tôi cho rằng, từ một nhà phát triển trình biên dịch PoV (mà tôi không, nhưng tôi đã học một chút về họ)
Ciprian Tomoiagă

10
Giá trị được truechuyển đổi thành intluôn mang lại 1, dấu chấm. Tất nhiên, nếu điều kiện của bạn chỉ là "sự thật" chứ không phải boolgiá trị true, thì đó là một vấn đề hoàn toàn khác.
TC

44

Câu trả lời từ CompuChip cho thấy rằng intcả hai đều được tối ưu hóa cho cùng một lắp ráp, vì vậy điều đó không quan trọng.

Nếu giá trị là một ma trận thì sao?

Tôi sẽ giải thích điều này một cách tổng quát hơn, tức là nếu valuethuộc loại có cấu tạo và nhiệm vụ đắt (và chuyển động thì rẻ).

sau đó

T value = init1;
if (condition)
   value = init2;

là tối ưu phụ vì trong trường hợp conditionđúng, bạn thực hiện việc khởi tạo không cần thiết init1và sau đó bạn thực hiện việc gán bản sao.

T value;
if (condition)
   value = init2;
else
   value = init3;

Điều này tốt hơn. Nhưng vẫn chưa tối ưu nếu việc xây dựng mặc định là đắt và nếu bản sao xây dựng đắt hơn thì việc khởi tạo.

Bạn có giải pháp toán tử điều kiện tốt:

T value = condition ? init1 : init2;

Hoặc, nếu bạn không thích toán tử điều kiện, bạn có thể tạo một hàm trợ giúp như sau:

T create(bool condition)
{
  if (condition)
     return {init1};
  else
     return {init2};
}

T value = create(condition);

Tùy thuộc vào những gì init1init2bạn cũng có thể xem xét điều này:

auto final_init = condition ? init1 : init2;
T value = final_init;

Nhưng một lần nữa tôi phải nhấn mạnh rằng điều này chỉ phù hợp khi việc xây dựng và chuyển nhượng thực sự tốn kém đối với loại đã cho. Và thậm chí sau đó, chỉ bằng cách lập hồ sơ, bạn biết chắc chắn.


3
Đắt tiền không được tối ưu hóa. Ví dụ, nếu hàm tạo mặc định xóa ma trận, trình biên dịch có thể nhận ra rằng phép gán chỉ là ghi đè các số 0 đó, vì vậy hoàn toàn không phải là 0 và ghi trực tiếp vào bộ nhớ này. Tất nhiên, tối ưu hóa là những con thú khó tính nên thật khó để dự đoán khi nào họ đá tại hay không ...
Matthieu M.

@MatthieuM. tất nhiên. Những gì tôi có nghĩa là bởi "đắt" là "tốn kém để thực hiện (bằng một thước đo, có thể là một trong hai đồng hồ CPU, sử dụng tài nguyên, vv) ngay cả sau khi tối ưu hóa trình biên dịch.
bolov

Đối với tôi, dường như việc xây dựng mặc định sẽ đắt nhưng lại rẻ.
plugwash

6
@plugwash Hãy xem xét một lớp có một mảng được phân bổ rất lớn. Hàm tạo mặc định cấp phát và khởi tạo mảng, điều này rất tốn kém. Phương thức khởi tạo move (not copy!) Chỉ có thể hoán đổi con trỏ với đối tượng nguồn và không cần cấp phát hoặc khởi tạo mảng lớn.
TrentP

1
Miễn là các phần đơn giản, tôi chắc chắn sẽ thích sử dụng ?:toán tử hơn là giới thiệu một chức năng mới. Rốt cuộc, bạn sẽ không chỉ truyền điều kiện cho hàm mà còn cả một số đối số của hàm tạo. Một số trong số đó thậm chí có thể không được sử dụng create()tùy thuộc vào tình trạng bệnh.
cmaster - Khôi phục monica

12

Trong ngôn ngữ hợp ngữ giả,

    li    #0, r0
    test  r1
    beq   L1
    li    #1, r0
L1:

có thể có hoặc không nhanh hơn nhanh hơn

    test  r1
    beq   L1
    li    #1, r0
    bra   L2
L1:
    li    #0, r0
L2:

tùy thuộc vào mức độ phức tạp của CPU thực tế. Đi từ đơn giản nhất đến huyền ảo nhất:

  • Với bất kỳ CPU nào được sản xuất sau khoảng năm 1990, hiệu suất tốt phụ thuộc vào mã phù hợp trong bộ đệm lệnh . Do đó, khi nghi ngờ, hãy giảm thiểu kích thước mã. Điều này có lợi cho ví dụ đầu tiên.

  • Với một CPU " theo thứ tự, đường ống năm giai đoạn ", vẫn là thứ gần như bạn nhận được trong nhiều bộ vi điều khiển, sẽ có bong bóng đường ống mỗi khi một nhánh — có điều kiện hoặc không điều kiện — được sử dụng, vì vậy điều quan trọng là phải giảm số lượng lệnh rẽ nhánh. Điều này cũng có lợi cho ví dụ đầu tiên.

  • Các CPU phức tạp hơn một chút — đủ ưa thích để thực hiện " thực thi không theo thứ tự ", nhưng không đủ ưa thích để sử dụng các triển khai được biết đến nhiều nhất của khái niệm đó — có thể tạo ra bong bóng đường ống bất cứ khi nào chúng gặp phải các nguy cơ ghi sau ghi . Điều này có lợi cho ví dụ thứ hai , nơi r0chỉ được viết một lần bất kể điều gì. Các CPU này thường đủ ưa thích để xử lý các nhánh vô điều kiện trong trình tìm nạp lệnh, vì vậy bạn không chỉ giao dịch hình phạt ghi sau khi ghi cho hình phạt nhánh.

    Không biết có ai còn làm loại CPU này không nữa. Tuy nhiên, các CPU mà làm sử dụng "hiện thực nổi tiếng nhất" của thi out-of-trật tự là khả năng góc cắt trên các hướng dẫn ít thường xuyên được sử dụng, vì vậy bạn cần phải nhận thức được rằng việc này có thể xảy ra. Một ví dụ thực tế là sự phụ thuộc dữ liệu sai vào các đăng ký đích trong popcntlzcnttrên các CPU Sandy Bridge .

  • Ở đầu cuối cao nhất, công cụ OOO sẽ kết thúc việc phát hành chính xác cùng một chuỗi hoạt động nội bộ cho cả hai đoạn mã — đây là phiên bản phần cứng của "đừng lo lắng về điều đó, trình biên dịch sẽ tạo ra cùng một mã máy." Tuy nhiên, kích thước mã vẫn quan trọng và bây giờ bạn cũng nên lo lắng về khả năng dự đoán của nhánh có điều kiện. Các lỗi dự đoán nhánh có khả năng gây ra hiện tượng xả toàn bộ đường ống , gây ra thảm họa cho hiệu suất; xem Tại sao xử lý mảng được sắp xếp nhanh hơn mảng chưa được sắp xếp? để hiểu điều này có thể tạo ra sự khác biệt như thế nào.

    Nếu chi nhánh rất khó lường, và CPU của bạn có điều kiện-thiết lập hoặc điều kiện-di chuyển hướng dẫn, đây là thời gian để sử dụng chúng:

        li    #0, r0
        test  r1
        setne r0
    

    hoặc là

        li    #0, r0
        li    #1, r2
        test  r1
        movne r2, r0
    

    Phiên bản thiết lập có điều kiện cũng nhỏ gọn hơn bất kỳ phiên bản thay thế nào khác; nếu hướng dẫn đó có sẵn, nó thực tế được đảm bảo là Điều đúng đắn cho tình huống này, ngay cả khi nhánh có thể đoán trước được. Phiên bản di chuyển có điều kiện yêu cầu một thanh ghi cào bổ sung và luôn lãng phí litài nguyên giá trị gửi và thực thi của một lệnh; nếu thực tế nhánh có thể dự đoán được, thì phiên bản nhánh có thể nhanh hơn.


Tôi muốn nói lại điểm thứ hai của bạn về việc liệu CPU có một công cụ không theo thứ tự bị trì hoãn bởi các nguy cơ ghi sau khi ghi hay không. Nếu một CPU có một động cơ không theo thứ tự có thể xử lý các mối nguy hiểm như vậy mà không bị chậm trễ thì không có vấn đề gì, nhưng cũng không có vấn đề gì nếu CPU không có động cơ không theo thứ tự nào cả .
supercat

@supercat Đoạn văn ở cuối nhằm đề cập đến trường hợp đó nhưng tôi sẽ suy nghĩ về cách làm cho nó rõ ràng hơn.
zwol

Tôi không biết CPU hiện tại nào có bộ nhớ cache khiến mã được thực thi tuần tự chạy nhanh hơn từ lần thứ hai đến lần đầu tiên (một số bộ phận ARM dựa trên flash có giao diện có thể đệm một vài hàng dữ liệu flash, nhưng có thể tìm nạp mã một cách tuần tự nhanh như khi chúng thực thi nó, nhưng chìa khóa để làm cho mã nhánh chạy nhanh trên những mã đó là sao chép nó vào RAM). Các CPU không có bất kỳ thực thi không theo thứ tự nào phổ biến hơn nhiều so với những CPU sẽ bị trì hoãn bởi các nguy cơ ghi sau khi ghi.
supercat

Điều này rất sâu sắc
Julien__

9

Trong mã chưa được kích thích, ví dụ đầu tiên chỉ định một biến luôn luôn một lần và đôi khi hai lần. Ví dụ thứ hai chỉ gán một biến một lần. Điều kiện giống nhau trên cả hai đường dẫn mã, vì vậy điều đó không quan trọng. Trong mã được tối ưu hóa, nó phụ thuộc vào trình biên dịch.

Như thường lệ, nếu bạn lo lắng, hãy tạo assembly và xem trình biên dịch thực sự đang làm gì.


1
Nếu lo ngại về hiệu suất thì họ sẽ không biên dịch không được tối ưu hóa. Nhưng chắc chắn trình tối ưu hóa "tốt" như thế nào phụ thuộc vào trình biên dịch / phiên bản.
old_timer

AFAIK không có bình luận nào về kiến ​​trúc trình biên dịch / CPU nào, v.v., vì vậy có khả năng, trình biên dịch của họ không tối ưu hóa. Họ có thể đang biên dịch trên bất kỳ thứ gì từ PIC 8-bit đến Xeon 64-bit.
Neil

8

Điều gì sẽ khiến bạn nghĩ rằng bất kỳ cái nào trong số chúng ngay cả một lớp lót nhanh hơn hay chậm hơn?

unsigned int fun0 ( unsigned int condition, unsigned int value )
{
    value = 5;
    if (condition) {
        value = 6;
    }
    return(value);
}
unsigned int fun1 ( unsigned int condition, unsigned int value )
{

    if (condition) {
        value = 6;
    } else {
        value = 5;
    }
    return(value);
}
unsigned int fun2 ( unsigned int condition, unsigned int value )
{
    value = condition ? 6 : 5;
    return(value);
}

Nhiều dòng mã của ngôn ngữ cấp cao hơn giúp trình biên dịch làm việc nhiều hơn, vì vậy nếu bạn muốn đưa ra quy tắc chung về nó, hãy cung cấp cho trình biên dịch nhiều mã hơn để làm việc. Nếu thuật toán giống như các trường hợp trên thì người ta sẽ mong đợi trình biên dịch với tối ưu hóa tối thiểu để tìm ra điều đó.

00000000 <fun0>:
   0:   e3500000    cmp r0, #0
   4:   03a00005    moveq   r0, #5
   8:   13a00006    movne   r0, #6
   c:   e12fff1e    bx  lr

00000010 <fun1>:
  10:   e3500000    cmp r0, #0
  14:   13a00006    movne   r0, #6
  18:   03a00005    moveq   r0, #5
  1c:   e12fff1e    bx  lr

00000020 <fun2>:
  20:   e3500000    cmp r0, #0
  24:   13a00006    movne   r0, #6
  28:   03a00005    moveq   r0, #5
  2c:   e12fff1e    bx  lr

Tuy nhiên, không có gì ngạc nhiên khi nó thực hiện chức năng đầu tiên theo một thứ tự khác, cùng thời gian thực hiện.

0000000000000000 <fun0>:
   0:   7100001f    cmp w0, #0x0
   4:   1a9f07e0    cset    w0, ne
   8:   11001400    add w0, w0, #0x5
   c:   d65f03c0    ret

0000000000000010 <fun1>:
  10:   7100001f    cmp w0, #0x0
  14:   1a9f07e0    cset    w0, ne
  18:   11001400    add w0, w0, #0x5
  1c:   d65f03c0    ret

0000000000000020 <fun2>:
  20:   7100001f    cmp w0, #0x0
  24:   1a9f07e0    cset    w0, ne
  28:   11001400    add w0, w0, #0x5
  2c:   d65f03c0    ret

Hy vọng rằng bạn có được ý tưởng mà bạn có thể vừa thử điều này nếu không rõ ràng rằng các triển khai khác nhau không thực sự khác nhau.

Theo một ma trận, không chắc điều đó quan trọng như thế nào,

if(condition)
{
 big blob of code a
}
else
{
 big blob of code b
}

chỉ cần đặt cùng một trình bao bọc if-then-else xung quanh các đốm mã lớn nếu chúng có giá trị = 5 hoặc một cái gì đó phức tạp hơn. Tương tự như vậy, phép so sánh ngay cả khi nó là một đoạn mã lớn thì nó vẫn phải được tính toán, và bằng hoặc không bằng một cái gì đó thường được biên dịch với giá trị phủ định, nếu (điều kiện) làm điều gì đó thường được biên dịch như thể không điều kiện goto.

00000000 <fun0>:
   0:   0f 93           tst r15     
   2:   03 24           jz  $+8         ;abs 0xa
   4:   3f 40 06 00     mov #6, r15 ;#0x0006
   8:   30 41           ret         
   a:   3f 40 05 00     mov #5, r15 ;#0x0005
   e:   30 41           ret         

00000010 <fun1>:
  10:   0f 93           tst r15     
  12:   03 20           jnz $+8         ;abs 0x1a
  14:   3f 40 05 00     mov #5, r15 ;#0x0005
  18:   30 41           ret         
  1a:   3f 40 06 00     mov #6, r15 ;#0x0006
  1e:   30 41           ret         

00000020 <fun2>:
  20:   0f 93           tst r15     
  22:   03 20           jnz $+8         ;abs 0x2a
  24:   3f 40 05 00     mov #5, r15 ;#0x0005
  28:   30 41           ret         
  2a:   3f 40 06 00     mov #6, r15 ;#0x0006
  2e:   30 41

chúng tôi vừa mới xem qua bài tập này với một người khác gần đây trên stackoverflow. Điều thú vị là trình biên dịch mips này trong trường hợp đó không chỉ nhận ra các chức năng giống nhau, mà còn có một chức năng chỉ cần chuyển sang chức năng kia để tiết kiệm không gian mã. Không làm điều đó ở đây mặc dù

00000000 <fun0>:
   0:   0004102b    sltu    $2,$0,$4
   4:   03e00008    jr  $31
   8:   24420005    addiu   $2,$2,5

0000000c <fun1>:
   c:   0004102b    sltu    $2,$0,$4
  10:   03e00008    jr  $31
  14:   24420005    addiu   $2,$2,5

00000018 <fun2>:
  18:   0004102b    sltu    $2,$0,$4
  1c:   03e00008    jr  $31
  20:   24420005    addiu   $2,$2,5

một số mục tiêu khác.

00000000 <_fun0>:
   0:   1166            mov r5, -(sp)
   2:   1185            mov sp, r5
   4:   0bf5 0004       tst 4(r5)
   8:   0304            beq 12 <_fun0+0x12>
   a:   15c0 0006       mov $6, r0
   e:   1585            mov (sp)+, r5
  10:   0087            rts pc
  12:   15c0 0005       mov $5, r0
  16:   1585            mov (sp)+, r5
  18:   0087            rts pc

0000001a <_fun1>:
  1a:   1166            mov r5, -(sp)
  1c:   1185            mov sp, r5
  1e:   0bf5 0004       tst 4(r5)
  22:   0204            bne 2c <_fun1+0x12>
  24:   15c0 0005       mov $5, r0
  28:   1585            mov (sp)+, r5
  2a:   0087            rts pc
  2c:   15c0 0006       mov $6, r0
  30:   1585            mov (sp)+, r5
  32:   0087            rts pc

00000034 <_fun2>:
  34:   1166            mov r5, -(sp)
  36:   1185            mov sp, r5
  38:   0bf5 0004       tst 4(r5)
  3c:   0204            bne 46 <_fun2+0x12>
  3e:   15c0 0005       mov $5, r0
  42:   1585            mov (sp)+, r5
  44:   0087            rts pc
  46:   15c0 0006       mov $6, r0
  4a:   1585            mov (sp)+, r5
  4c:   0087            rts pc

00000000 <fun0>:
   0:   00a03533            snez    x10,x10
   4:   0515                    addi    x10,x10,5
   6:   8082                    ret

00000008 <fun1>:
   8:   00a03533            snez    x10,x10
   c:   0515                    addi    x10,x10,5
   e:   8082                    ret

00000010 <fun2>:
  10:   00a03533            snez    x10,x10
  14:   0515                    addi    x10,x10,5
  16:   8082                    ret

và trình biên dịch

với mã này tôi cũng mong đợi các mục tiêu khác nhau phù hợp

define i32 @fun0(i32 %condition, i32 %value) #0 {
  %1 = icmp ne i32 %condition, 0
  %. = select i1 %1, i32 6, i32 5
  ret i32 %.
}

; Function Attrs: norecurse nounwind readnone
define i32 @fun1(i32 %condition, i32 %value) #0 {
  %1 = icmp eq i32 %condition, 0
  %. = select i1 %1, i32 5, i32 6
  ret i32 %.
}

; Function Attrs: norecurse nounwind readnone
define i32 @fun2(i32 %condition, i32 %value) #0 {
  %1 = icmp ne i32 %condition, 0
  %2 = select i1 %1, i32 6, i32 5
  ret i32 %2
}


00000000 <fun0>:
   0:   e3a01005    mov r1, #5
   4:   e3500000    cmp r0, #0
   8:   13a01006    movne   r1, #6
   c:   e1a00001    mov r0, r1
  10:   e12fff1e    bx  lr

00000014 <fun1>:
  14:   e3a01006    mov r1, #6
  18:   e3500000    cmp r0, #0
  1c:   03a01005    moveq   r1, #5
  20:   e1a00001    mov r0, r1
  24:   e12fff1e    bx  lr

00000028 <fun2>:
  28:   e3a01005    mov r1, #5
  2c:   e3500000    cmp r0, #0
  30:   13a01006    movne   r1, #6
  34:   e1a00001    mov r0, r1
  38:   e12fff1e    bx  lr


fun0:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #6, r15
    cmp.w   #0, r12
    jne .LBB0_2
    mov.w   #5, r15
.LBB0_2:
    pop.w   r4
    ret

fun1:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #5, r15
    cmp.w   #0, r12
    jeq .LBB1_2
    mov.w   #6, r15
.LBB1_2:
    pop.w   r4
    ret


fun2:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #6, r15
    cmp.w   #0, r12
    jne .LBB2_2
    mov.w   #5, r15
.LBB2_2:
    pop.w   r4
    ret

Bây giờ về mặt kỹ thuật có sự khác biệt về hiệu suất trong một số giải pháp này, đôi khi kết quả là 5 trường hợp có một bước nhảy qua kết quả là 6 mã, và ngược lại, một nhánh có nhanh hơn thực thi thông qua không? người ta có thể tranh luận nhưng cách thực hiện sẽ khác nhau. Nhưng đó là điều kiện if so với if not trong mã dẫn đến trình biên dịch thực hiện if this jump over else thực thi thông qua. nhưng điều này không nhất thiết là do phong cách mã hóa mà là do so sánh và các trường hợp if và else trong bất kỳ cú pháp nào.


0

Được rồi, vì assembly là một trong các thẻ, tôi sẽ chỉ giả sử mã của bạn là mã giả (và không nhất thiết là c) và được con người dịch nó thành hợp ngữ 6502.

Lựa chọn đầu tiên (không có khác)

        ldy #$00
        lda #$05
        dey
        bmi false
        lda #$06
false   brk

Lựa chọn thứ 2 (với cái khác)

        ldy #$00
        dey
        bmi else
        lda #$06
        sec
        bcs end
else    lda #$05
end     brk

Giả định: Điều kiện nằm trong thanh ghi Y, đặt giá trị này thành 0 hoặc 1 trên dòng đầu tiên của một trong hai tùy chọn, kết quả sẽ nằm trong bộ tích lũy.

Vì vậy, sau khi đếm chu kỳ cho cả hai khả năng của mỗi trường hợp, chúng ta thấy rằng cấu trúc thứ nhất nói chung nhanh hơn; 9 chu kỳ khi điều kiện là 0 và 10 chu kỳ khi điều kiện là 1, trong khi phương án hai cũng là 9 chu kỳ khi điều kiện là 0, nhưng 13 chu kỳ khi điều kiện là 1. ( số chu kỳ không bao gồm BRKphần cuối ).

Kết luận: If onlynhanh hơnIf-Else cấu tạo.

Và để hoàn thiện, đây là một value = condition + 5giải pháp tối ưu hóa :

ldy #$00
lda #$00
tya
adc #$05
brk

Điều này làm giảm thời gian của chúng tôi xuống còn 8 chu kỳ ( một lần nữa không bao gồm BRKở cuối ).


6
Thật không may cho câu trả lời này, việc đưa cùng một mã nguồn vào trình biên dịch C (hoặc vào trình biên dịch C ++) tạo ra kết quả đầu ra rất khác so với việc đưa nó vào não của Glen. Không có sự khác biệt, không có tiềm năng "tối ưu hóa", giữa bất kỳ lựa chọn thay thế nào ở cấp mã nguồn. Chỉ cần sử dụng cái dễ đọc nhất (có lẽ là if / else).
Quuxplusone

1
@Vâng. Trình biên dịch có khả năng tối ưu hóa cả hai biến thể thành phiên bản nhanh nhất hoặc có khả năng thêm chi phí bổ sung vượt xa sự khác biệt giữa hai biến thể. Hoặc cả hai.
jpaugh

1
Giả sử rằng " không nhất thiết là C " có vẻ là một lựa chọn hợp lý, vì câu hỏi được gắn thẻ là C ++ (thật không may, nó không quản lý để khai báo các loại biến liên quan).
Toby Speight
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.