Добавление строк в C#, как это делает компилятор?

A = string.Concat("abc","def") 

B = "abc" + "def"

А против Б

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

Как компилятор обрабатывает эти строки?

Спасибо!

6 ответов

Решение

Самое первое, что я сделал, присоединившись к команде компилятора C#, - переписал оптимизатор для конкатенации строк. Хорошие времена.

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

a + b --> String.Concat(a, b)
a + b + c --> String.Concat(a, b, c)
a + b + c + d --> String.Concat(a, b, c, d)
a + b + c + d + e --> String.Concat(new String[] { a, b, c, d, e })

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

Вот интересный. Предположим, у вас есть метод M, который возвращает строку:

s = M() + "";

Если M() возвращает ноль, то результатом является пустая строка. (null + empty пусто.) Если M не возвращает null, результат остается неизменным при конкатенации пустой строки. Следовательно, это на самом деле оптимизировано как вовсе не вызов String.Concat! Это становится

s = M() ?? ""

Аккуратно, а?

В C# оператор сложения для строк является просто синтаксическим сахаром для String.Concat. Вы можете убедиться в этом, открыв выходной узел в отражателе.

Следует также отметить, что если в вашем коде есть строковые литералы (или константы), как, например, в примере, компилятор даже изменит это на B = "abcdef",

Но если вы используете String.Concat с двумя строковыми литералами или константами, String.Concat будет по-прежнему вызываться, пропуская оптимизацию, и поэтому + операция будет на самом деле быстрее.

Итак, подведем итог:

stringA + stringB становится String.Concat(stringA, stringB),
"abc" + "def" становится "abcdef"
String.Concat("abc", "def") остается такой же

Что-то еще, что я просто должен был попробовать:

В C++ / CLI, "abc" + "def" + "ghi"на самом деле переводится на String.Concat(String.Concat("abc", "def"), "ghi")

Если строки являются литералами, как в вашем вопросе, то объединение строк, назначенных B будет сделано во время компиляции. Ваш пример переводится как:

string a = string.Concat("abc", "def");
string b = "abcdef";

Если строки не являются литералами, компилятор переведет + оператор в Concat вызов.

Так что это...

string x = GetStringFromSomewhere();
string y = GetAnotherString();

string a = string.Concat(x, y);
string b = x + y;

... переводится на это во время компиляции:

string x = GetStringFromSomewhere();
string y = GetAnotherString();

string a = string.Concat(x, y);
string b = string.Concat(x, y);

На самом деле, B разрешается во время компиляции. Вы закончите с B = "abcdef" тогда как для A конкатенация откладывается до времени выполнения.

В данном конкретном случае два фактически идентичны. Компилятор преобразует второй вариант, использующий + оператор, в вызов Concat, первый вариант.

Ну, это так, если две действительно содержат строковые переменные, которые были объединены.

Этот код:

B = "abc" + "def";

на самом деле превращается в это, без конкатенации вообще:

B = "abcdef";

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

Однако, если бы вы использовали что-то вроде этого:

A = String.Concat(stringVariable1, stringVariable2);
B = stringVariable1 + stringVariable2;

Тогда эти два сгенерируют один и тот же код.

Однако я хотел бы точно знать, что сказали эти "многие", так как я думаю, что это что-то другое.

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

Например, если вы делаете это:

String s = "test";
for (int index = 1; index <= 10000; index++)
    s = s + "test";

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

Кроме того, в каждой такой новой строке будет скопировано все содержимое старой, что означает, что вы будете перемещать большой объем памяти.

Тогда как следующий код:

StringBuilder sb = new StringBuilder("test");
for (int index = 1; index <= 10000; index++)
    sb.Append("test");

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

Таким образом, с точки зрения использования памяти и использования процессора, последний вариант намного лучше.

Кроме этого, я бы постарался не слишком сосредоточиваться на том, "является ли вариант кода X лучше, чем Y", помимо того, что у вас уже есть. Например, сейчас я использую StringBuilder только потому, что знаю об этом деле, но это не значит, что весь код, который я пишу, использующий его, действительно нуждается в нем.

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

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