Почему String.Concat не оптимизирован для StringBuilder.Append? - PullRequest
21 голосов
/ 01 февраля 2010

Я обнаружил, что константы строковых выражений оптимизируются компилятором в одну строку.

Теперь, когда конкатенация строк известна только во время выполнения, почему компилятор не оптимизирует конкатенацию строк в циклах и конкатенациях, скажем, более 10 строк для использования StringBuilder.Append вместо этого? Я имею в виду, это возможно, верно? Создайте StringBuilder, возьмите каждую связь и превратите ее в Append() вызов.

Есть ли причина, по которой эти должны или могут не оптимизироваться ? Чего мне не хватает?

Ответы [ 8 ]

41 голосов
/ 01 февраля 2010

Определенный ответ должен прийти от команды разработчиков компилятора. Но позвольте мне сделать удар здесь ...

Если ваш вопрос, почему компилятор не включит это:

string s = "";
for( int i = 0; i < 100; i ++ )
    s = string.Concat( s, i.ToString() );

в это:

StringBuilder sb = new StringBuilder();
for( int i = 0; i < 100; i++ )
    sb.Append( i.ToString() );
string s = sb.ToString();

Наиболее вероятный ответ: Это не оптимизация . Это переписывание кода, который вводит новые конструкции, основанные на знаниях и намерениях, которые имеет разработчик, а не компилятор.

Для этого типа изменений потребуется, чтобы компилятор знал больше о BCL, чем это необходимо. Что если завтра станет доступным более оптимальный сервис сборки строк? Должен ли компилятор использовать это?

Что если бы ваши условия цикла были более сложными , должен ли компилятор попытаться выполнить некоторый статический анализ, чтобы решить, будет ли результат такого перезаписи все еще функционально эквивалентным? Во многих отношениях это похоже на решение проблемы остановки .

Наконец, я не уверен, что во всех случаях это приведет к более быстрому выполнению кода. Существует инстанция для StringBuilder и изменения размера его внутреннего буфера при добавлении текста. Фактически, стоимость добавления сильно зависит от размера конкатенируемой строки, от того, сколько она существует, как выглядит нагрузка на память. Это вещи, которые компилятор не может предсказать заранее.

Ваша работа как разработчика состоит в том, чтобы писать хорошо работающий код. Компилятор может помочь, только сделав определенные безопасные , сохраняющие инварианты оптимизации. Не переписывать свой код для вас.

30 голосов
/ 01 февраля 2010

Ответ Л.Бускина превосходен; Мне нужно добавить пару вещей.

Во-первых, JScript.NET выполняет эту оптимизацию. JScript часто используется менее опытными программистами для задач, которые включают построение больших строк в циклах, таких как создание объектов JSON, данных HTML и так далее.

Поскольку эти программисты могут не знать о стоимости n-квадрата для наивного размещения строк, могут не знать о существовании конструкторов строк и часто писать код с использованием этого шаблона, мы чувствовали, что было бы разумно использовать эту оптимизацию в JScript.NET.

Программисты на C #, как правило, лучше осведомлены о базовых затратах на код, который они пишут, и больше знают о наличии готовых компонентов, таких как StringBuilder, поэтому им меньше нужна эта оптимизация. И что более важно, философия дизайна C # заключается в том, что это язык "делай то, что я сказал" с минимумом "магии"; JScript - это язык «делай то, что я имею в виду», который делает все возможное, чтобы выяснить, как лучше всего служить тебе, даже если это иногда означает неправильное предположение. Обе философии верны и полезны.

Иногда это «идет другим путем». Сравните этот выбор с выбором, который мы делаем для переключателей на струнах. Переключатели на строках фактически скомпилированы как создание словаря, содержащего строки, а не как последовательность сравнений строк. Эта оптимизация может быть плохой; это может быть быстрее, просто сделать сравнение строк. Но здесь мы предполагаем, что вы «подразумевали» переход к поиску в таблице, а не к серии операторов «если» - если бы вы имели в виду последовательность операторов if, вы могли бы легко написать это самостоятельно.

