А Вы слышали про питон? Классная штука. :) Серъезно. Почему? Знаете как в нем оформляются блоки, то бишь какие в нем операторные скобки? Их нет. Есть только табуляция. Из-за этого, программу нельзя не оттабулировать - это просто прекрасно! Представляете? Если ты хочешь цикл - будь добр - поставь табуляцию. Вот так:
for i in xrange(1,11): print "%(i)d*%(i)d=%(i2)d"%{'i':i, 'i2':i*i}
Я например считаю, на таком языке надо учиться программировать. К тому же здесь отсутствует жесткая типизация, но при всем при этом есть типы, - то есть совсем не типы, а классы! Все есть объект (даже числа и строки) - и это правильно!
Питон кросплатформенен, есть прекрасно работающая версия под Windows, а его пакеты - это просто сказка - пиши не хочу. К тому же для него есть нормальные (специально для некоторых повторяю - нормальные) HTML шаблоны - то есть шаблоны сделанные по принципу - вот шаблон, вот то что надо в него подставить - получить результат. Они называются ZopePageTemplates, конечно у них есть недостатки - но это лучше смешения логики и html кода или всяческих "полушаблонов".
Вообщем все просто классно. Но почему же история названа печальной? Итак, обо всем по порядку...
Было лето или утро, или тучи, или день,
Или ветер, или вечер, или дождик или тень.
Было рано или осень, или месяц или пыль -
То ли завтра, то ли в полдень мне приснилась эта быль
(из м-ф "...три синих-синих озера малинового цвета")
Давным-давно я занимался созданием системы тестирования с тестами в формате QTI (пытался составить конкуренцию xdls :) ). Даже что-то сделал (сейчас это мне кажется просто невероятным - но факт написал что-то, что работало). Написал кстати на perl. Идеологию perl я тогда еще не понял (не понял и сейчас - просто еще мало знаю, но знаю что мой код был просто ужасен). Потом мне сказали переделать это под python (не скажу что я был счастлив - скорее наоборот). Но после изучения переписал - и довольно быстро.
Сразу после этого мне было сказано перетащить все это под Zope. Это было трудно и тяжело, но я смог - перетащил. И спустя несколько дней, выяснил что у меня в программе есть утечка памяти - я сразу же бросился изучать документацию и нашел модуль gc (garbage collector). И что Вы думаете? Собрал мусор - а память у Zopе не уменьшилась. В ходе длительных экспериментов выяснил что python, как бы оставляет память для себе - то есть если после сборки мусора еще раз произвести разбор xml дерева теста, то еще памяти не выделится. Это было печально - но я думал что это проблемы модуля сбора мусора или модуля по построению DOM. Это было не так.
Спустя много месяцев я случайно (а может это промысел божий?) написал следующие строки кода:
a = range(1,10000000) del a
Правда мило? Сначала заполняем массив числами от 0 до 9999999, а потом удаляем его. И знаете что самое интересное? Запустите python с этим примером. Свыше 100 мегабайт. Осталось. У питона. Я не поверил своим глазам. Подозвал коллег - проверили вместе. Жрет сука! Я был в шоке. Сразу вспомнил историю про Zope и те тесты, что я делал. Ладно - тесты пока никто не использует (Zope и так постоянно виснет - но это уже другая история). Ладненько решил я - может gc все исправит - реакция ноль (то есть питон опять как бы резервирует память на свои нужды).
Что же делать? Искал в yandex и google - нет таких проблем. Может у меня неправильный Питон? Скачал версию 2.4.2 (final) (за свой трафик между прочим) - то же самое, тот же расход памяти. И тогда после недолгих поисков я нашел проект python в SourgeForge и решил помочь разработчикам устранить ошибку.
Что ж я потратил часть своего времени и преодолев все тернии стал одним из участников SourgeForge. Что ж в своем запросе я полностью привел пример кода и указал на "удержание памяти" интерпретатором. Я указал версию Питона конфигурацию системы (в том числе и некоторые аппаратные характеристики). Я сделал все что было действительно надо и даже еще чуть-чуть (по хорошему должно было хватить двух строк кода и небольшого комментария):
Я думал, что мне скажут приблизительно следующее:"Молодец. Так держать. Исправим в следующем релизе. Как это мы сами не заметили?". Но не тут-то было! В первом комментарии мне сказали, что освобождение памяти зависит от системы (то есть от реализации функций malloc() free()). Ладно, хорошо - но даже под Windows память не освобождалась. Бред.
Comment By: Josiah Carlson (josiahcarlson) Date: 2005-10-27 15:29 Message: Logged In: YES user_id=341410 From what I understand, whether or not the Python runtime "frees" memory (which can be freed) is generally dependant on platform malloc() and free().
Во втором комментарии мне пояснил, что мол выделение 10 миллионов целых чисел выделит их навсегда. Гении блин! Я это и сам понял. А эта фраза - "If you don't want that, don't do that ;-)" Офигеть просто - мол, не хочешь просто так выкинуть кучу памяти - не выделяй ее.
Comment By: Tim Peters (tim_one) Date: 2005-10-27 15:38 Message: Logged In: YES user_id=31435 Space for integer objects in particular lives in an immortal free list of unbounded size, so it's certain in the current implementation that doing range(10000000) will hang on to space for 10 million integers forever. If you don't want that, don't do that ;-) Iterating over xrange(10000000) instead will consume very little RAM.
Я наверное наивен - но я надеялся...надеялся что меня просто не поняли. Ведь разве можно положить в основу совсем неплохого языка программирования такую ужасную модель управления памятью ? И написал еще один комментарий:
Comment By: sin (sin_avatar) Date: 2005-10-31 01:15 Message: Logged In: YES user_id=1368129 Certainly, i 'am not a C guru, but i uderstood - if interpreter keep more than 100Mb, and not keep useful information - it's suxx. Fore example if i use my script as a part Zope portal - it would be awful. Certainly my script was just example - but if i use mult-thread server wrote on python and create list in each thread - i would take memory from system and i cannot give it back.
Я сказал, что я конечно не гуру в C, но это ненормально занимать свыше 100Мб и не хранить никаких полезных для меня данных. Я даже намекнул что такое поведение может плохо сказаться на работе многопоточного сервера (а ведь все началось с системы тестирования под Zope если вы помните!). Я даже правильно указал на мою проблему - я просто не могу (при всем моем желании) освободить память.
Что же мне ответили? Что это не ошибка. И что я не слушаю предложение по использованию xrange для организации цикла. Когда я прочитал этот комментарий я пришел в бешенство - я же сказал в чем моя проблема, дело не в цикле - это просто пример! Пример который может вылезать потом в других модулях и вызывать такое нехорошее поведение Питона!
Comment By: Josiah Carlson (josiahcarlson) Date: 2005-10-31 01:56 Message: Logged In: YES user_id=341410 Suggested close because and/or: a) not a bug (integer freelists) b) platform specific malloc/free behavior on the list->ob_item member (some platforms will drop to 121M allocated memory after the deletion) c) OP didn't listen when it was suggested they use xrange() instead of range()
А все продолжал эксперименты с Питом пытаясь найти более эффектный пример, такой пример, чтобы они меня поняли. Я даже написал неплохой пример кода который вызывает такое же поведение Питона, но при этом использует xrange, и похожий пример - который не вызывает ничего подобного:
Comment By: sin (sin_avatar) Date: 2005-10-31 04:06 Message: Logged In: YES user_id=1368129 One more time. >>> a = [1 for i in xrange(10000000)] >>> del a This code clear - and this not raise "memory keeping"(!) >>> a = [i for i in xrange(10000000)] >>> del a But this code take about 163 Mb on my freeBSD. I can't udestood why you don't care about this? P.S. somebody explain me phrase "a) not a bug (integer freelists)"
Что ж впервые получил действительно обширный комментарий: мне объяснили зачем было заложено такое поведение Питона в его код - для увеличения быстродействия. Но кое с чем я в этом комментарии не согласен. Что на многих платформах оператор free не работает. Да?! А как же тогда для них пишут приложения? Может стоит использовать платформенно зависимый оператор? Я уверен что все эти разработчики в состоянии организовать платформенно-зависимую компиляцию ядра интерпретатора. Неужели непонятно - что мне все равно как именно использует память интерпретатор, только до той поры пока я действительно не захочу освободить память. Мне просто нужно это сделать. Конечно все это ничего. Я был огорчен. Но когда я дочитал до конца комментария - я просто оцепенел и смотрел в монитор стеклянными глазами. Они снова предлагали мне использовать xrange для организации цикла. Я был в шоке. Мои коллеги были согласны, что в этом есть что-то ненормальное - ведь сказано же проблема не в том, чтобы организовать цикл. Это просто пример!
Comment By: Josiah Carlson (josiahcarlson) Date: 2005-10-31 14:11 Message: Logged In: YES user_id=341410 Integers are immutable. That is, each integer with a different value will be a different integer object (some integers with the same value will also be different objects). In CPython, id(obj) is the memory location of that object. >>> a = 9876 >>> id(a) 9121340 >>> a += 1 >>> id(a) 8738760 It is assumed by the CPython runtime, based on significant experience by CPython programmers, that if you are using a large number of integers now, that you are probably going to use a large number of integers in the future. When an integer object is allocated, the object hangs around for as long as it is needed, and when it is no longer needed, it is placed on a list of allocated integer objects which can be re-used, but which are never freed (the integer freelist). This allows Python to allocate blocks of integers at a time when necessary (which speeds up allocations), re-use unused integer objects (which removes additional allocations in the majority of cases), and removes the free() call (on many platforms, free() doesn't work). Integer freelists are not going away, and the behavior you are experiencing is a result of integer freelists (though the 163M rather than 123M memory used is a result of FreeBSD's memory manager not being perfect). As Tim Peters suggested, if you need to iterate through 10 million unique integers, do so via xrange, but don't save them all. If you can't think about how to do such a thing in your application, you can ask users on the comp.lang.python newsgroup, or via the python-list@python.org mailing list, but remember to include the code you are using now and a description of what you want it to do.
Мысленно я уже сдался. Я понял они не хотят исправлять это. И тут я получил последний комментарий. Комментарий, в котором мне говорили, что создавать массив на 10 миллионов элементов - плохая идея по разным причинам, что изменять такое поведение интерпретатора не в их планах, что я просто должен поменять свой стиль написания программ и это моя единственная надежда. Это все написал Тим Петерс - тот, кто сформулировал Zen правила Питона. А знаете каков его последний аргумент? Что создание многопоточного сервера в каждом потоке которого выделяется 10 миллионов целых чисел - слишком невероятная идея. Разве я говорил о 10 миллионах в каждом потоке о нет и нет. Уже при 10 потоках мне хватит и 1 миллиона. В системе Zope по умолчанию где-то около 20 потоков. 500 тысяч в каждом потоке. Это много? Это не мало - но совсем не много. Это вполне реально.
Comment By: Tim Peters (tim_one) Date: 2005-10-31 15:00 Message: Logged In: YES user_id=31435 sin_avatar, it's the number of integer objects _simultaneously alive_ that matters, not the total number of integer objects ever created. Creating a list (or any other in-memory container) containing millions of integers can be a bad idea on many counts. I'm closing this as WontFix, as there are no plans to replace the integer freelist. I think it would be good if an upper bound were placed on its size (as most other internal freelists have), but that's trickier than it sounds and there are no plans to do that either. > I can't udestood why you don't care about this? It's a tradeoff: it's much easier to avoid in real life than it is to change the implementation in a way that would actually help (the int freelist is an important speed optimization for most integer-heavy apps). Changing your coding practices to live with this is your only realistic hope for relief. BTW, the idea that you might really need to create a list with 10 million integers in each thread of a threaded server is too bizarre to argue about ;-)
Скажите мне, Вы знаете язык программирования, в котором нельзя освободить память, нельзя даже если ты хочешь? Это просто невероятно - ведь это естественно - освободить память. Самое смешное - вы можете создать строку из 10 миллионов символов и память освободиться. Почему же она освобождается? Почему тут "замечательная команда разработчиков" питона умудрилась освободить память? Не знаю. Лично мне проще думать что работу со строками писали не они. Не иметь возможности освободить память - это ненормально. Я бы даже согласился на импорт еще одного модуля, модуля только с одной функцией - освобождением зря занимаемой памяти. Ведь такое поведение Питона, порой будет мешать написать простой для чтения код. Одно из правил Zen Питона: "Простое лучше сложного", а другое гласит "Сложное лучше усложненного" - Тим Петерс противоречит сам себе - он принуждает в некоторых случаях усложнять программы. Принуждает таким вот поведением языка. А самое ужасное никто из разработчиков не понимает порочности следования такому курсу.
Я не знаю, что мне сделать еще - я хочу привлечь внимание общественности к этому ужасному факту, я действительно хочу помочь этому языку программирования, в отличие от его разработчиков. Я пытался указать этот недостаток в Виккипедии, но там мой комментарий сочли недостаточно нейтральным (хотя я действительно старался быть нейтральным) и вместо того чтобы отредактировать - просто удалили его, хотя разработчик вправе знать о такой "особенности" этого языка программирования. Поэтому я написал эту статью - чтобы, те кому не безразлично будущее этого языка устранили этот недостаток.
октябрь-ноябрь 2005 года