Эффективная конкатенация строк в С++

Я слышал, как несколько человек выражали беспокойство по поводу оператора "+" в std::string и различные обходные пути для ускорения конкатенации. Действительно ли это необходимо? Если да, то каков наилучший способ конкатенации строк в С++?

Ответ 1

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

Теперь после этого отказа от ответственности я отвечу на ваш реальный вопрос...

Эффективность строкового класса STL зависит от реализации используемого STL.

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

Почему оператор + не эффективен:

Взгляните на этот интерфейс:

template <class charT, class traits, class Alloc>
basic_string<charT, traits, Alloc>
operator+(const basic_string<charT, traits, Alloc>& s1,
          const basic_string<charT, traits, Alloc>& s2)

Вы можете видеть, что после каждого + возвращается новый объект. Это означает, что каждый раз используется новый буфер. Если вы делаете тонну дополнительных операций, это неэффективно.

Почему вы можете сделать его более эффективным:

  • Вы гарантируете эффективность, а не доверяете делегату, чтобы сделать это эффективно для вас.
  • класс std::string ничего не знает о максимальном размере вашей строки и о том, как часто вы будете конкатенировать с ней. У вас могут быть эти знания и вы можете делать что-то на основе этой информации. Это приведет к меньшему перераспределению.
  • Вы будете управлять буферами вручную, чтобы быть уверенным, что вы не будете копировать всю строку в новые буферы, когда вы этого не хотите.
  • Вы можете использовать стек для буферов вместо кучи, который намного эффективнее. Оператор
  • string + создает новый строковый объект и возвращает его, используя новый буфер.

Соображения для реализации:

  • Следите за длиной строки.
  • Держите указатель в конце строки и в начале, или просто в начале, и используйте начало + длину как смещение, чтобы найти конец строки.
  • Убедитесь, что буфер, в котором хранится ваша строка, достаточно велик, поэтому вам не нужно перераспределять данные.
  • Используйте strcpy вместо strcat, поэтому вам не нужно перебирать длину строки, чтобы найти конец строки.

Структура канатной структуры:

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

Ответ 2

Зарезервируйте свое конечное пространство раньше, затем используйте метод append с буфером. Например, предположим, что ваша конечная длина строки должна составлять 1 миллион символов:

std::string s;
s.reserve(1000000);

while (whatever)
{
  s.append(buf,len);
}

Ответ 3

Я бы не стал беспокоиться об этом. Если вы сделаете это в цикле, строки всегда будут выделять память для минимизации перераспределения - просто используйте operator+= в этом случае. И если вы сделаете это вручную, что-то вроде этого или дольше

a + " : " + c

Затем он создает временные файлы - даже если компилятор может устранить некоторые копии возвращаемого значения. Это происходит потому, что в последовательном названии operator+ он не знает, ссылается ли ссылочный параметр на именованный объект или на временный объект, возвращаемый с вызова operator+. Я бы предпочел не беспокоиться об этом, прежде чем не профилировать в первую очередь. Но давайте возьмем пример, чтобы показать это. Сначала мы вводим круглые скобки, чтобы сделать привязку понятной. Я приводил аргументы непосредственно после объявления функции, которое использовалось для ясности. Ниже я покажу, каким получилось следующее выражение:

((a + " : ") + c) 
calls string operator+(string const&, char const*)(a, " : ")
  => (tmp1 + c)

Теперь, в этом добавлении, tmp1 - это то, что было возвращено первым вызовом operator + с показанными аргументами. Мы предполагаем, что компилятор действительно умный и оптимизирует копию возвращаемого значения. Таким образом, мы получаем одну новую строку, содержащую конкатенацию a и " : ". Теперь это происходит:

(tmp1 + c)
calls string operator+(string const&, string const&)(tmp1, c)
  => tmp2 == <end result>

Сравните это со следующим:

std::string f = "hello";
(f + c)
calls string operator+(string const&, string const&)(f, c)
  => tmp1 == <end result>