16 голосов
/ 01 февраля 2010

Для одной конкатенации нескольких строк (например, a + b + c + d + e + f + g + h + i + j) вы действительно хотите, чтобы использовал String.Concat IMO. Он имеет издержки на создание массива для каждого вызова, но имеет то преимущество, что метод может определить точную длину результирующей строки, прежде чем ему потребуется выделить какую-либо память. StringBuilder.Append(a).Append(b)... дает только одно значение за раз, поэтому сборщик не знает, сколько памяти выделить.

Что касается выполнения в циклах - в этот момент вы добавили новую локальную переменную, и вам нужно добавить код для обратной записи в строковую переменную точно в нужное время (вызов StringBuilder.ToString()). Что происходит, когда вы работаете в отладчике? Разве не было бы довольно странно не видеть, как значение растет, а становится видимым только в конце цикла? О, и, конечно, вы должны выполнить соответствующую проверку того, что значение не используется ни в какой точке до конца цикла ...

6 голосов
/ 01 февраля 2010

Две причины:

  • Вы не можете программно определить места, где производительность будет строго выше.
  • «Оптимизация» замедлит работу, если будет выполнена неправильно.

Вы можете предложить людям использовать правильные вызовы для своего приложения, но в какой-то момент разработчик несет ответственность за правильное его решение.

Редактировать: Относительно сокращения,у нас есть еще пара проблем:

  • Единственный способ точно узнать о достижении отсечки - это сложный анализ потока.Количество мест, где можно найти разделы, которые можно преобразовать, очень мало.
  • Анализ потока дорог.Если вы сделаете это во время выполнения, вся программа будет работать медленнее из-за редкой вероятности того, что один фрагмент плохо написанного кода будет быстрее.Если вы делаете это во время компиляции, это не ошибка в соответствии с синтаксисом языка, но вы можете выдать предупреждение - и это именно то, что делает FXCop (медленный, но доступный инструмент анализа потока).Подумайте, всегда ли FXCop должен был работать с компилятором;столько часов люди будут просто ждать запуска кода.И если это было во время выполнения, добро пожаловать во времена запуска JVM ...
2 голосов
/ 01 февраля 2010

Потому что задача компилятора - генерировать семантически правильный код. Изменение вызовов String.Concat на вызовы StringBuilder.Append приведет к изменению семантики кода.

1 голос
/ 01 февраля 2010

Я считаю, что это было бы слишком сложно для авторов компиляторов. И когда вы ссылаетесь на промежуточные строки внутри циклов, кроме конкатенации (например, передавая их некоторым другим методам или около того), эта оптимизация будет невозможна.

0 голосов
/ 01 февраля 2010

String является неизменяемым типом, поэтому использование конкатенации строки выполняется медленнее, чем StringBuilder.Append.

Редактировать: Чтобы прояснить мою точку зрения, когда вы говорите о том, почему String.Concat не оптимизирован до StringBuilder.Append, класс StringBuilder имеет совершенно другую семантику по сравнению с неизменным типом String. Почему вы должны ожидать, что компилятор оптимизирует это, поскольку это явно две разные вещи? Кроме того, StringBuilder является изменяемым типом, который может динамически изменять свою длину, почему компилятор должен оптимизировать неизменяемый тип для изменяемого типа? Это дизайн и семантика, заложенные в спецификации ECMA для .NET Framework, независимо от языка.

Это все равно, что попросить компилятор (и, возможно, ожидать слишком многого) скомпилировать char и оптимизировать его в int, потому что int работает на 32 битах вместо 8 битов и будет считаться быстрее!

0 голосов
/ 01 февраля 2010

Возможно, из-за того, что сложно сопоставить такой шаблон в коде, и в случае, если компилятор не может выполнить сопоставление по какой-либо причине, производительность кода внезапно становится ужасной. Такая оптимизация кода будет стимулировать написание такого кода, что еще больше усилит негативное влияние в тех случаях, когда компилятор больше не может выполнять оптимизацию.

Для объединения известного набора строк, StringBuilder не быстрее, чем String.Concat.

...