Подтвердить что ты не робот

С# - Можно ли объединить ящики?

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

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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

Неа.

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

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

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

Ответ 2

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

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

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

Ответ 3

По умолчанию не существует класса типа 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;
}

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

Ответ 4

int i = 5;
object boxedInt = i;

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

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

Существует пример этой практики, хотя:. Framework 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 для свойств зависимостей, просто чтобы назвать случай, когда бокс /unboxing неизбежен даже в эти "современные времена".

Ответ 5

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

Я бы также избежал сохранения каждого значения в 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);
}

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

Ответ 6

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

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

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

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

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