Làm thế nào quan trọng là liên kết bộ nhớ? Nó vẫn còn quan trọng?


15

Từ bây giờ, tôi đã tìm kiếm và đọc rất nhiều về căn chỉnh bộ nhớ, cách thức hoạt động và cách sử dụng nó. Bài viết có liên quan nhất mà tôi tìm thấy bây giờ là bài viết này .

Nhưng ngay cả với điều đó tôi vẫn có một số câu hỏi về nó:

  1. Ngoài hệ thống nhúng, chúng ta thường có bộ nhớ lớn trong máy tính khiến việc quản lý bộ nhớ ít bị chỉ trích hơn, tôi hoàn toàn tối ưu hóa, nhưng bây giờ, nó thực sự có thể tạo ra sự khác biệt nếu chúng ta so sánh cùng một chương trình với hoặc không có bộ nhớ được sắp xếp lại và căn chỉnh?
  2. Là liên kết bộ nhớ có lợi thế khác? Tôi đã đọc ở đâu đó rằng CPU hoạt động tốt hơn / nhanh hơn với bộ nhớ được căn chỉnh bởi vì cần ít hướng dẫn hơn để xử lý (nếu một trong hai bạn có một liên kết cho một bài viết / điểm chuẩn về nó?), Trong trường hợp đó, sự khác biệt có thực sự đáng kể? Có nhiều lợi thế hơn hai?
  3. Trong liên kết bài viết, tại chương 5, tác giả nói:

    Coi chừng: trong C ++, các lớp trông giống như cấu trúc có thể phá vỡ quy tắc này! (Việc chúng có làm hay không phụ thuộc vào cách các lớp cơ sở và các hàm thành viên ảo được triển khai và thay đổi theo trình biên dịch.)

  4. Bài báo nói chủ yếu về các cấu trúc, nhưng khai báo biến cục bộ cũng bị ảnh hưởng bởi nhu cầu này?

    Bạn có biết làm thế nào để căn chỉnh bộ nhớ hoạt động chính xác trong C ++ vì nó dường như có một số khác biệt?

Câu hỏi trước đây có chứa từ "căn chỉnh", nhưng nó không cung cấp bất kỳ câu trả lời nào cho các câu hỏi trên.


Trình biên dịch C ++ có xu hướng làm điều này nhiều hơn (chèn phần đệm khi cần thiết hoặc có lợi) cho bạn. Từ liên kết bạn đã đề cập, hãy xem phần 12 "Công cụ" để biết những thứ bạn có thể sử dụng.
rwong

Câu trả lời:


11

Có, cả việc căn chỉnh và sắp xếp dữ liệu của bạn có thể tạo ra sự khác biệt lớn về hiệu suất, không chỉ một vài phần trăm mà là vài đến hàng trăm phần trăm.

Thực hiện vòng lặp này, hai hướng dẫn quan trọng nếu bạn chạy đủ các vòng lặp.

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

Có và không có bộ đệm, và với việc căn chỉnh có và không có bộ đệm trong dự đoán nhánh và bạn có thể thay đổi hiệu suất của hai hướng dẫn đó theo một lượng đáng kể (dấu thời gian):

min      max      difference
00016DDE 003E025D 003C947F

Một bài kiểm tra hiệu suất bạn có thể rất dễ dàng tự làm. thêm hoặc loại bỏ các xung quanh mã được kiểm tra và thực hiện chính xác thời gian, di chuyển các hướng dẫn được kiểm tra dọc theo một dải địa chỉ đủ rộng để chạm vào các cạnh của các dòng bộ đệm, v.v.

Cùng một loại điều với truy cập dữ liệu. Một số kiến ​​trúc phàn nàn về các truy cập không được phân bổ (thực hiện đọc 32 bit tại địa chỉ 0x1001 chẳng hạn), bằng cách cung cấp cho bạn một lỗi dữ liệu. Một số trong những bạn có thể vô hiệu hóa lỗi và thực hiện cú đánh hiệu suất. Những người khác cho phép truy cập không được phân bổ, bạn chỉ cần đạt được hiệu suất.

Nó đôi khi là "hướng dẫn" nhưng hầu hết thời gian là chu kỳ đồng hồ / xe buýt.

