LINUX.ORG.RU

Зачем нужны GC, Java, .NET?


0

0

Имеем для каждого объекта счетчик ссылок на него. Исчезает последняя ссылка --> объект удаляется. Запрещаем при этом ручную работу с указателями. Получаем то же самое, что и в Java или .NET, но без огромного оверхеда. Те же Genie или Vala работают по такому принципу, хотя там и есть возможность рулить памятью руками (но чисто опционально). При этом они не таскают за собой большую дуру под названием JVM и работают куда быстрее.

Так в чем прикол «безопасного программирования» по Сану или Микрософту в таком случае?

Ответ на: комментарий от Eldar_Sonar

мне-то зачем читать? я хорошо знаю что такое счетчики ссылок, и где их можно использовать...

ott ★★★★★
()
Ответ на: комментарий от anonymous

Отлично. Только где объект будет искать ссылка на себя? И что делать, если ссылка в какой-то момент времени начала ссылаться на другой объект? Чем здесь деструктор поможет?

Eldar_Sonar
() автор топика
Ответ на: комментарий от Eldar_Sonar

Тем более что разделяемые объекты можно создавать и уничтожать вне действия их потребителей. Всё зависит от задач и С++ не обязвает решать всё одинм образом.

anonymous
()
Ответ на: комментарий от ott

А, так вы Д'артаньян на белом коне и все знаете, а мы тут чисто для массовки. Простите, забылся.

Eldar_Sonar
() автор топика
Ответ на: комментарий от Eldar_Sonar

Что за ссылки начинающие ссылающиеся на другие объекты? Вы про С что-ли с указателями и процедурами?

anonymous
()
Ответ на: комментарий от Absurd

> Учитывая сколько времени треды висят на IO и на мутексах время вполне можно выкроить.

Т.е. ты считаешь, что если тред висит на IO, то системе в целом больше нечем заняться, кроме как уборкой мусора?

на Си

постоянно


дублируют куски памяти


для подстраховки



эээ, что, простите?

mannaz
()
Ответ на: комментарий от Eldar_Sonar

>И что делать, если ссылка в какой-то момент времени начала ссылаться на другой объект?

Oo

yoghurt ★★★★★
()
Ответ на: комментарий от mannaz

>> Учитывая сколько времени треды висят на IO и на мутексах время вполне можно выкроить.

Т.е. ты считаешь, что если тред висит на IO, то системе в целом больше нечем заняться, кроме как уборкой мусора?

Все равно она ей занимается так или иначе. Выбор в том чистить ли память сразу при выходе из скопа или асинхронно.

на Си постоянно дублируют куски памяти для подстраховки

эээ, что, простите?

Блин, ну почему я должен постоянно разжевывать тривиальные вещи. Если ты кому-то отдал результатом свой указатель, то есть вероятность что его сломают. Поэтому на Си обычно дублируют структуру на которую указывает указатель и перекладывают ответственность по ломанию/неломанию на клиента кода.

Absurd ★★★
()
Ответ на: комментарий от Eldar_Sonar

Мне одному ТС показался троллем, который в каждом следующем комменте противоречит собственному же предыдущему?

naryl ★★★★★
()
Ответ на: комментарий от naryl

А мне он показался разработчиком вылезшим из недр корпорации sonarplus.ru

anonymous
()
Ответ на: комментарий от anonymous

>Что за ссылки начинающие ссылающиеся на другие объекты? Вы про С что-ли с указателями и процедурами?

public class A { ... //some variables and methods here }

A a = new A(); A b = new A(); a = b;

И как без ГЦ в данном (пусть тупом и нежизненном) варианте избежать утечки памяти?

Nagwal ★★★★
()
Ответ на: комментарий от Nagwal

Пардон за форматирование.

Что за ссылки начинающие ссылающиеся на другие объекты? Вы про С что-ли с указателями и процедурами?


public class A {
... //some variables and methods here
}

A a = new A();
A b = new A();
a = b;

И как без ГЦ в данном (пусть тупом и нежизненном) варианте избежать утечки памяти?
Nagwal ** (*) (17.12.2009 0:22:36)

Nagwal ★★★★
()
Ответ на: комментарий от Nagwal

Счетчиком ссылок, через конструкторы, деструкторы, перегрузу копирования и инициализации. Будет убого и медленно, как раз в стиле с++.

anonymous
()
Ответ на: комментарий от Absurd

Все равно она ей занимается так или иначе. Выбор в том чистить ли память сразу при выходе из скопа или асинхронно.

jtootf в этом треде интересные ссылки засветил. Помимо прочего там есть любопытное с точки зрения методологии исследование GC vs malloc. Придраться есть к чему, но результаты, на мой взгляд, репрезентативны и показывают GC во вполне хорошем свете:

Quantifying the Performance of Garbage Collection vs. Explicit Memory Management

In particular, when garbage collection has five times as much memory as required, its runtime performance matches or slightly exceeds that of explicit memory management. However, garbage collection’s performance degrades substantially when it must use smaller heaps. With three times as much memory, it runs 17% slower on average, and with twice as much memory, it runs 70% slower.

Таким образом, выбор несколько сложнее, чем то, как ты его охарактеризовал.

Блин, ну почему я должен постоянно разжевывать тривиальные вещи. Если ты кому-то отдал результатом свой указатель, то есть вероятность что его сломают. Поэтому на Си обычно дублируют структуру на которую указывает указатель и перекладывают ответственность по ломанию/неломанию на клиента кода.

Довольно нелепо заявлять подобное без конкретных примеров API или проектов, т.к. в общем случае сказанное не имеет отношения к устоявшейся практике. Х**ню ты сказал, короче.

mannaz
()
Ответ на: комментарий от anonymous

>А деструкторы объектов разве не могут уменьшить количество ссылок у совместно используемого ими объекта? И сам разделяемый объект удалит себя после обнуления ссылок на него. И никакой утечки памяти нет.

И когда ты всё это реализуешь, учтя все возможные случаи, то ты получишь GC.

anonymous
()
Ответ на: комментарий от anonymous

И сюда влезу. Как насчёт статического анализа работы с памятью? В той ветке я писал. Мне интересно, пытался ли кто-нибудь это делать и что у него получилось. Касаемо же вообще GC против явного выделения против подсчёта ссылок, могу сказать вот что:

при GC объекты разного размера можно класть один за другим. Это важно, поскольку позволяет использовать память более компактно, чем при явном выделении памяти. При явном выделении память может фрагментироваться и, если объекты нельзя подвинуть, она может фрагментироваться в тяжёлой форме (пустая память будет занимать O(1), но при этом постоянно будет требоваться новая память от системы). На поиск подходящего под требуемый объект свободного куска в фрагментированной памяти тоже нужно время, и не факт, что маленькое. Либо нужно создавать пулы для объектов разного размера (а у пулов есть запасы, а эти запасы не используются). При подсчёте ссылок на каждое создание-удаление ссылки нужно провести дополнительное действие. Это уже не говоря о том, что подсчёт ссылок не гарантирует отсутствие утечек. Так что, я думаю, что само по себе GC ещё не так плохо. С другой стороны, если объекты подвижны, возникает проблема: мы не можем использовать адрес объекта для его идентификации. Поскольку объекты двигаются, их адреса меняются. В частности, мы не можем хешировать объект его адресом, а это плохо и может привести к двум видам последствий: либо к объекту, который потенциально может быть хеш-ключём, нужно привязать код (и объект станет больше). Либо хеш-таблицу периодически нужно перехешировать (в худшем случае - после каждой сборки мусора).

Есть поколенческие сборки мусора. Они двигают только те объекты, которые менялись достаточно недавно. При этом, объекты делятся на несколько поколений,которые физически хранятся в разных областях памяти (или там есть пара бит, хранящих код поколения). Но тут тоже есть подводный камень. Хорошо, когда объект ссылается только на своих ровесников и предков. Но если вдруг старый объект поменялся и начинает ссылаться на потомков, то его нужно учитывать отдельно и опять страдает либо компактность, либо производительность.

Так что и поколенческие сборки мусора - не панацея.
Вот. Хотя это теория, на практике я работаю в лиспах с поколенческой сборкой мусора. Что могу сказать? Работает...
И ещё есть такая подстава в GC (сам я с ней не сталкивался) - это то, что финализация объекта может вызываться в произвольное время. Если объект задействует внешние ресурсы или ссылается на внешние объекты (например, на какие-то данные в прилинкованной разделяемой библиотеке), то некоторые баги в его работе, связанные с неправильнмы отслеживанием взаимозависимостей, могут сказываться случайно, в зависимости от того, как ляжет карта. Так что и с точки зрения надёжности, GC - это, вообще говоря, не идеал.

den73 ★★★★★
()
Ответ на: комментарий от den73

Есть поколенческие сборки мусора. Они двигают только те объекты, которые менялись достаточно недавно. При этом, объекты делятся на несколько поколений,которые физически хранятся в разных областях памяти (или там есть пара бит, хранящих код поколения). Но тут тоже есть подводный камень. Хорошо, когда объект ссылается только на своих ровесников и предков. Но если вдруг старый объект поменялся и начинает ссылаться на потомков, то его нужно учитывать отдельно и опять страдает либо компактность, либо производительность.

Если старый объект ссылается на новый, новый переходит в разряд старых, не вижу никаких проблем.

И ещё есть такая подстава в GC (сам я с ней не сталкивался) - это то, что финализация объекта может вызываться в произвольное время.

А может и вообще не вызваться, насколько я помню.

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

Для освобождения ресурсов используется метод close, который вызывается вручную (или почти вручную, при использовании using в C#, например). Освобождение ресурса в финализаторе это самый последний случай, когда в программе имеется баг, из за которого управление не перешло на вызов close (например код не был exception-safe). Полагаться на вызов финализатора для освобождения ресурса - моветон. Финализатор и С++ деструктор - абсолютно разные вещи. Если это понимать, то никаких проблем с надёжностью быть не должно.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

>А может и вообще не вызваться, насколько я помню.
Если объект не удаляется сборщиком, то да.

Love5an
()

Подсчёт ссылок намного медленнее правильных GC. Допустим, есть у нас дерево на 1.5Gb, и кто-то удаляет его корневой узел. Если оно реализовано на смартпоинтерах, придётся ждать, пока всё не будет удалено. Если это GC, то его будут подчищать по частям в свободное время, и латентность приложения будет равномерной.

И я не говорю про структуры с циклическими ссылками. Банальный double linked list убьёт этот дурацкий подсчёт ссылок в момент.

anonymous
()
Ответ на: комментарий от Legioner

>Для освобождения ресурсов используется метод close, который вызывается вручную

В итоге имеем туже самую проблему (выделения/освобождения) для остальных ресурсов, котороя была в с++ для памяти. Только деструкторы нам уже не помощники, а using при выделении более одного ресурса - говно. В итоге где-то выиграли, где-то проиграли.

З.Ы. ИМХО если у программиста кривые руки, то никакой GC ему не поможет, а программист с «прямыми руками» сможет нормально и на c++ управлять памятью.

pathfinder ★★★★
()
Ответ на: комментарий от den73

Посмотри на MLTon - там region analysis + GC для всего, что анализу не поддаётся.

И, кстати, ещё одно преимущество GC - weak pointers. Офигенно удобная штука.

anonymous
()
Ответ на: комментарий от pathfinder

> а программист с «прямыми руками» сможет нормально и на c++ управлять памятью.

Не сможет. В случае с удалением очень больших структур - ничто не поможет, нужен полноценный GC.

anonymous
()
Ответ на: комментарий от pathfinder

Никакой проблемы нет. «Ресурсы» это в 99% случаев - память. А другие «ресурсы» требуют совершенно иного подхода.

З.Ы. ИМХО если у программиста кривые руки, то никакой GC ему не поможет, а программист с «прямыми руками» сможет нормально и на c++ управлять памятью.

2й курс? 3й? Лабы сдал?

Love5an
()
Ответ на: комментарий от Love5an

> А другие «ресурсы» требуют совершенно иного подхода.

Да уж. Видал я любителей закрывать файл в финализаторе. Тестикулы за такое давить надо.

anonymous
()
Ответ на: комментарий от Love5an

>2й курс? 3й? Лабы сдал?

Институт давно закончил. Работаю. Реализую телекоммуникационные протоколы для оборудования которое должно работать без участия человека 24/7. В таких условиях любые, повторюсь, ЛЮБЫЕ утечки памяти неприемлемы. И не надо говорить что я далек от проблемы утечек памяти.

pathfinder ★★★★
()
Ответ на: комментарий от pathfinder

Я ничего против GC не имею. Просто некоторые думают что он может решить проблему так, что это решение будет бесплатным и мы не потеряем вообще ничего.

pathfinder ★★★★
()
Ответ на: комментарий от mannaz

>Довольно нелепо заявлять подобное без конкретных примеров API или проектов,

Практически везде где на полученную из библиотеки структуру надо сказать free() после использования.

т.к. в общем случае сказанное не имеет отношения к устоявшейся практике.

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

Absurd ★★★
()
Ответ на: комментарий от Absurd

> Практически везде где на полученную из библиотеки структуру надо сказать free() после использования.

Ты не загоняешься случайно? Это всего лишь одно из соглашений о том, кто выделяет память под результат - caller или callee. Расскажи, с чего ты взял, что «на Си для подстраховки постоянно дублируют куски памяти когда отдают их внешнему коду», и что «обычно» так и делается.

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


Т.е. ты хочешь сказать, что при использовании GC блоки и реюзаются больше, и дублируются реже? Какой в этом толк, если «мгновенное» потребление памяти при этом все равно больше, а производительность сравнима или немного выше только если ее доступно в «пять раз больше, чем нужно»?

mannaz
()
Ответ на: комментарий от Legioner

> Если старый объект ссылается на новый, новый переходит в разряд старых, не вижу никаких проблем

В какой момент переходит? Он ведь не может просто так перейти, а (по твоему же определению) тянет за собой весь молодняк, на который ссылается. Чёткость понятия «поколение» при этом размывается, все пытаются по блату пролезть в деды.

А может и вообще не вызваться, насколько я помню

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

Для освобождения ресурсов используется метод close

Ну, то есть, старое-доброе ручное управление ресурсами.

den73 ★★★★★
()
Ответ на: комментарий от Eldar_Sonar

~T() можно выполнить для любого типа T:)

dilmah ★★★★★
()
Ответ на: комментарий от mannaz

>> Практически везде где на полученную из библиотеки структуру надо сказать free() после использования.

Ты не загоняешься случайно?

Дублирование данных действительно иногда встречается. Но пример Absurd-a мне кажется не очень удачным. Сделать дубликат иногда бывает полезно, когда жизненный цикл объекта разнесен по разным слоям архитектурной абстракции приложения (простите за заумность слов), т. е. одни должны создать объект, а другие должны использовать, и кто-то этот объект должен освободить. При этом в рамках текущей архитектуры нельзя заранее определить когда такой объект перестанет быть нужен, не залезая в конкретную реализацию отдельного слоя абстракции. По этому иногда делают отдельную копию для каждого из уровней абстракции. Пускай каждая из обособленных частей программы сама следит за жизненным циклом своего объекта.

Естественно использование GC позволяет избавиться от всей этой фигни.

pathfinder ★★★★
()
Ответ на: комментарий от anonymous

> В случае с удалением очень больших структур - ничто не поможет, нужен полноценный GC.
Почему же. Структура может быть выделена на стеке, либо в отдельной области памяти. Если известно, что она не содержит указателей ни ни что внешнее, её можно удалить «оптом». В случае С для этого можно написать отдельный выделитель конкретно под эту структуру, вместо malloc(). Ну или разбить структуру на страницы.

Кстати, о страницах. Есть ещё один способ организации памяти - который используется в реляционных СУБД. При наличии ограничений ссылочной целостности он гарантирует отстуствие висящих указателей. База строится постранично. При расположении базы в памяти id-ом объекта может быть его адрес, что даёт быстроту разыменования ссылок.

Вообще, я тут подумал, что ручное управление - оно на самом деле ещё не совсем ручное, если тупо использовать malloc(). Потому что за сценой происходят какие-то сложные вещи. Хотя бы из-за той же фрагментации. Представим себе примерно такую программу:

// извините, если что, уже забыл C
struct { listNode *next; size_t size } listNode;
listNode *Root=NULL;

void Push (void *ptr,size_t size) {
listNode *newRoot=(listNode *)ptr;
newRoot->size=size;
newRoot->next=Root;
Root=newRoot;
}

int main(argc,argv) {
for (size_of i=size_of(listNode);i<100000000;i+=32) {
void *p1=malloc(i);
void *p2=malloc(i+16);
free(p1);
Push(p2,i+16);
}
getch();
while(Root) {
listNode* it=Root;
Root=Root->next;
free(it);
}
}

Эта программа потребует примерно вдвое большего диапазона адресов, чем то количество памяти, которое она занимает. Можно довести этот коэффициент до 0.9999, если удалять не каждый второй элемент, а сделать некий почти всюду пустой фрактал. Сборка мусора здесь помогла бы уложить объекты поближе друг к другу. Другое дело, что и операционная система тоже не дура и может организовать память постранично.

den73 ★★★★★
()

>Имеем для каждого объекта счетчик ссылок на него. Исчезает последняя ссылка --> объект удаляется.

А если два объекта ссылаются друг на друга? :)

