2017-07-21 17 views
20

Tình hình chungLàm thế nào để mong muốn cam kết cấp phát bộ nhớ trong C + +?

Một ứng dụng cực kỳ chuyên sâu trên cả hai băng thông, sử dụng CPU, GPU và sử dụng cần phải chuyển về 10-15GB mỗi giây từ một GPU khác. Nó đang sử dụng API DX11 để truy cập GPU, do đó việc tải lên GPU chỉ có thể xảy ra với các bộ đệm yêu cầu ánh xạ cho mỗi lần tải lên duy nhất. Quá trình tải lên diễn ra theo khối lượng 25MB tại một thời điểm và 16 luồng đang ghi bộ đệm để ánh xạ bộ đệm đồng thời. Không có nhiều điều có thể được thực hiện về bất kỳ điều này. Mức độ đồng thời thực tế của các bài viết nên thấp hơn, nếu nó không phải vì lỗi sau.

Đó là một trạm làm việc mạnh mẽ với 3 GPU Pascal, bộ vi xử lý Haswell cao cấp và RAM bốn kênh. Không nhiều có thể được cải thiện trên phần cứng. Nó đang chạy một phiên bản máy tính để bàn của Windows 10.

Vấn đề thực tế

Khi tôi vượt qua ~ 50% CPU tải, một cái gì đó trong MmPageFault() (bên trong nhân Windows, được gọi là khi truy cập vào bộ nhớ đã được ánh xạ vào của bạn không gian địa chỉ, nhưng không được hệ điều hành cam kết) phá vỡ khủng khiếp, và 50% tải CPU còn lại đang bị lãng phí trên một spin-lock bên trong MmPageFault(). CPU sẽ được sử dụng 100% và hiệu năng của ứng dụng sẽ giảm hoàn toàn.

Tôi phải giả định rằng điều này là do số lượng bộ nhớ khổng lồ cần được phân bổ cho quá trình mỗi giây và cũng hoàn toàn không được ánh xạ từ quá trình mỗi khi bộ đệm DX11 chưa được ánh xạ. Tương ứng, nó thực sự là hàng ngàn cuộc gọi đến MmPageFault() mỗi giây, xảy ra tuần tự như memcpy() được viết tuần tự cho bộ đệm. Đối với mỗi trang không được cam kết gặp phải.

Một tải CPU vượt quá 50%, khóa spin lạc quan trong hạt nhân Windows bảo vệ quản lý trang hoàn toàn làm giảm hiệu suất một cách khôn ngoan.

cân nhắc

Bộ đệm được phân bổ bởi người lái xe DX11. Không có gì có thể được điều chỉnh về chiến lược phân bổ. Sử dụng một bộ nhớ khác nhau API và đặc biệt là tái sử dụng là không thể.

Cuộc gọi tới API DX11 (ánh xạ/hủy ánh xạ bộ đệm) tất cả xảy ra từ một chuỗi đơn lẻ. Các hoạt động sao chép thực tế có thể xảy ra đa luồng trên nhiều luồng hơn là có các bộ xử lý ảo trong hệ thống.

Giảm yêu cầu băng thông bộ nhớ là không thể. Đó là một ứng dụng thời gian thực. Trong thực tế, giới hạn cứng hiện là băng thông PCIe 3.0 16x của GPU chính. Nếu có thể, tôi sẽ cần phải đẩy mạnh hơn nữa.

Tránh không thể sao chép đa luồng, vì có hàng đợi người tiêu dùng sản xuất độc lập không thể hợp nhất một cách tầm thường.

Sự xuống cấp hiệu suất của khóa spin dường như quá hiếm (vì trường hợp sử dụng đang đẩy nó xa) trên Google, bạn sẽ không tìm thấy kết quả duy nhất cho tên của chức năng khóa quay.

Nâng cấp lên API cung cấp quyền kiểm soát nhiều hơn đối với ánh xạ (Vulkan) đang được tiến hành nhưng không phù hợp với tư cách là bản sửa lỗi ngắn hạn. Chuyển sang một hạt nhân hệ điều hành tốt hơn hiện không phải là một lựa chọn cho cùng một lý do.

Giảm tải CPU cũng không hoạt động; có quá nhiều công việc cần được thực hiện ngoài bản sao đệm (thường tầm thường và không tốn kém).

Các Câu hỏi

Có thể làm gì?

Tôi cần giảm đáng kể số lượng pagefaults riêng lẻ. Tôi biết địa chỉ và kích thước của bộ đệm đã được ánh xạ vào quá trình của tôi, và tôi cũng biết rằng bộ nhớ chưa được cam kết.

Làm cách nào để đảm bảo rằng bộ nhớ được cam kết với số lượng giao dịch ít nhất có thể?

Cờ lạ cho DX11 sẽ ngăn không phân bổ bộ đệm sau khi hủy ánh xạ, Windows API để bắt buộc thực hiện giao dịch đơn lẻ, khá nhiều điều được hoan nghênh.

Trạng thái hiện tại

// In the processing threads 
{ 
    DX11DeferredContext->Map(..., &buffer) 
    std::memcpy(buffer, source, size); 
    DX11DeferredContext->Unmap(...); 
} 
+1

có vẻ như bạn đang ở khoảng 400 M cho tất cả 16 luồng cùng nhau. Khá thấp. Bạn có thể xác minh rằng bạn không vượt quá điều này trong ứng dụng của bạn? Tiêu thụ bộ nhớ là gì? Tôi tự hỏi nếu bạn có một rò rỉ bộ nhớ. – Serge

+0

Mức tiêu thụ tối đa là khoảng 7-8GB, nhưng điều đó là bình thường, cho rằng tổng số đường ống xử lý cần> 1s đệm để bù đắp cho tất cả các loại tắc nghẽn. Có, đó là "chỉ" 400MB, 25 lần mỗi giây. Và nó hoạt động tốt, cho đến khi tải CPU cơ bản vượt quá 50% và hiệu năng của khóa spin đột ngột tăng đột biến từ 0 đến ~ 40-50% của việc sử dụng CPU hoàn chỉnh. Cũng ảnh hưởng đến các quá trình khác trên hệ thống cùng một lúc. – Ext3h

+1

1. Bộ nhớ vật lý của bạn là gì? bạn có thể giết tất cả các quy trình hoạt động khác không? 2. đoán # 2 kể từ khi bạn nhìn thấy ngưỡng 50%, bạn có thể nhận được vào một số vấn đề với siêu phân luồng. Bạn có bao nhiêu lõi vật lý? số 8? Bạn có thể vô hiệu hóa siêu phân luồng không? Cố gắng chạy như nhiều chủ đề như có cpus vật lý trong trường hợp của bạn trên một máy sạch. – Serge

Trả lời

11

workaround hiện tại, đơn giản hóa mã giả:

// During startup 
{ 
    SetProcessWorkingSetSize(GetCurrentProcess(), 2*1024*1024*1024, -1); 
} 
// In the DX11 render loop thread 
{ 
    DX11context->Map(..., &resource) 
    VirtualLock(resource.pData, resource.size); 
    notify(); 
    wait(); 
    DX11context->Unmap(...); 
} 
// In the processing threads 
{ 
    wait(); 
    std::memcpy(buffer, source, size); 
    signal(); 
} 

VirtualLock() buộc hạt nhân để sao lưu các dải địa chỉ cụ thể với RAM ngay lập tức. Các cuộc gọi đến bổ sung chức năng VirtualUnlock() là tùy chọn, nó xảy ra ngầm (và không có thêm chi phí) khi phạm vi địa chỉ được unmapped từ quá trình này. (Nếu gọi một cách rõ ràng, chi phí khoảng 1/3 của chi phí khóa.)

Để VirtualLock() làm việc ở tất cả, SetProcessWorkingSetSize() cần phải được gọi đầu tiên, là tổng của tất cả các vùng bộ nhớ bị khóa bởi VirtualLock() không thể vượt quá kích thước thiết lập làm việc tối thiểu được cấu hình cho quy trình. Đặt kích thước bộ làm việc "tối thiểu" thành thứ gì đó cao hơn dấu chân bộ nhớ cơ bản của quá trình của bạn không có tác dụng phụ trừ khi hệ thống của bạn thực sự có khả năng hoán đổi, quá trình của bạn sẽ vẫn không tiêu thụ RAM nhiều hơn kích thước bộ làm việc thực tế.


Chỉ cần sử dụng VirtualLock(), mặc dù trong đề cá nhân và sử dụng bối cảnh DX11 hoãn cho Map/Unmap cuộc gọi, đã ngay lập tức giảm hình phạt hiệu suất từ ​​40-50% xuống còn hơi dễ chấp nhận hơn 15%.

Loại bỏ việc sử dụng một bối cảnh thu nhập hoãn lại, và độc quyền kích hoạt cả tất cả những lỗi lầm mềm, cũng như de-phân bổ tương ứng khi unmapping trên một chủ đề duy nhất, đã cho tăng hiệu suất cần thiết. Tổng chi phí của khóa quay đó hiện giảm xuống còn < 1% tổng mức sử dụng CPU.


Tóm tắt?

Khi bạn mong đợi các lỗi mềm trên Windows, hãy thử những gì bạn có thể để giữ tất cả chúng trong cùng một chuỗi. Thực hiện một song song memcpy chính nó là không có vấn đề, trong một số trường hợp thậm chí cần thiết để sử dụng đầy đủ băng thông bộ nhớ. Tuy nhiên, đó là chỉ khi bộ nhớ đã được cam kết RAM. VirtualLock() là cách hiệu quả nhất để đảm bảo điều đó.

(Trừ khi bạn đang làm việc với một API như DirectX để ánh xạ bộ nhớ vào quá trình của bạn, bạn có thể không gặp phải bộ nhớ không bị ảnh hưởng thường xuyên. Nếu bạn đang làm việc với tiêu chuẩn C++ new hoặc malloc bộ nhớ của bạn được gộp lại và tái chế trong quá trình của bạn dù sao thì các lỗi mềm rất hiếm.)

Chỉ cần đảm bảo tránh mọi dạng lỗi trang đồng thời khi làm việc với Windows.

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