Cái nào nhanh hơn: if (bool) hoặc if (int)?


94

Giá trị nào tốt hơn để sử dụng? Boolean true hay Integer 1?

Chủ đề trên đã khiến tôi phải làm một số thí nghiệm với boolinttrong ifđiều kiện. Vì vậy, chỉ vì tò mò, tôi đã viết chương trình này:

int f(int i) 
{
    if ( i ) return 99;   //if(int)
    else  return -99;
}
int g(bool b)
{
    if ( b ) return 99;   //if(bool)
    else  return -99;
}
int main(){}

g++ intbool.cpp -S tạo mã asm cho từng chức năng như sau:

  • mã asm cho f(int)

    __Z1fi:
       LFB0:
             pushl  %ebp
       LCFI0:
              movl  %esp, %ebp
       LCFI1:
              cmpl  $0, 8(%ebp)
              je    L2
              movl  $99, %eax
              jmp   L3
       L2:
              movl  $-99, %eax
       L3:
              leave
       LCFI2:
              ret
  • mã asm cho g(bool)

    __Z1gb:
       LFB1:
              pushl %ebp
       LCFI3:
              movl  %esp, %ebp
       LCFI4:
              subl  $4, %esp
       LCFI5:
              movl  8(%ebp), %eax
              movb  %al, -4(%ebp)
              cmpb  $0, -4(%ebp)
              je    L5
              movl  $99, %eax
              jmp   L6
       L5:
              movl  $-99, %eax
       L6:
              leave
       LCFI6:
              ret

Đáng ngạc nhiên, g(bool)tạo ra nhiều asmhướng dẫn hơn ! Nó có nghĩa if(bool)là chậm hơn một chút if(int)? Tôi đã từng nghĩ rằng boolnó được thiết kế đặc biệt để được sử dụng trong câu lệnh có điều kiện chẳng hạn như if, vì vậy tôi đã mong đợi g(bool)tạo ra các lệnh asm ít hơn, do đó làm cho g(bool)hiệu quả hơn và nhanh hơn.

BIÊN TẬP:

Tôi hiện không sử dụng bất kỳ cờ tối ưu hóa nào. Nhưng ngay cả khi không có nó, tại sao nó tạo ra nhiều asm hơn g(bool)là một câu hỏi mà tôi đang tìm kiếm một câu trả lời hợp lý. Tôi cũng nên nói với bạn rằng -O2cờ tối ưu hóa tạo ra giống hệt như asm. Nhưng đó không phải là câu hỏi. Câu hỏi là những gì tôi đã hỏi.



32
Đây cũng là một thử nghiệm không công bằng trừ khi bạn so sánh chúng với các tính năng tối ưu hóa hợp lý được bật.
Daniel Pryden

9
@Daniel: Tôi không sử dụng bất kỳ cờ tối ưu hóa nào với cả hai. Nhưng ngay cả khi không có nó, tại sao nó tạo ra nhiều asm hơn g(bool)là một câu hỏi mà tôi đang tìm kiếm một câu trả lời hợp lý.
Nawaz

8
Tại sao bạn lại gặp khó khăn khi đọc asm, nhưng không chỉ chạy chương trình và định thời gian cho kết quả ? Số lượng hướng dẫn không thực sự nói nhiều về hiệu suất. Bạn cần phải tính đến không chỉ độ dài của lệnh mà còn cả sự phụ thuộc và loại lệnh (một số trong số chúng được giải mã bằng đường dẫn vi mã chậm hơn, đơn vị thực thi mà chúng yêu cầu, độ trễ và thông lượng của lệnh là gì, có phải chi nhánh? Truy cập bộ nhớ?
jalf

2
@ người dùng không xác định và @Malvolio: Đó là rõ ràng; Tôi không làm tất cả những điều này cho mã sản xuất. Như tôi đã đề cập ở phần đầu của bài viết rằng "Vì vậy, chỉ vì tò mò, tôi đã viết chương trình này" . Vì vậy, nó hoàn toàn là giả thuyết .
Nawaz

3
Đó là một câu hỏi chính đáng. Chúng tương đương hoặc nhanh hơn. ASM có thể được đăng nhằm mục đích hữu ích hoặc suy nghĩ thấu đáo, vì vậy thay vì sử dụng nó như một cách để né tránh câu hỏi và nói "chỉ viết mã có thể đọc được", chỉ cần trả lời câu hỏi hoặc STFU nếu bạn không biết hoặc không có bất cứ điều gì hữu ích để nói;) Đóng góp của tôi là câu hỏi có thể trả lời được, và "chỉ cần viết mã có thể đọc được" không có gì khác ngoài việc né tránh câu hỏi.
Triynko

