Как эти люди избегают создания мусора?

Вот интересная статья , которую я нашел в Интернете.

В нем говорится о том, как эта фирма способна анализировать огромный объем финансовых данных в управляемой среде, в основном путем повторного использования объектов и избежания неизменяемых объектов, таких как строки. Затем они продолжают и показывают, что их программа не выполняет GC во время фазы непрерывной работы.

Это довольно впечатляет, и я хотел бы знать, есть ли у кого-нибудь еще здесь подробное руководство относительно того, как это сделать. С одной стороны, мне интересно, как, черт возьми, вы можете избежать использования строки, когда некоторые данные внутри сообщений явно являются строками, и какое бы клиентское приложение ни смотрело сообщения, они захотят передать эти строки? Кроме того, что вы выделяете на этапе запуска? Как вы узнаете, что этого достаточно? Это просто вопрос получения большого куска памяти и сохранения ссылки на него, чтобы GC не сработал? Как насчет того, какое клиентское приложение использует сообщения? Должно ли оно быть написано в соответствии с этими строгими стандартами?

нужен ли мне специальный инструмент для просмотра памяти? До сих пор я использовал профилировщик памяти SciTech.

16
задан Chris Martin 4 November 2014 в 06:41
поделиться

5 ответов

Я нашел статью, на которую вы ссылаетесь, довольно несовершенной:

  • Она предполагает и хочет, чтобы вы предположили, что сборка мусора является окончательным убийцей задержки. Они не объяснили, почему они так думают, и не объяснили, каким образом их система, по сути, не является замаскированным сборщиком мусора, изготовленным на заказ.
  • Здесь говорится о объеме памяти , очищенном при сборке мусора, что не имеет значения: время, затрачиваемое на сборку мусора, больше зависит от количества объектов , независимо от их размера. .
  • Таблица «результатов» внизу не дает никакого сравнения с системой, в которой используется сборщик мусора .NET.

Конечно, это не означает, что они лгут, и это не имеет ничего общего со сборкой мусора, но в основном это означает, что статья просто пытается звучать впечатляюще, не раскрывая ничего полезного, что вы могли бы использовать для создания своего собственного .

9
ответ дан 30 November 2019 в 22:30
поделиться

Некоторое время я работал с продуктом CEP под названием StreamBase . Один из их инженеров сказал мне, что они переносят свой код C ++ на Java, потому что они получают лучшую производительность, меньше ошибок и лучшую переносимость на JVM, в значительной степени избегая GC. Я полагаю, что эти аргументы применимы и к CLR.

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

Вот некоторая информация с их сайта :

StreamBase избегает сборки мусора двумя способами: не используя объекты, а используя только минимальный набор объектов, который нам нужен.

Во-первых, мы избегаем использования объектов, используя примитивные типы Java (Boolean, byte, int, double и long) для представления наших данных для обработки.Каждый тип данных StreamBase представлен одним или несколькими примитивными типами. Управляя только примитивными типами, мы можем эффективно хранить данные в выделенных стеком или массивом областях памяти. Затем мы можем использовать такие методы, как параллельные массивы или вызов методов, для эффективной передачи данных.

Во-вторых, когда мы действительно используем объекты, мы осторожны с их созданием и уничтожением. Мы стремимся объединять объекты в пул, а не выпускать их для сборки мусора. Мы стараемся управлять жизненным циклом объекта таким образом, чтобы объекты либо перехватывались сборщиком мусора в молодом поколении, либо сохранялись навсегда.

Наконец, мы тестируем это внутренне, используя тестовую программу, которая измеряет сборку мусора по кортежу. Чтобы достичь наших высоких скоростей, мы стараемся полностью исключить сборку мусора по кортежу, как правило, с большим успехом.

2
ответ дан 30 November 2019 в 22:30
поделиться

Насколько я понял, в статье не говорится, что они не используют строки. Они не используют неизменяемые строки. Проблема с неизменяемыми строками заключается в том, что при синтаксическом анализе большинство сгенерированных строк представляют собой просто выбрасываемые строки.

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

2
ответ дан 30 November 2019 в 22:30
поделиться

В 99% случаев вы будете зря тратить деньги своих боссов, пытаясь достичь этого. В статье описывается абсолютно экстремальный сценарий, когда им нужна последняя капля производительности. Как вы можете прочитать в статье, в .NET Framework есть отличные части, которые нельзя использовать при попытке избавиться от мусора. Некоторые из самых основных частей BCL используют выделение памяти (или, как это называется в статье, «производить мусор»). Вам нужно будет найти способ обойти эти методы. И даже когда вам нужны абсолютно невероятно быстрые приложения, вам лучше сначала попытаться создать приложение / архитектуру, которые могут масштабироваться (использовать несколько машин), прежде чем пытаться пройти путь без GC. Единственная причина, по которой они используют маршрут без GC, заключается в том, что им нужна абсолютно низкая задержка.ИМО, когда вам нужна абсолютная скорость, но вы не заботитесь об абсолютном минимальном времени отклика, будет сложно оправдать архитектуру без GC. Кроме того, если вы попытаетесь создать клиентское приложение без сборщика мусора (например, Windows Forms или приложение WPF); Забудьте об этом, эти рамки представления постоянно создают новые объекты.