Nhìn vào các triển khai memcpy trong gcc cho các mục tiêu khác nhau. Giả sử bạn đang sao chép cấu trúc có kích thước 0x43 byte, bạn có thể tìm thấy một triển khai sao chép một byte để lại 0x42 sau đó sao chép 0x40 byte trong các khối hiệu quả lớn sau đó 0x2 cuối cùng có thể thực hiện dưới dạng hai byte riêng lẻ hoặc dưới dạng chuyển 16 bit. Sắp xếp và mục tiêu phát huy tác dụng nếu các địa chỉ nguồn và đích nằm trên cùng một liên kết là 0x1003 và 0x2003, sau đó bạn có thể thực hiện một byte, sau đó 0x40 trong các khối lớn sau đó là 0x2, nhưng nếu một là 0x1002 và 0x1003 khác, thì nó sẽ là 0x1002. thực sự xấu xí và thực sự chậm.

Hầu hết thời gian là chu kỳ xe buýt. Hoặc tệ hơn là số lần chuyển. Sử dụng bộ xử lý với bus dữ liệu rộng 64 bit, như ARM và thực hiện chuyển bốn từ (đọc hoặc ghi, LDM hoặc STM) tại địa chỉ 0x1004, đó là địa chỉ được căn chỉnh từ và hoàn toàn hợp pháp, nhưng nếu bus là 64 bit rộng, có khả năng lệnh đơn sẽ chuyển thành ba lần chuyển trong trường hợp này là 32 bit ở 0x1004, 64 bit ở 0x1008 và 32 bit ở 0x100A. Nhưng nếu bạn có cùng một hướng dẫn nhưng tại địa chỉ 0x1008, nó có thể thực hiện chuyển bốn từ duy nhất tại địa chỉ 0x1008. Mỗi lần chuyển có thời gian thiết lập liên quan. Vì vậy, sự khác biệt địa chỉ 0x1004 đến 0x1008 có thể nhanh hơn nhiều lần, thậm chí / đặc biệt khi sử dụng bộ đệm và tất cả đều là bộ nhớ cache.

Nói về, ngay cả khi bạn đọc hai từ tại địa chỉ 0x1000 so với 0x0FFC, 0x0FFC bị lỗi bộ nhớ cache sẽ gây ra hai dòng đọc bộ đệm trong đó 0x1000 là một dòng bộ đệm, dù sao bạn cũng bị phạt một dòng bộ đệm. truy cập (đọc nhiều dữ liệu hơn sử dụng) nhưng sau đó tăng gấp đôi. Làm thế nào các cấu trúc của bạn được căn chỉnh hoặc dữ liệu của bạn nói chung và tần suất truy cập dữ liệu đó, v.v., có thể gây ra lỗi bộ đệm.

Cuối cùng, bạn có thể tước dữ liệu của mình để xử lý dữ liệu bạn có thể tạo ra các vụ trục xuất, bạn có thể gặp xui xẻo thực sự và cuối cùng chỉ sử dụng một phần bộ nhớ cache của bạn và khi bạn lướt qua nó, các blob dữ liệu tiếp theo va chạm với một blob trước đó . Bằng cách trộn dữ liệu của bạn hoặc sắp xếp lại các chức năng trong mã nguồn, v.v. bạn có thể tạo hoặc xóa các xung đột, vì không phải tất cả các bộ nhớ cache được tạo bằng nhau, trình biên dịch sẽ không giúp bạn ở đây. Ngay cả việc phát hiện ra hiệu suất hoặc cải thiện là ở bạn.

Tất cả những điều chúng tôi đã thêm vào để cải thiện hiệu suất, các đường dữ liệu rộng hơn, đường ống dẫn, bộ nhớ cache, dự đoán nhánh, nhiều đơn vị / đường dẫn thực thi, v.v. Thường sẽ giúp ích, nhưng tất cả chúng đều có những điểm yếu, có thể bị khai thác một cách cố ý hoặc vô tình. Có rất ít trình biên dịch hoặc thư viện có thể làm được về nó, nếu bạn quan tâm đến hiệu suất, bạn cần điều chỉnh và một trong những yếu tố điều chỉnh lớn nhất là căn chỉnh mã và dữ liệu, không chỉ căn chỉnh trên 32, 64, 128, 256 ranh giới bit, nhưng cũng là nơi mọi thứ có liên quan với nhau, bạn muốn các vòng lặp được sử dụng nhiều hoặc dữ liệu được sử dụng lại để không hạ cánh theo cùng một cách bộ đệm, mỗi cái đều muốn riêng. Trình biên dịch có thể giúp ví dụ sắp xếp các hướng dẫn cho kiến ​​trúc siêu vô hướng, sắp xếp lại các hướng dẫn liên quan đến nhau không quan trọng,

