Sử dụng thực tế từ khóa `stackalloc`


134

Có ai đã từng thực sự sử dụng stackallockhi lập trình trong C # chưa? Tôi nhận thức được những gì đang làm, nhưng lần duy nhất nó xuất hiện trong mã của tôi là do tình cờ, vì Intellisense gợi ý nó khi tôi bắt đầu nhập static, chẳng hạn.

Mặc dù nó không liên quan đến các kịch bản sử dụng stackalloc, nhưng tôi thực sự đã thực hiện một số lượng đáng kể di sản trong các ứng dụng của mình, vì vậy thỉnh thoảng tôi có thể sử dụng unsafemã. Nhưng tuy nhiên tôi thường tìm cách tránh unsafehoàn toàn.

Và vì kích thước ngăn xếp cho một luồng trong .Net là ~ 1Mb (sửa tôi nếu tôi sai), tôi thậm chí còn dè dặt hơn khi sử dụng stackalloc.

Có một số trường hợp thực tế mà người ta có thể nói: "đây chính xác là lượng dữ liệu và xử lý phù hợp để tôi không an toàn và sử dụng stackalloc"?


5
chỉ nhận thấy rằng System.Numberssử dụng nó rất nhiều referencesource.microsoft.com/#mscorlib/system/...
Slai

Câu trả lời:


150

Lý do duy nhất để sử dụng stackalloclà hiệu suất (cho tính toán hoặc interop). Bằng cách sử dụng stackallocthay vì mảng được phân bổ heap, bạn tạo ra áp lực GC ít hơn (GC cần chạy ít hơn), bạn không cần phải ghim mảng xuống, phân bổ nhanh hơn mảng heap, nó được tự động giải phóng trên phương thức thoát (mảng được phân bổ heap chỉ được giải phóng khi GC chạy). Ngoài ra, bằng cách sử dụng stackallocthay vì bộ phân bổ riêng (như malloc hoặc .Net tương đương), bạn cũng có được tốc độ và tự động phân bổ khi thoát khỏi phạm vi.

Hiệu suất khôn ngoan, nếu bạn sử dụng, stackallocbạn sẽ tăng đáng kể khả năng truy cập bộ nhớ cache trên CPU do tính cục bộ của dữ liệu.


26
Địa phương của dữ liệu, điểm tốt! Đó là những gì bộ nhớ được quản lý sẽ hiếm khi đạt được khi bạn muốn phân bổ một số cấu trúc hoặc mảng. Cảm ơn!
Groo

22
Phân bổ heap thường nhanh hơn cho các đối tượng được quản lý so với không được quản lý vì không có danh sách miễn phí để duyệt qua; CLR chỉ tăng con trỏ heap. Đối với địa phương, phân bổ tuần tự có nhiều khả năng kết thúc được tạo ra cho các quy trình được quản lý chạy dài vì nén heap.
Shea

1
"phân bổ nhanh hơn một mảng heap" Tại sao vậy? Chỉ là địa phương? Dù bằng cách nào, đó chỉ là một cú va chạm, không?
Max Barraclough

2
@MaxBarraclough Bởi vì bạn thêm chi phí GC để phân bổ heap trong suốt vòng đời ứng dụng. Tổng chi phí phân bổ = phân bổ + thỏa thuận, trong trường hợp này là vết gập con trỏ + Heap GC, so với vết gập con trỏ + ngăn giảm con trỏ Stack
Pop Catalin

35

Tôi đã sử dụng stackalloc để phân bổ bộ đệm cho [gần] công việc DSP thời gian thực. Đó là một trường hợp rất cụ thể mà hiệu suất cần phải nhất quán nhất có thể. Lưu ý rằng có một sự khác biệt giữa tính nhất quán và thông lượng tổng thể - trong trường hợp này tôi không quan tâm đến việc phân bổ heap quá chậm, chỉ với tính không xác định của việc thu gom rác tại thời điểm đó trong chương trình. Tôi sẽ không sử dụng nó trong 99% trường hợp.


25

stackallocchỉ liên quan đến mã không an toàn. Đối với mã được quản lý, bạn không thể quyết định nơi phân bổ dữ liệu. Các loại giá trị được phân bổ trên ngăn xếp trên mỗi mặc định (trừ khi chúng là một phần của loại tham chiếu, trong trường hợp đó chúng được phân bổ trên heap). Các loại tham chiếu được phân bổ trên heap.

Kích thước ngăn xếp mặc định cho ứng dụng vanilla .NET đơn giản là 1 MB, nhưng bạn có thể thay đổi điều này trong tiêu đề PE. Nếu bạn bắt đầu các chủ đề một cách rõ ràng, bạn cũng có thể đặt kích thước khác thông qua quá tải hàm tạo. Đối với các ứng dụng ASP.NET, kích thước ngăn xếp mặc định chỉ là 256K, đây là điều cần lưu ý nếu bạn chuyển đổi giữa hai môi trường.


Có thể thay đổi kích thước ngăn xếp mặc định từ Visual Studio?
cấu hình

@configurator: Không xa như tôi biết.
Brian Rasmussen

17

Khởi tạo Stackalloc của nhịp. Trong các phiên bản trước của C #, kết quả của stackalloc chỉ có thể được lưu trữ vào một biến cục bộ con trỏ. Kể từ C # 7.2, stackalloc hiện có thể được sử dụng như một phần của biểu thức và có thể nhắm mục tiêu một khoảng, và điều đó có thể được thực hiện mà không cần sử dụng từ khóa không an toàn. Như vậy, thay vì viết

Span<byte> bytes;
unsafe
{
  byte* tmp = stackalloc byte[length];
  bytes = new Span<byte>(tmp, length);
}

Bạn có thể viết đơn giản:

Span<byte> bytes = stackalloc byte[length];

Điều này cũng cực kỳ hữu ích trong các trường hợp bạn cần một số dung lượng đầu để thực hiện thao tác, nhưng muốn tránh phân bổ bộ nhớ heap cho kích thước tương đối nhỏ

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>

Nguồn: C # - All About Span: Khám phá Mainstay .NET mới


4
Cảm ơn vì tiền hỗ trợ. Có vẻ như mỗi phiên bản mới của C # gần gũi hơn với C ++, đây thực sự là một điều tốt IMHO.
Groo

1
Như có thể thấy ở đâyđây , Spanlà than ôi không có sẵn trong .NET framework 4.7.2 và thậm chí không có trong 4.8 ... Vì vậy, tính năng ngôn ngữ mới này vẫn còn hạn chế sử dụng trong thời điểm này.
Frederic

2

Có một số câu trả lời tuyệt vời trong câu hỏi này nhưng tôi chỉ muốn chỉ ra rằng

Stackalloc cũng có thể được sử dụng để gọi API gốc

Nhiều hàm riêng yêu cầu người gọi phân bổ bộ đệm để có kết quả trả về. Ví dụ: hàm CfGetPlaceholderInfocfapi.h có chữ ký sau.

HRESULT CfGetPlaceholderInfo(
HANDLE                    FileHandle,
CF_PLACEHOLDER_INFO_CLASS InfoClass,
PVOID                     InfoBuffer,
DWORD                     InfoBufferLength,
PDWORD                    ReturnedLength);

Để gọi nó trong C # thông qua interop,

[DllImport("Cfapi.dll")]
public static unsafe extern HResult CfGetPlaceholderInfo(IntPtr fileHandle, uint infoClass, void* infoBuffer, uint infoBufferLength, out uint returnedLength);

Bạn có thể sử dụng stackalloc.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
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.