2010-04-30 27 views
5

Tôi đang cố gắng tìm ra bao nhiêu chu kỳ đồng hồ hoặc tổng số hướng dẫn cần để truy cập một con trỏ trong C. Tôi không nghĩ rằng tôi biết làm thế nào để tìm ra ví dụ, p-> x = d-> a + f-> bCó bao nhiêu hướng dẫn để truy cập con trỏ trong C?

tôi giả sử hai tải trên mỗi con trỏ, chỉ cần đoán rằng sẽ có tải cho con trỏ và tải cho giá trị. Vì vậy, trong hoạt động này, độ phân giải con trỏ sẽ là một yếu tố lớn hơn nhiều so với việc bổ sung thực tế, như xa như cố gắng để tăng tốc độ mã này lên, phải không?

Điều này có thể phụ thuộc vào trình biên dịch và kiến ​​trúc được triển khai, nhưng tôi có đi đúng hướng không?

Tôi đã thấy một số mã trong đó mỗi giá trị sử dụng trong tiếng nói, 3 bổ sung, xuất thân từ một loại

f2->sum = p1->p2->p3->x + p1->p2->p3->a + p1->p2->p3->m 

của cấu trúc, và tôi đang cố gắng để xác định cách xấu này là

+0

phụ thuộc vào chế độ địa chỉ imho - gần nhảy/nhảy dài, tính toán địa chỉ ... –

+0

nhớ trình biên dịch * nên * di chuyển rất nhiều thứ này vào ngăn xếp sau khi tìm nạp nó một lần. Nếu nó không phải là, và bạn không cần phải lo lắng về đa luồng, bạn có thể cache con trỏ tự đuổi theo. –

+3

@Robert: nếu đa luồng sẽ ảnh hưởng đến dereferencing con trỏ trong ví dụ, sau đó mã cần serialization rõ ràng - một trình biên dịch tối ưu sẽ luôn có thể cache 'p3' vào thanh ghi và sử dụng nó cho tất cả 3 thành viên truy cập (giả sử có không sử dụng thành viên 'volatile' nào). –

Trả lời

8

này phụ thuộc vào kiến trúc trong tầm tay.

Một số kiến ​​trúc có thể tham khảo/bộ nhớ dereference cho một lệnh mà không cần tải nó vào thanh ghi, một số khác thì không. Một số kiến ​​trúc không có khái niệm hướng dẫn tính toán bù trừ cho bạn để dereference và sẽ làm cho bạn tải địa chỉ bộ nhớ, thêm bù đắp của bạn vào nó, và sau đó cho phép bạn dereference vị trí bộ nhớ. Tôi chắc chắn có nhiều biến thể chip-to-chip.

Sau khi bạn vượt qua những điều này, mỗi lệnh sẽ thay đổi lượng thời gian tùy thuộc vào kiến ​​trúc. Thành thật mà nói, đó là một chi phí rất, rất tối thiểu.

Đối với câu hỏi ngay lập tức của bạn dereferencing một chuỗi các mặt hàng, sự chậm chạp sẽ đến trong thực tế là có khả năng một địa phương nghèo tham chiếu xa hơn bạn đi trong một chuỗi dereferencing. Điều này có nghĩa là nhiều lần nhớ cache hơn, có nghĩa là nhiều lần truy cập đến bộ nhớ chính (hoặc đĩa!) Hơn để lấy dữ liệu. Bộ nhớ chính rất chậm so với CPU.

+2

+1 để đề cập đến các tác động bộ nhớ cache –

+1

Tôi không nghĩ rằng nó là tối thiểu. Trong tối ưu hóa mã như trên, tôi đã thấy 3 - 8x tăng tốc thoát khỏi các con trỏ và sử dụng truy cập mảng bình thường. Vấn đề thậm chí còn tồi tệ hơn nếu con trỏ thực sự là cấu trúc. – Derek

+0

@derek Trước tiên, nó chỉ là một chi phí tiềm năng xấu nếu mã được thực hiện liên tục, trong trường hợp đó, trừ khi bạn đang truy tìm bộ nhớ cache, tra cứu bộ nhớ liên tục sẽ được lưu trong bộ nhớ DTLB (trong trường hợp x86). Nó vẫn còn tốt đẹp để sử dụng đăng ký khi có thể, đó là những gì trình biên dịch _does_. Ví dụ trong câu trả lời của tôi cho thấy rằng có thể có con trỏ truy cập ngay cả khi gán các biến cục bộ cho nhau. –

1

Phụ thuộc những gì bạn đang làm, một con trỏ tầm thường dereference y = *z; nơi

