2011-09-12 57 views
12

裝箱將值類型轉換爲對象類型。或者像MSDN所說的那樣,裝箱是一種「將托架堆上的引用類型對象內的結構包裝起來的操作」。C# - 可以將箱子放在一起嗎?

但是,如果您嘗試通過查看IL代碼來深入瞭解,您只能看到魔術字「盒子」。

投機,我想,運行時有某種仿製藥爲主的祕密級了它的袖子,像Box<T>public T Value屬性,拳擊一個int會是什麼樣子:

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

拆箱的INT會更便宜:return box.Value;

不幸的是,我的性能需求量很大的服務器應用程序做了一些拳擊,特別是小數點。更糟糕的是,這些盒子壽命很短,這讓我懷疑我付了兩次,一次用於裝盒,然後再用垃圾箱收集盒子。

如果我自己分配這個內存,我會考慮在這裏使用對象池。但是由於實際的對象創建隱藏在IL中的一個魔術字背後,我有什麼選擇?

我的具體問題:

  • 是否有誘導運行拿箱子從池中,而不是instanciating他們現有的機制?
  • 拳擊期間創建的實例的類型是什麼?是否有可能手動控制拳擊過程,但仍然與拆箱兼容?

如果這最後一個問題似乎很奇怪,我的意思是,我可以手動創建自己的Box<T>DecimalBox類,池吧,盒/拆箱。但我不想去修改代碼中使用盒裝值的各個地方(又名unbox)。

+0

你有沒有考慮通過利用泛型/自定義對象/等減少拳擊操作? 「盒子」指令是故意不透明的,儘管你可以通過上面的「盒子」類來模仿它。 – dlev

+10

「我的應用程序做了一些拳擊」。一場奇怪的比賽,唯一的勝利舉動就是不玩。 –

+5

「更糟糕的是,這些盒子壽命很短,這讓我懷疑我付了兩次,一次用於裝盒,然後再用垃圾箱收集盒子。」你介紹了這個,對吧?即你知道GC是你應用程序中的瓶頸,因爲你測量了,而不是因爲你猜測。我設法獲得GC綁定的唯一時間是頻繁分配大型字節數組。如果Gen0系列是瓶頸,你應該重新考慮你的設計。 – CodesInChaos

回答

10

投機,我想,運行時有某種基於泛型類的祕密了它的袖子

你猜測的差不多吧。 邏輯上您可以將盒子看作是一種神奇的Box<T>類型,其行爲與您所描述的類似(具有更多的魔法;例如,可爲空的值類型框的方式有點不同尋常)。作爲實際實現細節,運行時不會使用泛型類型。拳擊存在於CLR v1中,這是在泛型類型被添加到類型系統之前。

我的性能匱乏的服務器應用程序做了一點點的拳擊,特別是小數點。

如果當你這樣做,那麼停止這樣做它傷害。不要試圖讓拳擊更便宜,而是首先停止做。你爲什麼要裝一個小數?

更糟的是,這些箱子是短暫的,這使我懷疑我的垃圾收集箱,我用它做後付款兩次,一次爲instanciating框,然後再。

短命是更好比長壽命;用短期堆對象支付收集他們一旦,然後他們已經死了。 隨着對象繼續生存,使用壽命長的堆對象會一次又一次地支付這筆費用。

當然,您可能擔心的關於短期物品的成本並不是收集本身的成本。相反,這是收集壓力;分配的更短期的對象等於更頻繁的垃圾收集。

分配成本非常低。移動GC堆上的指針,將小數點複製到該位置,完成。

如果我自己在分配這個內存,我會考慮在這裏使用一個對象池。

對;您支付更多收集長壽命物體的費用,但是由於產生的收集壓力較小,所以收集總量更少。這可能是一場勝利。

是否存在一個誘導運行時從池中取出框而不是實例化它們的現有機制?

沒有。

拳擊期間創建的實例的類型是什麼?是否有可能手動控制拳擊過程,但仍然與拆箱兼容?

箱子的類型是箱子的類型。只需通過調用GetType來詢問它;它會告訴你。盒子是神奇的;它們是它們包含的東西的類型。

就像我之前所說的,不是試圖讓拳擊更便宜,而是首先不要這樣做。

+0

