В те дни, когда я программировал на C, я вспоминаю, что когда две строки объединяются, ОС должна выделить память для объединенной строки, затем программа может скопировать весь текст строки в новую область памяти, а затем старая память должна вручную быть выпущеным. Таким образом, если это делается несколько раз, как в случае присоединения к списку, ОС должна постоянно выделять все больше и больше памяти, просто чтобы освободить ее после следующей конкатенации. Гораздо лучшим способом сделать это в C было бы определение общего размера объединенных строк и выделение необходимой памяти для всего объединенного списка строк.
Теперь в современных языках программирования (например, C #), я обычно вижу, что содержимое коллекций объединяется путем перебора коллекции и добавления всех строк, по одной за раз, к одной строковой ссылке. Разве это не неэффективно, даже с современной вычислительной мощностью?
источник
Ответы:
Ваше объяснение, почему оно неэффективно, является точным, по крайней мере, на языках, с которыми я знаком (C, Java, C #), хотя я бы не согласился с тем, что повсеместно распространено выполнение большого количества конкатенации строк. В C # код я работаю, есть обильное использование
StringBuilder
,String.Format
и т.д. , которые все памяти экономии techiniques , чтобы избежать чрезмерной перераспределению.Поэтому, чтобы получить ответ на ваш вопрос, мы должны задать еще один вопрос: если объединение строк никогда не является проблемой, почему классы любят
StringBuilder
иStringBuffer
существуют ? Почему использование таких классов включено в учебники и уроки даже для начинающих? Почему, казалось бы, советы по оптимизации до такой зрелости так важны?Если бы большинство разработчиков, объединяющих строки, основывали свой ответ исключительно на своем опыте, большинство сказали бы, что это никогда не изменится, и отказались бы от использования таких инструментов в пользу «более читабельного»
for (int i=0; i<1000; i++) { strA += strB; }
. Но они никогда не измеряли это.Реальный ответ на этот вопрос может быть найден в этом SO-ответе , который показывает, что в одном случае при объединении 50 000 строк (что, в зависимости от вашего приложения, может быть обычным явлением), даже небольших, это привело к снижению производительности в 1000 раз .
Если производительность буквально ничего не значит, во что бы то ни стало, объедините все. Но я бы не согласился с тем, что использование альтернатив (StringBuilder) сложно или менее читабельно , и поэтому будет разумной практикой программирования, которая не должна вызывать защиту «преждевременной оптимизации».
ОБНОВИТЬ:
Я думаю, что это сводится к тому, чтобы знать вашу платформу и следовать ее лучшим практикам, которые, к сожалению, не универсальны . Два примера из двух разных «современных языков»:
Это не совсем кардинальный грех - не знать сразу все нюансы каждой платформы, но игнорировать такие важные проблемы, как эта, почти то же самое, что переходить с Java на C ++ и не заботиться об освобождении памяти.
источник
strA + strB
это точно так же , как с помощью StringBuilder. Это имеет 1x хит производительности. Или 0x, в зависимости от того, как вы измеряете. Для более подробной информации, codinghorror.com/blog/2009/01/...Это не эффективно, примерно по причинам, которые вы описали. Строки в C # и Java являются неизменяемыми. Операции со строками возвращают отдельный экземпляр вместо изменения исходного, в отличие от того, что было в C. При объединении нескольких строк на каждом шаге создается отдельный экземпляр. Выделение и последующий сбор мусора неиспользованных экземпляров может привести к снижению производительности. Только на этот раз управление памятью выполняется для вас сборщиком мусора.
И C #, и Java представляют класс StringBuilder в виде изменяемой строки специально для задач этого типа. Эквивалентом в C будет использование связанного списка объединенных строк вместо их объединения в массив. C # также предлагает удобный метод Join для строк для присоединения к коллекции строк.
источник
Строго говоря, это менее эффективное использование циклов ЦП, поэтому вы правы. Но как насчет времени разработчика, затрат на обслуживание и т. Д. Если вы добавите в уравнение стоимость времени, то почти всегда эффективнее будет делать то, что проще, чем при необходимости профилировать и оптимизировать медленные биты.
«Первое правило оптимизации программы: не делай этого. Второе правило оптимизации программы (только для экспертов!): Пока не делай этого».
источник
Очень сложно что-либо сказать о производительности без практического теста. Недавно я был очень удивлен, обнаружив, что в JavaScript наивная конкатенация строк обычно выполняется быстрее, чем рекомендуемое решение «составить список и объединить» (протестируйте здесь , сравните t1 с t4). Я все еще озадачен тем, почему это происходит.
Вот несколько вопросов, которые вы можете задать, рассуждая о производительности (особенно об использовании памяти): 1) насколько велик мой вклад? 2) насколько умен мой компилятор? 3) как моя среда выполнения управляет памятью? Это не является исчерпывающим, но это отправная точка.
Насколько велик мой вклад?
Сложное решение часто будет иметь фиксированные накладные расходы, возможно, в виде дополнительных операций, которые необходимо выполнить, или, возможно, в дополнительной памяти, необходимой. Поскольку эти решения предназначены для работы с большими случаями, у разработчиков, как правило, не возникнет проблем с введением этих дополнительных затрат, поскольку чистый выигрыш важнее микрооптимизации кода. Таким образом, если ваш ввод достаточно мал, наивное решение может иметь лучшую производительность, чем сложное, хотя бы для избежания этих издержек. (определение того, что является «достаточно маленьким», является трудной частью, хотя)
Насколько умен мой компилятор?
Многие компиляторы достаточно умны, чтобы «оптимизировать» переменные, которые записываются, но никогда не читаются. Аналогичным образом, хороший компилятор также может преобразовать наивную конкатенацию строк в (базовую) библиотеку, и, если многие из них создаются без каких-либо операций чтения, нет необходимости преобразовывать их обратно в строку между этими операциями (даже если ваш исходный код, кажется, делает именно это). Я не могу сказать, делают ли это какие-либо компиляторы или в какой степени это делается (AFAIK Java по крайней мере заменяет несколько конкатов в одном выражении на последовательность операций StringBuffer), но это возможно.
Как моя среда выполнения управляет памятью?
В современных процессорах узким местом обычно является не процессор, а кеш; если ваш код обращается ко многим «удаленным» адресам памяти за короткое время, то время, необходимое для перемещения всей этой памяти между уровнями кэша, превышает большинство оптимизаций в используемых инструкциях. Это особенно важно в средах выполнения с сборочными сборщиками мусора, поскольку самые последние созданные переменные (например, внутри одной и той же области видимости функции) обычно находятся в смежных адресах памяти. Эти среды выполнения также регулярно перемещают память назад и вперед между вызовами методов.
Одним из способов, которым это может повлиять на конкатенацию строк (отказ от ответственности: это дикое предположение, я не достаточно осведомлен, чтобы сказать наверняка), было бы, если бы память для наивного была выделена близко к остальной части кода, который ее использует (даже если он выделяет и освобождает его несколько раз), в то время как память для объекта библиотеки была выделена далеко от него (поэтому многие контекстные изменения меняются, пока ваш код вычисляет, библиотека потребляет, ваш код вычисляет больше и т. д., что вызовет много ошибок кэша). Конечно, для больших входных данных OTOH пропадание кеша произойдет в любом случае, поэтому проблема многократного распределения становится более выраженной.
Тем не менее, я не защищаю использование того или иного метода, только то, что тестирование, профилирование и бенчмаркинг должны предшествовать любому теоретическому анализу производительности, поскольку большинство систем в настоящее время слишком сложны, чтобы их можно было полностью понять без глубокого знания предмета.
источник
StringBuilder
его внутри, все, что ему нужно сделать, это не вызыватьtoString
до тех пор, пока переменная действительно не понадобится. Если я правильно помню, он делает это для одного выражения, мое единственное сомнение - применимо ли это к нескольким операторам в одном и том же методе. Я ничего не знаю о внутренностях .NET, но я верю, что подобная стратегия может быть использована и компилятором C #.Джоэл написал отличную статью на эту тему некоторое время назад. Как отмечали некоторые другие, это сильно зависит от языка. Из-за способа реализации строк в C (с нулевым символом в конце, без поля длины) стандартная процедура библиотеки strcat очень неэффективна. Джоэл представляет альтернативу с небольшим изменением, которое намного эффективнее.
источник
Нет.
Вы читали «Печальную трагедию театра микрооптимизации» ?
источник