2013-03-05 28 views
10

tôi đã rối tung xung quanh với mã tồi tệ nhất tôi có thể viết, (về cơ bản cố gắng để phá vỡ mọi thứ) và tôi nhận thấy rằng đoạn mã này:biến toàn cầu chậm lại đang

for(int i = 0; i < N; ++i) 
    tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
end 
std::cout << x; 

trong đó N là một chạy biến toàn cầu chậm hơn đáng kể sau đó:

int N = 10000; 
for(int i = 0; i < N; ++i) 
    tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
end 
std::cout << x; 

Điều gì sẽ xảy ra với biến toàn cầu làm cho nó chạy chậm hơn?

+1

Tôi cho rằng trình biên dịch có thể nghi ngờ rằng 'x' được sửa đổi bên trong hàm' tan' ngăn cản nhiều tối ưu hóa. Tôi không chắc chắn rằng điều này xảy ra ở đây mặc dù. – Pubby

+0

@Pubby Sự khác biệt duy nhất là giảm tốc độ N mặc dù? –

+0

tốt, nếu 'x' là cục bộ thì rõ ràng là không thể sửa đổi bên trong' tan'. Vì vậy, khai báo không thành vấn đề. – Pubby

Trả lời

7

tl; dr: Phiên bản cục bộ giữ N trong sổ đăng ký, phiên bản toàn cầu thì không. Khai báo hằng số với const và sẽ nhanh hơn bất kể bạn khai báo nó như thế nào.


Dưới đây là đoạn code ví dụ tôi sử dụng:

#include <iostream> 
#include <math.h> 
void first(){ 
    int x=1; 
    int N = 10000; 
    for(int i = 0; i < N; ++i) 
    tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
    std::cout << x; 
} 
int N=10000; 
void second(){ 
    int x=1; 
    for(int i = 0; i < N; ++i) 
    tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
    std::cout << x; 
} 
int main(){ 
    first(); 
    second(); 
} 

(tên test.cpp).

Để xem mã trình tạo đã tạo, tôi chạy g++ -S test.cpp.

tôi nhận được một tập tin rất lớn, nhưng với một số tìm kiếm thông minh (Tôi đã tìm kiếm cho tan), tôi thấy những gì tôi muốn:

từ first chức năng:

Ltmp2: 
    movl $1, -4(%rbp) 
    movl $10000, -8(%rbp) ; N is here !!! 
    movl $0, -12(%rbp) ;initial value of i is here 
    jmp LBB1_2  ;goto the 'for' code logic 
