스택 크기가 50 배인 스레드를 기본값으로 만들 때 어떤 위험이 있습니까?


228

나는 현재 매우 성능이 중요한 프로그램을 연구 중이며 리소스 소비를 줄이는 데 도움이 될 수있는 하나의 경로로 작업자 스레드의 스택 크기를 늘리고 있으므로 액세스 할 데이터의 대부분을 이동할 float[]수 있습니다 스택 (을 사용하여 stackalloc).

나는 한 읽기 스레드의 기본 스택 크기가 너무 내 모든 이동하기 위해, 1MB입니다 것을 float[]내가 (50메가바이트 ~에) 약 50 배 스택을 확장해야 할 것들.

나는 이것이 일반적으로 "안전하지 않은"것으로 간주되고 권장되지 않는다는 것을 이해하지만이 방법에 대해 현재 코드를 벤치 마크 한 후 처리 속도 530 % 증가 함을 ! 따라서 추가 조사없이이 옵션을 간단히 통과 할 수 없기 때문에 내 질문으로 이어집니다. 스택을 그렇게 큰 크기로 늘리는 것과 관련된 위험은 무엇이며 (잘못 될 수있는 것), 그러한 위험을 최소화하기 위해 어떤 예방책을 취해야합니까?

내 테스트 코드

public static unsafe void TestMethod1()
{
    float* samples = stackalloc float[12500000];

    for (var ii = 0; ii < 12500000; ii++)
    {
        samples[ii] = 32768;
    }
}

public static void TestMethod2()
{
    var samples = new float[12500000];

    for (var i = 0; i < 12500000; i++)
    {
        samples[i] = 32768;
    }
}

98
+1. 진심으로. 당신은 무엇을 확인하십시오 당신은 표준 밖에서 바보 같은 질문을하고 당신은 당신의 특정 시나리오에서 숙제를하고 결과를 측정하기 때문에 고려하는 것이 합리적이라고 매우 좋은 경우를 만듭니다. 이것은 매우 좋습니다-나는 많은 질문으로 그것을 그리워합니다. 아주 좋았습니다-슬프게도 많은 C # 프로그래머는 이러한 기회를 알지 못합니다. 그렇습니다. 종종 필요하지는 않지만 때로는 중요하고 휴를 변화시킵니다.
TomTom

5
어레이에서 스택으로 이동하는 것만으로 처리 속도가 530 % 차이가 나는 두 코드를보고 싶습니다. 그것은 단지 옳지 않은 느낌입니다.
Dialecticus

13
그 길을 뛰어 넘기 전에 : 관리되는 메모리 외부 에 데이터를 할당 Marshal.AllocHGlobal하기 위해 ( FreeHGlobal너무도 잊지 마십시오 ) 사용해 보셨습니까 ? 그런 다음 포인터를로 캐스팅하면 정렬해야합니다. float*
Marc Gravell

2
많은 할당을하면 기분이 좋아집니다. Stackalloc은 프로세서 수준에서 매우 강력한 로컬 리티를 생성 / 생성 할 수있는 모든 GC 문제를 무시합니다. 이것은 고성능 수학적 프로그램을 작성하고 정확하게이 동작을 수행하지 않고 차이를 만들지 않는 한 마이크로 최적화처럼 보이는 것 중 하나입니다.
TomTom

6
내 의혹 :이 방법 중 하나는 모든 루프 반복에서 경계 검사를 트리거하는 반면 다른 방법은 그렇지 않거나 최적화되지 않습니다.
pjc50

답변:


45

테스트 코드를 Sam과 비교할 때 우리 모두가 옳았다 고 판단했습니다!
그러나 다른 것들에 대해 :

  • 메모리 액세스 (읽기 및 쓰기)는 스택, 전역 또는 힙 등 어디에서나 빠릅니다 .
  • 그러나이를 할당 하는 것은 스택에서 가장 빠르며 힙에서 가장 느립니다.

다음과 같이 진행됩니다 : stack<global < heap. (할당 시간)
기술적으로 스택 할당은 실제로 할당이 아니며 런타임은 스택의 일부 (프레임?)가 배열에 예약되어 있는지 확인합니다.