Но если вы действительно этого хотите, на самом деле это довольно просто. Вот простой способ:

  • Выяснить, какие части .NET API нельзя использовать (вы можете написать инструмент, который анализирует сборки .NET с помощью механизма самоанализа ).
  • Напишите программу, которая проверяет код, который вы или ваши разработчики пишете, чтобы убедиться, что они не выделяют напрямую или не используют «запрещенные» методы .NET, используя безопасный список, созданный в предыдущем пункте (FxCop - отличный инструмент для этого) .
  • Создавайте пулы объектов, которые вы инициализируете во время запуска. Остальная часть программы может повторно использовать существующий объект, чтобы им не приходилось выполнять какие-либо новые операции.
  • Если вам нужно манипулировать строками, используйте для этого байтовые массивы и храните байтовые массивы в пуле (WCF также использует эту технику). Вам нужно будет создать API, который позволяет манипулировать этими байтовыми массивами.
  • И последнее, но не менее важное: профиль, профиль, профиль.

Удачи

0
ответ дан 30 November 2019 в 22:30
поделиться

С самого начала следует отметить одну вещь: «Традиционная мудрость при разработке технологии обмена сообщениями с низкой задержкой требовала использования неуправляемого C ++ или языка ассемблера». В частности, они говорят о своего рода случае, когда люди часто сразу отказываются от решения .NET (или Java). В этом отношении относительно наивное решение C ++, вероятно, тоже не принесет успеха.

Еще одна вещь, которую следует здесь учитывать, заключается в том, что они по сути не столько избавились от GC, сколько заменили его - там есть код, управляющий временем жизни объекта, но это их собственный код.

Есть несколько способов сделать это вместо этого. Вот один. Скажем, мне нужно создать и уничтожить несколько объектов Foo во время работы моего приложения.Создание Foo параметризуется с помощью int, поэтому нормальный код будет выглядеть следующим образом:

public class Foo
{
    private readonly int _bar;
    Foo(int bar)
    {
        _bar = bar;
    }
    /* other code that makes this class actually interesting. */
}

public class UsesFoo
{
    public void FooUsedHere(int param)
    {
        Foo baz = new Foo(param)
        //Do something here
        //baz falls out of scope and is liable to GC colleciton
    }
}

Другой подход:

public class Foo
{
    private static readonly Foo[] FOO_STORE = new Foo[MOST_POSSIBLY_NEEDED];
    private static Foo FREE;
    static Foo()
    {
        Foo last = FOO_STORE[MOST_POSSIBLY_NEEDED -1] = new Foo();
        int idx = MOST_POSSIBLY_NEEDED - 1;
        while(idx != 0)
        {
            Foo newFoo = FOO_STORE[--idx] = new Foo();
            newFoo._next = FOO_STORE[idx + 1];
        }
        FREE = last._next = FOO_STORE[0];
    }
    private Foo _next;
    //Note _bar is no longer readonly. We lose the advantages
    //as a cost of reusing objects. Even if Foo acts immutable
    //it isn't really.
    private int _bar;
    public static Foo GetFoo(int bar)
    {
        Foo ret = FREE;
        FREE = ret._next;
        return ret;
    }
    public void Release()
    {
        _next = FREE;
        FREE = this;
    }
    /* other code that makes this class actually interesting. */
}

public class UsesFoo
{
    public void FooUsedHere(int param)
    {
        Foo baz = Foo.GetFoo(param)
        //Do something here
        baz.Release();
    }
}

Дополнительные сложности могут быть добавлены, если вы используете многопоточность (хотя для действительно высокой производительности в неинтерактивной среде вы может захотеть иметь либо один поток, либо отдельные хранилища классов Foo для каждого потока), и если вы не можете заранее предсказать MOST_POSSIBLY_NEEDED (самый простой - создать новый Foo () по мере необходимости, но не выпускать их для GC, что можно легко сделать в приведенном выше коде, создав новый Foo, если FREE._next имеет значение null).

Если мы допустим небезопасный код, мы можем получить еще большие преимущества, если Foo будет структурой (и, следовательно, массивом, содержащим непрерывную область памяти стека), _next будет указателем на Foo, а GetFoo () будет возвращать указатель.

Я, конечно, не могу сказать, что на самом деле делают эти люди, но вышеизложенное препятствует активации GC. Это будет быстрее только в условиях очень высокой пропускной способности, если нет, то, вероятно, лучше позволить GC делать свое дело (GC действительно помогает вам, несмотря на 90% вопросов о том, что он рассматривает его как большой плохой).

Существуют и другие подходы, которые аналогичным образом избегают сборки мусора. В C ++ операторы new и delete могут быть переопределены, что позволяет изменять поведение по умолчанию при создании и уничтожении, и обсуждение того, как и почему это можно сделать, может вас заинтересовать.

Практический вывод из этого - когда объекты либо удерживают ресурсы, кроме памяти, которые являются дорогими (например, соединения с базами данных), либо «учатся» по мере их использования (например, XmlNameTables). В этом случае полезно объединение объектов (ADO.NET-соединения по умолчанию делают это за кулисами). В этом случае, хотя простая очередь - это выход, поскольку дополнительные накладные расходы с точки зрения памяти не имеют значения. Вы также можете отказаться от объектов при конкуренции за блокировку (вы хотите повысить производительность, а конкуренция за блокировку повредит ему больше, чем отказ от объекта), что, я сомневаюсь, сработает в их случае.

5
ответ дан 30 November 2019 в 22:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: