2014-11-24 27 views
15

Tôi đã biết rằng thiết lập một trường chậm hơn nhiều so với thiết lập một biến địa phương, nhưng nó cũng xuất hiện rằng thiết lập một trường với biến cục bộ chậm hơn nhiều so với thiết lập biến cục bộ với một trường. Tại sao điều này? Trong cả hai trường hợp, địa chỉ của trường được sử dụng.Tại sao thiết lập một trường nhiều lần chậm hơn so với nhận được một trường?

public class Test 
{ 
    public int A = 0; 
    public int B = 4; 

    public void Method1() // Set local with field 
    { 
     int a = A; 

     for (int i = 0; i < 100; i++) 
     { 
      a += B; 
     } 

     A = a; 
    } 

    public void Method2() // Set field with local 
    { 
     int b = B; 

     for (int i = 0; i < 100; i++) 
     { 
      A += b; 
     } 
    } 
} 

Kết quả benchmark với 10e + 6 lần lặp là:

 
Method1: 28.1321 ms 
Method2: 162.4528 ms 
+2

Nó phụ thuộc vào rất nhiều thứ, nhưng giải thích rõ ràng nhất là không cần phải truy cập DRAM (giá trị trong bộ đệm CPU), trong khi thiết lập không (ghi đè bộ nhớ cache ... tức là giá trị được ghi vào cả bộ nhớ cache và bộ nhớ hệ thống). Lưu ý rằng việc thiết lập một biến cục bộ có thể dẫn đến không có truy cập bộ nhớ nào cả, vì trình biên dịch có thể đã tối ưu hóa biến cục bộ thành một thanh ghi. –

+0

@PeterDuniho - Tôi nghĩ chỉ có người dân địa phương mới đủ điều kiện cho bộ đệm CPU? – toplel32

+0

Như tôi đã đề cập trong nhận xét của tôi, người dân địa phương thường không được lưu trữ trong RAM hệ thống. Nhưng truy cập bộ nhớ _all_, bất kể loại biến, có đủ điều kiện để lưu vào bộ nhớ đệm hay không. Bộ nhớ cache không quan tâm (hoặc thậm chí biết) tại sao bạn đang sử dụng một địa chỉ bộ nhớ cụ thể; nó lưu trữ bất kỳ và tất cả dữ liệu mà nó có thể khi bộ nhớ hệ thống được tham gia. –

Trả lời

15

Chạy này trên máy tính của tôi, tôi nhận được sự khác biệt thời gian tương tự, tuy nhiên nhìn vào mã JITted cho 10M lặp, thì rõ ràng để xem lý do tại sao đây là trường hợp:

Phương pháp A:

mov  r8,rcx 
; "A" is loaded into eax 
mov  eax,dword ptr [r8+8] 
xor  edx,edx 
; "B" is loaded into ecx 
mov  ecx,dword ptr [r8+0Ch] 
nop  dword ptr [rax] 
loop_start: 
; Partially unrolled loop, all additions done in registers 
add  eax,ecx 
add  eax,ecx 
add  eax,ecx 
add  eax,ecx 
add  edx,4 
cmp  edx,989680h 
jl  loop_start 
; Store the sum in eax back to "A" 
mov  dword ptr [r8+8],eax 
ret 

Và Phương pháp B:

; "B" is loaded into edx 
mov  edx,dword ptr [rcx+0Ch] 
xor  r8d,r8d 
nop word ptr [rax+rax] 
loop_start: 
; Partially unrolled loop, but each iteration requires reading "A" from memory 
; adding "B" to it, and then writing the new "A" back to memory. 
mov  eax,dword ptr [rcx+8] 
add  eax,edx 
mov  dword ptr [rcx+8],eax 
mov  eax,dword ptr [rcx+8] 
add  eax,edx 
mov  dword ptr [rcx+8],eax 
mov  eax,dword ptr [rcx+8] 
add  eax,edx 
mov  dword ptr [rcx+8],eax 
mov  eax,dword ptr [rcx+8] 
add  eax,edx 
mov  dword ptr [rcx+8],eax 
add  r8d,4 
cmp  r8d,989680h 
jl  loop_start 
rep ret 

Như bạn có thể thấy từ lắp ráp, Phương pháp A sẽ nhanh hơn đáng kể vì giá trị của A và B đều được đặt trong sổ đăng ký và tất cả bổ sung xảy ra ở đó mà không có ghi trung gian vào bộ nhớ. Phương pháp B mặt khác chịu tải và lưu vào "A" trong bộ nhớ cho mỗi lần lặp đơn.

2

Trong trường hợp 1 a được lưu trữ rõ ràng trong một thanh ghi. Bất cứ điều gì khác sẽ là một kết quả biên dịch khủng khiếp.

Có lẽ, NET JIT là không sẵn sàng/có thể chuyển đổi các cửa hàng để A đăng ký cửa hàng trong trường hợp 2.

tôi nghi ngờ này buộc bởi mô hình bộ nhớ NET vì đề khác không bao giờ có thể cho biết sự khác biệt giữa hai phương thức của bạn nếu chúng chỉ quan sát A là 0 hoặc tổng. Họ không thể bác bỏ lý thuyết rằng việc tối ưu hóa không bao giờ xảy ra. Điều đó làm cho nó được cho phép theo ngữ nghĩa của máy trừu tượng .NET.

Không ngạc nhiên khi thấy .NET JIT thực hiện tối ưu hóa ít. Điều này nổi tiếng với những người theo dõi của thẻ performance trên Stack Overflow.

Tôi biết từ kinh nghiệm rằng JIT có nhiều khả năng tải bộ nhớ cache trong bộ nhớ. Đó là lý do tại sao trường hợp 1 (rõ ràng) không truy cập B với mỗi lần lặp lại.

Tính toán đăng ký rẻ hơn khi truy cập bộ nhớ. Điều này thậm chí còn đúng nếu bộ nhớ được đề cập nằm trong bộ nhớ cache CPU L1 (vì nó là trường hợp ở đây).

Tôi nghĩ chỉ những người dân địa phương mới đủ điều kiện cho bộ đệm CPU?

Điều này không thể vì CPU thậm chí không biết địa phương là gì. Tất cả các địa chỉ đều giống nhau.

+0

Phần cuối cùng làm tôi ngạc nhiên; có điều gì như truy cập trường ở tất cả sau khi biên dịch JIT? Nếu không thì truy cập trường A.B.C.D sẽ nhanh như khi truy cập A phải không? – toplel32

+0

Địa chỉ của D chỉ có thể được tính toán sau khi điều hướng qua A.B, B.C và B.D nếu đó là các loại tham chiếu. Đó là đắt tiền bởi vì nó quầy hàng đường ống. – usr

+0

Nếu tất cả các loại này là các loại giá trị, độ lệch của D trong A được biết một cách tĩnh và truy cập D nhanh như bất kỳ trường nào khác. – usr

-2

method2: Trường được đọc ~ 100x và thiết lập ~ 100x quá = 200x larg_0 (this) + 100x ldfld (lĩnh vực tải) + 100x stfld (lĩnh vực quy định) + 100x ldloc (địa phương)

method1: lĩnh vực là đọc 100x nhưng không được đặt nó tương đương với phương pháp1 trừ đi 100x ldarg_0 (điều này)

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