2011-09-12 31 views
12

El boxeo convierte un tipo de valor en un tipo de objeto. O como dice MSDN, el boxeo es una "operación para envolver la estructura dentro de un objeto de tipo de referencia en el montón administrado".C# - ¿Es posible agrupar cajas?

Pero si intenta profundizar en eso mirando el código IL, solo verá la palabra mágica "caja". clase secreta

Especular, supongo que el tiempo de ejecución tiene algún tipo de medicamentos genéricos basado en la manga, como Box<T> con una propiedad public T Value, y el boxeo un int se vería así:

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

Unboxing del int haría sea ​​mucho más barato: return box.Value;

Por desgracia, mi aplicación de servidor de rendimiento hambre hace un poco de boxeo, en concreto de decimales. Peor aún, estas cajas son de corta duración, lo que me hace sospechar que pagué dos veces, una para la instanciación de la caja y otra para la basura que recoge la caja una vez que he terminado con ella.

Si estuviera alojando esta memoria yo mismo, consideraría el uso de un conjunto de objetos aquí. Pero dado que la creación del objeto real está oculta detrás de una palabra mágica en el IL, ¿cuáles son mis opciones?

Mis preguntas específicas:

  • ¿Existe un mecanismo existente para inducir el tiempo de ejecución para llevar cajas de una piscina en lugar de instanciando ellos?
  • ¿Cuál es el tipo de instancia creada durante el boxeo? ¿Es posible tomar el control del proceso de boxeo de forma manual, y aún así ser compatible con el desempaquetado?

Si esta última pregunta parece extraño, lo que quiero decir es que podía crear mi propia Box<T> o DecimalBox clase, la piscina, y la caja/unbox manualmente. Pero no quiero tener que ir y modificar los diversos lugares en el código que consumen el valor en caja (también conocido como unbox it).

+0

¿Ha considerado reducir las operaciones de boxeo utilizando genéricos/objetos personalizados/etc.? La instrucción 'box' es intencionalmente opaca, aunque puedes, por supuesto, imitarla a través de tu clase' Box 'anterior. – dlev

+10

"mi aplicación hace un poco de boxeo". Un juego extraño, el único movimiento ganador es no jugar. –

+5

"Peor aún, estas cajas son de corta duración, lo que me hace sospechar que pago dos veces, una para la instanciación de la caja y luego otra para la basura que recoge la caja después de que haya terminado con ella". Usted perfiló eso, ¿verdad? es decir, usted sabe que el GC es un cuello de botella en su aplicación porque lo midió, y no porque lo haya adivinado. La única vez que conseguí unirme a GC fue asignar con frecuencia matrices de bytes grandes. Si las colecciones Gen0 son un cuello de botella, debería reconsiderar su diseño. – CodesInChaos

Respuesta

10

Especular, supongo que el tiempo de ejecución tiene algún tipo de base-genéricos clase secreta en la manga

Su especulación es casi correcto. Lógicamente puede pensar en una caja como un tipo mágico Box<T> que se comporta como usted describe (con algunos más bits de magia, por ejemplo, la forma en que el cuadro de tipos de valor nula es un poco inusual.) Como un detalle de implementación real , el tiempo de ejecución no lo hace con tipos genéricos. El boxeo existía en CLR v1, que era antes de que se agregaran tipos genéricos al sistema de tipo.

mi aplicación de servidor hambrienta de rendimiento hace un poco de boxeo, específicamente de decimales.

si le duele cuando haces eso entonces dejar de hacer eso. En lugar de tratar de hacer que el boxeo sea más barato, deje de hacerlo en primer lugar. ¿Por qué estás boxeando un decimal?

Peor aún, estas cajas son de corta duración, lo que me hace sospechar que pagar dos veces, una vez para instanciando la caja y luego de nuevo para recoger la basura la caja después de que he terminado con ella.