有些情況下,拳擊很難避免。其中一種情況是當您使用靜態屬性註冊和GetValue/SetValue類型訪問來構建實體框架時,即認爲有點像WPF依賴對象/屬性。我們最終可能會掀起一些'Reflection.Emit'巫術魔術來完全避免拳擊,但在這一點上這是一個巨大的任務...查看下面的答案,我們提出的盒子緩存,同時解決了我們的問題:) –

2

默認情況下沒有類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

這是如何緩存任何幫助?它仍然創建相同的對象,只是從不釋放它們。 – configurator

+0

如果您經常使用相同的值,它不會創建對象。但是,如果大多數價值都是獨一無二的,那就很糟糕由於我不知道OP在做什麼,我不知道緩存是否有用。當拳擊小整數時,Java內部使用類似的模式(如果我正確記得,-128到127)。 – CodesInChaos

0
int i = 5; 
object boxedInt = i; 

分配一個值類型爲System.Object或多或少都存在拳擊至於你的代碼來說(我不會進入裝箱操作技術細節)。

保持你的十進制值在System.Object變量可以從裝箱和創建System.Object實例中刪除一點時間,但你總是必須取消裝箱。如果你不得不經常改變這些值,那麼這將變得更加困難,因爲每一次改變都是一項任務,因此至少是一次拳擊。

有這種做法的一個例子,雖然 - .net框架在一個類似的類使用預盒裝布爾值國內:

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變量依賴屬性,只是要舉出一個即使在這些「現代」中也不可避免的拳擊/拆箱的案例。

4

運行時完成您所描述的幾乎所有功能,但是不涉及泛型,因爲泛型不是原始框架的一部分。

如果您正在使用某些需要裝箱值的代碼,那麼您可以做的關於裝箱的事情並不多。你可以創建一個對象,它使用相同的語法通過重寫隱式轉換來返回值,但這仍然需要是一個對象,而且基本上可以完成相同數量的工作。

試圖合併盒裝值很可能會降低性能而不是提高性能。垃圾收集器專門用於高效地處理短暫的對象,如果將對象放在一個池中,它們將是長壽命的對象。當對象在垃圾回收中存活時,它們將被移動到下一個堆,這涉及將對象從內存中的一個位置複製到另一個位置。因此,通過彙集對象,實際上可能會導致垃圾收集器的更多工作,而不是更少。

+0

你的回答非常正確,但在許多情況下,合併策略確實有意義。是的,你最終會得到更長壽的物品,這些物品很貴,因爲它們經受了很多收藏。但如果一切都集中起來,那麼幾乎沒有任何收藏品,因爲沒有任何收藏壓力。合併的想法並不是要降低每個對象的收集成本;相反,這個想法是減少收集的總數。 –

+0

是的,在某些情況下,池化可以起到幫助作用,但將十進制值集中起來不可能爲已有值產生很多命中。如果重複使用相同的值,緩存結果將比緩存輸入參數更有效。 – Guffa

+0

我同意按價值*彙集*可能不是一個好主意。但是你可以把盒子放在一起。當箱子不再使用時,它會返回到池中並清空。我們在編譯器團隊中使用這種技術來減輕採集壓力。不利的一面是,你失去了自動收集存儲的許多好處;它只有在大多數時候你明確表達你何時完成一個盒子以便它能夠回到池中時纔有效。 –

0

不幸的是,你不能掛鉤拳擊過程,但是,你可以使用隱式轉換來獲得優勢,使其看起來像拳擊。

我也會避免將每個值存儲在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

我剛剛在我們的對象數據庫引擎中使用了我們的盒緩存實現的博客。小數有如此廣泛的範圍值是緩存盒不會是非常有效的,但如果你發現自己不必使用對象的字段或對象[]數組存儲了很多共同的價值觀念,這可能幫助:

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

使用後,我們的內存使用情況就像瘋了一樣下降。基本上,數據庫中的所有內容都存儲在object []數組中,並且可以有很多GB的數據,所以這非常有用。

有您將要使用三種主要方法:

  • object BoxCache<T>.GetBox(T value) - 獲取該值的框,如果一個 緩存否則盒給你。
  • object BoxCache<T>.GetOrAddBox(T value) - 獲取一個值,如果一個是 緩存的值,否則它會將一個值添加到緩存中並將其返回。
  • void AddValues<T>(IEnumerable<T> values) - 將指定的 值的框添加到緩存中。
相關問題