...

А так - это и есть сборка мусора на счётчиках ссылок. У меня в программах на Форте такие объекты ещё 15 лет назад были :)

KRoN73 ★★★★★
()
Ответ на: комментарий от pathfinder

>а программист с «прямыми руками» сможет нормально и на c++ управлять памятью.

Ну, мы когда-то и на ассемблере проблем с выделением памяти не имели ;) Что не мешает сегодня всё равно где удобно использовать Java, а где удобно - и PHP :D

KRoN73 ★★★★★
()
Ответ на: комментарий от den73

> Кстати, для поддержки финализации сборщик мусора вынужден вести реестр всех живых объектов, у которых есть финализация, и это тоже стоит ресурсов.

По-моему, не обязан. finalize() - метод класса Object, поэтому он есть всегда.

svr69 ★★
()
Ответ на: комментарий от KRoN73

Вот именно. Кто умеет писать программы, у того они будут получаться на любом языке программирования. А кто не умеет, тому и сборщики мусора не помогут. И специализации у языков разные.

Ещё можно спорить, что автоматическая коробка передач (сборщик мусора) типа круче ручной коробки (явная работа с памятью).

anonymous
()
Ответ на: комментарий от Love5an

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

anonymous
()
Ответ на: комментарий от svr69

> По-моему, не обязан. finalize() - метод класса Object, поэтому он есть всегда
Я не знаю, как это в Яве. Логично было бы просто _забыть_ про объект, который стал мусором. В этом, собственно и есть смысл сборщика мусора. Ведь современный (двигающий) сборщик собирает не мусор. Он собирает живые объекты. Чтобы вызвать финализатор, нужно не только собрать живые объекты, но и мусор. С точки зрения реализации, я бы сделал так. По умолчанию финализация объекта ничего не делает и про него можно просто забыть. Если финализация объекта что-то делает, то его нужно учесть. Во время сборки мусора смотрим в эту таблицу и отмечаем в ней все живые объекты. Те объекты, которые не оказались живыми - они мёртвые. Но таблица финализаторов на них ещё ссылается. Поэтому вызываем для них финализатор и с этого момента о них действительно можно забыть.

