2012-01-09 6 views
4

그래서 Windows에서이 프로그램을 실행하여 D 가비지 수집기가 제대로 작동하는지 테스트하려고했습니다.D 가비지 수집기가 작동합니까?

DMD 2.057 및 2.058 베타 모두 내가 -release, -inline, -O을 지정 여부에 관계없이 동일한 결과를 제공 등

코드 :

import core.memory, std.stdio; 

extern(Windows) int GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); 

struct MEMORYSTATUSEX 
{ 
    uint Length, MemoryLoad; 
    ulong TotalPhys, AvailPhys, TotalPageFile, AvailPageFile; 
    ulong TotalVirtual, AvailVirtual, AvailExtendedVirtual; 
} 

void testA(size_t count) 
{ 
    size_t[] a; 
    foreach (i; 0 .. count) 
     a ~= i; 
    //delete a; 
} 

void main() 
{ 
    MEMORYSTATUSEX ms; 
    ms.Length = ms.sizeof; 

    foreach (i; 0 .. 32) 
    { 
     testA(16 << 20); 
     GlobalMemoryStatusEx(ms); 
     stderr.writefln("AvailPhys: %s MiB", ms.AvailPhys >>> 20); 
    } 
} 

출력이었다

AvailPhys: 3711 MiB 
AvailPhys: 3365 MiB 
AvailPhys: 3061 MiB 
AvailPhys: 2747 MiB 
AvailPhys: 2458 MiB 
core.exception.OutOfMemoryError 

delete a; 문을 주석 처리하지 않은 경우 출력은

AvailPhys: 3714 MiB 
AvailPhys: 3702 MiB 
AvailPhys: 3701 MiB 
AvailPhys: 3702 MiB 
AvailPhys: 3702 MiB 
... 

그래서 분명히 ... GC가 실제로 작동합니까?

답변

3

이것은 회귀와 같습니다. D1에서는 발생하지 않습니다 (DMD 1.069). 데이빗 심차 (David Simcha)는 최근에 GC를 최적화 했으므로 이와 관련이있을 수 있습니다. 버그 보고서를 제출하십시오.

+0

... whoa. D2에서 완전히 깨졌습니다? – Mehrdad

+0

아니요, 당연히 아닙니다 - 당신이 방금 모퉁이 케이스 또는 뭔가를 쳤을 것입니다. –

+0

재밌 네요. 문자 그대로 GC를 명시 적으로 테스트하려고 시도한 첫 번째 것입니다. – Mehrdad

1

작동하지 않습니다. 현재의 구현은 결코 메모리를 운영 체제로 릴리스하지 않습니다. GC가 메모리를 다시 사용하기는하지만 실제로 누수가 아닙니다.

+0

그렇다면 'core.exception.OutOfMemoryError'가 던져진 거래는 무엇입니까? 그리고 내가'delete'라고 말했을 때 어떻게 메모리가 OS로 돌아 갔습니까? – Mehrdad

+2

Perl은 메모리를 운영체제로 되돌려 보내지 않으며 20 년이 넘었습니다. –

+0

@BradGilbert : 당신이 뭘 하려는지 말할 수는 없지만, 내가 추측하는 것이 좋다. – Mehrdad

2

P. 메이크 파일에서 DFLAGS를 -debug=PRINTF으로 설정하여 Druntime을 다시 작성하면 GC가 콘솔을 통해 할당/할당 해제 할시기에 대한 정보를 얻게됩니다. :)

8

여기의 문제는 잘못된 포인터입니다. D의 가비지 컬렉터는 보수적인데, 이는 포인터가 무엇인지, 그렇지 않은지 항상 알 수는 없다는 것을 의미합니다. 포인터로 해석되는 경우 GC 할당 메모리를 가리키는 비트 패턴이 포인터라고 가정해야합니다. 큰 블록은 잘못된 포인터에 대한 더 큰 목표이기 때문에 이것은 대용량 할당의 경우 주로 문제입니다.

testA()에 전화 할 때마다 약 48MB를 할당하고 있습니다. 필자의 경험으로 볼 때 32 비트 시스템에서 블록에 잘못된 포인터가 있다는 것을 보장하기에 충분합니다. 64 비트 주소 공간이 훨씬 희소하기 때문에 64 비트 모드 (Linux, OSX 및 FreeBSD에서 지원되지만 Windows는 지원하지 않음)로 코드를 컴파일하면 더 나은 결과를 얻을 수 있습니다.

내 GC 최적화 (저는 CyberShadow가 언급 한 David Simcha입니다)에는 두 개의 배치가있었습니다. 하나는 6 개월 이상 지속되어 아무런 문제가 발생하지 않았습니다. 다른 하나는 여전히 풀 요청으로 검토 중이며 아직 주 대기 시간 트리에 포함되어 있지 않습니다. 이들은 아마도 문제가되지 않습니다.

단기적으로 해결책은 이러한 거대한 블록을 수동으로 해제하는 것입니다. 장기적으로, 적어도 힙에 대해서는 정밀한 스캐닝을 추가해야합니다. (정확한 스택 스캐닝은 훨씬 어려운 문제입니다.) 저는 몇 년 전에이 작업을 수행하기위한 패치를 작성했지만 템플릿과 컴파일 시간 함수 평가에 의존하여 각 데이터 유형에 대한 포인터 오프셋 정보를 생성하기 때문에 거부되었습니다. 잘하면이 정보는 결국 컴파일러에 의해 직접 생성되며 가비지 수집기에 대한 정확한 힙 스캔 패치를 다시 만들 수 있습니다.

+0

그래도 실제 문제일까요? 우선, 포인터는 랜덤하지 않습니다 - 가상 메모리의 하위 16 MiB (주소 0에서 주소 16 << 20)에 걸쳐 있습니다. 가상 메모리의 영역에는 아무 것도 없다는 것이 확실합니다. (심지어!)가 있더라도 어레이의 * 1 *이 수집되는 것을 막을 것입니다 (각 영역은> 16 MiB이므로). * 전체 배열. 그리고 그 외에, GC가 처음에'size_t' 블록을 무시하지 않아야합니까? 나는 그것이 포인터 ('NO_SCAN')를 포함 할 수있는 데이터만을 스캔 할 것이라고 생각 했는가? – Mehrdad

+0

@Mehrdad : 두 가지 : 하나, 거짓 포인터는 ** 할당 된 블록에서 ** 오지 않습니다. 그들은 정적 데이터, 스택, RTTI 등에서 오는 것입니다. 둘째, GC는 가장 낮은 주소를 사용하여 할당을 시작하지 않습니다. 그것은 어떤 임의의 주소에서 위로 시작합니다. (나는 세부 사항을 모른다.) new byte [48 * 1024 * 1024]와 같은 것을 사용하여 48 MB를 루프에 할당 해 보라. 나는 그것이 오래 걸릴지라도 메모리가 부족하거나 적어도 엄청난 양을 사용할 것이라고 확신한다. – dsimcha

+0

[문제] (http://d.puremagic.com/issues/show_bug.cgi?id=7251)가 '해결 된 잘못된 것'으로 표시된 이유를 알고 계십니까? – Mehrdad

관련 문제