2012-03-14 29 views
12

Tại sự kiện GoingNative, trong Interactive Panel về Ngày 2, ở phút thứ 9, Chandler Carruth nói:Bí danh là gì và nó ảnh hưởng như thế nào đến hiệu suất?

Pointers tạo ra vấn đề răng cưa. Chúng làm chậm các tệp nhị phân của bạn chứ không làm tăng tốc chúng.

Điều này có nghĩa là gì? Điều này có thể được minh họa bằng một ví dụ (đơn giản) không?

+0

Tại sao bạn không hỏi Chandler Carruth? –

+0

có thể trùng lặp [bí danh nghiêm ngặt] (http://stackoverflow.com/questions/754929/strict-aliasing) –

+0

Thử xem [this] (http://cslibrary.stanford.edu/104/). Nó thực sự khá tốt.Bí danh là khi bạn lấy con trỏ của một objecct thay vì đối tượng mình, như ông giải thích. –

Trả lời

14

Bí danh ảnh hưởng đến hiệu suất bằng cách ngăn trình biên dịch thực hiện tối ưu hóa nhất định. Ví dụ:

void foo(int *array,int *size,int *value) { 
    for(int i=0;i<*size;++i) { 
     array[i] = 2 * *value; 
    } 
} 

Nhìn vào mã này bạn có thể hy vọng rằng trình biên dịch có thể tải *value một lần bên ngoài vòng lặp và sau đó đặt mọi phần tử trong mảng giá trị mà rất nhanh chóng. Nhưng đây không phải là trường hợp do răng cưa. Bởi vì *value có thể là bí danh cho phần tử của mảng mà nó có thể thay đổi trên bất kỳ lần lặp nào đã cho. Do đó, mã phải tải giá trị mỗi lần lặp lại duy nhất, dẫn đến sự chậm lại lớn.

Nếu các biến thể không alias thì đoạn code trên sẽ tương đương như sau:

void foo(int *array,int size,int value) { 
    for(int i=0;i<size;++i) { 
     array[i] = 2 * value; 
    } 
} 

Sử dụng LLVM của online demo để lấy mã được tạo ra, đây là những kết quả khác nhau:

1) với răng cưa

foo:         # @foo 
    .cfi_startproc 
# BB#0: 
    cmpl $0, (%rsi) 
    jle .LBB0_3 
# BB#1: 
    xorl %eax, %eax 
    .align 16, 0x90 
.LBB0_2:        # %.lr.ph 
             # =>This Inner Loop Header: Depth=1 
    movl (%rdx), %ecx 
    addl %ecx, %ecx 
    movl %ecx, (%rdi,%rax,4) 
    incq %rax 
    cmpl (%rsi), %eax 
    jl .LBB0_2 
.LBB0_3:        # %._crit_edge 
    ret 
    .size foo, .Ltmp1-foo 
    .cfi_endproc 
.Leh_func_end0: 

2) Nếu không có răng cưa

foo:         # @foo 
    .cfi_startproc 
# BB#0: 
    testl %esi, %esi 
    jle .LBB0_3 
# BB#1:         # %.lr.ph 
    addl %edx, %edx 
    .align 16, 0x90 
.LBB0_2:        # =>This Inner Loop Header: Depth=1 
    movl %edx, (%rdi) 
    addq $4, %rdi 
    decl %esi 
    jne .LBB0_2 
.LBB0_3:        # %._crit_edge 
    ret 
    .size foo, .Ltmp1-foo 
    .cfi_endproc 
.Leh_func_end0: 

Bạn có thể thấy rằng phiên bản với răng cưa đã làm nhiều việc hơn trong cơ thể loop (giữa các nhãn LBB0_2LBB0_3).

+0

sẽ const từ khóa giúp? ví dụ, 'foo (..., const int * value)' thay vào đó, ngụ ý rằng nội dung của '* value' sẽ không thay đổi. – bumfo

+3

@bumfo Không, tôi sẽ không mong đợi rằng để cải thiện bất cứ điều gì trong trường hợp này, bởi vì 'const' không thực sự ngụ ý rằng giá trị sẽ không được thay đổi. Tất cả nó nói là chức năng sẽ không thay đổi nó thông qua con trỏ đó. (Mặc dù kỹ thuật 'const' thậm chí không có nghĩa là nhiều.) – bames53

1

một con trỏ là một giá trị đại diện cho một địa chỉ bộ nhớ đôi khi 2 con trỏ có thể đại diện cho cùng một thats địa chỉ bộ nhớ gì răng cưa là

int * p; 
*p = 5; 

int * alias; 
alias = p; 

biến alias là một bí danh của p và *alias bằng 5 nếu bạn thay đổi *alias sau đó *p thay đổi cùng với nó

+0

cách này làm chậm tệp nhị phân? – unexplored

+0

@unexplored Indirection làm chậm các tệp nhị phân vì bạn không biết dữ liệu thực tế là gì cho đến khi bạn tìm nạp dữ liệu ở đâu. Để có được dữ liệu bạn cần để lấy địa chỉ của dữ liệu và sau đó bạn cuối cùng có thể lấy dữ liệu đó là yêu cầu bộ nhớ (trừ khi được lưu trữ). –

+0

@ JesusRamos chính xác cho tôi nếu tôi sai, nhưng nó sẽ không giống nhau mà không có sự hướng dẫn? Tôi có nghĩa là khi dereferencing 'p' trong trường hợp này, dữ liệu không được biết đến cho đến khi được lấy một trong hai. – unexplored

12

các loại vấn đề Chandler đã nói về có thể dễ dàng được minh họa bằng một đơn giản strcpy:

char *stpcpy (char * dest, const char * src); 

Khi thực hiện thao tác này, bạn có thể giả định rằng bộ nhớ được trỏ đến bởi dest hoàn toàn tách biệt với bộ nhớ được trỏ đến bởi src. Trình biên dịch) có thể muốn tối ưu hóa nó bằng cách đọc một khối ký tự từ chuỗi được trỏ đến bởi src và viết tất cả chúng một lần vào dest. Nhưng nếu dest chỉ đến một byte trước src, hành vi của điều này sẽ khác với bản sao ký tự theo từng ký tự đơn giản.

Ở đây vấn đề bí danh là src có thể bí danh dest và mã được tạo phải được thực hiện kém hiệu quả hơn nếu có thể là src không được phép bí danh dest.

Các thực strcpy sử dụng một từ khóa phụ, Restrict (đó là technically only part of C, not C++, mà nói với trình biên dịch cho rằng srcdest không chồng chéo lên nhau, và điều này cho phép trình biên dịch để tạo ra mã nhiều hiệu quả hơn.


Đây là một ví dụ đơn giản hơn, nơi chúng ta có thể thấy sự khác biệt lớn trong hội đồng:

void my_function_1(int* a, int* b, int* c) { 
    if (*a) *b = *a; 
    if (*a) *c = *a; 
} 

void my_function_2(int* __restrict a, int* __restrict b, int* __restrict c) { 
    if (*a) *b = *a; 
    if (*a) *c = *a; 
} 

Giả sử đây là sự đơn giản hóa chức năng nó thực sự có ý nghĩa khi sử dụng hai câu lệnh if chứ không phải chỉ là if (*a) { *b=*a; *c=*a; }, nhưng mục đích là như nhau.

Chúng tôi có thể giả định khi viết điều này a != b vì có một số lý do tại sao nó sẽ không có ý nghĩa đối với my_function được sử dụng như thế.Nhưng trình biên dịch không thể giả định rằng, và làm một cửa hàng của b và tái tải của a từ bộ nhớ trước khi thực hiện dòng thứ hai, để trang trải các trường hợp b == a:

0000000000400550 <my_function_1>: 
    400550:  8b 07     mov (%rdi),%eax 
    400552:  85 c0     test %eax,%eax     <= if (*a) 
    400554:  74 0a     je  400560 <my_function_1+0x10> 
    400556:  89 06     mov %eax,(%rsi) 
    400558:  8b 07     mov (%rdi),%eax 
    40055a:  85 c0     test %eax,%eax     <= if (*a) 
    40055c:  74 02     je  400560 <my_function_1+0x10> 
    40055e:  89 02     mov %eax,(%rdx) 
    400560:  f3 c3     repz retq 

Nếu chúng ta loại bỏ nguy cơ răng cưa bằng cách thêm __restrict, trình biên dịch tạo ra mã ngắn hơn và nhanh hơn:

0000000000400570 <my_function_2>: 
    400570:  8b 07     mov (%rdi),%eax 
    400572:  85 c0     test %eax,%eax 
    400574:  74 04     je  40057a <_Z9my_function_2PiS_S_+0xa> 
    400576:  89 06     mov %eax,(%rsi) 
    400578:  89 02     mov %eax,(%rdx) 
    40057a:  f3 c3     repz retq 
4

Hãy xem xét các chức năng sau:

void f(float* lhs, float* rhs, float* out, int size) { 
    for(int i = 0; i < size; i++) { 
     out[i] = *lhs + *rhs; 
    } 
} 

Phiên bản nhanh nhất của chức năng này là gì? Có lẽ, bạn có thể rút *lhs + *rhs ra khỏi vòng lặp. Vấn đề là những gì sẽ xảy ra khi các bí danh con trỏ. Hãy tưởng tượng những gì tối ưu hóa mà không nếu tôi gọi nó như thế này:

float arr[6] = { ... }; 
f(arr, arr + 1, arr, 6); 

Như bạn có thể thấy, vấn đề là *lhs + *rhs không thể kéo lên ra khỏi vòng lặp, vì out[i] đổi giá trị của họ. Trong thực tế, trình biên dịch không thể tống tất cả logic ra khỏi vòng lặp. Vì vậy, trình biên dịch không thể thực hiện tối ưu hóa "rõ ràng", bởi vì nếu các tham số bí danh thì logic bây giờ là không chính xác. Tuy nhiên, nếu phao được lấy theo giá trị, thì trình biên dịch biết rằng chúng không thể bí danh và có thể thực hiện tời.

Tất nhiên, chức năng này khá ngớ ngẩn nhưng nó thể hiện quan điểm.

Các vấn đề liên quan