`stackalloc` 키워드의 실제 사용


134

stackallocC #에서 프로그래밍 하는 동안 실제로 사용한 사람이 있습니까? 내가하는 일을 알고 있지만 코드에 표시되는 유일한 시간은 실수로 발생하는 것 static입니다. 예를 들어 Intellisense는 입력을 시작할 때 제안하기 때문 입니다.

의 사용 시나리오와 관련이 없지만 stackalloc실제로는 앱에서 상당한 양의 레거시 interop을 수행하므로 매번 unsafe코드 사용에 의지 할 수 있습니다. 그럼에도 불구하고 나는 보통 unsafe완전히 피하는 방법을 찾습니다 .

그리고 .Net의 단일 스레드에 대한 스택 크기는 ~ 1Mb (잘못된 경우 수정)이므로을 사용하지 않아도됩니다 stackalloc.

"이것은 내가 안전하지 않고 사용할 수있는 정확한 양의 데이터와 처리 방법"이라고 말할 수있는 실용적인 사례가 stackalloc있습니까?


답변:


150

사용하는 유일한 이유 stackalloc는 성능 (계산 또는 interop)입니다. stackalloc힙 할당 배열 대신 사용 하면 GC 압력이 줄어들고 (GC를 덜 실행해야 함) 배열을 고정 할 필요가 없으며 힙 배열보다 할당 속도가 빠르며 메소드에서 자동으로 해제됩니다. exit (힙 할당 된 배열은 GC가 실행될 때만 할당 해제됩니다). 또한 stackallocmalloc 또는 .Net과 같은 기본 할당 자 대신 스코프 종료시 속도와 자동 할당 해제를 얻습니다.

성능면에서는 stackalloc데이터의 지역성으로 인해 CPU에서 캐시 적중 가능성이 크게 증가합니다.


26
데이터의 지역성, 좋은 지적! 여러 구조 또는 배열을 할당하려고 할 때 관리되는 메모리가 거의 달성하지 못하는 것입니다. 감사!
— Groo

22
힙 할당은 일반적으로 트래킹 할 사용 가능한 목록이 없기 때문에 관리 대상 객체에 비해 관리 대상 객체보다 빠릅니다. CLR은 단지 힙 포인터를 증가시킵니다. 지역의 경우 순차적 압축은 힙 압축으로 인해 장기 실행 관리 프로세스에 대해 배치 될 가능성이 높습니다.
— Shea

1
"힙 어레이보다 할당하는 것이 더 빠릅니다"왜 그런가요? 그냥 지역? 어느 쪽이든 포인터 범프 일뿐입니다.
— Max Barraclough

2
@MaxBarraclough 응용 프로그램 수명 동안 힙 할당에 GC 비용을 추가하기 때문입니다. 총 할당 비용 = 할당 + 할당 해제,이 경우 포인터 범프 + GC 힙, 포인터 범프 + 포인터 감소 스택
— Pop Catalin

35

나는 거의 실시간 DSP 작업을 위해 버퍼를 할당하기 위해 stackalloc을 사용했다. 성능이 최대한 일관되어야하는 것은 매우 구체적인 사례였습니다. 일관성과 전체 처리량 사이에는 차이가 있습니다.이 경우 힙 할당이 너무 느리지 않고 프로그램의 해당 시점에서 가비지 수집의 결정적이지 않기 때문에 걱정하지 않았습니다. 99 %의 경우에는 사용하지 않을 것입니다.


25

stackalloc안전하지 않은 코드에만 관련됩니다. 관리 코드의 경우 데이터를 할당 할 위치를 결정할 수 없습니다. 값 유형은 기본값으로 스택에 할당됩니다 (값이 참조 유형의 일부가 아닌 경우 힙에 할당되지 않은 경우). 참조 유형은 힙에 할당됩니다.

일반 바닐라 .NET 애플리케이션의 기본 스택 크기는 1MB이지만 PE 헤더에서이를 변경할 수 있습니다. 스레드를 명시 적으로 시작하는 경우 생성자 오버로드를 통해 다른 크기를 설정할 수도 있습니다. ASP.NET 응용 프로그램의 경우 기본 스택 크기는 256K에 불과하므로 두 환경간에 전환하는 경우 명심해야합니다.


Visual Studio에서 기본 스택 크기를 변경할 수 있습니까?
— 구성자

@ configurator : 내가 아는 한.
— Brian Rasmussen

17

스팬의 스택 할당 초기화. 이전 버전의 C #에서는 stackalloc의 결과를 포인터 로컬 변수에만 저장할 수있었습니다. C # 7.2부터 stackalloc을 표현식의 일부로 사용할 수 있으며 범위를 대상으로 할 수 있으며 unsafe 키워드를 사용하지 않고도 수행 할 수 있습니다. 따라서 글을 쓰는 대신

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

간단하게 작성할 수 있습니다.

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

이것은 작업을 수행하기 위해 약간의 스크래치 공간이 필요하지만 비교적 작은 크기의 힙 메모리 할당을 피하려는 경우에도 매우 유용합니다.

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

출처 : C #-스팬 정보 : 새로운 .NET 메인 스테이 탐색


4
팁 고마워. C #의 각 새 버전은 C ++에 조금 더 가까워지는 것 같습니다. 실제로 IMHO가 좋습니다.
— Groo

1
여기 와 여기 에서 볼 수 있듯이 Span.NET 프레임 워크 4.7.2에서는 사용할 수 없으며 4.8에서는 사용할 수 없습니다 ... 따라서 새로운 언어 기능은 현재로서는 제한적으로 사용됩니다.
— Frederic

2

이 질문에는 훌륭한 답변이 있지만 그 점을 지적하고 싶습니다.

Stackalloc을 사용하여 네이티브 API를 호출 할 수도 있습니다.

많은 기본 함수는 호출자가 리턴 결과를 얻기 위해 버퍼를 할당해야합니다. 예를 들어 CfGetPlaceholderInfo 함수의 cfapi.h서명은 다음과 같습니다.

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

interop을 통해 C #으로 호출하려면

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

stackalloc을 사용할 수 있습니다.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.