문제

저는 현재 취미 프로젝트로 C#으로 광선 추적기를 작업하고 있습니다.C++ 구현에서 몇 가지 트릭을 구현하여 적절한 렌더링 속도를 얻으려고 노력 중인데 문제가 발생했습니다.

광선 추적기가 렌더링하는 장면의 객체는 KdTree 구조에 저장되고 트리의 노드는 차례로 배열에 저장됩니다.제가 겪고 있는 최적화는 캐시 라인에 최대한 많은 트리 노드를 맞추려고 노력하는 것입니다.이를 수행하는 한 가지 방법은 노드가 왼쪽 자식 노드에 대한 포인터만 포함하도록 하는 것입니다.그러면 오른쪽 자식이 배열의 왼쪽 자식 바로 뒤에 오는 것이 암시됩니다.

노드는 구조체이며 트리 구성 중에 정적 메모리 관리자 클래스에 의해 성공적으로 배열에 배치됩니다.트리를 탐색하기 시작하면 처음에는 제대로 작동하는 것 같습니다.그런 다음 렌더링 초기 지점(매번 거의 같은 위치)에서 루트 노드의 왼쪽 하위 포인터가 갑자기 널 포인터를 가리키게 됩니다.나는 배열이 힙에 있을 때 가비지 수집기가 구조체를 이동했다는 결론에 도달했습니다.

메모리에 주소를 고정하기 위해 여러 가지 시도를 했지만 그 중 어느 것도 필요한 만큼 전체 애플리케이션 수명 동안 지속되지 않는 것 같습니다.'fixed' 키워드는 단일 메소드 호출 중에만 도움이 되는 것으로 보이며 'fixed' 배열 선언은 노드가 아닌 단순 유형에서만 수행될 수 있습니다.이를 수행할 수 있는 좋은 방법이 있습니까? 아니면 C#이 의도하지 않은 작업의 경로에 너무 멀리 떨어져 있는 것입니까?

그런데 C++로 변경하는 것은 아마도 고성능 프로그램을 위한 더 나은 선택일 수도 있지만 선택 사항이 아닙니다.

도움이 되었습니까?

해결책

첫째, 일반적으로 C#을 사용하는 경우 가비지 수집기가 항목을 이동하므로 갑자기 null 참조를 얻을 수 없습니다. 가비지 수집기도 모든 참조를 업데이트하므로 항목 이동에 대해 걱정할 필요가 없습니다.

메모리에 항목을 고정할 수 있지만 이로 인해 해결되는 것보다 더 많은 문제가 발생할 수 있습니다.우선, 이는 가비지 수집기가 메모리를 적절하게 압축하는 것을 방해하고 그런 식으로 성능에 영향을 미칠 수 있습니다.

귀하의 게시물에서 제가 말하고 싶은 한 가지는 구조체를 사용하면 원하는 대로 성능에 도움이 되지 않을 수 있다는 것입니다.C#은 구조체와 관련된 모든 메서드 호출을 인라인하지 못하며 최신 런타임 베타에서 이 문제를 수정했음에도 불구하고 구조체가 제대로 수행되지 않는 경우가 많습니다.

개인적으로 저는 이와 같은 C++ 트릭이 일반적으로 C#에 잘 적용되지 않는다고 말하고 싶습니다.당신은 조금 놓아주는 법을 배워야 할 수도 있습니다.성능을 향상시키는 다른 더 미묘한 방법이 있을 수 있습니다.)

다른 팁

정적 메모리 관리자는 실제로 무엇을 하고 있나요?안전하지 않은 작업(P/Invoke, 안전하지 않은 코드)을 수행하지 않는 한, 표시되는 동작은 프로그램의 버그이며 CLR의 동작으로 인한 것이 아닙니다.

둘째, 구조 간의 링크와 관련하여 '포인터'란 무엇을 의미합니까?문자 그대로 안전하지 않은 KdTree* 포인터를 의미합니까?그러지 마세요.대신 배열에 대한 인덱스를 사용하십시오.단일 트리의 모든 노드가 동일한 배열에 저장될 것으로 예상하므로 배열에 대한 별도의 참조가 필요하지 않습니다.단일 인덱스만 있으면 됩니다.