Câu trả lời:


99

Có nghĩa với tôi. Trình biên dịch của bạn rõ ràng định nghĩa một boolgiá trị 8-bit và hệ thống ABI của bạn yêu cầu nó "xúc tiến" các đối số số nguyên nhỏ (<32-bit) thành 32-bit khi đẩy chúng vào ngăn xếp cuộc gọi. Vì vậy, để so sánh a bool, trình biên dịch tạo mã để tách byte ít quan trọng nhất của đối số 32 bit mà g nhận được và so sánh với nó cmpb. Trong ví dụ đầu tiên, intđối số sử dụng 32 bit đầy đủ đã được đẩy lên ngăn xếp, vì vậy nó chỉ đơn giản so sánh với toàn bộ điều với cmpl.


4
Tôi đồng ý. Điều này giúp làm sáng tỏ rằng khi chọn một loại biến, bạn đang chọn nó cho hai mục đích có khả năng cạnh tranh, không gian lưu trữ so với hiệu suất tính toán.
Triynko

3
Điều này cũng áp dụng cho các quy trình 64-bit, __int64nhanh hơn int? Hay CPU giao dịch số nguyên 32 bit với các tập lệnh 32 bit riêng biệt?
Crend King

1
@CrendKing có lẽ bạn nên đặt một câu hỏi khác?
Tên hiển thị

81

Biên dịch với -03mang lại cho tôi những điều sau đây:

f:

    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

g:

    pushl   %ebp
    movl    %esp, %ebp
    cmpb    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

.. vì vậy nó biên dịch để về cơ bản cùng mã, trừ cmplvs cmpb. Điều này có nghĩa là sự khác biệt, nếu có, không quan trọng. Đánh giá bằng mã chưa được tối ưu hóa là không công bằng.


Chỉnh sửa để làm rõ quan điểm của tôi. Mã không được tối ưu hóa là để gỡ lỗi đơn giản, không phải để tăng tốc độ. So sánh tốc độ của mã chưa được tối ưu hóa là vô nghĩa.


8
Nhiều khi tôi đồng ý với kết luận của bạn, tôi nghĩ bạn đang bỏ qua phần thú vị. Tại sao nó sử dụng cmplcho cái này và cmpbcho cái kia?
jalf

22
@jalf: Bởi vì a boollà một byte đơn và an intlà bốn. Tôi không nghĩ có điều gì đặc biệt hơn thế.
CB Bailey

7
Tôi nghĩ các phản hồi khác chú ý nhiều hơn đến lý do: đó là vì nền tảng được đề cập được coi boollà loại 8 bit.
Alexander Gessler

9
@Nathan: Không. C ++ không có kiểu dữ liệu bit. Loại nhỏ nhất là charbyte theo định nghĩa và là đơn vị nhỏ nhất có thể định địa chỉ. boolKích thước của được thực thi xác định và có thể là 1, 4 hoặc 8, hoặc bất cứ điều gì. Tuy nhiên, các trình biên dịch có xu hướng biến nó thành một.
GManNickG

6
@Nathan: Cũng khó trong Java. Java cho biết dữ liệu mà boolean đại diện là giá trị của một bit, nhưng cách bit đó được lưu trữ như thế nào vẫn là việc thực thi được xác định. Máy tính thực dụng đơn giản là không giải quyết các bit.
GManNickG

26

Khi tôi biên dịch điều này với một bộ tùy chọn lành mạnh (cụ thể là -O3), đây là những gì tôi nhận được:

Đối với f():

        .type   _Z1fi, @function
_Z1fi:
.LFB0:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        cmpl    $1, %edi
        sbbl    %eax, %eax
        andb    $58, %al
        addl    $99, %eax
        ret
        .cfi_endproc