Sự giám sát lớn nhất là giả định rằng bộ xử lý là nút cổ chai. Đã không đúng trong một thập kỷ trở lên, việc cung cấp bộ xử lý là vấn đề và đó là vấn đề như các lần nhấn hiệu năng căn chỉnh, đập bộ đệm, v.v. Với một công việc nhỏ ngay cả ở cấp mã nguồn, sắp xếp lại dữ liệu trong cấu trúc, sắp xếp thứ tự khai báo biến / cấu trúc, sắp xếp các hàm trong mã nguồn và thêm một chút mã để căn chỉnh dữ liệu, có thể cải thiện hiệu suất nhiều lần hoặc hơn.


+1 nếu chỉ cho đoạn cuối cùng của bạn. Băng thông bộ nhớ là vấn đề quan trọng nhất đối với bất kỳ ai đang cố gắng viết mã nhanh hiện nay, không phải số lượng lệnh. Và điều này có nghĩa là tối ưu hóa mọi thứ để giảm bỏ lỡ bộ nhớ cache, có thể được thực hiện bằng cách sửa đổi căn chỉnh trong nhiều trường hợp, là vô cùng quan trọng.
Jules

Nếu mã và dữ liệu của bạn được lưu vào bộ nhớ cache và bạn thực hiện đủ các vòng lặp / chu kỳ trên dữ liệu đó thì số lệnh và vị trí của các lệnh nằm trong một dòng tìm nạp, nơi các nhánh nằm trong ống so với những gì chúng dựa vào, có vấn đề. Nhưng trong các hệ thống dựa trên kịch và / hoặc flash, trước tiên bạn phải lo lắng về việc cung cấp bộ xử lý có.
old_timer

15

Vâng, căn chỉnh bộ nhớ vẫn còn vấn đề.

Một số bộ xử lý thực sự không thể thực hiện đọc trên các địa chỉ không liên kết. Nếu bạn đang chạy trên phần cứng như vậy và bạn lưu trữ số nguyên của mình không liên kết, bạn có thể phải đọc chúng với hai hướng dẫn theo sau là một số hướng dẫn khác để đưa các byte khác nhau vào đúng vị trí để bạn thực sự có thể sử dụng nó . Vì vậy, dữ liệu phù hợp là quan trọng hiệu suất.

Tin tốt là bạn thực sự không cần phải quan tâm. Hầu như bất kỳ trình biên dịch nào cho hầu hết mọi ngôn ngữ sẽ tạo ra mã máy tuân theo các yêu cầu căn chỉnh của hệ thống đích. Bạn chỉ cần bắt đầu suy nghĩ về nó nếu bạn đang kiểm soát trực tiếp biểu diễn trong bộ nhớ của dữ liệu, điều này không cần thiết ở bất cứ đâu gần như thường xuyên như trước đây. Đó là một điều thú vị cần biết và cực kỳ quan trọng để biết liệu bạn có muốn hiểu việc sử dụng bộ nhớ từ các cấu trúc khác nhau mà bạn đang tạo hay không và làm thế nào để có thể sắp xếp lại mọi thứ để hiệu quả hơn (tránh đệm). Nhưng trừ khi bạn cần loại điều khiển đó (và đối với hầu hết các hệ thống mà bạn không có), bạn có thể vui vẻ trải qua toàn bộ sự nghiệp mà không biết hoặc không quan tâm đến nó.


1
Đặc biệt, ARM không hỗ trợ truy cập không liên kết. Và đó là CPU hầu hết mọi thứ di động sử dụng.
Jan Hudec

Cũng lưu ý rằng Linux mô phỏng truy cập không liên kết với một số chi phí thời gian chạy, nhưng Windows (CE và Điện thoại) không và cố gắng truy cập không liên kết sẽ đơn giản làm sập ứng dụng.
Jan Hudec

