2014-12-23 2 views
16

Я читал, что GC (Garbage Collectors) перемещает данные в Heap по соображениям производительности, что я не совсем понимаю, почему, поскольку это оперативная память, возможно, для лучшего последовательного доступа, но мне интересно, обновлены ли ссылки в Stack, перемещение происходит в куче. Но, возможно, адрес смещения остается тем же, но другие части данных могут быть перемещены сборщиками мусора, но я не уверен.Уточняются ли ссылки, когда сборщики мусора перемещают данные в кучу?

Я думаю, что этот вопрос относится к деталям реализации, поскольку не все сборщики мусора могут выполнять такую ​​оптимизацию, или они могут это делать, но не обновлять ссылки (если это обычная практика среди реализаций сборщика мусора). Но я хотел бы получить общий ответ, специфичный для сборщиков мусора CLR (Common Language Runtime).

А также я читал Эрик Липперта «Ссылки не являются адресами» статьи here, и следующий абзац смутил меня немного:

Если вы думаете о ссылке на самом деле являются непрозрачным GC обрабатывать затем становится ясно, что для нахождения адреса, связанного с дескриптором , вам нужно как-то «исправить» объект. Вы должны сообщить GC «до », и этот объект с этим дескриптором не должен перемещаться в памяти , потому что у кого-то может быть указатель на него ». (Там различные способы, чтобы сделать то, что выходит за рамки этого стяжки.)

Это звучит как для ссылочных типов, мы не хотим перемещать данные. Тогда что еще мы храним в куче, которую мы можем перемещать для оптимизации производительности? Может быть, напечатайте информацию, которую мы храним там? Кстати, на случай, если вам интересно, о чем идет речь, Эрик Липперт немного сравнивает ссылки на указатели и пытается объяснить, как может быть неправильно говорить, что ссылки - это просто адреса, даже если это то, как C# реализует его.

А также, если какое-либо из моих предположений неправильно, пожалуйста, исправьте меня.

+0

Если я правильно помню, да. В GC есть «передислоцирующая» фаза, которая перемещает все объекты для удаления/уменьшения фрагментации памяти, и на этом этапе обновляются ссылки на перемещенные объекты. Я попытаюсь найти ссылку из Channel9 (или, возможно, из статьи MSDN), и обновит этот комментарий. Изменить: Вот ссылка: http://msdn.microsoft.com/en-us/library/ee787088(v=vs.110).aspx#what_happens_during_a_garbage_collection (посмотрите на фазу передислокации). – kha

+1

@AdamHouldsworth: Но мой вопрос состоит в том, чтобы узнать, как это происходит: поддерживает ли он опорные значения, обновляя их, когда весь объект перемещается на некоторые другие адреса памяти или просто не перемещает начальный адрес массива объектов так, чтобы он не требует изменения ссылочного значения. – Tarik

+0

@kha: Ваши ссылки были бы высоко оценены! Благодарю. – Tarik

ответ

15

Да, ссылки обновляются во время сбор мусора. При необходимости объекты перемещаются при уплотнении кучи. Уплотнение служит двум основным целям:

  • Это делает программы более эффективными, используя более эффективные кэши данных процессора. Это очень и очень много для современных процессоров, оперативная память чрезвычайно медленная по сравнению с движком исполнения, толстая на два порядка. Процессор может быть остановлен для сотен инструкций, когда ему нужно дождаться, когда ОЗУ предоставит переменное значение.
  • решает фрагментация проблема, с которой страдают кучи. Фрагментация происходит при выпуске небольшого объекта, окруженного живыми объектами. Отверстие, которое нельзя использовать ни для чего другого, кроме объекта с равным или меньшим размером. Плохая эффективность использования памяти и КПД процессора. Обратите внимание, что LOH, куча больших объектов в .NET, не уплотняется и, следовательно, страдает от этой проблемы фрагментации. Много вопросов об этом в SO.

Несмотря на дидактику Эрика, ссылка на объект действительно является просто адресом. Указатель, точно такой же, какой вы использовали в C или C++. Очень эффективно, обязательно так. И весь GC должен сделать после перемещения объекта, обновляет адрес, хранящийся в этом указателе на перемещенный объект. CLR также позволяет выделять дескрипторы для объектов, дополнительные ссылки. Выставляется как тип GCHandle в .NET, но необходимо только в том случае, если GC нуждается в помощи, определяющей, должен ли объект оставаться в живых или его не следует перемещать.Имеет значение только если вы взаимодействуете с неуправляемым кодом.

Что не так просто - найти этот указатель назад. CLR сильно инвестируется в обеспечение того, что можно сделать надежно и эффективно. Такие указатели могут храниться во многих разных местах. Более легкими для поиска являются ссылки на объекты, хранящиеся в поле объекта, статическая переменная или GCHandle. Жесткие - это указатели, хранящиеся в стеке процессора или регистре CPU. Например, для аргументов метода и локальных переменных.

Одна гарантия, которую CLR должна обеспечить, чтобы это произошло, состоит в том, что GC всегда может надежно прокладывать стопку нити. Таким образом, он может найти локальные переменные обратно, которые хранятся в фрейме стека. Затем ему нужно знать , где, чтобы посмотреть в такой стек кадров, это работа компилятора JIT. Когда он компилирует метод, он не просто генерирует машинный код для метода, но также создает таблицу, которая описывает, где хранятся эти указатели. Более подробную информацию об этом вы найдете в файле this post.

