2017-08-30 44 views
0

我碰到下面的代碼從Writing Large, Responsive .NET Framework AppsStringBuilder with Caching,ThreadStatic

下面的代碼使用StringBuilder創建了一個字符串,如SomeType<T1, T2, T3>,並演示緩存StringBuilder以提高性能。

public void Test3() 
     { 
      Console.WriteLine(GenerateFullTypeName("SomeType", 3)); 
     } 

     // Constructs a name like "SomeType<T1, T2, T3>" 
     public string GenerateFullTypeName(string name, int arity) 
     { 
      //StringBuilder sb = new StringBuilder(); 
      StringBuilder sb = AcquireBuilder(); 

      sb.Append(name); 
      if (arity != 0) 
      { 
       sb.Append("<"); 
       for (int i = 1; i < arity; i++) 
       { 
        sb.Append("T"); sb.Append(i.ToString()); sb.Append(", "); 
       } 
       sb.Append("T"); sb.Append(arity.ToString()); sb.Append(">"); 
      } 

      //return sb.ToString(); 
      /* Use sb as before */ 
      return GetStringAndReleaseBuilder(sb); 
     } 
     [ThreadStatic] 
     private static StringBuilder cachedStringBuilder; 

     private static StringBuilder AcquireBuilder() 
     { 
      StringBuilder result = cachedStringBuilder; 
      if (result == null) 
      { 
       return new StringBuilder(); 
      } 
      result.Clear(); 
      cachedStringBuilder = null; 
      return result; 
     } 

     private static string GetStringAndReleaseBuilder(StringBuilder sb) 
     { 
      string result = sb.ToString(); 
      cachedStringBuilder = sb; 
      return result; 
     } 

但是,下面的兩個修改方法在緩存StringBuilder方面更好嗎?只有AcquireBuilder需要知道如何緩存它。

private static StringBuilder AcquireBuilder() 
     { 
      StringBuilder result = cachedStringBuilder; 
      if (result == null) 
      { 
       //unlike the method above, assign it to the cache 
       cachedStringBuilder = result = new StringBuilder(); 
       return result; 
      } 
      result.Clear(); 
      //no need to null it 
      // cachedStringBuilder = null; 
      return result; 
     } 

     private static string GetStringAndReleaseBuilder(StringBuilder sb) 
     { 
      string result = sb.ToString(); 
      //other method does not to assign it again. 
      //cachedStringBuilder = sb; 
      return result; 
     } 

另一個問題是原始方法不是線程安全的,爲什麼ThreadStatic在演示中使用?

+1