그래도 나는 이것을 조심하는 것이 좋습니다.
다음을 권장합니다.

  1. 자주 배열을 만들어야 할 때 함수를 떠나지 경우 (예 : 참조를 전달하여) 스택을 사용하면 크게 향상됩니다.
  2. 어레이를 재활용 할 수 있다면 가능할 때마다 재활용하십시오! 힙은 장기 오브젝트 스토리지에 가장 적합한 장소입니다. (글로벌 메모리를 오염시키는 것은 좋지 않습니다; 스택 프레임이 사라질 수 있습니다)

( 참고 : 1. 값 유형에만 적용됩니다. 참조 유형은 힙에 할당되며 혜택은 0으로 줄어 듭니다)

질문 자체에 대답하기 위해 : 대형 스택 테스트에서 전혀 문제가 발생하지 않았습니다.
시스템이 부족한 경우 스레드를 만들 때 함수 호출에주의하고 메모리가 부족하면 스택 오버플로가 발생할 수있는 유일한 문제라고 생각합니다.

아래 섹션은 나의 초기 답변입니다. 잘못된 것으로 테스트가 정확하지 않습니다. 참조 용으로 만 유지됩니다.


내 테스트는 스택 할당 메모리와 전역 메모리가 어레이에서 사용하기 위해 힙 할당 메모리보다 적어도 15 % 느리다는 것을 나타냅니다 (시간 120 % 소요)!

이것은 내 테스트 코드 이며 샘플 출력입니다.

Stack-allocated array time: 00:00:00.2224429
Globally-allocated array time: 00:00:00.2206767
Heap-allocated array time: 00:00:00.1842670
------------------------------------------
Fastest: Heap.

  |    S    |    G    |    H    |
--+---------+---------+---------+
S |    -    | 100.80 %| 120.72 %|
--+---------+---------+---------+
G |  99.21 %|    -    | 119.76 %|
--+---------+---------+---------+
H |  82.84 %|  83.50 %|    -    |
--+---------+---------+---------+
Rates are calculated by dividing the row's value to the column's.

.NET 4.5.1에서 i7 4700 MQ를 사용하여 Windows 8.1 Pro (업데이트 1 포함)에서 테스트했습니다.
테스트했으며 x86 및 x64로 테스트했으며 결과는 동일합니다.

편집 : 모든 스레드 201MB의 스택 크기를 늘리고 샘플 크기를 5 천만으로 늘리고 반복을 5로 줄였습니다
. 결과는 위와 같습니다.

Stack-allocated array time: 00:00:00.4504903
Globally-allocated array time: 00:00:00.4020328
Heap-allocated array time: 00:00:00.3439016
------------------------------------------
Fastest: Heap.

  |    S    |    G    |    H    |
--+---------+---------+---------+
S |    -    | 112.05 %| 130.99 %|
--+---------+---------+---------+
G |  89.24 %|    -    | 116.90 %|
--+---------+---------+---------+
H |  76.34 %|  85.54 %|    -    |
--+---------+---------+---------+
Rates are calculated by dividing the row's value to the column's.

그러나 스택이 실제로 느려지 는 것처럼 보입니다 .


나는에 따라, 동의 할 거라고 내 벤치 마크의 결과 (결과 페이지의 하단에 코멘트를 참조) 쇼 스택이 더 빨리 힙에 비해 소폭 빠른 글로벌보다, 그이다; 내 결과가 정확하다는 것을 확실히하기 위해 테스트는 20 번 실행되었으며 각 방법은 테스트 반복마다 100 번 호출되었습니다. 벤치 마크를 정확하게 실행하고 있습니까?
Sam

일관되지 않은 결과가 나타납니다. 완전한 신뢰, x64, 릴리스 구성, 디버거 없음으로 모두 스택 속도가 훨씬 빠르지 만 (1 % 미만의 차이; 변동). 추가 테스트가 필요합니다! 편집 : 스택 오버플로 예외가 발생해야합니다. 배열에 충분히 할당하면됩니다. O_o
Vercas 2018 년

그래, 알아 내가했던 것처럼 벤치 마크를 몇 번 반복해야 할 수도 있습니다. 아마 평균 5 회 이상 실행하십시오.
Sam

1
@Voo 첫 번째 실행은 100 번째 테스트만큼이나 많은 시간이 걸렸습니다. 내 경험상이 Java JIT는 .NET에 전혀 적용되지 않습니다. .NET이하는 유일한 "웜업"은 처음 사용될 때 클래스와 어셈블리를로드하는 것입니다.
Vercas

