#c #c #string #memory-management #standard-library
#c #строка #управление памятью #stl
Вопрос:
У меня ситуация, когда мне нужно обработать большие (много ГБ) объемы данных как таковые:
- создайте большую строку, добавив множество строк меньшего размера (C char *)
- обрезать строку
- преобразуйте строку в C const std::string для обработки (только для чтения)
- повторить
Данные на каждой итерации независимы.
Мой вопрос в том, что я хотел бы минимизировать (если возможно, исключить) использование выделяемой кучи памяти, поскольку на данный момент это моя самая большая проблема с производительностью.
Есть ли способ преобразовать строку C (char *) в строку stl C (std::string), не требуя, чтобы std::string внутренне выделял / копировал данные?
В качестве альтернативы, могу ли я использовать stringstreams или что-то подобное для повторного использования большого буфера?
Редактировать: Спасибо за ответы, для ясности, я думаю, что пересмотренный вопрос был бы:
Как я могу эффективно создать (с помощью нескольких добавлений) строку stl C . И если выполнять это действие в цикле, где каждый цикл полностью независим, как я могу повторно использовать это выделенное пространство?
Ответ №1:
На самом деле вы не можете сформировать std::string без копирования данных. Stringstream, вероятно, повторно использовал бы память от передачи к передаче (хотя я думаю, что стандарт умалчивает о том, действительно ли это необходимо), но это все равно не позволило бы избежать копирования.
Общий подход к такого рода проблемам заключается в написании кода, который обрабатывает данные на шаге 3, для использования пары итераторов begin / end; затем он может легко обработать либо std::string, вектор символов, пару необработанных указателей и т.д. В отличие от передачи ему типа контейнера, такого как std::string, он больше не будет знать или заботиться о том, как была выделена память, поскольку она по-прежнему будет принадлежать вызывающей стороне. Доведение этой идеи до логического завершения — это boost::range , который добавляет все перегруженные конструкторы, чтобы по-прежнему позволять вызывающей стороне просто передавать строку / вектор / список / любой контейнер с помощью .begin() и .end() или отдельных итераторов.
Написав свой код обработки для работы с произвольным диапазоном итераторов, вы могли бы затем даже написать пользовательский итератор (не так сложно, как кажется, в основном просто объект с некоторыми стандартными typedefs и operator /*/=/==/!= перегружен, чтобы получить итератор только для пересылки), который заботится о переходе к следующему фрагменту каждый раз, когда он достигает конца того, над которым он работает, пропуская пробелы (я предполагаю, что это то, что вы имели в виду под trim). Что вам вообще никогда не приходилось собирать всю строку последовательно. Будет ли это выигрышем или нет, зависит от того, сколько фрагментов / насколько больших фрагментов у вас есть. По сути, это то, чем является SGI rope, упомянутый Мартином Йорком: строка, в которой append формирует связанный список фрагментов вместо непрерывного буфера, который, таким образом, подходит для гораздо более длинных значений.
ОБНОВИТЬ (поскольку я все еще вижу случайные положительные отзывы по этому ответу):
C 17 вводит другой вариант: std::string_view, который заменил std::string во многих сигнатурах функций, является ссылкой на символьные данные, не являющейся владельцем. Он неявно конвертируется из std::string, но также может быть явно сконструирован из смежных данных, принадлежащих где-то еще, избегая ненужного копирования, налагаемого std::string.
Комментарии:
1. Я думаю, что ваше решение — лучший подход (изменение кода обработки), к сожалению, в этой ситуации это не вариант.
2. Существует ли стандартный способ добиться повторного использования буфера? Я просто не хочу полагаться на реализацию на конкретной платформе.
3. Если только этот обрабатывающий код не является библиотечной функцией, которая не использует итераторы или строки, просто обычный старый размер
char*
.
Ответ №2:
Возможно ли вообще использовать строку C на шаге 1? Если вы используете string::reserve(size_t)
, вы можете выделить достаточно большой буфер, чтобы предотвратить многократное выделение кучи при добавлении строк меньшего размера, а затем вы можете просто использовать ту же самую строку C на протяжении всех оставшихся шагов.
Смотрите эту ссылку для получения дополнительной информации о reserve
функции.
Комментарии:
1. Ваше решение похоже на то, что мне нужно, хотя, когда вы говорите «вы можете просто использовать ту же самую строку C «, вы имеете в виду использование clear (), а затем продолжение построения следующей строки?
2. На данный момент это решение для меня самое простое, хотя я не уверен, действительно ли повторное использование указано стандартом (хотя, похоже, оно работает в моей реализации)
3. Использование clear() должно сработать. Насколько я знаю, clear() не освобождает память, используемую строкой, и поэтому не влияет на пространство, выделенное reserve().
4. @Akusete @e. Джеймс: (Да, я знаю, что прошло 2,5 года) Как я прочитал соответствующие части стандарта, clear() определяется в терминах erase(), а erase() конкретно не определяет, является ли блок памяти, используемый после вызова erase(), таким же, как тот, который использовался перед вызовом erase(), и не определяет, что capacity() остается неизменным. Смотрите, например, 21.3.3 и 21.3.5.5. Разумные реализации, безусловно, могут функционировать таким образом, но обязательно рассматривайте это только как полезную оптимизацию — не обязательное условие для правильного функционирования программы.
5. @Николас Найт: Справедливое замечание. Как всегда, стандартная процедура заключается в выполнении оптимизаций только после проведения измерений и убеждения, что это необходимо. Интересно, знал ли я вообще об этом в ’08?
:)
Ответ №3:
Чтобы помочь с действительно большими строками, SGI имеет класс Rope в своем STL.
Нестандартный, но может быть полезным.
http://www.sgi.com/tech/stl/Rope.html
По-видимому, rope будет в следующей версии стандарта 🙂
Обратите внимание на шутку разработчика. Веревка — это большая строка. (Ha Ha) 🙂
Ответ №4:
Это нестандартный ответ, не затрагивающий вопрос напрямую, но «обдумывающий» его. Может быть полезно, может и нет…
Обработка std::string только для чтения на самом деле не требует очень сложного подмножества функций std::string. Есть ли возможность, что вы могли бы выполнить поиск / замену в коде, который выполняет всю обработку std:: strings, чтобы вместо этого он принимал какой-то другой тип? Начните с пустого класса:
класс lightweight_string { };
Затем замените все ссылки std::string на lightweight_string . Выполните компиляцию, чтобы точно выяснить, какие операции необходимы для lightweight_string, чтобы он действовал как промежуточная замена. Затем вы можете заставить свою реализацию работать так, как вы хотите.
Ответ №5:
Достаточно ли независима каждая итерация, чтобы вы могли использовать один и тот же std::string для каждой итерации? Хотелось бы надеяться, что ваша реализация std::string достаточно умна, чтобы повторно использовать память, если вы присвоите ей const char *, когда она ранее использовалась для чего-то другого.
Назначение символа * в std::string всегда должно, по крайней мере, копировать данные. Управление памятью является одной из основных причин использования std::string, поэтому вы не сможете переопределить его.
Ответ №6:
В этом случае, может быть, было бы лучше обработать char * напрямую, вместо того, чтобы присваивать его std::string .
Комментарии:
1. Да, это было бы, хотя входные данные (C char *) и выходные данные (std:: string) не находятся под моим контролем.