Статьи

Представляем Rattlesnake.CLR

Итак, потратив довольно много времени на изучение кодовой базы leveldb и несколько лет работы с RavenDB, я могу с уверенностью сказать, что CLR чрезвычайно затрудняет создание высокопроизводительных серверных систем с использованием CLR.

В основном, проблемы связаны с ГХ и памятью. В частности, отсутствие какого-либо способа управления распределением памяти и / или GC означает, что мы не можем оптимизировать эти сценарии каким-либо значимым образом. В то же время я не хочу возвращаться в неуправляемый мир. Как уже упоминалось, я только что вернулся из очень глубокого погружения в нетривиальную кодовую базу C ++, и хотя я считаю эту кодовую базу действительно хорошей, нельзя сказать, что мне приятно всегда думать обо всем, что CLR просто отнимает.

Поэтому я решил, что буду что-то делать с этим. И Rattlesnake.CLR родился:

образ

Основные функции Rattlesnake.CLR включают в себя явное управление памятью при необходимости. Скажем, мы знаем, что нам понадобится некоторое количество памяти на некоторое время, и тогда все это можно будет выбросить. Это очень часто встречается в сценариях, таких как веб-запрос, почти вся память, которую вы генерируете во время обработки веб-запроса, может быть немедленно освобождена. В случае RavenDB память, которую мы потребляем во время индексации, может быть свободной сразу же после прекращения индексации. В настоящее время это болезненный процесс, заключающийся в том, что мы размещаем внутри одного поколения и надеемся, что он не будет слишком дорогим или что мы не получим полную остановку всего сервера, пока он освобождает память. Это также делает это действительно трудно делать такие вещи, как ограничить объем памяти, используемый вашим кодом.

Другое требование, которое у меня есть, заключается в том, что Rattlesnake.CLR должен иметь возможность выполнять существующие сборки .NET без каких-либо дополнительных шагов. Так как мне не нравится делать порты вещей, которые уже существуют.

Чтобы обработать этот сценарий с заданными ограничениями, мы имеем:

        var heap = Heap.Create(HeapOptions.None, 

            1024 * 1024,

            512 * 1024 * 1024);

         

        using(MemoryAllocations.AllocateFrom(heap))

        {

           var sb = new StringBuilder();

           for(var i = 0; i < 100; i ++ )

                 sb.AppendLine(i);

          Console.WriteLine(sb.ToString());

       }

        

       heap.Destroy(); 

Весь код внутри оператора using размещен в нашей собственной куче. В строке 13 мы уничтожаем всю эту память одним махом.

Есть несколько замечаний по этому поводу, которые мы, вероятно, должны рассмотреть:

  • По умолчанию память, выделенная этой формой, не подчиняется какой-либо форме GC. Идея в том, что вся эта куча освобождается немедленно.
  • Обратите внимание, что последние два параметра для Heap.Create. Первый — это начальный размер кучи, а второй — максимальный размер. Теперь у нас есть реальный способ фактически ограничить объем памяти, который будет использовать кусок кода. Это действительно важно для серверных приложений, где важно избегать подкачки.
  • Теперь мы можем выяснить, сколько памяти использует определенный фрагмент кода, и соответственно распределить наши ресурсы.
  • Вы можете использовать несколько куч одновременно, хотя в данный момент времени в качестве распределения по умолчанию можно установить только одну кучу.

Существует явный метод heap.GarbageCollect (), который выполняет GC только для этой кучи и который вы можете планировать по своему усмотрению. Вы можете иметь две кучи и выделять из одной, пока вы собираете другую. И да, это означает, что GC, использующие эти методы, не остановят процесс!

Память, выделенная в куче, очевидно, действительна только до тех пор, пока куча действительна. Это означает, что после уничтожения кучи вы не сможете получить доступ ни к одному из объектов, которые были там созданы. Это имеет значение для таких вещей, как кэш. Мы предоставляем метод MemoryAllocations.AllocateOnGlobalHeap <T> (args), чтобы вместо этого использовать глобальную кучу, если вы хотите, чтобы эта память всегда была доступна и подчинялась GC.

Пока еще рано, но мы уже видим некоторые действительно интересные улучшения производительности!

Как это работает?

В то время как ранний эксперимент с Rattlensake.CLR был основан на среде исполнения Mono. Я быстро решил, что хочу продолжать использовать MS CLR. Теперь, чтобы справиться с этим, мне пришлось сделать некоторые неестественные вещи (если не сказать больше), но я думаю, что мне даже удалось сделать это поддерживаемым вариантом. По сути, мы используем для этого CLR Hosting API. Особенно:

  • ICLRGCManager
  • IHostMalloc
  • IHostMemoryManager

Вы можете использовать Rattlesnake.CLR следующим образом:

. \ Rattlesnake.exe Raven.Server.exe

Ради интереса, мы также разрешили устанавливать ограничения на кучу по умолчанию, так что вы можете быть уверены, что не выделяете слишком много.

. \ Rattlesnake.exe Raven.Server.exe —max-default-heap-size = 256 МБ

Мы все еще проводим некоторые тесты, но это выглядит действительно хорошо.