C # - Можно ли объединять коробки? - PullRequest
12 голосов
/ 12 сентября 2011

Бокс преобразует тип значения в тип объекта. Или, как говорит MSDN, бокс - это «операция по переносу структуры внутри объекта ссылочного типа в управляемой куче».

Но если вы попытаетесь углубиться в это, взглянув на код IL, вы увидите только волшебное слово "box".

Предположим, я полагаю, что среда выполнения имеет своего рода секретный класс, основанный на обобщениях, например, Box<T> со свойством public T Value, и бокс int будет выглядеть так:

int i = 5;
Box<int> box = new Box<int>;
box.Value = 5;

Распаковка int будет намного дешевле: return box.Value;

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

Если бы я сам распределял эту память, я бы рассмотрел использование здесь пула объектов. Но поскольку фактическое создание объекта скрыто за волшебным словом в IL, каковы мои варианты?

Мои конкретные вопросы:

  • Существует ли существующий механизм, побуждающий среду выполнения извлекать блоки из пула, а не создавать их экземпляры?
  • Какой тип экземпляра создается во время бокса? Можно ли вручную управлять процессом упаковки, но при этом быть совместимым с распаковкой?

Если последний вопрос кажется странным, я имею в виду, что я могу создать свой собственный класс Box<T> или DecimalBox, объединить его в пул и создать / распаковать вручную. Но я не хочу идти и модифицировать различные места в коде, которые используют упакованное значение (иначе распаковать его).

Ответы [ 6 ]

10 голосов
/ 12 сентября 2011

Предположим, я полагаю, что у среды выполнения есть какой-то секретный класс на основе генериков в рукаве

Ваши предположения почти верны. Логически вы можете думать о блоке как о магическом типе Box<T>, который ведет себя так, как вы его описываете (с еще несколькими магическими моментами; например, способ, которым блок типов значений, допускающих обнуляемое значение, немного необычен.) Как фактическая деталь реализации, среда выполнения не делает это с универсальными типами.Бокс существовал в CLR v1, что было до того, как в систему типов были добавлены универсальные типы.

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

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

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

Кратковременный лучше , чем долгоживущий;с недолговечными объектами кучи вы платите, чтобы собрать их один раз и тогда они мертвы. С долгоживущими объектами кучи вы оплачиваете эту стоимость снова и снова, пока объект продолжает выживать.

Конечно, стоимость, о которой вы, вероятно, беспокоитесь относительно недолговечных объектов, нестоимость коллекции как таковой.Скорее, это сбор давления ;чем больше недолговечных объектов выделено, тем больше частых сборщиков мусора.

Стоимость выделения довольно минимальна.Переместите указатель на кучу GC, скопируйте десятичное число в это место, готово.

Если бы я сам выделял эту память, я бы рассмотрел здесь использование пула объектов.

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

Существует ли существующий механизм, побуждающий среду выполнения брать ящики из пула, а не создавать их?

Нет.

Какой тип экземпляра создается во время бокса?Можно ли вручную управлять процессом упаковки, но при этом быть совместимым с распаковкой?

Тип коробки - это тип упаковываемой вещи.Просто спросите об этом, вызвав GetType;это скажет тебе.Коробки волшебные;они - то, что они содержат.

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

4 голосов
/ 12 сентября 2011

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

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

Попытка объединения в штучной упаковке значений скорее всего приведет к снижению производительности, чем к ее увеличению. Сборщик мусора специально создан для эффективной обработки недолговечных объектов, и если вы поместите объекты в пул, они будут долгоживущими объектами. Когда объекты переживают сборку мусора, они перемещаются в следующую кучу, которая включает в себя копирование объекта из одного места в памяти в другое. Таким образом, объединяя объект в пул, вы фактически можете вызвать гораздо больше работы для сборщика мусора, чем меньше.

2 голосов
/ 12 сентября 2011

По умолчанию нет класса, подобного Box<T>.Тип по-прежнему является исходным типом, но является ссылкой.Поскольку Decimal неизменный, вы не можете изменить значение после создания.Таким образом, вы не можете использовать пул с обычными десятичными числами в штучной упаковке.

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

Ваш собственный тип ящика не может быть распакован из object со стандартным приведением.Поэтому вам нужно будет адаптировать потребляющий код.