LBB1_1:    ;the loop is this segment 
    movl -4(%rbp), %eax 
    cvtsi2sd %eax, %xmm0 
    movl -4(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -4(%rbp) 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan   
    callq _tan 
    callq _tan 
    movl -12(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -12(%rbp) 
LBB1_2: 
    movl -12(%rbp), %eax ;value of n kept in register 
    movl -8(%rbp), %ecx 
    cmpl %ecx, %eax ;comparing N and i here 
    jl LBB1_1  ;if less, then go into loop code 
    movl -4(%rbp), %eax 

chức năng thứ hai:

Ltmp13: 
    movl $1, -4(%rbp) ;i 
    movl $0, -8(%rbp) 
    jmp LBB5_2 
LBB5_1:    ;loop is here 
    movl -4(%rbp), %eax 
    cvtsi2sd %eax, %xmm0 
    movl -4(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -4(%rbp) 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    movl -8(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -8(%rbp) 
LBB5_2: 
    movl _N(%rip), %eax ;loading N from globals at every iteration, instead of keeping it in a register 
    movl -8(%rbp), %ecx 

Vì vậy, từ mã trình lắp ráp, bạn có thể thấy (hoặc không), trong phiên bản cục bộ, N được giữ trong sổ đăng ký trong suốt quá trình tính toán, trong khi ở phiên bản toàn cầu, N được đọc lại từ toàn cầu tại mỗi lần lặp.

Tôi tưởng tượng lý do chính tại sao điều này xảy ra là cho những thứ như luồng, trình biên dịch không thể chắc chắn rằng N không được sửa đổi.

nếu bạn thêm một const để việc kê khai của N (const int N=10000), nó sẽ nhanh hơn so với phiên bản địa phương mặc dù:

movl -8(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -8(%rbp) 
LBB5_2: 
    movl -8(%rbp), %eax 
    cmpl $9999, %eax ;9999 used instead of 10000 for some reason I do not know 
    jle LBB5_1 

N được thay thế bằng một hằng số.

+0

Bạn sẽ nhận được gì nếu bạn bật tối ưu hóa khi biên dịch? – Mankarse

+4

Luồng không có trong quá trình quyết định của trình biên dịch, vì các điều kiện chủng tộc là hành vi không xác định. 'N' được nạp mỗi lần lặp bởi vì trình biên dịch không thể chắc chắn rằng' tan' không sửa đổi 'N'. – Mankarse

+0

Điều đó giống như một lỗi tối ưu hóa nghiêm trọng: Biến cục bộ chưa sửa đổi và "chưa được khai báo" như 'N' có thể được xử lý không bao giờ được sửa đổi sau khi khởi tạo, và do đó được thay thế bằng hằng số, thậm chí không có' const'. – Yakk

7

Phiên bản chung không thể được tối ưu hóa để đặt nó vào sổ đăng ký.

+0

vì nó cần phải có thể truy cập từ các phạm vi khác nhau .. – Krishnabhadra

+0

@Krishnabhadra: Không phải trong trường hợp này. Vấn đề là trình biên dịch không biết không ai khác sẽ sử dụng nó. –

+0

tự hỏi nếu vua của tối ưu hóa, ICC, có thể xử lý này – hanshenrik

5

Tôi giả định trình tối ưu hóa không biết nội dung của hàm tan khi biên dịch mã ở trên.

Tức là, những gì tan không xác định - tất cả những gì bạn biết là xếp thứ vào ngăn xếp, chuyển đến một số địa chỉ, sau đó dọn dẹp ngăn xếp sau đó.

Trong trường hợp biến toàn cục, trình biên dịch không biết tan làm gì với N. Trong trường hợp địa phương, không có con trỏ "lỏng lẻo" hoặc tham chiếu đến Ntan hợp pháp có thể nhận được tại: do đó trình biên dịch biết giá trị nào N sẽ mất.

Trình biên dịch có thể làm phẳng vòng lặp - bất cứ nơi nào từ hoàn toàn (một khối phẳng 10000 dòng), một phần (100 vòng lặp dài, mỗi dòng 100 dòng), hoặc không hoàn toàn (chiều dài 10000 vòng 1 dòng), hoặc bất cứ điều gì ở giữa.

Trình biên dịch biết cách nhiều hơn khi các biến của bạn là cục bộ, bởi vì khi chúng là toàn cầu, nó có rất ít kiến ​​thức về cách chúng thay đổi hoặc ai đọc chúng. Vì vậy, ít giả định có thể được thực hiện.

Ngạc nhiên thay, đây cũng là lý do tại sao người ta khó có thể lý giải về hình cầu.

7

tôi đã làm một chút thử nghiệm với các câu hỏi và câu trả lời của @rtpg,

thử nghiệm với các câu hỏi

Trong tập tin main1.h biến N toàn cầu

int N = 10000; 

Sau đó, trong tệp main1.c, 1000 tính toán của tình huống:

#include <stdio.h> 
#include "sys/time.h" 
#include "math.h" 
#include "main1.h" 



extern int N; 

int main(){ 

     int k = 0; 
     timeval static_start, static_stop; 
     int x = 0; 

     int y = 0; 
     timeval start, stop; 
     int M = 10000; 

     while(k <= 1000){ 

       gettimeofday(&static_start, NULL); 
       for (int i=0; i<N; ++i){ 
         tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
       } 
       gettimeofday(&static_stop, NULL); 

       gettimeofday(&start, NULL); 
       for (int j=0; j<M; ++j){ 
         tan(tan(tan(tan(tan(tan(tan(tan(y++)))))))); 
       } 
       gettimeofday(&stop, NULL); 

       int first_interval = static_stop.tv_usec - static_start.tv_usec; 
       int last_interval = stop.tv_usec - start.tv_usec; 

       if(first_interval >=0 && last_interval >= 0){ 
         printf("%d, %d\n", first_interval, last_interval); 
       } 

       k++; 
     } 

     return 0; 
} 

Các kết quả được thể hiện trong biểu đồ sau (tần số/micro):

the histogram for the comparison output time in both methods Các hộp màu đỏ là những biến không toàn cầu có trụ sở đã kết thúc vòng lặp for (N), và màu xanh lá cây trong suốt M đã kết thúc dựa vòng lặp for (không toàn cầu).

Có bằng chứng để nghi ngờ rằng varialbe toàn cầu bên ngoài hơi chậm.

thử nghiệm câu trả lời Lý do @rtpg rất mạnh. Theo nghĩa này, một biến toàn cục có thể chậm hơn.

Speed of accessing local vs. global variables in gcc/g++ at different optimization levels

Để kiểm tra giả thuyết này tôi sử dụng một biến toàn cầu đăng ký để kiểm tra việc thực hiện. Đây là main1.h tôi với biến toàn cầu

int N asm ("myN") = 10000; 

Kết quả mới histogram:

Results with register global variable

kết luận có hiệu suất cải thiện khi các biến toàn cầu là trong sổ đăng ký. Không có vấn đề biến "cục bộ" hoặc "cục bộ". Hiệu suất phụ thuộc vào truy cập vào biến.

0

tôi nghĩ rằng đây có thể là một lý do: Vì biến toàn cầu được lưu trữ trong bộ nhớ heap, mã của bạn cần truy cập bộ nhớ heap mỗi lần. Có thể vì mã lý do ở trên chạy chậm.

+0

biến toàn cầu được lưu trữ trong phân đoạn DATA của STACK và không phải trong heap. – niko