2017-08-30 12 views
0

私はWriting Large, Responsive .NET Framework Appsから下のコードを見つけました。StringBuilder 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; 
     } 

ただし、以下の2つの修正されたメソッドは、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; 
     } 

もう1つの問題は、元のメソッドはスレッドセーフではないということです。なぜなら、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がアプリケーションのパフォーマンスに悪影響を与えることができる方法のbery悪い例だと思いません。一時的な文字列は基本的に第2世代に入ることはなく、すぐに処理されます。 WCF転送ストリーム用のバッファ、プール内のこのバッファの返却、またはタスクが一般的にどのように動作するか(同じアイデア)、腸を割り当てることができますが、StringBuilderではなく、バッファのようなものが良いでしょう。ここで

+0

が同意して、Lazyを使用するのが私のバージョンより優れています。しかし、私は 'AcquireBuilder'と' GetStringAndReleaseBuilder'の元のメソッドが 'StringBuilder'に関連している理由を理解していません。これはVSの読み込みの例です。 – Pingpong

+1

悪い例。それと同じくらい簡単です。誰も完璧ではありません。 – eocron

+0

マルチスレッド文字列構築が必要な場合は、ThreadLocal を使用して、構築された文字列を取得した後でクリアすることができます。私はStringBuildersを何度も何度も割り当ててパフォーマンスに影響を与えるいくつかの場所で、グローバルコードをプロダクションコードで使用しています。実際には、該当するシナリオでGCの圧力を軽減するのに役立ちます。 –

0

「のオリジナルメソッドはスレッドセーフではありません」との答えは基本的に、どのような著者が行われている - 値が唯一このスレッドのために無駄になりますので、それはスレッドセーフになりThreadStaticAttributeとマークプロパティ。異なるスレッドは、異なる値/参照を持つことになります。そして、この "キャッシュ"は、スレッド自体の存続期間中だけ存続します。メソッド自体がスレッドセーフでない場合でも、そのメソッドがアクセスする値はスレッドセーフです。

今、私はこれは一般的には素晴らしい例だと思いますが、これはどういう意味ですか?あなたはとにかくきれいにすることができます。

スレッドごとの静的値に特に興味がある場合は、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)を参照してください。 Lazy が同じことを達成することに注意してください。 –

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>で)動作しますが、私たちはあなたの「最適化」を適用した場合に何が起こることになる、それをテスト?このインスタンスがまだ使用中であっても、このクラスのすべてのメソッドに対してStringBuilderという1つの(キャッシュされた)インスタンスのみを使用するので、出力文字列は間違っています(SomeType<T1, T2, T3>SomeType<T1, T2, T3>)。ですから、インスタンスは使用後にのみフィールドに格納され、再度使用されている場合はフィールドから削除されます。