我偶然发现了来自Writing Large, Responsive .NET Framework Apps的以下代码。
下面的代码使用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?
发布于 2017-08-31 04:46:39
焦点放在创建新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传输流,在池中返回这个缓冲区,或者任务一般是如何工作的(同样的想法),并分配它们的肠子,但不是StringBuilder,duh。
发布于 2017-08-31 04:47:07
以下是“原始方法不是线程安全的”的答案
基本上,作者所做的是用ThreadStaticAttribute标记属性,这使得它是线程安全的,因为值只对这个线程有效。不同的线程会有不同的值/引用。并且这个“缓存”将只在线程本身的生命周期内有效。即使方法本身不是线程安全的,它访问的值也是。
我不认为,这通常是一个很好的例子,因为这是什么意思?你保留了一个sting builder的实例,无论如何你都要把它清理掉。
如果您对每个线程的静态值特别感兴趣,那么拥有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;
}发布于 2017-08-31 06:29:37
这个例子只展示了主要思想,并没有深入探讨。让我们用新方法扩展我们的类来添加命名空间。
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的一个(现金)实例。这就是为什么实例仅在使用后才存储在字段中,如果再次使用则从字段中删除。
https://stackoverflow.com/questions/45968920
复制相似问题