Я слышал, как несколько людей выражали беспокойство по поводу оператора "+" в std :: string и различных обходных путей для ускорения конкатенации. Действительно ли это необходимо? Если да, то как лучше всего объединить строки в C ++?
108
libstdc++
делает это, например . Таким образом, при вызове operator + с временными библиотеками он может достичь почти такой же хорошей производительности - возможно, аргумент в пользу его использования по умолчанию для удобства чтения, если нет тестов, показывающих, что это узкое место. Тем не менее, стандартизированная переменнаяappend()
была бы оптимальной и удобочитаемой ...Ответы:
Дополнительная работа, вероятно, того не стоит, если вам действительно не нужна эффективность. Вы, вероятно, получите гораздо лучшую эффективность, просто используя вместо этого оператор + =.
Теперь, после этого отказа от ответственности, я отвечу на ваш вопрос ...
Эффективность строкового класса STL зависит от реализации используемого вами STL.
Вы можете гарантировать эффективность и больший контроль , выполняя конкатенацию вручную с помощью встроенных функций c.
Почему оператор + неэффективен:
Взгляните на этот интерфейс:
Вы можете видеть, что новый объект возвращается после каждого +. Это означает, что каждый раз используется новый буфер. Если вы делаете кучу дополнительных + операций, это неэффективно.
Почему можно сделать его более эффективным:
Соображения по реализации:
Структура данных веревки:
Если вам нужны действительно быстрые конкатенации, подумайте об использовании веревочной структуры данных .
источник
Зарезервируйте последнее место раньше, а затем используйте метод добавления с буфером. Например, предположим, что вы ожидаете, что ваша окончательная длина строки будет составлять 1 миллион символов:
источник
Я бы не беспокоился об этом. Если вы делаете это в цикле, строки всегда будут предварительно выделять память, чтобы минимизировать перераспределение - просто используйте
operator+=
в этом случае. И если вы делаете это вручную, что-то вроде этого или дольшеЗатем он создает временные объекты, даже если компилятор может удалить некоторые копии возвращаемых значений. Это связано с тем, что в последовательно вызываемом
operator+
элементе неизвестно, ссылается ли ссылочный параметр на именованный объект или на временный объект, возвращенный из вспомогательногоoperator+
вызова. Я бы предпочел не беспокоиться об этом, пока не профилировал. Но давайте возьмем пример, чтобы показать это. Сначала мы введем круглые скобки, чтобы сделать привязку понятной. Я помещаю аргументы сразу после объявления функции, которое используется для ясности. Ниже я показываю, что получается в результате:Теперь, помимо этого,
tmp1
это то , что было возвращено первым вызовом оператора + с показанными аргументами. Мы предполагаем, что компилятор действительно умен и оптимизирует копию возвращаемого значения. Таким образом, мы получаем одну новую строку, которая содержит конкатенациюa
и" : "
. Вот что происходит:Сравните это со следующим:
Он использует одну и ту же функцию для временной и именованной строки! Таким образом, компилятор должен скопировать аргумент в новую строку, добавить к ней и вернуть его из тела
operator+
. Он не может взять воспоминание о временном и добавить к нему. Чем больше выражение, тем больше копий строк нужно сделать.Следующие Visual Studio и GCC будут поддерживать семантику перемещения C ++ 1x (дополняя семантику копирования). ) и ссылки на rvalue в качестве экспериментального дополнения. Это позволяет выяснить, ссылается ли параметр на временный или нет. Это сделает такие дополнения удивительно быстрыми, так как все вышеперечисленное будет в одном «конвейере добавления» без копий.
Если это окажется узким местом, вы все равно можете сделать
В
append
вызовы добавьте аргумент*this
и затем возвращает ссылку на себя. Так что там не делается копирование временных файлов. Или, в качестве альтернативы,operator+=
можно использовать, но вам понадобятся уродливые круглые скобки для исправления приоритета.источник
libstdc++
дляoperator+(string const& lhs, string&& rhs)
делаетreturn std::move(rhs.insert(0, lhs))
. Тогда, если оба являются временными, его,operator+(string&& lhs, string&& rhs)
еслиlhs
имеется достаточная доступная емкость, будет просто напрямуюappend()
. Я думаю, что это рискует быть медленнее, чемoperator+=
если быlhs
не было достаточно емкости, поскольку тогда он возвращается кrhs.insert(0, lhs)
, что не только должно расширять буфер и добавлять новое содержимое, напримерappend()
, но также должно перемещаться по исходному содержимомуrhs
справа.operator+=
, чтоoperator+
все еще должно возвращать значение, поэтому оно должно бытьmove()
любым операндом, к которому оно было добавлено. Тем не менее, я полагаю, что это довольно незначительные накладные расходы (копирование пары указателей / размеров) по сравнению с глубоким копированием всей строки, так что это хорошо!Для большинства приложений это не имеет значения. Просто напишите свой код, блаженно не зная, как именно работает оператор +, и возьмите дело в свои руки, только если он станет очевидным узким местом.
источник
В отличие от .NET System.Strings, строки std :: strings в C ++ являются изменяемыми и поэтому могут быть построены с помощью простой конкатенации так же быстро, как и с помощью других методов.
источник
operator+
не должен возвращать новую строку. Разработчики могут вернуть один из его операндов, измененный, если этот операнд был передан по ссылке rvalue.libstdc++
делает это, например . Таким образом, при вызовеoperator+
с временными библиотеками он может достичь такой же или почти такой же хорошей производительности - что может быть еще одним аргументом в пользу его использования по умолчанию, если нет тестов, показывающих, что он представляет собой узкое место.возможно, вместо этого std :: stringstream?
Но я согласен с мнением, что вам, вероятно, следует просто сохранить его в удобном для обслуживания и понятном виде, а затем профилировать, чтобы увидеть, действительно ли у вас проблемы.
источник
В Imperfect C ++ Мэтью Уилсон представляет динамический конкатенатор строк, который предварительно вычисляет длину конечной строки, чтобы иметь только одно выделение перед конкатенацией всех частей. Мы также можем реализовать статический конкатенатор, играя с шаблонами выражений .
Такая идея была реализована в реализации STLport std :: string - которая не соответствует стандарту из-за этого точного взлома.
источник
Glib::ustring::compose()
из привязок glibmm к GLib делает следующее: оценивает иreserve()
s конечную длину на основе предоставленной строки формата и varargs, затем обрабатываетappend()
каждый (или его форматированную замену) в цикле. Я думаю, это довольно распространенный способ работы.std::string
operator+
выделяет новую строку и каждый раз копирует две строки операндов. повторять много раз, и это становится дорого, O (n).std::string
append
и,operator+=
с другой стороны, увеличивайте пропускную способность на 50% каждый раз, когда струна должна расти. Что значительно сокращает количество выделений памяти и операций копирования, O (log n).источник
operator+
которых один или оба аргумента передаются по ссылке rvalue, могут полностью избежать выделения новой строки путем объединения в существующий буфер один из операндов (хотя им, возможно, придется перераспределить, если у него недостаточно емкости).Для маленьких струн это не имеет значения. Если у вас большие строки, лучше хранить их в векторном виде или в другой коллекции как части. И адаптируйте свой алгоритм для работы с таким набором данных вместо одной большой строки.
Я предпочитаю std :: ostringstream для сложной конкатенации.
источник
Как и в большинстве случаев, проще чего-то не делать, чем делать.
Если вы хотите выводить большие строки в графический интерфейс, может случиться так, что все, что вы выводите, может обрабатывать строки по частям лучше, чем как большая строка (например, объединение текста в текстовом редакторе - обычно они сохраняют строки как отдельные конструкции).
Если вы хотите вывести данные в файл, используйте потоковую передачу данных, а не создавайте большую строку и выводите ее.
Я никогда не считал необходимым ускорять конкатенацию, если бы удалил ненужную конкатенацию из медленного кода.
источник
Вероятно, лучшая производительность, если вы предварительно выделите (зарезервируете) пространство в результирующей строке.
Использование:
источник
Простой массив символов, инкапсулированный в класс, который отслеживает размер массива и количество выделенных байтов, является самым быстрым.
Хитрость заключается в том, чтобы в начале выделить только одно большое выделение.
в
https://github.com/pedro-vicente/table-string
Контрольные точки
Для Visual Studio 2015 отладочная сборка x86, значительное улучшение по сравнению с C ++ std :: string.
источник
std::string
. Они не просят альтернативный класс строк.Вы можете попробовать это с резервированием памяти для каждого элемента:
источник