Добавление строк в 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 только потому, что знаю об этом деле, но это не значит, что весь код, который я пишу, использующий его, действительно нуждается в нем.
Старайтесь не тратить время на микрооптимизацию своего кода, пока не поймете, что у вас есть узкое место. В то время обычный совет по поводу меры сначала, вырезанный позже, все еще действует.