<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Andrey Smirnov's Blog (memory)</title><link>http://www.smira.ru/</link><description></description><language>en</language><lastBuildDate>Sun, 11 Jan 2015 19:24:27 GMT</lastBuildDate><generator>http://getnikola.com/</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>aptly Memory Usage Optimization</title><link>http://www.smira.ru/en/posts/aptly-memory-usage-optimization.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;Next &lt;a class="reference external" href="http://www.aptly.info"&gt;aptly&lt;/a&gt; version (0.4) would contain some changes to lower
memory requirements while doing general operations: memory usage will be decreased
by factor of 3. aptly is written in Go language, so this is a short story of optimizing Go
program memory usage.&lt;/p&gt;
&lt;p&gt;When I have been developing aptly, I suspected that memory usage would be not optimal, as
aptly is processing huge amounts of package metadata (for example, when mirroring upstream
Debian repositories consisting of 30000 packages). Memory usage went unnoticed until
I was testing aptly in virtual machine with just 512 MB of memory, aptly was performing poorly
because Linux was busy in swapping. This was something completely unexpected: so much memory?
how could that be?&lt;/p&gt;
&lt;p&gt;First I applied some general optimizations which were trivial:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;some long operations (like mirroring) were happening in single function and some big data structures
weren't required full time during function execution. So assigning &lt;tt class="docutils literal"&gt;nil&lt;/tt&gt; to them allowed Go's garbage
collector to reclaim unused memory faster.&lt;/li&gt;
&lt;li&gt;reusing buffers for structure encoding (this is safe, as there're no concurrent operations and resulting
byte slice is copied immediately).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of creating buffer every time...&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="c1"&gt;// Encode does msgpack encoding of Package
&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Package&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;

    &lt;span class="nx"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MsgpackHandle&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;... re-use buffer:&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="c1"&gt;// Internal buffer reused by all Package.Encode operations
&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;encodeBuf&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;