corta vida es mejor de larga duración; con objetos de montón de vida corta que pagas para cobrarlos una vez y luego están muertos. Con los objetos de montón de larga duración usted paga ese costo una y otra vez a medida que el objeto continúa sobreviviendo.

Por supuesto, el costo que probablemente le preocupa con respecto a los objetos efímeros no es el costo de la recolección per se. Más bien, es la presión de la colección ; más objetos de vida corta asignados equivalen a colecciones de basura más frecuentes.

El costo de asignación es bastante mínimo. Mueva un puntero en el montón del GC, copie el decimal en esa ubicación, listo.

Si estuviera alojando esta memoria yo mismo, consideraría el uso de un conjunto de objetos aquí.

Derecha; usted paga más el costo de recolectar el objeto de larga duración, pero obtiene un menor número de cobros porque se produce menos presión de recolección. Eso puede ser una victoria.

¿Existe un mecanismo existente para inducir el tiempo de ejecución para tomar cajas de una agrupación en lugar de instanciarlas?

Nope.

¿Cuál es el tipo de instancia creada durante el boxeo? ¿Es posible tomar el control del proceso de boxeo de forma manual, y aún así ser compatible con el desempaquetado?

El tipo de la caja es el tipo de caja. Solo pregunte llamando a GetType; Te lo diré. Las cajas son mágicas; ellos son el tipo de cosa que contienen.

Como dije antes, en lugar de tratar de hacer que el boxeo sea más barato, simplemente no lo haga en primer lugar.

+0

Hay situaciones donde el boxeo es muy difícil de evitar. Una de esas situaciones es cuando se está creando un marco de entidad con registro de propiedad estática y acceso de tipo GetValue/SetValue, es decir, se parece un poco a los objetos/propiedades de dependencia de WPF. Probablemente eventualmente aumentemos un poco de magia vudú 'Reflection.Emit' para evitar el boxeo por completo, pero esa es una gran empresa en este punto ... ver respuesta a continuación para el caché de caja que hicimos que resolvió nuestros problemas mientras tanto :) –

2

No hay clase como Box<T> por defecto. El tipo sigue siendo el tipo original, pero es una referencia. Como Decimal es inmutable, no puede cambiar el valor después de la creación. Entonces, realmente no puedes usar el agrupamiento con decimales normales en caja.

Usted puede evitar el boxeo para los valores que se vuelvan a producir mediante la implementación de un caché. O necesita implementar su propio tipo de cuadro.

Su propio tipo de cuadro no se puede eliminar de object con un molde estándar. Por lo tanto, deberá adaptar el código de consumo.


Escribir su propio método que devuelve un valor en caja:

[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; 
} 

Un problema con esta sencilla aplicación es que la caché nunca elimina los elementos. Es decir. es una pérdida de memoria

+0

¿De qué manera es útil esta memoria caché? Todavía crea los mismos objetos, simplemente nunca los desasigna. – configurator

+0

No crea objetos si usa los mismos valores a menudo. Pero si la mayoría de los valores son únicos, será malo. Como no sé qué está haciendo el OP, no sé si un caché sería útil. Java usa un patrón similar internamente cuando encajona enteros pequeños (-128 a 127 si recuerdo correctamente). – CodesInChaos

0
int i = 5; 
object boxedInt = i; 

Asignación de un tipo de valor a un System.Object es más o menos todo lo que hay al boxeo en lo que se refiere a su código (no voy a entrar en operación el boxeo detalles técnicos).

Manteniendo sus valores decimales en System.Object las variables pueden restarle un poco de tiempo al boxeo y crear instancias de System.Object, pero siempre tiene que deshacer la casilla. Esto se vuelve más difícil o imposible si tiene que cambiar esos valores con frecuencia ya que cada cambio es una tarea, y por lo tanto un boxeo al menos.

No es un ejemplo de esta práctica, aunque - el marco .NET utiliza valores booleanos pre-encajonados internamente en una clase similar a esto:

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; } } 
} 

El sistema WPF menudo utiliza System.Object variables para las propiedades de dependencia, justo para nombrar un caso donde el boxeo/unboxing es inevitable incluso en estos "tiempos modernos".

4

El motor de ejecución más o menos lo que usted describe, sin embargo los genéricos no participa, como los genéricos no era parte del marco original.

No es mucho lo que se puede hacer sobre el boxeo, si está utilizando un código que espera valores en caja. Podría crear un objeto que use la misma sintaxis para devolver un valor anulando la conversión implícita, pero aún así tendría que ser un objeto, y básicamente haría la misma cantidad de trabajo de todos modos.

Es probable que intentar agrupar los valores en caja degrade el rendimiento en lugar de aumentarlo. El recolector de basura está hecho especialmente para manejar objetos efímeros de manera eficiente, y si los coloca en un grupo, serán objetos de larga vida. Cuando los objetos sobreviven a una recolección de basura, se moverán al siguiente montón, lo que implica copiar el objeto de un lugar en la memoria a otro. Por lo tanto, al agrupar el objeto, en realidad puede causar mucho más trabajo para el recolector de basura en lugar de menos.

+0

Su respuesta es bastante correcta, pero la estrategia de agrupación tiene sentido en muchos casos. Sí, terminas con objetos más longevos, que son caros porque sobreviven muchas colecciones. Pero si todo está agrupado, entonces apenas hay colecciones porque nada está ejerciendo presión sobre la colección. La idea de agrupar no es disminuir el costo por objeto de la colección; más bien, la idea es disminuir el número total de colecciones. –

+0

Sí, la agrupación puede ayudar en algunas situaciones, pero la agrupación de valores decimales probablemente no genere muchos resultados para valores ya existentes. Si los mismos valores se usaran una y otra vez, el almacenamiento en caché del resultado sería mucho más eficiente que el almacenamiento en caché de los parámetros de entrada. – Guffa

+0

Acepto que agrupar * por valor * probablemente no sea una buena idea. Pero puedes juntar solo la caja; cuando la caja ya no se utiliza, regresa al grupo y se vacía. Usamos esta técnica en el equipo del compilador para disminuir la presión de la colección.El inconveniente es que pierde muchos de los beneficios del almacenamiento recopilado automáticamente; solo funciona si la mayoría de las veces eres explícito al decir cuándo terminaste con un cuadro para que pueda volver al grupo. –

0

Desafortunadamente no se puede enganchar el proceso de boxeo, sin embargo, puede usar conversiones implícitas a su favor para que parezca boxeo.

También me mantendría alejado de almacenar cada valor en un Dictionary - su problema de memoria empeorará. Aquí hay un marco de boxeo que podría ser útil.

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); 
} 

Con toda honestidad que debe hacer otra pregunta - y pedir consejo sobre la manera de deshacerse de este problema que tiene el boxeo.

0

Acabo de bloguear sobre nuestra implementación de caché de caja que utilizamos en nuestro motor de base de datos de objetos. Los decimales tienen un rango de valores tan amplio que las cajas de caché no serían muy efectivas, pero si usted necesariamente utiliza campos de objetos o matrices de objetos para almacenar muchos valores comunes, esto podría ayudar:

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

Nuestro uso de memoria cayó como loco después de usar esto. Esencialmente, todo lo que se encuentra en la base de datos se almacena en matrices de objetos [] y puede contener muchos GB de datos, por lo que fue muy beneficioso.

Hay tres métodos principales que tendrá que utilizar:

  • object BoxCache<T>.GetBox(T value) - Obtiene una caja para el valor si uno se almacena en caché de lo contrario lo cajas por usted.
  • object BoxCache<T>.GetOrAddBox(T value) - Obtiene una caja para el valor si uno es en caché de lo contrario, agrega uno en la caché y lo devuelve.
  • void AddValues<T>(IEnumerable<T> values) - Agrega casillas de los especificados valores en la memoria caché.
Cuestiones relacionadas