2
@Voo 내 벤치 마크와 그가이 답변에 대한 코멘트에 추가 한 요 지점을 테스트하십시오. 코드를 함께 모으고 수백 가지 테스트를 실행하십시오. 그런 다음 돌아와서 결론을보고하십시오. 나는 테스트를 매우 철저히 수행했으며 .NET은 Java와 같은 바이트 코드를 해석하지 않으며 즉시 JIT합니다.
Vercas

28

처리 속도가 530 % 증가했습니다.

그것은 내가 말한 가장 큰 위험입니다. 벤치 마크에 심각한 문제가 있습니다.이 코드를 예측할 수없는 코드는 어딘가에 숨겨진 버그가 있습니다.

과도한 재귀를 제외하고 .NET 프로그램에서 많은 스택 공간을 사용하는 것은 매우 어렵습니다. 관리되는 메소드의 스택 프레임 크기는 스톤으로 설정됩니다. 단순히 메소드의 인수와 메소드의 로컬 변수의 합계입니다. CPU 레지스터에 저장 될 수있는 것을 빼면 무시할 수 있습니다.

스택 크기를 늘려도 아무 것도 달성 할 수 없으며 사용되지 않는 주소 공간을 확보하게됩니다. 물론 메모리를 사용하지 않음으로써 성능 향상을 설명 할 수있는 메커니즘은 없습니다.

이것은 기본 프로그램, 특히 C로 작성된 프로그램과 달리 스택 프레임에 배열을위한 공간을 예약 할 수도 있습니다. 스택 버퍼 뒤의 기본 맬웨어 공격 벡터가 넘칩니다. C #에서도 가능합니다.stackalloc 키워드 . 그렇게하면 명백한 위험은 임의의 스택 프레임 손상뿐만 아니라 그러한 공격에 노출되는 안전하지 않은 코드를 작성해야합니다. 버그를 진단하기가 매우 어렵습니다. 나중에 지터에서 이에 대한 대책이 있습니다. 지터가 .NET 4.0부터 시작하여 스택 프레임에 "쿠키"를 넣는 코드를 생성하고 메소드가 반환 될 때 여전히 쿠키가 손상되지 않았는지 확인합니다. 사고가 발생할 경우이를 가로 채거나보고 할 수있는 방법없이 데스크탑에 즉시 충돌이 발생합니다. 그것은 ... 사용자의 정신 상태에 위험합니다.

운영 체제에서 시작한 프로그램의 기본 스레드는 x64를 대상으로 프로그램을 컴파일 할 때 기본적으로 1MB 스택, 4MB를 갖습니다. 빌드 빌드 후 이벤트에서 / STACK 옵션을 사용하여 Editbin.exe를 실행해야합니다. 32 비트 모드에서 실행할 때 프로그램을 시작하기 전에 일반적으로 최대 500MB를 요청할 수 있습니다. 스레드는 물론 훨씬 쉬울 수 있으며 위험 영역은 일반적으로 32 비트 프로그램의 경우 약 90MB를 가리 킵니다. 프로그램이 오랫동안 실행되고 주소 공간이 이전 할당에서 조각난 경우 트리거됩니다. 이 실패 모드를 얻으려면 전체 주소 공간 사용량이 공연보다 높아야합니다.

코드를 세 번 확인하십시오. 매우 잘못된 것이 있습니다. 명시 적으로 코드를 작성하지 않으면 더 큰 스택으로 x5 속도를 높일 수 없습니다. 항상 안전하지 않은 코드가 필요합니다. C #에서 포인터를 사용하면 항상 더 빠른 코드를 생성 할 수있는 이점이 있으며 배열 범위 검사를받지 않습니다.


21
보고 된 5 배 속도 향상은에서로 이동 float[]했습니다 float*. 큰 스택은 단순히 그것이 달성 된 방법이었습니다. 일부 시나리오에서 x5 속도 향상은 해당 변경에 전적으로 합리적입니다.
Marc Gravell

3
좋아, 질문에 대답하기 시작할 때 아직 코드 스 니펫이 없었습니다. 아직 충분히 가까워
Hans Passant

22

나는 권한, GC (스택을 스캔해야 함) 등을 예측하는 방법을 모르는 예약을 할 것입니다. 모두 영향을받을 수 있습니다. 관리되지 않는 메모리를 대신 사용하고 싶을 것입니다.

var ptr = Marshal.AllocHGlobal(sizeBytes);
try
{
    float* x = (float*)ptr;
    DoWork(x);
}
finally
{
    Marshal.FreeHGlobal(ptr);
}

