ещё чуть-чуть о работе
Jun. 2nd, 2003 12:34 amВ дополнение к этому.
В конце концов я переписал программу (она теперь есть в CVS, кстати) так, чтобы она использовала простую версию slab-аллокатора. Вот что это значит на практике. Модуль аллокации памяти выделят только блоки размером степенью двойки, от 27 (т.к. меньше не оказывается нужно в программе, вследствие того, что каждый хранящийся item вместе с контрольной структурой выходит длиннее) до 220. Для каждого класса размера (т.е. для каждого возможного размера от 128 байт до 1Mb) хранится простой связанный список свободных на данный момент блоков этого размера. Когда блок освобождается, он добавляется в этот список; когда требуется новый блок, берётся из этого списка. Вопрос, таким образом, только в том, как в этот список попадают новые свободные блоки. Когда нужен новый блок, а список пуст, вызывается malloc() и у него берётся сразу 1Mb памяти (slab), к-й делится на блоки нужного размера, и их адреса добавляются в список.
Из-за того, что у malloc()'а берётся всегда 1Mb, эта память всегда оказывается выделенной с помощью mmap() (malloc() всегда использует mmap() для запросов размером больше 128kb). Выделенные слэбы никогда не возвращаются в систему. Вследствие этого внешней фрагментации (т.е. фрагментации, возникающей вследствие образования маленьких "дырок" в адресном пространстве) нет в принципе. Зато довольно высока внутренняя фрагментация, т.е. потеря памяти за счёт того, что выделяется обычно больше памяти, чем на самом деле нужно (окгругляется до ближайшей степени двойки).
В данный момент внутренняя фрагментация составляет примерно 33%, т.е. треть памяти расходуется зря. Но нас это не слишком беспокоит. Во-первых, намного важнее, чем расчётливый расход памяти, было довести программу до состояния, когда она гарантированно бежит неограниченно долгое время без проблем — и сейчас она в таком состоянии (и последняя версия уже бежит несколько суток). Даже используемых нами сейчас примерно 5Gb кэша (разбитые на штук 6 процессов) и даже с такой внутренней фрагментацией хватает для того, чтобы очень убыстрить работу сайта. Hit rate при этом дошёл примерно до 80% (т.е. из той инфомации, что кэшируется, только 20% идёт из БД, а остальное из кэша). Во-вторых, slab-аллокатор можно сильно улучшить, чем я займусь через пару дней, наверное. Самое главное — заставить его работать с разными вариантами классов размеров, необяазтельно со степенями двойки. Это очень легко. Потом можно подобрать такие классы, которые именно в случае ЖЖ уменьшают фрагментацию. Мне ещё пока неясно, стоит ли возиться с динамическим анализом (т.е. чтобы аллокатор сам анализировал настоящие размеры запрашиваемых блоков, к-е ему передаются, и перекраивал свои класс размеров динамически) или просто обеспечить возможность чтения размеров из конфиг-файла или по протоколу.
В конце концов я переписал программу (она теперь есть в CVS, кстати) так, чтобы она использовала простую версию slab-аллокатора. Вот что это значит на практике. Модуль аллокации памяти выделят только блоки размером степенью двойки, от 27 (т.к. меньше не оказывается нужно в программе, вследствие того, что каждый хранящийся item вместе с контрольной структурой выходит длиннее) до 220. Для каждого класса размера (т.е. для каждого возможного размера от 128 байт до 1Mb) хранится простой связанный список свободных на данный момент блоков этого размера. Когда блок освобождается, он добавляется в этот список; когда требуется новый блок, берётся из этого списка. Вопрос, таким образом, только в том, как в этот список попадают новые свободные блоки. Когда нужен новый блок, а список пуст, вызывается malloc() и у него берётся сразу 1Mb памяти (slab), к-й делится на блоки нужного размера, и их адреса добавляются в список.
Из-за того, что у malloc()'а берётся всегда 1Mb, эта память всегда оказывается выделенной с помощью mmap() (malloc() всегда использует mmap() для запросов размером больше 128kb). Выделенные слэбы никогда не возвращаются в систему. Вследствие этого внешней фрагментации (т.е. фрагментации, возникающей вследствие образования маленьких "дырок" в адресном пространстве) нет в принципе. Зато довольно высока внутренняя фрагментация, т.е. потеря памяти за счёт того, что выделяется обычно больше памяти, чем на самом деле нужно (окгругляется до ближайшей степени двойки).
В данный момент внутренняя фрагментация составляет примерно 33%, т.е. треть памяти расходуется зря. Но нас это не слишком беспокоит. Во-первых, намного важнее, чем расчётливый расход памяти, было довести программу до состояния, когда она гарантированно бежит неограниченно долгое время без проблем — и сейчас она в таком состоянии (и последняя версия уже бежит несколько суток). Даже используемых нами сейчас примерно 5Gb кэша (разбитые на штук 6 процессов) и даже с такой внутренней фрагментацией хватает для того, чтобы очень убыстрить работу сайта. Hit rate при этом дошёл примерно до 80% (т.е. из той инфомации, что кэшируется, только 20% идёт из БД, а остальное из кэша). Во-вторых, slab-аллокатор можно сильно улучшить, чем я займусь через пару дней, наверное. Самое главное — заставить его работать с разными вариантами классов размеров, необяазтельно со степенями двойки. Это очень легко. Потом можно подобрать такие классы, которые именно в случае ЖЖ уменьшают фрагментацию. Мне ещё пока неясно, стоит ли возиться с динамическим анализом (т.е. чтобы аллокатор сам анализировал настоящие размеры запрашиваемых блоков, к-е ему передаются, и перекраивал свои класс размеров динамически) или просто обеспечить возможность чтения размеров из конфиг-файла или по протоколу.
no subject
Date: 2003-06-02 02:55 am (UTC)Может, было бы полезно собирать статистику по отношению количества использованных/выделенных блоков у каждого класса размеров, и по числу отказов. В случае, если в каком-то из классов большой избыток свободных блоков, а в другом из классов -- недостаток, можно было бы попробовать объединять (или резать) лишние свободные блоки "пустующего" класса и выдавать их "нуждающемуся".
Процесс перераспределения можно было бы запускать тогда, когда интенсивность отказов по одному из классов достигала бы заданного значения. Наличие статистики по свободному месту позволит сразу отказаться от [дорогостоящего] перераспределения, если свободного места нет ни у кого (пиковые нагрузки, etc).
Re:
Date: 2003-06-02 05:28 am (UTC)Если немного точнее быть, то в протоколе общение с кэшем есть команда delete, к-я удаляет ненужный блок, но она использыется на практике в ЖЖ довольно редко, так что свободные блоки, к-е она создаёт, быстро заполняются.
Вследствие того, что сам код кэша настолько "жаден", судить о "несправедливом" использовании одного класса размеров по отношению к другому приходится судить по другим признакам: общему кол-ву слэбов, выделенных на данный класс в процессе заполнения кэша, а также возрастом самого старого элемента в данном классе, в секундах (если в каком-то классе все элементы очень молоды относительно других классов, это значит, что кэш на этом классе менее эффективен и ему надо давать больше памяти). Код для проведения такого анализа и распределения памяти в следующих запусках согласно его результатам ещё предстоит написать ;)