2017-08-30 58 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的一个(兑现)实例,即使此实例仍在使用中。所以这就是为什么实例只有在使用后才存储在字段中,如果再次使用,则从字段中删除。