2
Mặc dù điều này gần như đúng, lưu ý rằng một số nền tảng (bao gồm x86) có các yêu cầu căn chỉnh khác nhau tùy thuộc vào hướng dẫn nào sẽ được sử dụng , điều này không dễ để trình biên dịch tự khắc phục, vì vậy đôi khi bạn cần phải đệm để đảm bảo một số thao tác nhất định (ví dụ: các hướng dẫn SSE, nhiều thao tác yêu cầu căn chỉnh 16 byte) có thể được sử dụng cho một số thao tác. Ngoài ra, việc thêm phần đệm bổ sung để hai mục thường được sử dụng cùng xuất hiện trên cùng một dòng bộ đệm (cũng là 16 byte) có thể có ảnh hưởng rất lớn đến hiệu suất trong một số trường hợp và cũng không được tự động.
Jules

3

Vâng, nó vẫn còn quan trọng, và trong một số thuật toán quan trọng về hiệu năng, bạn không thể dựa vào trình biên dịch.

Tôi sẽ chỉ liệt kê một vài ví dụ:

  1. Từ câu trả lời này :

Thông thường, microcode sẽ lấy số lượng 4 byte thích hợp từ bộ nhớ, nhưng nếu nó không được căn chỉnh, nó sẽ phải tìm nạp hai vị trí 4 byte từ bộ nhớ và tái tạo lại số lượng 4 byte mong muốn từ các byte thích hợp của hai vị trí

  1. Bộ hướng dẫn SSE yêu cầu căn chỉnh đặc biệt. Nếu nó không được đáp ứng, bạn phải sử dụng các chức năng đặc biệt để tải và lưu trữ dữ liệu vào bộ nhớ chưa được phân bổ. Điều đó có nghĩa là hai hướng dẫn thêm.

Nếu bạn không làm việc với các thuật toán quan trọng về hiệu năng, hãy quên đi việc sắp xếp bộ nhớ. Nó không thực sự cần thiết cho lập trình bình thường.


1

Chúng ta có xu hướng tránh các tình huống mà nó quan trọng. Nếu nó quan trọng, nó quan trọng. Dữ liệu không được phân bổ được sử dụng để xảy ra, ví dụ như khi xử lý dữ liệu nhị phân, dường như ngày nay có thể tránh được (mọi người sử dụng XML hoặc JSON rất nhiều).

NẾU bạn bằng cách nào đó quản lý để tạo ra một mảng số nguyên không được sắp xếp, thì trên bộ xử lý intel điển hình, quá trình xử lý mã của bạn sẽ chạy chậm hơn một chút so với dữ liệu được căn chỉnh. Trên bộ xử lý ARM, nó chạy chậm hơn một chút nếu bạn báo cho trình biên dịch dữ liệu không được phân bổ. Nó có thể chạy chậm khủng khiếp, chậm hơn rất nhiều hoặc cho kết quả sai, tùy thuộc vào kiểu máy xử lý và hệ điều hành, nếu bạn sử dụng dữ liệu không được phân bổ mà không báo cho trình biên dịch.

Giải thích tham chiếu đến C ++: Trong C, tất cả các trường trong một cấu trúc phải được lưu trữ theo thứ tự bộ nhớ tăng dần. Vì vậy, nếu bạn có các trường char / double / char và muốn mọi thứ được căn chỉnh, bạn sẽ có một byte char, bảy byte không được sử dụng, tám byte đôi, một byte char, bảy byte không được sử dụng. Trong cấu trúc C ++, nó giống nhau về khả năng tương thích. Nhưng đối với các cấu trúc, trình biên dịch có thể sắp xếp lại các trường, do đó bạn có thể có một byte char, một byte byte khác, sáu byte không được sử dụng, 8 byte kép. Sử dụng 16 thay vì 24 byte. Trong cấu trúc C, các nhà phát triển thường sẽ tránh tình huống đó và có các trường theo thứ tự khác ở vị trí đầu tiên.