1
부가 질문 : 왜 GC가 스택을 스캔해야합니까? 에 의해 할당 된 메모리 stackalloc는 가비지 수집 대상이 아닙니다.
dcastro

6
@ dcastro 스택에 존재하는 참조를 확인하려면 스택을 스캔해야합니다. 나는 그것이 그렇게 거대한 것에 도달했을 때 그것이 무엇을할지 모르겠다 stackalloc-그것은 그것을 뛰어 넘을 필요가 있으며, 당신은 그렇게 쉽게 할 수 있기를 희망한다-그러나 내가 만들고자하는 요점은 그것이 소개한다는 것이다. 불필요한 합병증 / 우려. IMO stackalloc는 스크래치 버퍼로 훌륭하지만 전용 작업 공간의 경우 스택을 남용하거나 혼동하지 않고 어딘가에 청크-메모리를 할당하는 것이 더 기대됩니다.
Marc Gravell

8

잘못 될 수있는 한 가지는 그렇게 할 권한이 없다는 것입니다. 완전 신뢰 모드에서 실행하지 않는 한 프레임 워크는 더 큰 스택 크기에 대한 요청을 무시합니다 (MSDN 참조 Thread Constructor (ParameterizedThreadStart, Int32)).

시스템 스택 크기를 엄청난 수로 늘리는 대신 힙에서 반복 및 수동 스택 구현을 사용하도록 코드를 다시 작성하는 것이 좋습니다.


1
좋은 생각입니다. 대신 반복하겠습니다. 그 외에도 코드가 완전 신뢰 모드로 실행 중이므로주의해야 할 사항이 있습니까?
Sam

6

고성능 C는 일반 C #과 같은 방식으로 액세스 할 수 있지만 문제의 시작일 수 있습니다. 다음 코드를 고려하십시오.

float[] someArray = new float[100]
someArray[200] = 10.0;

범위를 벗어난 예외가 예상되며 요소 200에 액세스하려고하지만 허용되는 최대 값은 99이므로이 방법이 완전히 의미가 있습니다. stackalloc 경로로 이동하면 배열 주위에 바운드 검사를하는 객체가 없습니다. 다음은 예외를 나타내지 않습니다.

Float* pFloat =  stackalloc float[100];
fFloat[200]= 10.0;

위에서 당신은 100 개의 float를 저장하기에 충분한 메모리를 할당하고이 메모리의 시작 위치에서 시작하는 sizeof (float) 메모리 위치 + float 값 10을 유지하기 위해 200 * sizeof (float)를 설정하고 있습니다. 플로트에 할당 된 메모리이며 아무도 그 주소에 무엇을 저장할 수 있는지 알 수 없습니다. 운이 좋으면 현재 사용하지 않는 메모리를 사용했을 수도 있지만 동시에 다른 변수를 저장하는 데 사용 된 위치를 덮어 쓸 수도 있습니다. 요약 : 예측할 수없는 런타임 동작.


사실 틀 렸습니다. 런타임 및 컴파일러 테스트는 여전히 있습니다.
TomTom

9
@TomTom erm, 아니오; 대답은 장점이 있습니다. 질문은에 대해 이야기합니다 stackalloc.이 경우 우리는 float*다른 검사에 대해 이야기합니다 -같은 검사가 없습니다. 그것은라고 unsafe아주 좋은 이유. 개인적으로 나는 unsafe좋은 이유가있을 때 사용하기에 완전히 행복 하지만 소크라테스는 합리적인 지적을합니다.
Marc Gravell

@Marc 표시된 코드의 경우 (JIT가 실행 된 후) 컴파일러가 모든 액세스가 인바운드 인 것으로 추론하기 때문에 더 이상 바운드 검사가 없습니다. 일반적으로 이것은 확실히 차이를 만들 수 있습니다.
Voo

6

Java 또는 C #과 같은 JIT 및 GC를 사용한 마이크로 벤치마킹 언어는 약간 복잡 할 수 있으므로 일반적으로 기존 프레임 워크를 사용하는 것이 좋습니다. Java는 mhf 또는 Caliper를 제공합니다. 그들에게 다가가는 것. Jon Skeet는 이것을 썼다 맹목적으로 가장 중요한 것들을 돌보는 것으로 가정했습니다. 워밍업 후 테스트 당 30 초가 내 인내심이 너무 높기 때문에 타이밍을 약간 조정했습니다 (5 초).