&lt;span class="c1"&gt;// Encode does msgpack encoding of Package, []byte should be copied, as buffer would
// be used for the next call to Encode
&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Package&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;encodeBuf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nx"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;encodeBuf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MsgpackHandle&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;encodeBuf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Second, I had to find reliable way to measure memory consumption, that was easy thanks to &lt;a class="reference external" href="http://blog.cloudflare.com/recycling-memory-buffers-in-go"&gt;CloudFlare blog post&lt;/a&gt;. What I discovered first was:&lt;/p&gt;
&lt;img alt="mem stats for aptly snapshot verify" src="http://www.smira.ru/galleries/mem-verify0.png"&gt;
&lt;img alt="mem stats for aptly snapshot verify" src="http://www.smira.ru/galleries/mem-mirror-update1.png"&gt;
&lt;p&gt;&lt;a href="http://www.smira.ru/en/posts/aptly-memory-usage-optimization.html"&gt;Read more…&lt;/a&gt; (1 min remaining to read)&lt;/p&gt;</description><category>aptly</category><category>go</category><category>golang</category><category>memory</category><guid>http://www.smira.ru/en/posts/aptly-memory-usage-optimization.html</guid><pubDate>Wed, 05 Mar 2014 13:21:41 GMT</pubDate></item><item><title>Пример использования guppy/heapy</title><link>http://www.smira.ru/en/posts/20110824guppy-heapy-usage.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;a href="http://guppy-pe.sourceforge.net/"&gt;Guppy&lt;/a&gt; - классный профилировщик памяти для Python. К сожалению, им довольно сложно пользоваться, а документация оставляет желать лучшего. Один из разработчиков pkgcore написал отличную статью об использовании Guppy, которая располагалась по адресу: http://www.pkgcore.org/trac/pkgcore/doc/dev-notes/heapy.rst. Статья больше недоступна, я нашел исходник на &lt;a href="https://bitbucket.org/osunix/ospkg/src/3aad1843cdd7/dev-notes/heapy.rst"&gt;bitbucket&lt;/a&gt; и превратил в PDF/HTML для простоты использования:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.smira.ru/wp-content/uploads/2011/08/heapy.html"&gt;How to use guppy/heapy for tracking down memory usage&lt;/a&gt;, &lt;a href="http://www.smira.ru/wp-content/uploads/2011/08/heapy.pdf"&gt;PDF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><guid>http://www.smira.ru/en/posts/20110824guppy-heapy-usage.html</guid><pubDate>Wed, 24 Aug 2011 04:09:27 GMT</pubDate></item><item><title>Поиск несуществующего memory leak</title><link>http://www.smira.ru/en/posts/20080209python-memory-leak-resolved.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Очень забавная история. Был работающий и написанный сервер вещаний (a-la Red5) на Python. В связи с развитием основной класс - класс протокола - был разрезан на несколько более мелких классов, которые собирались вместе множественным наследованием в единый монолит, который должен был по функциональности повторять исходный класс (до рефакторинга). Как показало вскрытие, старая версия стабильно работает неделю за неделей, а новая версия через неопределенный, но достаточно короткий период времени (несколько часов) откушивает всю доступную память и погибает.&lt;/p&gt;
&lt;p&gt;Типичная ситуация для memory leak, причём опытным путём стало понятно, что эта утечка как-то коррелирует с нагрузкой на сервер (что логично), но зависимость неоднозначная. Момент появления утечки был сужен до одного коммита - казалось бы дело за малым. Анализ исходников в поисках того момента, когда была внесена утечка ничего дает - исходники испаханы вдоль и поперек. Ничего.&lt;/p&gt;
&lt;p&gt;Анализ во время выполнения с помощью с помощью довольно замысловатых утилит из &lt;a href="http://wingolog.org/archives/2007/11/27/reducing-the-footprint-of-python-applications"&gt;этого поста&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ни к чему не привёл - нет ничего. Я пробовал считать количество объектов, которые видит модуль &lt;code&gt;gc&lt;/code&gt; в питоне (то есть тех, кто поддерживает &lt;em&gt;garbage collection&lt;/em&gt;), но количество не растет существенно и пропорционально нагрузке на сервер. Однако память процесс кушает и довольно быстро.&lt;/p&gt;
&lt;p&gt;В проекте было расширение, написанное на Си, - его анализ, исправление мелких неточностей результата не дает.&lt;/p&gt;
&lt;p&gt;Остается неутешительный вывод - в силу того, что число объектов в программе не растет, то значит либо растет размер каких-то объектов (например, строк путем конкатенации), либо это какая-то бага в Python, ОС или где-то еще. &lt;/p&gt;
&lt;p&gt;Последнее выглядит романтично и привлекательно, но на самом деле ничего не дает - такую ошибку еще тяжелее найти и поправить, однако багтрекеры FreeBSD, Python и Twisted молчат по поводу таких ошибок.&lt;/p&gt;
&lt;p&gt;Двигаемся дальше - создается тестовый клиент вещаний, который имитирует нагрузку на сервер: разное количество вещаний, авторы, подписчики вещания, различные ошибочные ситуации, чат. Тест, запущенный локально с локальным сервером (а нелокально тестовое окружение собрать тяжело - поток порядка 50-100 Мбит/с) не дает результатов - никаких следов утечки памяти ни за час, ни за два.&lt;/p&gt;
&lt;p&gt;Отчаяние, практически неделя потерянного впустую времени, тестирование различных вариантов... Что еще? Есть еще &lt;a href="http://twistedmatrix.com/"&gt;Twisted-Conch+Twisted-Manhole&lt;/a&gt; - живая консоль с интерпретатором питона в работающем сервере. Но и она не дает никаких результатов - garbage collection работает, никаких разумных объектов, которые могли бы дать утечку памяти нет.&lt;/p&gt;
&lt;p&gt;Озарение как всегда приходит в тот момент, когда его совсем не ждешь: ведь сервер вещает, то есть получает на вход поток в 0,5 Мбит/с, раздает его 200 клиентам и делает на выходе 100 Мбит/с, если у кого-то из этих 200 клиентов канал меньше по ширине 0,5 Мбит/с, то данные в буферах процессах начнут скапливаться. Для этого есть решение - дроп пакетов, он  был реализован и работал...&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Эврика&lt;/em&gt;! После разделения большого класса на кучу мелких фрагментов некоторые методы "разрезались" на части. И в них надо поставить вызов метода предка, чтобы все "куски" метода отработали, как и раньше. Но ведь в питоне есть еще &lt;a href="http://www.python.org/download/releases/2.3/mro/"&gt;MRO&lt;/a&gt; (Method Resolution Order), и надо отвыкнуть от того, что базовый класс в моём ощущении является последним в цепочке, то есть у него нет &lt;code&gt;super(Klass, self)&lt;/code&gt; - он есть! Просто метод, который включал анализ буфера записи, который в свою очередь вовремя включал механизм пропуска пакетов отключился. И хотя всё работало, но пропуск пакетов не включался и буферы записи росли неограниченно. Исправление в одной строке - вызова метода предка решило проблему. Обидно :)&lt;/p&gt;</description><guid>http://www.smira.ru/en/posts/20080209python-memory-leak-resolved.html</guid><pubDate>Sat, 09 Feb 2008 20:45:53 GMT</pubDate></item></channel></rss>