О реализации шагов std :: string
Я изучал производительность перемещения std::string
. В течение самого долгого времени я рассматривал струнные движения как почти свободные, полагая, что компилятор будет встроить все, и он будет включать только несколько дешевых заданий.
Фактически, моя ментальная модель для перемещения в буквальном смысле
string& operator=(string&& rhs) noexcept
{
swap(*this, rhs);
return *this;
}
friend void swap(string& x, string& y) noexcept
{
// for disposition only
unsigned char buf[sizeof(string)];
memcpy(buf, &x, sizeof(string));
memcpy(&x, &y, sizeof(string));
memcpy(&y, buf, sizeof(string));
}
Насколько я понимаю, это законная реализация, если memcpy
изменен для назначения отдельных полей.
К моему большому удивлению, найти gcc-реализацию перемещения предполагает создание новой строки и, возможно, выброс из-за выделения, несмотря на то, что она не является noexcept
.
Это даже соответствует? Не менее важно, не стоит ли думать, что движение почти бесплатное?
Смутно, std::vector<char>
сводится к тому, что я ожидаю.
реализация clang сильно отличается, хотя есть подозрительный std::string::reserve
Ответы
Ответ 1
Я только проанализировал версию GCC. Вот что происходит: код обрабатывает разные типы распределителей. Если у распределителя есть черта _S_propagate_on_move_assign
или _S_always_equal
, то движение будет почти бесплатным, как вы ожидаете. Это operator=
if
в move operator=
:
if (!__str._M_is_local()
&& (_Alloc_traits::_S_propagate_on_move_assign()
|| _Alloc_traits::_S_always_equal()))
// cheap move
else assign(__str);
Если условие истинно (_M_is_local()
означает небольшую строку, описание здесь), то движение будет дешевым.
Если он неверен, он вызывает нормальное assign
(а не движущееся). Это тот случай, когда:
- строка маленькая, поэтому
assign
сделает простой memcpy (дешевый) - или распределитель не имеет признака, всегда равного или не распространяющего-на-move-assign, поэтому присваивание будет выделять (не дешево)
Что это значит?
Это означает, что если вы используете распределитель по умолчанию (или любой распределитель с указанными ранее признаками), то движение по-прежнему почти бесплатное.
С другой стороны, сгенерированный код излишне огромен и может быть улучшен, я думаю. Он должен иметь отдельный код для обработки обычных распределителей или иметь лучший код assign
(проблема в том, что assign
не проверяет наличие _M_is_local()
, но оно проверяет пропускную способность, поэтому компилятор не может решить, требуется ли распределение или нет, поэтому он без необходимости добавляет кодовую ячейку выделения в исполняемый файл - вы можете проверить точные данные в исходном коде).
Ответ 2
Не совсем ответ, но это новая реализация С++ 11 std::string
без счетчика ссылок и с небольшой оптимизацией строки, что приводит к объемистому узлу. В частности, небольшая оптимизация строк приводит к тому, что 4 ветки обрабатывают 4 разных сочетания длин источника и цель назначения перемещения.
Когда добавлена опция -D_GLIBCXX_USE_CXX11_ABI=0
чтобы использовать pre С++ - 11 std::string
с опорным счетчиком и небольшую оптимизацию строк, код сборки выглядит намного лучше.
не следует ли думать, что движение почти бесплатное?
В " Ничто лучше, чем копировать или перемещать" Роджером Орром, слайды на стр. 47 говорится:
Сравнение копирования и перемещения
- Многие люди неправильно думают о том, чтобы двигаться как эффективно "свободные",
- Разница в производительности между копией и перемещением варьируется в широких пределах
- Для примитивного типа, такого как int, копирование или перемещение, фактически идентичны
- Перемещение происходит быстрее, чем копирование, когда требуется передать часть объекта только для передачи всего значения