따라서 먼저 Windows 7 x64의 .NET 4.5.1 결과-숫자는 5 초 동안 실행될 수있는 반복을 나타내므로 높을수록 좋습니다.

x64 JIT :

Standard       10,589.00  (1.00)
UnsafeStandard 10,612.00  (1.00)
Stackalloc     12,088.00  (1.14)
FixedStandard  10,715.00  (1.01)
GlobalAlloc    12,547.00  (1.18)

x86 JIT (그래서 여전히 슬프다) :

Standard       14,787.00   (1.02)
UnsafeStandard 14,549.00   (1.00)
Stackalloc     15,830.00   (1.09)
FixedStandard  14,824.00   (1.02)
GlobalAlloc    18,744.00   (1.29)

이것은 최대 14 %의 훨씬 더 합리적인 속도를 제공합니다 (그리고 대부분의 오버 헤드는 GC를 실행해야하기 때문에 현실적으로 최악의 시나리오라고 생각합니다). x86 결과는 흥미 롭습니다-무슨 일이 일어나고 있는지 완전히 명확하지는 않습니다.

코드는 다음과 같습니다.

public static float Standard(int size) {
    float[] samples = new float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float UnsafeStandard(int size) {
    float[] samples = new float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float Stackalloc(int size) {
    float* samples = stackalloc float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float FixedStandard(int size) {
    float[] prev = new float[size];
    fixed (float* samples = &prev[0]) {
        for (var ii = 0; ii < size; ii++) {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }
        return samples[size - 1];
    }
}

public static unsafe float GlobalAlloc(int size) {
    var ptr = Marshal.AllocHGlobal(size * sizeof(float));
    try {
        float* samples = (float*)ptr;
        for (var ii = 0; ii < size; ii++) {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }
        return samples[size - 1];
    } finally {
        Marshal.FreeHGlobal(ptr);
    }
}

static void Main(string[] args) {
    int inputSize = 100000;
    var results = TestSuite.Create("Tests", inputSize, Standard(inputSize)).
        Add(Standard).
        Add(UnsafeStandard).
        Add(Stackalloc).
        Add(FixedStandard).
        Add(GlobalAlloc).
        RunTests();
    results.Display(ResultColumns.NameAndIterations);
}

흥미로운 사실은 벤치 마크를 다시 확인해야합니다. 그래도 여전히 내 질문에 대답하지는 않지만 " ... 스택을 이러한 큰 크기로 늘리는 것과 관련된 위험은 무엇입니까 ... ". 내 결과가 틀리더라도 질문은 여전히 ​​유효합니다. 그럼에도 불구하고 노력에 감사드립니다.
Sam

1
@ Sam 12500000크기로 사용할 때 실제로 stackoverflow 예외가 발생합니다. 그러나 대부분 스택 할당 코드를 사용하는 것이 몇 배 더 빠르다는 기본 전제를 ​​거부하는 것이 었습니다. 우리는 여기서 가능한 최소한의 작업을하고 있으며 그 차이는 이미 약 10-15 %에 불과합니다. 실제로는 훨씬 더 낮을 것입니다. 이것은 제 생각에는 전체 토론을 분명히 바꿉니다.
Voo

5

성능 차이가 너무 크기 때문에 문제는 할당과 거의 관련이 없습니다. 어레이 액세스 때문일 수 있습니다.

함수의 루프 본문을 분해했습니다.

TestMethod1 :

IL_0011:  ldloc.0 
IL_0012:  ldloc.1 
IL_0013:  ldc.i4.4 
IL_0014:  mul 
IL_0015:  add 
IL_0016:  ldc.r4 32768.
IL_001b:  stind.r4 // <----------- This one
IL_001c:  ldloc.1 
IL_001d:  ldc.i4.1 
IL_001e:  add 
IL_001f:  stloc.1 
IL_0020:  ldloc.1 
IL_0021:  ldc.i4 12500000
IL_0026:  blt IL_0011

TestMethod2 :

IL_0012:  ldloc.0 
IL_0013:  ldloc.1 
IL_0014:  ldc.r4 32768.
IL_0019:  stelem.r4 // <----------- This one
IL_001a:  ldloc.1 
IL_001b:  ldc.i4.1 
IL_001c:  add 
IL_001d:  stloc.1 
IL_001e:  ldloc.1 
IL_001f:  ldc.i4 12500000
IL_0024:  blt IL_0012

ECMA 사양 에서 발생하는 예외와 명령 사용법을 확인할 수 있습니다 .

stind.r4: Store value of type float32 into memory at address

예외가 발생합니다.

System.NullReferenceException

stelem.r4: Replace array element at index with the float32 value on the stack.

예외는 다음과 같습니다.

System.NullReferenceException
System.IndexOutOfRangeException
System.ArrayTypeMismatchException

보시다시피, stelem배열 범위 검사 및 유형 검사에서 더 많은 작업을 수행합니다. 루프 본문은 거의 수행하지 않기 때문에 (값만 할당), 검사 오버 헤드가 계산 시간을 지배합니다. 따라서 성능이 530 % 차이가 나는 이유입니다.

그리고 이것은 또한 귀하의 질문에 대답합니다 : 위험은 배열 범위 및 유형 검사가 없다는 것입니다. 이것은 함수 선언; D에서 언급 한 것처럼 안전하지 않습니다.


4

편집 : (코드 및 측정의 작은 변화는 결과에 큰 변화를 일으킴)

먼저 디버거 (F5)에서 최적화 된 코드를 실행했지만 잘못되었습니다. 디버거없이 실행해야합니다 (Ctrl + F5). 둘째, 코드가 철저하게 최적화 될 수 있으므로 최적화 프로그램이 측정을 방해하지 않도록 코드를 복잡하게 만들어야합니다. 모든 메소드가 배열의 마지막 항목을 반환하도록했으며 배열이 다르게 채워졌습니다. 또한 OP에는 여분의 0이 TestMethod2있어 항상 10 배 느립니다.

제공 한 두 가지 외에도 다른 방법을 시도했습니다. 메소드 3에는 메소드 2와 동일한 코드가 있지만 함수가 선언 unsafe됩니다. 방법 4는 정기적으로 생성 된 배열에 포인터 액세스를 사용하는 것입니다. 방법 5는 Marc Gravell이 설명한 것처럼 관리되지 않는 메모리에 대한 포인터 액세스를 사용하고 있습니다. 다섯 가지 방법 모두 매우 비슷한 시간에 실행됩니다. M5가 가장 빠릅니다 (M1이 2 초에 가깝습니다). 가장 빠른 속도와 가장 느린 속도의 차이는 약 5 %이며 이는 제가 관심이없는 것이 아닙니다.

    public static unsafe float TestMethod3()
    {
        float[] samples = new float[5000000];

        for (var ii = 0; ii < 5000000; ii++)
        {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }

        return samples[5000000 - 1];
    }

    public static unsafe float TestMethod4()
    {
        float[] prev = new float[5000000];
        fixed (float* samples = &prev[0])
        {
            for (var ii = 0; ii < 5000000; ii++)
            {
                samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
            }

            return samples[5000000 - 1];
        }
    }

    public static unsafe float TestMethod5()
    {
        var ptr = Marshal.AllocHGlobal(5000000 * sizeof(float));
        try
        {
            float* samples = (float*)ptr;

            for (var ii = 0; ii < 5000000; ii++)
            {
                samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
            }

            return samples[5000000 - 1];
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }

M3은 "안전하지 않은"것으로 표시된 M2와 동일합니까? 오히려 더 빠를 지 의심 스러워요. 확실합니까?
Roman Starkov

@romkyns 방금 벤치 마크를 실행했으며 (M2 대 M3) 놀랍게도 M3은 실제로 M2보다 2.14 % 빠릅니다.
Sam

" 결론은 스택을 사용할 필요가 없다는 것입니다. "라고 제가 게시 한 것과 같은 큰 블록을 할당 할 때 동의하지만, 더 많은 벤치 마크 M1과 M2를 완료 한 후 ( 두 방법 모두 PFM의 아이디어 사용 ) M1이 M2보다 135 % 빠르기 때문에 동의하지 않아야합니다.
Sam

1
@ Sam 그러나 포인터 액세스와 배열 액세스를 여전히 비교하고 있습니다! 빨리 만드는 것 primarly입니다. TestMethod4vs TestMethod1는 훨씬 더 나은 비교입니다 stackalloc.
Roman Starkov 2016 년

@romkyns 아 네 좋은 지적, 나는 그것에 대해 잊었다; 벤치 마크를 다시 실행했습니다. 이제 8 %의 차이 만 있습니다 (M1이 둘 중 더 빠름).
Sam
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.