下面是'AcquireBuilder'的更好實現:['ObjectPool .Get'](https://docs.microsoft.com/aspnet/core/api/microsoft.extensions.objectpool.objectpool-1) 。這是ASP.NET自己使用的;我不確定爲什麼作者覺得有必要提出一些原創的東西。 –

+0

這已經[內置於框架](https://stackoverflow.com/questions/20029868/understanding-of-net-internal-stringbuildercache-class-configuration)。看起來很相似。請確保您需要它,請記住,沒有過期策略的緩存是內存泄漏。 –

回答

0

重點是創建一個新的StringBuilder實例。 該代碼會導致StringBuilder實現內的sb.ToString()和內部 分配的分配,但如果需要字符串結果,則不能 控制這些分配。

那麼根據例子,他們忽略了他們自己的話。最好是緩存並重用它(在使用前清理它)。除了那些沒有分配所需要的:

public static string GenerateFullTypeName(string name, int arity) 
    { 
     //StringBuilder sb = new StringBuilder(); 
     StringBuilder sb = cached.Value; 
     sb.Clear(); 

     sb.Append(name); 
     if (arity != 0) 
     { 
      sb.Append("<"); 
      for (int i = 1; i < arity; i++) 
      { 
       sb.Append("T"); sb.Append(i.ToString()); sb.Append(", "); 
      } 
      sb.Append("T"); sb.Append(arity.ToString()); sb.Append(">"); 
     } 

     //return sb.ToString(); 
     /* Use sb as before */ 
     return sb.ToString(); 
    } 

    [ThreadStatic] 
    private static Lazy<StringBuilder> cached = new Lazy<StringBuilder>(()=> new StringBuilder()); 

另外,我覺得這就是GC如何傷害你的應用程序的性能鈹壞榜樣。時間和短的字符串基本上不會進入第二代,並將很快處理。更好的是像WCF傳輸流的緩衝區,將緩衝區返回到池中,或者Task如何在一般情況下工作(相同的idead)並分配它們的腸子,但不是StringBuilder,呵呵。

+0

同意,使用Lazy比我的版本更好。但我不明白爲什麼'AcquireBuilder'和'GetStringAndReleaseBuilder'與'StringBuilder'相關的原始方法,它是VS讀取的示例。 – Pingpong

+1

不好的例子。就如此容易。沒有人是完美的。 – eocron

+0

如果需要多線程字符串構建,則可以使用ThreadLocal ,在獲取構建的字符串後進行清除。我在一些反覆地分配StringBuilders會影響性能的地方使用生產代碼的全局版本。這實際上有助於在適用的情況下降低GC壓力。 –

0

這裏是一個「原始的方法不是線程安全的」答案

,基本上,筆者所做的就是 - 標記屬性與ThreadStaticAttribute,這使得它線程安全的,因爲值將是無濟於事只爲這個線程。不同的線程會有不同的值/參考。而這個「緩存」只會在線程本身的生命週期內存在。即使方法本身不是線程安全的,它所訪問的值也是。

現在,我不認爲,這通常是一個很好的例子,因爲這是什麼意思?無論如何,你總是圍繞着一個st bu施工者的例子。

如果您對每個線程的靜態值感興趣,ThreadStaticAttribute是件好事。如果您對線程安全的靜態方法更感興趣,請查看lock

private static MyClass _myClass; 
private static object _lock = new object(); 

public static MyClass GetMyClass() 
{ 
    if (_myClass == null) 
    { 
     lock(_lock) 
     { 
      if (_myClass == null) 
      { 
       _myClass = new MyClass(); 
      } 
     } 

    } 
    return _myClass; 
} 
+0

我知道這一點。謝謝。但我不明白爲什麼AcquireBuilder和GetStringAndReleaseBuilder與StringBuilder相關的原始方法,它所說的是VS讀取的例子。 – Pingpong

+0

@Pingpong這只是一個壞例子。在現實世界中沒有任何意義。您不要在不斷清理它的同時創建並保留諸如'StringBuilder'之類的東西。一個很好的例子就是保持某種執行上下文,這對每個線程都是不同的。在線程執行的開始,你設置了這個上下文,並沿用了這個靜態屬性。 –

+0

你有正確的想法,但它在某些平臺上不是線程安全的。看到[這個線程](https://stackoverflow.com/questions/5958767/is-double-checked-locking-is-broken-a-java-only-thing)。請注意,懶惰達到同樣的事情。 –

0

此示例僅顯示主要思想,並不太深入。讓我們的類用新的方法擴展名稱空間。

public string GenerateFullTypeName(string name, int arity, string @namespace) 
{ 
    StringBuilder sb = AcquireBuilder(); 
    sb.Append(this.GenerateNamespace(@namespace)); 
    sb.Append(this.GenerateFullTypeName(name, arity)); 
    return GetStringAndReleaseBuilder(sb); 
} 

public string GenerateNamespace(string @namespace) 
{ 
    StringBuilder sb = AcquireBuilder(); 

    sb.Append(@namespace); 
    sb.Append("."); 

    return GetStringAndReleaseBuilder(sb); 
} 

並測試它Console.WriteLine(test.GenerateFullTypeName("SomeType", 3, "SomeNamespace"));原始代碼工作正常(輸出 字符串是SomeNamespace.SomeType<T1, T2, T3>),但如果我們將您的「優化」會發生什麼?輸出字符串將是錯誤的(SomeType<T1, T2, T3>SomeType<T1, T2, T3>),因爲我們僅對該類中的所有方法使用StringBuilder的一個(兌現)實例,即使此實例仍在使用中。所以這就是爲什麼實例只有在使用後才存儲在字段中,如果再次使用,則從字段中刪除。