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


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

11 87

11 ответов:

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

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

эффективность класса STL string зависит от реализации 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 + оператор создаст новый объект string и вернет его, следовательно, используя новый буфер.

рекомендации по реализации:

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

структура данных веревки:

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

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

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

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

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

a + " : " + c

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

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

теперь, в дополнение, tmp1 то, что было возвращено по первому звонку к оператору+ с аргументами. Мы предполагаем, что компилятор действительно умен и оптимизирует копию возвращаемого значения. Таким образом, мы получаем одну новую строку, содержащую конкатенацию 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 будут поддерживать c++1x переместить семантика (дополняя скопировать семантику) и rvalue ссылки в качестве экспериментального дополнения. Это позволяет выяснить, ссылается ли параметр на временный или нет. Это сделает такие дополнения удивительно быстрыми, так как все вышеперечисленное закончится в одном "add-pipeline" без копии.

если это окажется узким местом, вы можете сделать

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

at

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

критерии

для Visual Studio 2015, x86 debug build, существенное улучшение по сравнению с C++ std::string.

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