Данный список не в коей мере не претендует на полноту или ещё на что-либо. Просто набор моментов, которые показались мне интересными.
Неожиданное исчерпание свободной памяти.
Возникает при использовании пиннинга памяти. Не обязательно явном. Например при использовании асинхронного ввода-вывода ран-тайм скорее всего сам будет делать пиннинг памяти. В CLI такая возможность так же присутствует в явном виде, в Java — не знаю. Суть пиннинга — запрещение перемещения объекта в памяти. Это может препятствовать "уплотнению" используемой памяти во время сборки мусора, таким образом используемая память будет сильно фрагментирована и "кпд" использования памяти резко снижается. Здесь можно прочитать более подробно. Кратко суть такая: в C# при использовании асинхронных сокетов (IOCP) в сервере заканчивалось 3GB памяти (летели OutOfMemoryException), хотя "полезных" данных в памяти было примерно 200MB.
Постепенная и периодическая деградация производительности и рост используемой памяти под нагрузкой.
Подробности здесь. Кратко: в Java при использовании WeakHashMap и включении Concurrent GC вместо штатного начали проявляться упомянутые эффекты. Оказалось, что под нагрузкой большую часть элементов WeakHashMap постоянно "трогают", хотя реально не используют. Это "троганье" предотвращало удаление каких-либо объектов из WeakHashMap вообще. Соответственно он неограниченно рос, пока весь сервер не затыкался на некоторое время. Во время затыкания элементы WeakHashMap никто не "трогал", и соотв. они все удалялись. Все становится хорошо. А потом по новой. Со штатным GC такой эффект не наблюдается.
Ну более-менее стандартная проблема с временем жизни и владением объектами.
Многие по началу считают, что при наличии GC этих проблем просто нет. К сожалению это не так. GC обеспечивает только базовую потокобезопасность для ссылок (можно захватывать новые ссылки на объект, только если у тебя уже есть хотя бы одна), но не обеспечивает строгой потокобезопасности для ссылок (возможность захватывать новые ссылки на объект, если у тебя ещё нет ни одной). Т.о. если необходима строгая потокобезопасность, то — мьютекс + счётчик ссылок... Что-то мне это напоминает...
Один из примеров можно поглядеть здесь
Первые две проблемы от того, что GC хотят сделать полностью прозрачным и скрытым от программиста, типа он просто магически работает. К сожалению, полностью прозрачен он только на абстрактном уровне. Когда дело доходит до самых деталей начинается самое интересное — побочные эффекты GC становятся видны. Отсюда ещё одно неприятное следствие — программа хорошо работающая с одним типом или даже скорее реализацией GC, совсем не обязательно будет хорошо работать с другой.
Третья проблема от огромных лозунгов, что все проблемы с управлением памятью и иже с ними временем жизни и владением — решены GC. GC решена только проблема управлением памятью. А время жизни и владение, как были одними из сложных проблем разработки ПО, так ими и остались. И эти моменты по прежнему надо продумывать и контролировать вручную.
Какие мысли?
12.09.07 03:28: Перенесено из 'Архитектура программного обеспечения'
<...информация прочитана, усвоена и почикана...>
R>Какие мысли?
Главная мысль в том, что перечисленные тобой случаи можно назвать граничными условиями использования GC. Да, в них GC становится одним из условий решаемой задачи, поскольку нужно принимать в расчет его возможные (и невозможные) фокусы.
Но с другой стороны, есть масса очень простых случаев, когда GC берет на себя заботы программиста. Тем самым упрощая разработку и снижая количество ошибок. А если учесть, что при использовании GC выделение памяти обходится (по слухам) дешевле C-шных malloc-ов, а освобождается затем всем скопом, то GC может привести (и приводит, по benchmark-ам по крайней мере) к увеличению скорости работы программы на некоторых задачах.
Так что, если вспомнить закон потребления пива 20/80, то GC является отличным инструментом в 80% случаев. Зато в оставшихся 20% с ним придется бороться.
Явное же управление памятью наоборот, вызывает лишние затраты в 80% случаев, зато в 20% это именно то, что нужно.
Справедливости ради нужно сказать, что в C++, к счастью, есть возможность создавать объекты на стеке и передавать их по значению. Что часто очень серьезно снижает количество операций new/delete.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, remark, Вы писали:
R>Возникает при использовании пиннинга памяти. Не обязательно явном. Например при использовании асинхронного ввода-вывода ран-тайм скорее всего сам будет делать пиннинг памяти. В CLI такая возможность так же присутствует в явном виде, в Java — не знаю. Суть пиннинга — запрещение перемещения объекта в памяти. Это может препятствовать "уплотнению" используемой памяти во время сборки мусора, таким образом используемая память будет сильно фрагментирована и "кпд" использования памяти резко снижается.
pinning — это unsafe операция. А unsafe поэтому и называется unsafe, что вполне можно загасить GC, или сделать другие пакости типа обращение по невалидному указателю. Никто не обещал кайфа при unsafe (как и при работе с unmanaged ресурсами). То что 90 процентов(и может быть больше) обычной программы — это чистые managed типы где можно забыть про деструктор — это и есть успех GC.
R>Здесь можно прочитать более подробно. Кратко суть такая: в C# при использовании асинхронных сокетов (IOCP) в сервере заканчивалось 3GB памяти (летели OutOfMemoryException), хотя "полезных" данных в памяти было примерно 200MB.
Это просто бага. Притом бага не GC — а использования unsafe.
R>Кратко: в Java
Не буду высказываться — подробности GC в Java — мне неизвестны.
R>Многие по началу считают, что при наличии GC этих проблем просто нет. К сожалению это не так. GC обеспечивает только базовую потокобезопасность для ссылок (можно захватывать новые ссылки на объект, только если у тебя уже есть хотя бы одна), но не обеспечивает строгой потокобезопасности для ссылок (возможность захватывать новые ссылки на объект, если у тебя ещё нет ни одной).
Не понял? Статический конструктор действительно не потокобезопасен. Но это легко обходится. R>Т.о. если необходима строгая потокобезопасность, то — мьютекс + счётчик ссылок... Что-то мне это напоминает...
Не нужен подсчет ссылок. R>Один из примеров можно поглядеть здесь
Плохой пример. Это работа с unmanaged. К тому же ответ сильно удивил. Если очень интересно то на форме есть метод Disposing/ event Disposed. Можно вполне понять когда unmanaged handle HWND был закрыт.
R>Первые две проблемы от того, что GC хотят сделать полностью прозрачным и скрытым от программиста, типа он просто магически работает. К сожалению, полностью прозрачен он только на абстрактном уровне. Когда дело доходит до самых деталей начинается самое интересное — побочные эффекты GC становятся видны. Отсюда ещё одно неприятное следствие — программа хорошо работающая с одним типом или даже скорее реализацией GC, совсем не обязательно будет хорошо работать с другой.
Именно поэтому в принципе в Net — один тип GC(в действительности два). И скрыто потому, что выделением/невыделением каких-то объектов ты лучше можешь управлять производительностью чем редактированием самих параметров GC.
R>Третья проблема от огромных лозунгов, что все проблемы с управлением памятью и иже с ними временем жизни и владением — решены GC. GC решена только проблема управлением памятью. А время жизни и владение, как были одними из сложных проблем разработки ПО, так ими и остались.
Для managed objects — решены. А их подавляющее большинство. R>И эти моменты по прежнему надо продумывать и контролировать вручную.
А для этого и есть Dispose Pattern. Не слишком хорошая вещь — но работает.
R>Какие мысли?
Не убедил.
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, remark
E><...информация прочитана, усвоена и почикана...>
R>>Какие мысли?
E>Главная мысль в том, что перечисленные тобой случаи можно назвать граничными условиями использования GC. Да, в них GC становится одним из условий решаемой задачи, поскольку нужно принимать в расчет его возможные (и невозможные) фокусы.
Я думаю, тут всё зависит от масштаба и уровня приложения.
В крупном/серьёзном/развиваемом продукте такие "граничные условия" скорее всего рано или поздно появятся. Дальше будет основной вопрос — возможно ли их вообще обойти при использовании выбранных технических средств? ...
E>Но с другой стороны, есть масса очень простых случаев, когда GC берет на себя заботы программиста. Тем самым упрощая разработку и снижая количество ошибок. А если учесть, что при использовании GC выделение памяти обходится (по слухам) дешевле C-шных malloc-ов, а освобождается затем всем скопом, то GC может привести (и приводит, по benchmark-ам по крайней мере) к увеличению скорости работы программы на некоторых задачах.
Я думаю, не стоит развивать эту тему дальше... Каждый может сразу представить к чему это приведёт и сразу просто остаться при своём мнении
E>Так что, если вспомнить закон потребления пива 20/80, то GC является отличным инструментом в 80% случаев. Зато в оставшихся 20% с ним придется бороться.
E>Явное же управление памятью наоборот, вызывает лишние затраты в 80% случаев, зато в 20% это именно то, что нужно.
Да. Если ты пишешь только 80%. Если же ты пишешь и оставшиеся 20%, и соотв. у тебя есть решения для этих 20%, то не вижу смысла не применять их в других 80%. Точнее так — я не вижу больше проблемы.
E>Справедливости ради нужно сказать, что в C++, к счастью, есть возможность создавать объекты на стеке и передавать их по значению. Что часто очень серьезно снижает количество операций new/delete.
В C# тоже есть "структуры", которые создаются на стеке и передаются по значению.
Приемущество "Сишных" malloc/free в данном контексте — они просты как 2 копейки. В них просто нет ничего, что могло бы вообще вызывать какие-либо нарекания, или какие-либо проблемы. Именно они сами, не то, как их использовать.
Я не помню, что бы слышал какие либо нарекания именно в адрес самих malloc/free. Да, это низкоуровневое средство. И оно хорошо делает одну чётко определённую вещь...
Здравствуйте, GlebZ, Вы писали:
GZ>Здравствуйте, remark, Вы писали:
R>>Я не помню, что бы слышал какие либо нарекания именно в адрес самих malloc/free. GZ>Дефрагментация.
Если чесно, то никогда с этой проблемой на практике не сталкивался.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Если чесно, то никогда с этой проблемой на практике не сталкивался.
Сталкивался. Было много мелких объектов, алгоритм должен был быть очень быстрым. При выделении памяти — стандартный malloc проходил по всей цепочке пытаясь найти место, в результате проседала производительность. Пришлось переопределять new, и делать свой менеджер памяти. Примерно такая-же ситуация была с VirtualAlloc(работает по тому же принципу).
Кто-то писал, что в САПР переопределение стандартного менеджера памяти было необходимо(по тем же причинам).
Здравствуйте, remark, Вы писали:
R>Здесь можно прочитать более подробно. Кратко суть такая: в C# при использовании асинхронных сокетов (IOCP) в сервере заканчивалось 3GB памяти (летели OutOfMemoryException), хотя "полезных" данных в памяти было примерно 200MB.
В Java для асинхронного и memory-mapped IO используются unmanaged-буфферы, которые располагаются отдельно от основной кучи. Пиннинг делается только для критичных по скорости кусков нативного кода.
R>Постепенная и периодическая деградация производительности и рост используемой памяти под нагрузкой. R>Подробности здесь. Кратко: в Java при использовании WeakHashMap и включении Concurrent GC вместо штатного начали проявляться упомянутые эффекты. Оказалось, что под нагрузкой большую часть элементов WeakHashMap постоянно "трогают", хотя реально не используют. Это "троганье" предотвращало удаление каких-либо объектов из WeakHashMap вообще.
Бывает. Поэтому необходимо понимание механизмов работы GC. Обычно поэтому всегда вдобавок к Concurrent GC (обычно это просто трехцветный GC) еще включают обычный stop-the-world GC.
Кстати, при должной аппаратной поддержке этой проблемы можно было бы избежать
R>Многие по началу считают, что при наличии GC этих проблем просто нет. К сожалению это не так. GC обеспечивает только базовую потокобезопасность для ссылок (можно захватывать новые ссылки на объект, только если у тебя уже есть хотя бы одна), но не обеспечивает строгой потокобезопасности для ссылок (возможность захватывать новые ссылки на объект, если у тебя ещё нет ни одной). Т.о. если необходима строгая потокобезопасность, то — мьютекс + счётчик ссылок... Что-то мне это напоминает... R>Один из примеров можно поглядеть здесь
В данном случае мьютекс+счетчик — плохое решение. Дело в том, что работать с формой можно безопасно только из одного потока. Нужно было замаршалить вызов в GUI-поток, тогда никаких гонок бы не было.
В Java, кстати, по умолчанию таймеры как раз и работают в GUI-потоке.
R>Какие мысли?
Примеры надо покрасивее придумать
Здравствуйте, GlebZ, Вы писали:
R>>Здесь можно прочитать более подробно. Кратко суть такая: в C# при использовании асинхронных сокетов (IOCP) в сервере заканчивалось 3GB памяти (летели OutOfMemoryException), хотя "полезных" данных в памяти было примерно 200MB. GZ>Это просто бага. Притом бага не GC — а использования unsafe.
Это абсолютно не бага. Это архитектурная несовместимость GC и асинхронного высокопроизводительного zero-copy ввода-вывода.
R>>Какие мысли? GZ>Не убедил.
Я ни в чём и не убеждал
GZ>С уважением, Gleb.
Здравствуйте, GlebZ, Вы писали:
E>>Если чесно, то никогда с этой проблемой на практике не сталкивался. GZ>Сталкивался. Было много мелких объектов, алгоритм должен был быть очень быстрым. При выделении памяти — стандартный malloc проходил по всей цепочке пытаясь найти место, в результате проседала производительность. Пришлось переопределять new, и делать свой менеджер памяти. Примерно такая-же ситуация была с VirtualAlloc(работает по тому же принципу). GZ>Кто-то писал, что в САПР переопределение стандартного менеджера памяти было необходимо(по тем же причинам).
Ну так и ГЦ можно считать эдаким PoolBasedAllocator
От ОС то он все равно ее функциями память получает
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Здравствуйте, remark, Вы писали:
R>Это абсолютно не бага. Это архитектурная несовместимость GC и асинхронного высокопроизводительного zero-copy ввода-вывода.
Это исключительно из-за того что ОС не правильной системы.
Если бы ОС была правильной то никакой несовместимости бы небыло... оно бы еще и быстрее чем на классических ОС работало...
... << RSDN@Home 1.2.0 alpha rev. 673>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, GlebZ, Вы писали:
E>>Если чесно, то никогда с этой проблемой на практике не сталкивался. GZ>Сталкивался. Было много мелких объектов, алгоритм должен был быть очень быстрым. При выделении памяти — стандартный malloc проходил по всей цепочке пытаясь найти место, в результате проседала производительность. Пришлось переопределять new, и делать свой менеджер памяти. Примерно такая-же ситуация была с VirtualAlloc(работает по тому же принципу). GZ>Кто-то писал, что в САПР переопределение стандартного менеджера памяти было необходимо(по тем же причинам).
GZ>Так что бывает.
Я считал, что проблема фрагментации памяти для new/delete (malloc/free) заключается в другом. В том, что адресное пространство процесса постоянно растет. Что приводит к частым попаданиям мимо кэша или слишком частым вытеснениям занятых процессом страниц памяти в своп. Что особо чувствительно для 24x7 процессов, которые месяцами не выгружаются и не останавливаются.
А то, о чем ты написал -- это просто неподходящий алгоритм выделения памяти для конкретной задачи. Непосредственно к проблеме явного управления памяти он, имхо, не имеет отношения.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
WolfHound wrote: > R>Это абсолютно не бага. Это архитектурная несовместимость GC и > асинхронного высокопроизводительного zero-copy ввода-вывода. > Это исключительно из-за того что ОС не правильной системы. > Если бы ОС была правильной то никакой несовместимости бы небыло... оно > бы еще и быстрее чем на классических ОС работало...
Я вот лично не знаю как сделать полный аналог Boost.Interprocess без
ручного управления памятью. Хотя как GC работают я знаю в деталях.
Здравствуйте, remark, Вы писали:
R>Постепенная и периодическая деградация производительности и рост используемой памяти под нагрузкой. R>Подробности здесь. Кратко: в Java при использовании WeakHashMap и включении Concurrent GC вместо штатного начали проявляться упомянутые эффекты. Оказалось, что под нагрузкой большую часть элементов WeakHashMap постоянно "трогают", хотя реально не используют. Это "троганье" предотвращало удаление каких-либо объектов из WeakHashMap вообще. Соответственно он неограниченно рос, пока весь сервер не затыкался на некоторое время. Во время затыкания элементы WeakHashMap никто не "трогал", и соотв. они все удалялись. Все становится хорошо. А потом по новой. Со штатным GC такой эффект не наблюдается.
В статье есть еще одно условие — hashCode() должна всегда возвращать одно и то же значение, тогда как equals() работать нормально. Естественно это баг, баг в програме, который приведёт к проседанию производительности при любом GC на любой Map. Просто обычный stop-the-world _маскировал_ эту проблему.
Что касается частого выбрасывания элементов из weak map при CGC, то, действительно, из программы крутилок никаких нету (в .Net, afair, крутилка доступна самой программе). Зато есть крутилка в sun jvm, которая позволяет продлить время жизни weak элемента, даже после того, как на него пропали ссылки. Другое дело, что когда пришло время эту крутилку попользовать, то оказалось, что на солярке, при той комбинации параметров, что давала лучшую производительность, эта крутилка игнорится. Сантехники баг починили за месяц, но осадочек остался.
R>Третья проблема от огромных лозунгов, что все проблемы с управлением памятью и иже с ними временем жизни и владением — решены GC.
Дык, эта. Неуправляемыми ресурсами нужно управлять самостоятельно.
R>Какие мысли?
Здравствуйте, Cyberax, Вы писали:
C>Я вот лично не знаю как сделать полный аналог Boost.Interprocess без ручного управления памятью. Хотя как GC работают я знаю в деталях.
Я так понял это куча контейнеров которые шарятся между процессами?
А нафига?
Лично у меня никогда даже жилания не возникало.
Я даже между потоками стараюсь шарить только неизменяемые структуры данных.
А шарить между процессами изменяемые звучит как полная дичь.
... << RSDN@Home 1.2.0 alpha rev. 673>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
По моему, этой теме ближе раздел философии, а не дизайна. Как-то ожидал увидеть рекомендации построения приложений так, чтобы обойти подводные камни, да не вижу.
Однажды приучившись явно управлять памятью непросто от этого избавиться. Так и хочется поуправлять.
А то что GC пытаются сделать прозрачным да незаметным -- это хорошо. Задачи разные есть, каждой задаче свой инструмент, и свой подход.
Здравствуйте, WolfHound, Вы писали:
C>>Я вот лично не знаю как сделать полный аналог Boost.Interprocess без ручного управления памятью. Хотя как GC работают я знаю в деталях. WH>Я так понял это куча контейнеров которые шарятся между процессами?
Между процессами или отображаемыми в память файлами.
WH>А нафига? WH>Лично у меня никогда даже жилания не возникало. WH>Я даже между потоками стараюсь шарить только неизменяемые структуры данных. WH>А шарить между процессами изменяемые звучит как полная дичь.
Одно слово: кэши.
Я с его помощью сделал почти прозрачное транзакционное disk-backed кэширование (операционная система еще и предоставляет кэширование в памяти). По скорости ничего даже близко не приближается.
Здравствуйте, Cyberax, Вы писали:
C>Между процессами или отображаемыми в память файлами.
Детали.
C>Одно слово: кэши.
Какие и сколько данных в кеше?
C>Я с его помощью сделал почти прозрачное транзакционное disk-backed кэширование (операционная система еще и предоставляет кэширование в памяти).
Транзакционное? Что-то както сомневаюсь.
В самом Boost.Interprocess нет упоминаний о транзакциях.
А самому их делать
C>По скорости ничего даже близко не приближается.
Тоже не факт.
... << RSDN@Home 1.2.0 alpha rev. 673>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, eao197, Вы писали:
E>Я считал, что проблема фрагментации памяти для new/delete (malloc/free) заключается в другом. В том, что адресное пространство процесса постоянно растет. Что приводит к частым попаданиям мимо кэша или слишком частым вытеснениям занятых процессом страниц памяти в своп.
Та программа была под Dos и выполнялась на 286/386 машинах. Так что не было ни кэша, ни свопа.
E>Что особо чувствительно для 24x7 процессов, которые месяцами не выгружаются и не останавливаются.
Когда работал на С++, для серверов изначально не использовал STL по данной причине. Сейчас уже не знаю, по дурости или без. Поэтому сказать ничего не могу.
E>А то, о чем ты написал -- это просто неподходящий алгоритм выделения памяти для конкретной задачи. Непосредственно к проблеме явного управления памяти он, имхо, не имеет отношения.
Отчего же. GC — позволяет дефрагментировать память, и обходиться без прохода по спискам выделенных/пустых областей. То — что не умеет malloc. Так что замечание было в тему.
Здравствуйте, CreatorCray, Вы писали:
CC>Ну так и ГЦ можно считать эдаким PoolBasedAllocator
Нет. Процесс дефрагментации мешает его так назвать. CC>От ОС то он все равно ее функциями память получает
Вопрос в том, как использовать эту память.