int x = 1; 
int* z = &x; 
int y; 

có thể lắp ráp một cái gì đó như thế này trên x86:

mov eax, [z] 
mov eax, [eax] 
mov [y], eax 

y = x vẫn sẽ mất một dereference bộ nhớ:

mov eax, [x] 
mov [y], eax 

Hướng dẫn Mov bộ nhớ mất khoảng 2-4 chu kỳ IIRC.

Mặc dù, nếu bạn đang tải bộ nhớ từ các vị trí hoàn toàn ngẫu nhiên, bạn sẽ gây ra nhiều lỗi trang, dẫn đến chu kỳ đồng hồ bị lãng phí.

2

Một số IDE như VisualStudio cho phép bạn xem assembly được tạo cùng với mã nguồn.

How to view the assembly behind the code using Visual C++?

Sau đó, bạn có thể thấy kiến ​​trúc và thực hiện chính xác của bạn những gì nó trông như thế nào.

Nếu bạn đang sử dụng GDB (linux, mac) sử dụng disassemble

(gdb) disas 0x32c4 0x32e4 
Dump of assembler code from 0x32c4 to 0x32e4: 
0x32c4 <main+204>:  addil 0,dp 
0x32c8 <main+208>:  ldw 0x22c(sr0,r1),r26 
0x32cc <main+212>:  ldil 0x3000,r31 
0x32d0 <main+216>:  ble 0x3f8(sr4,r31) 
0x32d4 <main+220>:  ldo 0(r31),rp 
0x32d8 <main+224>:  addil -0x800,dp 
0x32dc <main+228>:  ldo 0x588(r1),r26 
0x32e0 <main+232>:  ldil 0x3000,r31 
End of assembler dump. 
+0

Tôi biên soạn iwth tùy chọn -S, và tôi tìm thấy một cái gì đó rất giống với những gì người khác đã nói về. – Derek

1

đâu nó có thể, trình biên dịch sẽ loại bỏ chi phí đó cho bạn bằng cách giữ các địa điểm cơ sở liên tục được sử dụng trong một thanh ghi (ví dụ. p1->p2->p3 trong ví dụ của bạn).

Tuy nhiên, đôi khi trình biên dịch không thể xác định con trỏ nào có thể các con trỏ khác được sử dụng trong chức năng của bạn - nghĩa là nó phải trở lại vị trí rất bảo thủ và tải lại giá trị từ con trỏ thường xuyên.

Đây là nơi mà từ khóa restrict của C99 có thể trợ giúp. Nó cho phép bạn thông báo cho trình biên dịch khi một số con trỏ nhất định không bao giờ được đánh dấu bởi các con trỏ khác trong phạm vi chức năng, mà có thể cải thiện sự tối ưu.


Ví dụ, mất chức năng này:

struct xyz { 
    int val1; 
    int val2; 
    int val3; 
}; 

struct abc { 
    struct xyz *p2; 
}; 

int foo(struct abc *p1) 
{ 
    int sum; 

    sum = p1->p2->val1 + p1->p2->val2 + p1->p2->val3; 

    return sum; 
} 

Dưới gcc 4.3.2 với mức tối ưu hóa -O1, nó biên dịch sang mã x86 này:

foo: 
    pushl %ebp 
    movl %esp, %ebp 
    movl 8(%ebp), %eax 
    movl (%eax), %edx 
    movl 4(%edx), %eax 
    addl (%edx), %eax 
    addl 8(%edx), %eax 
    popl %ebp 
    ret 

Như bạn có thể thấy, nó chỉ deferences p1 một lần - nó giữ giá trị p1->p2 trong thanh ghi %edx và sử dụng nó ba lần để tìm nạp ba giá trị từ cấu trúc đó.

+0

Tôi thực sự đã viết một chương trình thử nghiệm, được biên dịch với tùy chọn -S và tôi thấy rằng ngay cả đối với một trường hợp đơn giản như p1.p2-> p3-> giá trị hoặc một số thứ như vậy, nó được tải lại từ p1 mỗi lần. Rất bảo thủ không có tối ưu hóa – Derek

+0

@Derek: Bạn đã sử dụng mức tối ưu hóa nào? Với '-O1' hoặc cao hơn, nó nên tối ưu hóa các trường hợp đơn giản khá tốt (xem ví dụ tôi đã thêm vào câu trả lời của tôi). – caf

+0

Có, nó sẽ, nhưng nó mất một số khả năng đó một chương trình phức tạp hơn được. Đây là loại quan điểm của tôi – Derek

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