Đối với g():

        .type   _Z1gb, @function
_Z1gb:
.LFB1:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        cmpb    $1, %dil
        sbbl    %eax, %eax
        andb    $58, %al
        addl    $99, %eax
        ret
        .cfi_endproc

Họ vẫn sử dụng các hướng dẫn khác nhau để so sánh ( cmpbcho boolean so cmplvới int), nhưng nếu không thì các phần thân giống hệt nhau. Xem nhanh các hướng dẫn sử dụng của Intel cho tôi biết: ... không có gì nhiều. Không có điều gì như cmpbhoặc cmpltrong sách hướng dẫn của Intel. Tất cả đều có cmpvà tôi không thể tìm thấy bảng thời gian vào lúc này. Tuy nhiên, tôi đoán rằng không có sự khác biệt về đồng hồ giữa việc so sánh một byte ngay lập tức với so sánh một byte ngay lập tức, vì vậy đối với tất cả các mục đích thực tế, mã là giống hệt nhau.


đã chỉnh sửa để thêm phần sau dựa trên sự bổ sung của bạn

Lý do mã khác trong trường hợp không được tối ưu hóa là nó chưa được tối ưu hóa. (Vâng, đó là hình tròn, tôi biết.) Khi trình biên dịch xem AST và tạo mã trực tiếp, nó không "biết" bất cứ điều gì ngoại trừ những gì ở điểm ngay lập tức của AST. Tại thời điểm đó, nó thiếu tất cả thông tin ngữ cảnh cần thiết để biết rằng tại thời điểm cụ thể này, nó có thể coi kiểu được khai báo boolint. Một boolean rõ ràng là theo mặc định được coi là một byte và khi thao tác với byte trong thế giới Intel, bạn phải làm những việc như mở rộng ký hiệu để đưa nó đến độ rộng nhất định để đưa nó vào ngăn xếp, v.v. (Bạn không thể đẩy một byte .)

Tuy nhiên, khi trình tối ưu hóa xem AST và thực hiện phép thuật của nó, nó sẽ nhìn vào ngữ cảnh xung quanh và "biết" khi nào nó có thể thay thế mã bằng thứ gì đó hiệu quả hơn mà không thay đổi ngữ nghĩa. Vì vậy, nó "biết" nó có thể sử dụng một số nguyên trong tham số và do đó mất các chuyển đổi và mở rộng không cần thiết.


1
Haha, tôi thích cách trình biên dịch trả về 99, hoặc 99 + 58 = 157 = -99 (tràn 8bit có dấu) ... thú vị.
deceleratedcaviar

@Daniel: Ngay cả tôi cũng thích điều đó. Lúc đầu, tôi nói "-99 ở đâu" và ngay lập tức tôi nhận ra rằng nó đang làm một điều gì đó rất kỳ quặc.
Nawaz

7
lblà hậu tố chỉ được sử dụng trong cú pháp AT&T. Chúng chỉ đề cập đến các phiên bản cmpsử dụng toán hạng 4 byte (dài) và 1 byte (byte) tương ứng. Trong trường hợp có bất kỳ sự mơ hồ trong cú pháp intel, thông thường các toán hạng bộ nhớ được gắn thẻ với BYTE PTR, WORD PTRhoặc DWORD PTRthay vì đưa một hậu tố trên opcode.
CB Bailey

Bảng thời gian: agner.org/optimize Cả hai kích thước toán hạng của cmpđều có cùng hiệu suất và không có hình phạt đăng ký từng phần khi đọc %dil . (Nhưng điều đó không ngăn được tiếng kêu một cách thú vị khi tạo một gian hàng đăng ký một phần bằng cách sử dụng kích thước byte andtrên AL như một phần của phân nhánh không phân biệt chữ hoa chữ thường giữa 99 và -99.)
Peter Cordes

13

Với GCC 4.5 trên Linux và Windows ít nhất sizeof(bool) == 1,. Trên x86 và x86_64, bạn không thể truyền ít hơn giá trị của một thanh ghi mục đích chung cho một hàm (cho dù thông qua ngăn xếp hay thanh ghi tùy thuộc vào quy ước gọi, v.v.).