По-моему, в лиспе так обычно и делают. Думаю, и в Яве тоже.

den73 ★★★★★
()
Ответ на: комментарий от anonymous

не раньше чем тогда, когда к программированию и интернету будут допускать по IQ-цензу

Love5an
()
Ответ на: комментарий от den73

там не совсем о мусоре речь. finalize() вызывается как раз, когда объект еще жив, но по своему состоянию готов стать мусором, то есть не используется живыми потоками. Это и называется «finalization ready». По умолчанию этот метод и есть пустой. Просто быстрее вызвать пустой метод, чем смотреть, есть ли этот метод, есть ли у него какое-то тело и т.дт.

svr69 ★★
()
Ответ на: комментарий от den73

Ну, то есть, старое-доброе ручное управление ресурсами.

А лучше не придумали. Я не видел, по крайней мере, и вообще не представляю, как можно управлять любым ресурсом автоматически. Вообще идеальный синтаксический сахар, имхо, в D:

auto f = new File();
scope (exit) f.close();
...

аналог:

auto f = new File();
try {
...
} finally {
  f.close();
}

но без введения лишнего уровня вложенности, инициализация и освобождение рядом - лучше читается.

Legioner ★★★★★
()
Ответ на: комментарий от svr69

Ну можно рядом с указателем объекта хранить какой-нибудь бит, перегружен у него finalize или нет. Но, имхо, смысла нет. Всякие JIT-ы и так должны делать вызовы пустых функций практически нулевыми.

Legioner ★★★★★
()
Ответ на: комментарий от svr69

>Просто быстрее вызвать пустой метод, чем смотреть, есть ли этот метод, есть ли у него какое-то тело и т.дт.

Ага, а зачем тогда в .Net нужен GC.SuppresFinalize() и соответствующий атрибут (атрибут необходимости вызова finalize()) у каждого управляемого объекта?

pathfinder ★★★★
()
Ответ на: комментарий от Legioner

Да и вообще немного не в тему, такая функция пишется на любом языке с лямбдами, и к управлению ресурсами отношения не имеет.

Legioner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.