+1

Интересно представить себе мир, в котором ссылки являются адресами, опосредованными с помощью дескрипторов, которые являются индексами в таблицу адресов. Конечно, каждый доступ становится немного медленнее, но мы платим точно такое же наказание за вызовы виртуальных функций, не подчеркивая этого. При реализации такой схемы вы быстро поймете, что когда объект освобождается, вы получаете отверстие в таблице адресов, и теперь вы возвращаетесь к той же проблеме, что и раньше: как избавиться от отверстий, чтобы таблица была маленькой. Здесь нет бесплатного обеда! Мне нравится расспрашивать об этом как о проблеме интервью. –

+1

@ EricLippert, оригинальная операционная система Apple для моделей 68K (Lisa/MacIntosh) работала именно так, то есть двойная косвенность для доступа к памяти. – adrianm

+0

@adrianm: Даже на C++ нетрудно представить ситуации, когда схема может быть выгодной. Я занимался написанием встроенной библиотеки ARM-строк, используя этот принцип, для таргетинга машин с до 128 КБ ОЗУ (во многих случаях до 16 КБ). Если у каждого объекта C++ есть 16-разрядный дескриптор [с 128 КБ ОЗУ, это безопасная ставка, количество строк будет достаточно маленьким, чтобы соответствовать), который затем ссылается на 16-битный сдвиг пула строк и требует, чтобы только один C++ объект идентифицирует любой дескриптор (который должен быть сконструирован/разрушен), но позволяет нескольким дескрипторам идентифицировать строку, должен ... – supercat

5

Глядя на C++\CLI In Action, есть раздел о внутренних указателях против скалывания указателей:

C++/CLI обеспечивает два вида указателей, которые работают вокруг этой проблемы. Первый вид называется внутренним указателем, который обновляется во время выполнения , чтобы отразить новое местоположение объекта, на который указывает каждый раз, когда объект перемещается. Физический адрес, на который указывает , внутренний указатель никогда не остается прежним, но он всегда указывает на тот же объект. Другой вид называется указателем пиннинга, который предотвращает перемещение GC из объекта; другими словами, он связывает объект с конкретным физическим местоположением в куче CLR. С некоторыми ограничениями возможны конверсии между внутренними, пиннинг и собственными указателями.

Из этого можно сделать вывод, что ссылочные типы перемещаются в куче, и их адреса изменяются. После фазы Mark и Sweep объекты уплотняются внутри кучи, тем самым фактически переходя к новым адресам. CLR отвечает за отслеживание фактического местоположения хранилища и обновление этих внутренних указателей с использованием внутренней таблицы, следя за тем, чтобы при доступе он все же указывал на правильное местоположение объекта.

Там пример взят отсюда:

ref struct CData 
{ 
    int age; 
}; 

int main() 
{ 
    for(int i=0; i<100000; i++) // ((1)) 
     gcnew CData(); 

    CData^ d = gcnew CData(); 
    d->age = 100; 

    interior_ptr<int> pint = &d->age; // ((2)) 

    printf("%p %d\r\n",pint,*pint); 

    for(int i=0; i<100000; i++) // ((3)) 
     gcnew CData(); 

    printf("%p %d\r\n",pint,*pint); // ((4)) 
    return 0; 
} 

что объясняется:

В примере кода создается 100000 сироту CData объектов ((1)) так , что вы может заполнить хорошую часть кучи CLR. Затем создается файл CData , который хранится в переменной, и ((2)) указатель интервала до возраста члена этого объекта CData. Затем вы распечатываете адрес указателя , а также значение int, на которое указывает. Теперь, ((3)) вы создаете еще 100 000 детей-сирот; где-то вдоль линии происходит цикл сбора мусора (объекты-осины , созданные ранее ((1)) собираются, потому что они не имеют ссылки в любом месте). Обратите внимание, что вы не используете вызов GC :: Collect, потому что это не гарантирует принудительный цикл сбора мусора. Как вы уже отметили в обсуждении алгоритма сбора мусора в предыдущей главе, GC освобождает пространство путем удаления объектов-сирот , чтобы он мог выполнять дальнейшие распределения. В конце кода (по , в который раз произошла сборка мусора), вы снова ((4)) print вне указателя адрес и значение возраста. Это выход я на моей машине (обратите внимание, что адреса будут варьироваться в зависимости от машины к машине , так что ваши выходные значения не будут совпадать):

012CB4C8 100 
012A13D0 100 
+0

Это как мы можем иметь интерьер ?: 'var person = new Person(); var name = person.Name; ', так ли' name' становится внутренним указателем типа ссылки в C#? – Tarik

+0

В C# понятие «указатель интерьера» на самом деле не существует, только внутри небезопасного кода. Вы можете смотреть на нее логически, это «указатель интерьера», который всегда будет ссылаться на правильный адрес объекта во время распределения. При выполнении небезопасного кода вы извлекаете внутренний указатель, а не внутренний указатель. –

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