Ответ на измененный квестон («почему эта неочевидная оптимизация работает так хорошо» и «правда ли, что вы не должны использовать оператор + для строк»):
Я не уверен, о какой неочевидной оптимизации вы говорите. Но ответ на второй вопрос, я думаю, охватывает все основы.
Способ работы строк в C # заключается в том, что они выделяются как фиксированная длина и не могут быть изменены. Это означает, что каждый раз, когда вы пытаетесь изменить длину строки, создается вся новая строка, а старая строка копируется до нужной длины. Это очевидно медленный процесс. Когда вы используете String.Format, он внутренне использует StringBuilder для создания строки.
StringBuilders работают с использованием буфера памяти, который распределен более разумно, чем строки фиксированной длины, и, таким образом, работает значительно лучше в большинстве ситуаций. Я не уверен в деталях StringBuilder внутри, поэтому вам придется задать новый вопрос для этого. Я могу предположить, что он либо не перераспределяет старые части строки (вместо этого создает внутренне связанный список и фактически распределяет конечный вывод только тогда, когда это необходимо для ToString), либо перераспределяет с экспоненциальным ростом (когда ему не хватает памяти, он выделяет в следующий раз вдвое больше, поэтому для строки размером 2 ГБ потребуется всего лишь 30 раз перераспределить).
Ваш пример с вложенными циклами растет линейно. он берет небольшую строку и увеличивает ее до 1000, а затем привязывает эту 1000 к большей строке за одну большую операцию. Поскольку большая строка становится действительно большой, копия, полученная в результате создания новой строки, занимает много времени. Когда вы уменьшаете количество раз, что это делается (вместо этого, вместо того, чтобы чаще изменять размер меньшей строки), вы увеличиваете скорость. Конечно, StringBuilder еще умнее распределяет память и, следовательно, работает намного быстрее.