StringBuilder с кэшированием, ThreadStatic

Я наткнулся на приведенный ниже код из " Написание больших адаптивных приложений.NET Framework".

Код ниже создает строку вроде SomeType<T1, T2, T3> с помощью StringBuilderи демонстрация кеширования 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 используется в демоверсии?

3 ответа

Основное внимание уделяется линии, которая создает новый экземпляр StringBuilder. Код вызывает выделение для sb.ToString() и внутренних выделений в реализации StringBuilder, но вы не можете управлять этими выделениями, если хотите получить строковый результат.

Ну, согласно примеру, они игнорировали свои слова. Лучше было бы просто кэшировать его и использовать повторно (очистить перед использованием). Нет выделений кроме тех что нужно:

    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 может повредить производительности вашего приложения. Временная и короткая струны практически никогда не входят во 2-е поколение и будут утилизированы в ближайшее время. Лучше было бы что-то вроде буферов для потоков передачи WCF, с возвратом этого буфера в пул, или как Task работает в целом (то же самое) и распределяет их кишечник, но не StringBuilder, duh.

Вот ответ на "оригинальные методы не являются потокобезопасными"

По сути, автор сделал - помеченное свойство с 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;
}

Этот пример показывает только основную идею и не погружается слишком глубоко. Давайте расширим наш класс новыми методами для добавления пространства имен.

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 для всех методов в этом классе, даже этот экземпляр все еще используется. Вот почему экземпляр сохраняется в поле только после использования и удаляется из поля, если он снова используется.

Другие вопросы по тегам