1
Dữ liệu không được phân bổ xảy ra trong bộ nhớ. Các chương trình không có cấu trúc dữ liệu được đóng gói đúng cách có thể phải chịu các hình phạt hiệu suất lớn đối với việc sắp xếp các giá trị dường như không hợp lý. Ví dụ, trong mã lthreaded, hai giá trị trong một dòng bộ đệm sẽ gây ra các đường ống lớn khi hai luồng truy cập chúng cùng một lúc (tất nhiên bỏ qua các vấn đề an toàn của luồng).
greyfade

Trình biên dịch C ++ chỉ có thể sắp xếp lại các trường trong một số điều kiện nhất định, điều này có thể không được đáp ứng nếu bạn không biết các quy tắc đó. Trên hết, tôi không biết bất kỳ trình biên dịch C ++ nào thực sự sử dụng quyền tự do này.
Sjoerd

1
Tôi chưa bao giờ thấy một trình biên dịch C sắp xếp lại các trường. Tôi đã thấy nhiều phần đệm chèn và căn chỉnh giữa các ký tự / ints chẳng hạn ..
PaulHK

1

Nhiều điểm tốt đã được đề cập trong câu trả lời ở trên. Chỉ cần thêm ngay cả trong các hệ thống không nhúng liên quan đến tìm kiếm / khai thác dữ liệu hiệu năng của các vấn đề bộ nhớ và thời gian truy cập rất quan trọng đến mức khác với mã lắp ráp căn chỉnh được viết cho cùng.

Tôi cũng khuyên bạn nên đọc một cách đáng giá: http://dewaele.org/~robbe/thesis/wr/references/what-every-programmer-should-ledge-about-memory.2007.pdf


1

Làm thế nào quan trọng là liên kết bộ nhớ? Nó vẫn còn quan trọng?

Đúng. Không. Nó phụ thuộc.

Ngoài hệ thống nhúng, chúng ta thường có bộ nhớ lớn trong máy tính khiến việc quản lý bộ nhớ ít bị chỉ trích hơn, tôi hoàn toàn tối ưu hóa, nhưng bây giờ, nó thực sự có thể tạo ra sự khác biệt nếu chúng ta so sánh cùng một chương trình với hoặc không có bộ nhớ được sắp xếp lại và căn chỉnh?

Ứng dụng của bạn sẽ có dung lượng bộ nhớ nhỏ hơn và hoạt động nhanh hơn nếu được căn chỉnh chính xác. Trong ứng dụng máy tính để bàn thông thường, sẽ không có vấn đề gì ngoài các trường hợp hiếm gặp / không điển hình (như ứng dụng của bạn luôn kết thúc với cùng một nút cổ chai hiệu năng và yêu cầu tối ưu hóa). Nghĩa là, ứng dụng sẽ nhỏ hơn và nhanh hơn nếu được căn chỉnh chính xác, nhưng đối với hầu hết các trường hợp thực tế, nó không nên ảnh hưởng đến người dùng bằng cách này hay cách khác.

Là liên kết bộ nhớ có lợi thế khác? Tôi đã đọc ở đâu đó rằng CPU hoạt động tốt hơn / nhanh hơn với bộ nhớ được căn chỉnh bởi vì cần ít hướng dẫn hơn để xử lý (nếu một trong hai bạn có một liên kết cho một bài viết / điểm chuẩn về nó?), Trong trường hợp đó, sự khác biệt có thực sự đáng kể? Có nhiều lợi thế hơn hai?

Nó có thể. Đó là điều cần (có thể) ghi nhớ trong khi viết mã, nhưng trong hầu hết các trường hợp, điều đó đơn giản là không quan trọng (nghĩa là tôi vẫn sắp xếp các biến thành viên của mình theo dấu chân bộ nhớ và tần số truy cập - để giảm bớt bộ nhớ đệm - nhưng tôi làm vậy dễ sử dụng / đọc và tái cấu trúc mã, không nhằm mục đích lưu trữ).

Bạn có biết làm thế nào để căn chỉnh bộ nhớ hoạt động chính xác trong C ++ vì nó dường như có một số khác biệt?

Tôi đã đọc về nó khi các công cụ alignof xuất hiện (C ++ 11?) Tôi không bận tâm đến nó vì (tôi đang làm hầu hết các ứng dụng máy tính để bàn và phát triển máy chủ phụ trợ ngày nay).

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.