Он использует ту же функцию для временной и для именованной строки! Поэтому компилятор должен скопировать аргумент в новую строку и добавить к нему и вернуть его из тела operator+. Он не может взять память о временном и добавить к этому. Чем больше выражение, тем больше копий строк должно быть выполнено.

Далее Visual Studio и GCC будут поддерживать семантику перемещения С++ 1x (дополняющую семантику копирования) и ссылки rvalue в качестве экспериментального дополнения. Это позволяет выяснить, ссылается ли параметр на временный или нет. Это сделает такие дополнения удивительно быстрыми, так как все вышеперечисленное закончится одним "добавлением-конвейером" без копий.

Если это окажется узким местом, вы все равно можете

 std::string(a).append(" : ").append(c) ...

Вызов append добавляет аргумент к *this, а затем возвращает ссылку на себя. Таким образом, копирование временных объектов не производится. Или, альтернативно, можно использовать operator+=, но для исправления приоритета вам потребуются уродливые скобки.

Ответ 4

Для большинства приложений это не имеет значения. Просто напишите свой код, блаженно не осознавая, как работает оператор "+", и только берете дело в свои руки, если оно становится очевидным узким местом.

Ответ 5

В отличие от .NET System.Strings, С++ std:: strings являются изменяемыми и поэтому могут быть созданы с помощью простой конкатенации так же быстро, как и с помощью других методов.

Ответ 6

возможно, std:: stringstream вместо?

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

Ответ 7

В Imperfect С++ Мэтью Уилсон представляет динамический конкатенатор строк, который предварительно вычисляет длину окончательной строки, чтобы иметь только одно выделение перед конкатенацией всех частей. Мы также можем реализовать статический конкатенатор, играя с шаблонами выражений.

Такая идея реализована в реализации STLport std::string, которая не соответствует стандарту из-за этого точного взлома.

Ответ 8

std::string operator+ выделяет новую строку и копирует две строки операндов каждый раз. повторяйте много раз, и он становится дорогим, O (n).

std::string append и operator+=, с другой стороны, увеличивайте емкость на 50% каждый раз, когда строка должна расти. Это значительно сокращает количество распределений памяти и операций копирования O (log n).

Ответ 9

Для маленьких строк это не имеет значения. Если у вас есть большие строки, вам лучше их хранить, поскольку они находятся в векторе или в какой-то другой коллекции в качестве частей. И добавьте свой алгоритм для работы с таким набором данных, а не с одной большой строкой.

Я предпочитаю std:: ostringstream для сложной конкатенации.

Ответ 10

Как и в большинстве случаев, легче делать что-то, чем делать.

Если вы хотите выводить большие строки в графический интерфейс, может быть, что все, что вы выводите, может обрабатывать строки по частям лучше, чем как большая строка (например, конкатенация текста в текстовом редакторе - обычно они сохраняются линии как отдельные структуры).

Если вы хотите вывести файл, потоки данных, а не создание большой строки и вывод этого.

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

Ответ 11

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

Фокус в том, чтобы сделать только одно большое выделение при запуске.

в

https://github.com/pedro-vicente/table-string

Бенчмарки

Для Visual Studio 2015, сборка отладки x86, улучшение подсистемы над С++ std::string.

| API                   | Seconds           
| ----------------------|----| 
| SDS                   | 19 |  
| std::string           | 11 |  
| std::string (reserve) | 9  |  
| table_str_t           | 1  |  

Ответ 12

Вероятно, лучшая производительность, если предварительно выделить (зарезервировать) пространство в результирующей строке.

template<typename... Args>
std::string concat(Args const&... args)
{
    size_t len = 0;
    for (auto s : {args...})  len += strlen(s);

    std::string result;
    result.reserve(len);    // <--- preallocate result
    for (auto s : {args...})  result += s;
    return result;
}

Использование:

std::string merged = concat("This ", "is ", "a ", "test!");