Vì vậy, mã cho bool, khi chưa được tối ưu hóa, thực sự đi đến một độ dài nào đó để trích xuất giá trị bool đó từ ngăn xếp đối số (sử dụng một khe ngăn xếp khác để lưu byte đó). Nó phức tạp hơn là chỉ kéo một biến có kích thước đăng ký gốc.


Theo tiêu chuẩn C ++ 03, §5.3.3 / 1: " sizeof(bool)sizeof(wchar_t)được xác định bằng cách triển khai. " Vì vậy, nói như vậy sizeof(bool) == 1là không hoàn toàn chính xác trừ khi bạn đang nói về một phiên bản cụ thể của một trình biên dịch cụ thể.
ildjarn

9

Ở cấp độ máy không có cái gọi là bool

Rất ít kiến ​​trúc tập lệnh xác định bất kỳ loại toán hạng boolean nào, mặc dù thường có các lệnh kích hoạt một hành động trên các giá trị khác không. Đối với CPU, thông thường, mọi thứ đều là một trong những kiểu vô hướng hoặc một chuỗi của chúng.

Một trình biên dịch nhất định và một ABI nhất định sẽ cần chọn các kích thước cụ thể cho intboolkhi nào, giống như trong trường hợp của bạn, đây là các kích thước khác nhau, chúng có thể tạo ra mã hơi khác nhau và ở một số cấp độ tối ưu hóa, chúng có thể nhanh hơn một chút.

Tại sao bool là một byte trên nhiều hệ thống?

Sẽ an toàn hơn khi chọn một charloại cho bool vì ai đó có thể tạo ra một mảng thực sự lớn trong số chúng.

Cập nhật: bởi "an toàn hơn", ý tôi là: dành cho trình biên dịch và trình triển khai thư viện. Tôi không nói rằng mọi người cần phải thực hiện lại loại hệ thống.


2
+1 Hãy tưởng tượng chi phí trên x86 nếu boolđược biểu diễn bằng các bit; vì vậy byte sẽ là một sự cân bằng tốt đẹp cho tốc độ / dữ liệu nhỏ gọn trong nhiều triển khai.
hardmath

1
@Billy: Tôi nghĩ anh ấy không nói "sử dụng charthay vì bool" mà chỉ đơn giản là sử dụng " charloại" có nghĩa là "1 byte" khi đề cập đến kích thước mà trình biên dịch chọn cho boolcác đối tượng.
Dennis Zickefoose

Ồ, chắc chắn, ý tôi không phải là mỗi chương trình nên chọn, tôi chỉ đang nâng cao lý do tại sao kiểu bool hệ thống là 1 byte.
DigitalRoss

@Dennis: Ah, có lý.
Billy ONeal

7

Vâng, cuộc thảo luận rất vui. Nhưng chỉ cần kiểm tra nó:

Mã kiểm tra:

#include <stdio.h>
#include <string.h>

int testi(int);
int testb(bool);
int main (int argc, char* argv[]){
  bool valb;
  int  vali;
  int loops;
  if( argc < 2 ){
    return 2;
  }
  valb = (0 != (strcmp(argv[1], "0")));
  vali = strcmp(argv[1], "0");
  printf("Arg1: %s\n", argv[1]);
  printf("BArg1: %i\n", valb ? 1 : 0);
  printf("IArg1: %i\n", vali);
  for(loops=30000000; loops>0; loops--){
    //printf("%i: %i\n", loops, testb(valb=!valb));
    printf("%i: %i\n", loops, testi(vali=!vali));
  }
  return valb;
}

int testi(int val){
  if( val ){
    return 1;
  }
  return 0;
}
int testb(bool val){
  if( val ){
    return 1;
  }
  return 0;
}

Được biên dịch trên máy tính xách tay 64-bit Ubuntu 10.10 với: g ++ -O3 -o / tmp / test_i /tmp/test_i.cpp

So sánh dựa trên số nguyên:

sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.203s
user    0m8.170s
sys 0m0.010s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.056s
user    0m8.020s
sys 0m0.000s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.116s
user    0m8.100s
sys 0m0.000s

Kiểm tra boolean / in không ghi chú (và số nguyên nhận xét):

sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.254s
user    0m8.240s
sys 0m0.000s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.028s
user    0m8.000s
sys 0m0.010s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m7.981s
user    0m7.900s
sys 0m0.050s

Chúng giống nhau với 1 bài tập và 2 phép so sánh mỗi vòng lặp trên 30 triệu vòng lặp. Tìm thứ gì đó khác để tối ưu hóa. Ví dụ, không sử dụng strcmp một cách không cần thiết. ;)



0

Tiếp cận câu hỏi của bạn theo hai cách khác nhau:

Nếu bạn đang nói cụ thể về C ++ hoặc bất kỳ ngôn ngữ lập trình nào sẽ tạo ra mã lắp ráp cho vấn đề đó, chúng tôi bị ràng buộc với mã trình biên dịch sẽ tạo ra trong ASM. Chúng tôi cũng bị ràng buộc với việc biểu diễn true và false trong c ++. Một số nguyên sẽ phải được lưu trữ trong 32 bit và tôi có thể chỉ cần sử dụng một byte để lưu trữ biểu thức boolean. Đoạn mã Asm cho các câu lệnh có điều kiện:

Đối với số nguyên:

  mov eax,dword ptr[esp]    ;Store integer
  cmp eax,0                 ;Compare to 0
  je  false                 ;If int is 0, its false
  ;Do what has to be done when true
false:
  ;Do what has to be done when false

Đối với bool:

  mov  al,1     ;Anything that is not 0 is true
  test al,1     ;See if first bit is fliped
  jz   false    ;Not fliped, so it's false
  ;Do what has to be done when true
false:
  ;Do what has to be done when false

Vì vậy, đó là lý do tại sao so sánh tốc độ rất phụ thuộc vào biên dịch. Trong trường hợp trên, bool sẽ hơi nhanh vì cmpnó ngụ ý một phép trừ khi đặt cờ. Nó cũng mâu thuẫn với những gì trình biên dịch của bạn tạo ra.

Một cách tiếp cận khác, đơn giản hơn nhiều, là tự mình xem xét logic của biểu thức và cố gắng không lo lắng về cách trình biên dịch sẽ dịch mã của bạn, và tôi nghĩ đây là một cách suy nghĩ lành mạnh hơn nhiều. Cuối cùng thì tôi vẫn tin rằng mã được tạo bởi trình biên dịch thực sự đang cố gắng đưa ra một giải pháp trung thực. Ý tôi là, có thể nếu bạn tăng các trường hợp kiểm tra trong câu lệnh if và gắn với boolean ở một phía và số nguyên ở một phía khác, trình biên dịch sẽ tạo ra nó để mã được tạo sẽ thực thi nhanh hơn với các biểu thức boolean ở cấp máy.

Tôi đang coi đây là một câu hỏi khái niệm, vì vậy tôi sẽ đưa ra một câu trả lời khái niệm. Cuộc thảo luận này nhắc nhở tôi về những cuộc thảo luận mà tôi thường có về việc liệu hiệu quả mã có chuyển thành ít dòng mã hơn trong lắp ráp hay không. Có vẻ như khái niệm này thường được chấp nhận là đúng. Xem xét rằng việc theo dõi tốc độ ALU sẽ xử lý từng câu lệnh là không khả thi, tùy chọn thứ hai sẽ là tập trung vào các bước nhảy và so sánh trong lắp ráp. Trong trường hợp đó, sự phân biệt giữa các câu lệnh boolean hoặc các số nguyên trong mã bạn đã trình bày trở nên tương đối đại diện. Kết quả của một biểu thức trong C ++ sẽ trả về một giá trị mà sau đó sẽ được đưa ra một biểu diễn. Mặt khác, trong quá trình lắp ráp, các bước nhảy và so sánh sẽ dựa trên các giá trị số bất kể kiểu biểu thức nào đang được đánh giá lại trong câu lệnh if của bạn trong C ++. Đối với những câu hỏi này, điều quan trọng là phải nhớ rằng các câu lệnh hoàn toàn logic như những câu này kết thúc với một tổng chi phí tính toán lớn, mặc dù một bit duy nhất cũng có khả năng tương tự.

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.