Написание собственного метода, возвращающего упакованное значение:

[ThreadStatic]
private static Dictionary<T,object> cache;

public object BoxIt<T>(T value)
  where T:struct
{
  if(cache==null)
    cache=new Dictionary<T,object>();
  object result;
  if(!cache.TryGetValue(T,result))
  {
    result=(object)T;
    cache[value]=result;
  }
  return result;
}

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

0 голосов
/ 24 ноября 2017

Я только что написал в блоге о нашей реализации кеша блоков, которую мы используем в нашей объектной базе данных. Десятичные числа имеют настолько широкий диапазон значений, что блоки кэширования не будут очень эффективными, но если вы обнаружите, что обязательно используете поля объекта или массивы object [] для хранения большого количества общих значений, это может помочь:

http://www.singulink.com/CodeIndex/post/value-type-box-cache

Наше использование памяти упало как сумасшедшее после использования этого. По сути, все в базе данных хранится в массивах object [] и может содержать много ГБ данных, поэтому это было чрезвычайно полезно.

Существует три основных метода, которые вы хотите использовать:

  • object BoxCache<T>.GetBox(T value) - получает поле для значения, если оно кэшируется в противном случае он упаковывает его для вас.
  • object BoxCache<T>.GetOrAddBox(T value) - получает поле для значения, если оно в противном случае он добавляется в кеш и возвращает его.
  • void AddValues<T>(IEnumerable<T> values) - добавляет поля для указанного значения в кеш.
0 голосов
/ 12 сентября 2011

К сожалению, вы не можете подключить процесс бокса, однако вы можете использовать неявные преобразования в ваших интересах, чтобы он «выглядел» как бокс.

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

public class Box
{
    internal Box()
    { }

    public static Box<T> ItUp<T>(T value)
        where T : struct
    {
        return value;
    }

    public static T ItOut<T>(object value)
        where T : struct
    {
        var tbox = value as Box<T>;
        if (!object.ReferenceEquals(tbox, null))
            return tbox.Value;
        else
            return (T)value;
    }
}

public sealed class Box<T> : Box
    where T : struct
{
    public static IEqualityComparer<T> EqualityComparer { get; set; }
    private static readonly ConcurrentStack<Box<T>> _cache = new ConcurrentStack<Box<T>>();
    public T Value
    {
        get;
        private set;
    }

    static Box()
    {
        EqualityComparer = EqualityComparer<T>.Default;
    }

    private Box()
    {

    }

    ~Box()
    {
        if (_cache.Count < 4096) // Note this will be approximate.
        {
            GC.ReRegisterForFinalize(this);
            _cache.Push(this);
        }
    }

    public static implicit operator Box<T>(T value)
    {
        Box<T> box;
        if (!_cache.TryPop(out box))
            box = new Box<T>();
        box.Value = value;
        return box;
    }

    public static implicit operator T(Box<T> value)
    {
        return ((Box<T>)value).Value;
    }

    public override bool Equals(object obj)
    {
        var box = obj as Box<T>;
        if (!object.ReferenceEquals(box, null))
            return EqualityComparer.Equals(box.Value, Value);
        else if (obj is T)
            return EqualityComparer.Equals((T)obj, Value);
        else
            return false;
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

// Sample usage:
var boxed = Box.ItUp(100);
LegacyCode(boxingIsFun);
void LegacyCode(object boxingIsFun)
{
  var value = Box.ItOut<int>(boxingIsFun);
}

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

0 голосов
/ 12 сентября 2011
int i = 5;
object boxedInt = i;

Назначение типа значения для System.Object - это более или менее все, что нужно для бокса в том, что касается вашего кода (я не буду вдаваться в технические подробности операции с боксом).

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

Однако есть пример такой практики - .Net Framework использует предварительно упакованныевнутренние логические значения в классе, подобном следующему:

class BoolBox
{
    // boxing here
    private static object _true = true;
    // boxing here
    private static object _false = false;

    public static object True { get { return _true; } }
    public static object False { get { return _false; } }
}

Система WPF часто использует System.Object переменные для свойств зависимостей, просто чтобы назвать случай, когда бокс / распаковка неизбежны даже в эти «современные времена».

...