마지막으로, 정말로 KdTree* 포인터를 사용해야 한다면 정적 메모리 관리자는 예를 들어 다음을 사용하여 큰 블록을 할당해야 합니다.Marshal.AllocHGlobal 또는 기타 관리되지 않는 메모리 소스둘 다 이 큰 블록을 KdTree 배열로 처리해야 합니다(예:KdTree* C 스타일 인덱스) 그리고 "자유" 포인터를 범핑하여 이 배열에서 노드를 하위 할당해야 합니다.

이 배열의 크기를 조정해야 한다면 물론 모든 포인터를 업데이트해야 합니다.

여기서 기본적인 교훈은 안전하지 않은 포인터와 관리되는 메모리가 ~ 아니다 물론 스택 프레임 친화력이 있는 '고정' 블록 외부에서 혼합합니다(예:함수가 반환되면 고정된 동작이 사라집니다.GCHandle.Alloc(yourArray, GCHandleType.Pinned)를 사용하여 배열과 같은 임의 개체를 고정하는 방법이 있지만 거의 확실하게 해당 경로를 따르기를 원하지 않습니다.

자신이 하고 있는 일을 좀 더 자세히 기술해 주시면 더욱 현명한 답변을 얻으실 수 있습니다.

만약 너라면 정말 이렇게 하려면 GCHandle.Alloc 메서드를 사용하여 고정 문처럼 범위 끝에서 포인터가 자동으로 해제되지 않고 고정되어야 함을 지정할 수 있습니다.

그러나 다른 사람들이 말했듯이 이렇게 하면 가비지 수집기에 과도한 압력을 가하는 것입니다.노드 쌍을 유지하는 구조체를 만든 다음 노드 배열이 아닌 NodePair 배열을 관리하는 것은 어떻습니까?

메모리 덩어리에 완전히 관리되지 않는 액세스를 갖고 싶다면 관리되는 힙의 일부를 영구적으로 고정하는 것보다 관리되지 않는 힙에서 직접 메모리를 할당하는 것이 더 나을 것입니다. 자체적으로 압축).이를 수행하는 빠르고 간단한 방법 중 하나는 Marshal.AllocHGlobal 메서드를 사용하는 것입니다.

배열 참조와 인덱스 쌍을 저장하는 것이 실제로 금지되어 있습니까?

정적 메모리 관리자는 실제로 무엇을 하고 있나요?안전하지 않은 작업(P/Invoke, 안전하지 않은 코드)을 수행하지 않는 한, 표시되는 동작은 프로그램의 버그이며 CLR의 동작으로 인한 것이 아닙니다.

사실 나는 안전하지 않은 포인터에 대해 이야기하고 있었습니다.내가 원했던 것은 다음과 같았습니다 Marshal.AllocHGlobal, 단, 수명이 단일 메서드 호출을 초과하는 경우도 있습니다.생각해 보면 C++ 코드를 모방하는 데 너무 얽매였을 수도 있으므로 인덱스를 사용하는 것이 올바른 솔루션인 것 같습니다.

귀하의 게시물에서 제가 말하고 싶은 한 가지는 구조체를 사용하면 원하는 대로 성능에 도움이 되지 않을 수 있다는 것입니다.C#은 구조체와 관련된 메서드 호출을 인라인하지 못하며 최신 런타임 베타에서 이 문제를 수정했음에도 불구하고 구조체가 제대로 수행되지 않는 경우가 많습니다.

이 내용을 조금 살펴보니 .NET 3.5SP1에서 수정되었습니다.나는 이것이 당신이 런타임 베타라고 언급한 것이라고 가정합니다.사실, 이제 나는 이러한 변화로 인해 렌더링 속도가 두 배로 빨라졌다는 것을 이해하게 되었습니다.이제 구조체는 공격적으로 인라인되어 X86 시스템에서 성능이 크게 향상되었습니다(X64는 사전에 구조체 성능이 더 좋았습니다).

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top