2015-03-11 3 views
7

Я использую .NET Memory Profiler от SciTech, чтобы уменьшить частоту распределения памяти в моей программе и сократить частоту сборок мусора.GCHandle.Alloc выделяет память?

Удивительно, но, согласно профилировщику, наибольшее количество распределений, по-видимому, поступает из вызовов GCHandle.Alloc, которые я делаю для сортировки существующих .NET-массивов в native OpenGL.

Мое понимание заключается в том, что вызов GCHandle.Alloc не выделяет память, он только фиксирует существующую память на управляемой куче?

Я ошибаюсь или неправильно работает?

+0

Вероятно, вы пытаетесь рассказать вам, что мешает управляемому объекту собирать мусор. И да, это обычно будет GCHandle, необходимо сохранить, скажем, текстуру или живую сеть, которую вы передали OpenGL. Нехорошо, если они закрепляют ручки, они, вероятно, есть, но это всего лишь аспект любой используемой библиотеки обложек OpenGL. –

+0

Профилировщик даже присваивает определенную сумму памяти каждому GCHandle, который я выделяю - 8 байтов. И управляемая куча, кажется, растет с 8 байтами с каждым GCHandle.Alloc. Похоже, что на самом деле он выделяет пространство на управляемой куче, хотя я понятия не имею, для чего? – kaalus

+0

В CLR хранится отдельный стол дескриптора, где хранятся GCHandles. Конечно, 8 байт звучат правильно. Если это растет без ограничений, то вам, вероятно, не хватает требуемого вызова GCHandle.Free(), когда актив больше не нужен. –

ответ

9

.NET reference source доступно для просмотра всем, и вы можете посмотреть и узнать сами.

Если покопаться в GCHandle.Alloc, вы увидите, что он вызывает нативный метод под названием InternalAlloc:

[System.Security.SecurityCritical] // auto-generated 
[MethodImplAttribute(MethodImplOptions.InternalCall)] 
[ResourceExposure(ResourceScope.None)] 
internal static extern IntPtr InternalAlloc(Object value, GCHandleType type); 

свертывания в код CLR, вы видите внутренний вызов MarshalNative::InternalAlloc, который заканчивается до вызова:

hnd = GetAppDomain()->CreateTypedHandle(objRef, type); 

который в свою очередь вызывает ObjectHandle::CreateTypedHandle ->HandleTable::HndCreateHandle ->HandleTableCache->TableAllocSingleHandleFromCache который выделяет ручку, если она не существует в кэше.

Как @Antosha исправил меня, место вызова не через ComDelegate (что фактически мало что делает), но через MarshalNative. Выделение происходит, а не на управляемой куче , но внешняя куча, зарезервированная средой выполнения для управления корнями ручек в объектах GC. Единственное выделение, которое делает, происходит в управляемой куче IntPtr, которая удерживает указатель на адрес в таблице. Несмотря на это, вы все равно должны позвонить по телефону GCHandle.Free, как только закончите.

+1

Неправильное использование. 'GCHandle.Alloc' вызывает' MarshalNative :: GCHandleInternalAlloc', а не 'COMDelegate :: InternalAlloc'. Первый создает новую запись в таблице дескрипторов, поддерживаемой движком GC, и возвращает свой адрес. Эта таблица дескрипторов не является частью управляемой кучи, что означает, что вызов GCHandle.Alloc не влияет на размер управляемой кучи. – Antosha

+1

@ Антоша Спасибо, что исправили меня. Повторное чтение моего ответа также дает понять, что для GCHandle не имеет смысла вызывать 'ComDelegate' и что мое расследование во время выполнения было неправильным. Я исправил свой ответ. –

+1

Благодарим вас за исправление ответа; Я согласен с этим сейчас. Когда 'GCHandle' хранится в управляемой куче (например, в качестве поля объекта или элемента' GCHandle [] '), он потребляет тот же объем памяти, что и' IntPtr', то есть 4 байта на 32-битной архитектуры и 8 байтов на 64-битных архитектурах. Когда вы вызываете 'GCHandle.Alloc', дескриптор становится _allocated_, который дополнительно потребляет' IntPtr.Size' неуправляемой памяти времени выполнения, пока вы не вызовете 'GCHandle.Free', чтобы освободить дескриптор. – Antosha

0

из msdn

Новый GCHandle, который защищает объект от сборки мусора. Этот GCHandle должен быть выпущен Free, если он больше не нужен.

Так что, даже если реального распределения не сделано, я думаю, что освобождение предварительно выделенного не может быть сделано до вызова Free.

1

Профилировщик даже присваивает определенную сумму памяти каждому GCHandle, который я выделяю - 8 байтов. И управляемая куча, кажется, растет с 8 байтами с каждым GCHandle.Alloc. Похоже, что на самом деле он выделяет пространство на управляемой куче, хотя я понятия не имею, для чего?

Я не знаю, каким образом ручка может быть меньше :) Я сделал несколько тестов:

Console.WriteLine("Is 64 bit: {0}, IntPtr.Size: {1}", Environment.Is64BitProcess, IntPtr.Size); 

int[][] objects = new int[100000][]; 
for (int i = 0; i < objects.Length; i++) 
{ 
    objects[i] = new int[] { 0 }; 
} 

long w1 = Environment.WorkingSet; 

GCHandle[] handles = new GCHandle[objects.Length]; 

for (int i = 0; i < handles.Length; i++) 
{ 
    //handles[i] = new GCHandle(handles); 
    //handles[i] = GCHandle.Alloc(objects[i]); 
    handles[i] = GCHandle.Alloc(objects[i], GCHandleType.Pinned); 
} 

Console.WriteLine("Allocated"); 
long w2 = Environment.WorkingSet; 
Console.WriteLine("Used: {0}, by handle: {1}", w2 - w1, ((double)(w2 - w1))/handles.Length); 
Console.ReadKey(); 

Это небольшая программа. Если вы запустите, вы увидите, что «пустой» GCHandle (один из которых создан с new GCHandle()) занимает IntPtr.Size. Это понятно, если вы используете ILSpy, чтобы посмотреть на него: у него есть одно поле . Если вы привязываете некоторый объект, то он занимает 2*IntPtr.Size памяти. Это, вероятно, потому, что он должен написать что-то в CLR таблицы (размера IntPtr) плюс его внутренний IntPtr

Взятые из https://stackoverflow.com/a/18122621/613130

Он использует специальный стол GC ручки построен внутри CLR. Вы назначаете запись в эту таблицу с помощью GCHandle.Alloc() и позже выпускаете ее с помощью GCHandle.Free(). Сборщик мусора просто добавляет записи в эту таблицу к графику объектов, которые он обнаружил, когда он выполняет сбор.

Смежные вопросы