программистское: о памяти
Nov. 22nd, 2009 12:04 amВ обсуждении о компиляторах всплыл полезный совет: иногда об освобождении памяти лучше и не думать.
(кстати, всю эту дискуссию о компиляторах стоит проглядеть: там есть немало отличных ссылок)
Это очень полезный совет: иногда в языке и среде, которые казалось бы требуют тщательной работы с malloc()/free() или их эквивалентами, про free() можно просто забыть и не делать, если программа выполняется быстро и не требует очень много памяти. Полезный потому, что привыкший к тщательной дисциплине программист может об этом просто не подумать.
Но мне это напомнило еще вот какую давнюю мысль: по-моему, намного реже, чем следовало бы, программисты на языках с эксплицитной обработкой памяти пользуются отдельными кучами. "Отдельная куча" (private heap) означает всего лишь возможность отводить память в отдельном месте, идентифицируемом каким-то ключом. Например, в Win32 есть функции: HeapCreate() создает новую кучу и возвращает идентификатор, HeapAlloc() - вместе с желаемым размером получает идентификатор кучи и отводит память именно в ней, HeapFree() - очевидно, и HeapDestroy() - удалить всю кучу вместе со всей памятью в ней, которой еще не сделали HeapFree().
Иногда это называют не кучей, а "ареной", но суть та же. На самом деле самое главное во всем этом - возможность удалить кучу одним махом, потому что если она есть, и если вся память, что отводится из кучи, вместе не слишком велика, то отдельно освобождать ничего не надо. Собственно, можно обойтись без free() вообще. Куча тогда превращается в сплошной кусок памяти (или связанный список таких кускок, если надо), а malloc() становится тривиальным, он просто двигает указатель на свободную часть кучи.
Очень часто значительная часть логики программы устроена так. Начинаем строить какой-то объект (в C это может быть сложная структура, неважно), он в свою очередь создает и инициализирует другие объекты внутри себя, или целые массивы, или списки, или еще что, неважно. Все это по цепочке вложено друг в друга, а после создания еще начинает как-то работать и двигаться вместе, вызывать друг друга, хранить какую-то информацию итд. В конце концов объект выполнил свое дело и удаляется, по цепочке вначале удаляя все вложенные объекты и контейнеры и освобождая всю память. Все это делается через сложный танец new/delete или malloc/free. Но если вся память, что нужна объекту и всему, что в него вложено, не слишком велика на протяжении его жизни, то с помощью отдельной кучи только для этого объекта и всего, что в него вложено, можно избежать всего этого сложного танца и сделать код одновременно намного проще, лучше защищенным от ошибок и даже быстрее - да-да, быстрее, чем обычный танец malloc/free. Единственное, чем платишь - повышенным расходом памяти во время жизни объекта, да и то часто налог этот весьма невелик.
Я уже лет десять как не пишу под Windows, но до сих пор помню, какими полезными и правильными были функции для работы с отдельными кучами. Конечно, каждый может сам на коленке сколотить что-то свое для этого; я не раз такое встречал, да и сам несколько раз писал. Но все же меня удивляет, что в Юниксе нет стандартного интерфейса для этого дела. И я не раз видел исходники библиотек или приложений, которые бы этот простой прием сильно упростил и улучшил.
Memory leaks are the least of your problems in a compiler; it's not like it's a long running process. You run it, it terminates, the OS cleans up for you.
I did some work on SDCC years ago and it went through a brief "lets use a garbage collector!" phase until everybody realized it was contributing negative value. It was more efficient to simply free memory where convenient and leak it where not.
... и дальше:
I believe it's well known that compilers leak memory like sieves. But the thing is, it doesn't really matter in most contexts. If the leak is linear with the size of the program you're probably fine and no one will notice anyway.
(кстати, всю эту дискуссию о компиляторах стоит проглядеть: там есть немало отличных ссылок)
Это очень полезный совет: иногда в языке и среде, которые казалось бы требуют тщательной работы с malloc()/free() или их эквивалентами, про free() можно просто забыть и не делать, если программа выполняется быстро и не требует очень много памяти. Полезный потому, что привыкший к тщательной дисциплине программист может об этом просто не подумать.
Но мне это напомнило еще вот какую давнюю мысль: по-моему, намного реже, чем следовало бы, программисты на языках с эксплицитной обработкой памяти пользуются отдельными кучами. "Отдельная куча" (private heap) означает всего лишь возможность отводить память в отдельном месте, идентифицируемом каким-то ключом. Например, в Win32 есть функции: HeapCreate() создает новую кучу и возвращает идентификатор, HeapAlloc() - вместе с желаемым размером получает идентификатор кучи и отводит память именно в ней, HeapFree() - очевидно, и HeapDestroy() - удалить всю кучу вместе со всей памятью в ней, которой еще не сделали HeapFree().
Иногда это называют не кучей, а "ареной", но суть та же. На самом деле самое главное во всем этом - возможность удалить кучу одним махом, потому что если она есть, и если вся память, что отводится из кучи, вместе не слишком велика, то отдельно освобождать ничего не надо. Собственно, можно обойтись без free() вообще. Куча тогда превращается в сплошной кусок памяти (или связанный список таких кускок, если надо), а malloc() становится тривиальным, он просто двигает указатель на свободную часть кучи.
Очень часто значительная часть логики программы устроена так. Начинаем строить какой-то объект (в C это может быть сложная структура, неважно), он в свою очередь создает и инициализирует другие объекты внутри себя, или целые массивы, или списки, или еще что, неважно. Все это по цепочке вложено друг в друга, а после создания еще начинает как-то работать и двигаться вместе, вызывать друг друга, хранить какую-то информацию итд. В конце концов объект выполнил свое дело и удаляется, по цепочке вначале удаляя все вложенные объекты и контейнеры и освобождая всю память. Все это делается через сложный танец new/delete или malloc/free. Но если вся память, что нужна объекту и всему, что в него вложено, не слишком велика на протяжении его жизни, то с помощью отдельной кучи только для этого объекта и всего, что в него вложено, можно избежать всего этого сложного танца и сделать код одновременно намного проще, лучше защищенным от ошибок и даже быстрее - да-да, быстрее, чем обычный танец malloc/free. Единственное, чем платишь - повышенным расходом памяти во время жизни объекта, да и то часто налог этот весьма невелик.
Я уже лет десять как не пишу под Windows, но до сих пор помню, какими полезными и правильными были функции для работы с отдельными кучами. Конечно, каждый может сам на коленке сколотить что-то свое для этого; я не раз такое встречал, да и сам несколько раз писал. Но все же меня удивляет, что в Юниксе нет стандартного интерфейса для этого дела. И я не раз видел исходники библиотек или приложений, которые бы этот простой прием сильно упростил и улучшил.
no subject
Date: 2009-11-21 10:20 pm (UTC)no subject
Date: 2009-11-21 10:38 pm (UTC)Как не крути это высокая поэзия (разговоры о распределении памяти), хотя, видимо программирование уже давно ремесло.
Вообщем, мы все умрем:)
no subject
Date: 2009-11-21 10:52 pm (UTC)no subject
Date: 2009-11-21 10:59 pm (UTC)http://jordanmechner.com/wp-content/uploads/1989/10/popsource009.pdf
Особенно стр. 4 этого документа. Это к вопросу о "максимум сто мег".
no subject
Date: 2009-11-21 11:01 pm (UTC)То, что HeapAlloc() использует свой конкретный алгоритм, и в частности не так быстр, как, например, алгоритм сплошной аллокации без освобождения - в 99.99% случаев абсолютно неважно и никакой роли не играет. Давайте не забывать слова Кнута о преждевременной оптимизации.
no subject
Date: 2009-11-21 11:03 pm (UTC)Быстродействие — 20 тыс. оп./с. Оперативное ЗУ на ферритных сердечниках (16 384 слова, слова 45-разрядные).
no subject
Date: 2009-11-21 11:13 pm (UTC)no subject
Date: 2009-11-21 11:16 pm (UTC)Про часть арсенала аргумент хороший, тут согласен.
no subject
Date: 2009-11-21 11:35 pm (UTC)no subject
Date: 2009-11-22 12:26 am (UTC)До завершения она ведь не знает, когда именно что можно очистить.
no subject
Date: 2009-11-22 12:27 am (UTC)no subject
Date: 2009-11-22 12:47 am (UTC)no subject
Date: 2009-11-22 01:11 am (UTC)недавний пост одного из моих friends
Сборка libtorrent-rasterbar 0.14.6 уложила мне виртуальную машину, run out of swap space. Гиг памяти и гиг свопа.
no subject
Date: 2009-11-22 01:14 am (UTC)no subject
Date: 2009-11-22 01:17 am (UTC)no subject
Date: 2009-11-22 01:19 am (UTC)no subject
Date: 2009-11-22 01:34 am (UTC)"в компиляторе можно не заботиться о memory leaks" это "640K ought to be enough for anybody". Через несколько лет все компьютеры будут 64-битные и с 16-32 CPUs, а вот размер памяти в такой же пропорции не вырастет
Потом, даже если у нас не кончается память - все равно, освобождать память как можно быстрее как только мы перестали ей пользоваться - очень выгодно для производительности, потому что кэш.
no subject
Date: 2009-11-22 01:38 am (UTC)Ну что за подход, а? Ресурсов всегда не хватает, аксиома. Привыкнув к тому что 12гбайтный файл можно утянуть в память, отсортировать и иметь по нему быстрый доступ, очень трудно начать снова считать мегабайты. И когда это приходится делать - это неприятно.
Принца персии уместили в 130 кб, здорово. Но только принц персии - один, и графика там однообразна. А игр под айфон наверняка уже много, разных и более красочных. Это происходит в т.ч. от роста кол-ва ресурсов (и влезает больше, и программировать проще). И это - хорошо.
Собственно, кому я это говорю.
Короче, лично я - не вдохновился. Авторы принца персии молодцы, но повторять их подвиги без особой нужды лично я не стану.
no subject
Date: 2009-11-22 01:43 am (UTC)А ещё в древнем паскале (не помню версии, может, это ещё от Вирта идёт) для кучи были процедуры mark и release. Говоришь mark, делаешь какие хошь аллокации, говоришь release, куча сдувется обратно до состояния на момент mark. Но с раздельными удобнее.
no subject
Date: 2009-11-22 01:45 am (UTC)Во-вторых, если хоть одна (оригинальная) игра для айфона будет так же хороша, как первый принц, и станет таким же событием, меня это удивит.
В-третьих, да, все это верно, и я не призываю, конечно, всерьез втискиваться в сотни килобайтов в современной программе. Но уметь внимательно следить за памятью - не всегда, и везде, но в важных местах - все еще полезно, и полагаю, полезным быть в ближайшие лет 30 не перестанет.
И в этом смысле вышеприведенный документ меня несоменно вдохновляет. Смысл из него следует выносить не тот, что давайте все вернемся к 16-битной графике и запихаем ее в килобайты. А тот, что вот пример человека, который очень хорошо понимал и мог рассчитать, где у него ресурсы расходуются и на что. И умел там, где это надо, оптимизировать. Умение это неизбежно пригодится - не с 12-гигабайтным файлом, так с 50-гигабайтным.
no subject
Date: 2009-11-22 01:47 am (UTC)Вот насчет кэша это справедливо сказано, да. Но не всегда это важно.
no subject
Date: 2009-11-22 03:29 am (UTC)Что важно и что неважно - это интересный вопрос. За весьма недолгое время все изменилось несколько раз -
- диск быстрый, памяти мало, процессор медленный. Каждая уважающая себя программа заводит всякие временные файлы и хранит там все что только может пригодиться
- диск медленный, процессор быстрый. На диске только самые необходимые данные, причем часто компрессированные
- Память быстрая и много. Все кэшируется, кэшируется.
- Процессор гораздо быстрее памяти. Опять стараются минимизировать working set.
- Процессор слабый, зато их много. Как работает одна программа вообще неважно, важно как они работают все вместе.
Совет что можно особенно не париться с освобождением памяти вовремя был верен несколько лет назад, когда появилась быстрая и дешевая DDR. Сейчас опять главный bottleneck - memory bandwidth. Скоро, думаю, вообще ничего не будет важно по сравнению с минимизацией интеракций между разными процессами - потому что кэши будут большие, а bottleneck будет cache coherency
no subject
Date: 2009-11-22 04:16 am (UTC)no subject
Date: 2009-11-22 04:59 am (UTC)Память, в отличие от run-time, плохо масштабируется: в какой-то момент уже небольшой рост приводит к тому, что процесс падает или уходит в свап. Это, в частности, одна из проблем в model checking: ты можешь согласиться с тем, чтобы ждать недели, пока экспоненциальный алгоритм даст тебе ответ, но с экспоненциального размера моделью просто невозможно работать.
Private heap - хорошая штука.
no subject
Date: 2009-11-22 05:08 am (UTC)Например, в рантайме апача имеется соответствующая подсистема. В веб-сервере (и в любом stateless сервере) это единственная разумная стратегия работы с памятью. Память, связанная с запросом, живет не дольше, чем сам запрос. В апаче все еще несколько удобнее: пул может быть частью другого пула. Можно прибить пул и вместе с ним освобождаются все его дети. А освобождение кусков внутри пула не предусмотрено, ибо незачем.
Пы. Сы. В Калифорнийщину по поводу Хрома ездили?