Страницы

Поиск по вопросам

вторник, 27 ноября 2018 г.

C++11: Expression Templates VS RValue References

Доброго времени суток! В процессе размышлений над новыми возможностями привнесенными в стандарт языка С++, в частности rvalue-ссылками у меня возник вот какой вопрос. Насколько я понял, rvalue-ссылки позволяют оптимизировать возвращаемое значение, т.е. вместо копирования памяти происходит перемещение объекта в памяти (из одной области в другую). До нового стандарта, насколько мне известно, данная проблема решалась через expression templates, означает ли что применяя rvalue-ссылки можно забыть об expression templates? Или я что-то неверно понимаю? UPDATE: Уточню вопрос. Предположим у нас есть класс вида: namespace math { template class vector { public: vector() : mptr(0), msize(0) { } vector(size_t n) : msize(n) { mptr = new T[n]; } vector(const vector& other) : msize(other.msize) { if (mptr) delete[] m_ptr; if (msize == 0) { mptr = 0; } else { mptr = new T[msize]; std::copy(other.mptr, other.mptr + msize, m_ptr); } } ~vector() { if (mptr) delete[] mptr; }
vector& operator=(const vector& other) { msize = other.msize; if (mptr) delete[] m_ptr; if (msize == 0) { mptr = 0; } else { mptr = new T[msize]; std::copy(other.mptr, other.mptr + msize, m_ptr); } } vector& operator+=(const vector& x) { assert(x.size() == msize) std::transform(x.m_ptr, x.mptr+msize, mptr, std::plus()); } vector& operator-=(const vector& x) { assert(x.size() == msize) std::transform(x.m_ptr, x.mptr+msize, mptr, std::plus()); } // ... и так далее private: T* mptr; size_t msize; }; } // end namespace math Теперь предположим мы хотим перегрузить бинарные операторы (+,-, и т.д.) : template vector operator+ (const vector& x, const vector& y) { vector res(x); // временная переменная - копия x res += y; // складываем x+y return res; // возвращаем результат } Теперь мы воспользуемся нашим классом: math::vector a(1 << 18); math::vector b(1 << 18); math::vector c(1 << 18); math::vector d(1 << 18);
math::vector r = a * b - c + d; При выполнении этого кода будет создано как минимум 3 временных массива размера 2^18, в каждом операторе, которых можно избежать. Собственно это будет приводить к снижению производительности. Раньше это обходилось с помощью expression templates, которые разворачивали выражение a * b - c + d в один цикл следующим образом: math::vector r(n); for (size_t i = 0; i < n; i++) r[i] = a[i]* b[i] - c[i] + d[i]; В новом С++11 мы добавим к нашему классу конструктор перемещения и оператор перемещения (если я их правильно назвал), и объявление нашего класса поменяется следующим образом: namespace math { template class vector { public: vector(); vector(size_t n); vector(const vector&); // Перемещающий кострктор vector(const vector&& other); vector& operator=(const vector&); // Перемещающий оператор присваивания vector& operator=(const vector&&); // ... }; } Значит ли это, что теперь мы избавлены от копий и можем забыть про expression templates? Не об этом ли речь в http://www.artima.com/cppsource/rvalue.html в разделе 'Eliminating spurious copies'? P.S. Код писал без проверки - только чтобы показать идеи, так что за ошибки не пинайте :) Заранее спасибо за ответы!


Ответ

Хм. Насколько я понимаю, смысл rvalue-ссылок в том, что перемещения данных не происходит. То есть, данные, которыми пользовался объект, можно «присвоить» себе без копирования, т. к. объект всё равно собирается исчезнуть. Статья по теме rvalue references: http://www.artima.com/cppsource/rvalue.html (Бьярн среди авторов). Обновление: Я не встречался с expression template'ами раньше. Если они и правда делают то, о чём вы говорите, то rvalue references слабее, и не являются адекватной заменой. Смотрите. Если у вас есть код math::vector r = a * b - c * d + e; (я специально сделал умножение из сложения) и нужные move-конструкторы, что произойдёт? Этот код будет проинтерпретирован как math::vector&& tmp1 = a * b; math::vector&& tmp2 = c * d; math::vector&& tmp3 = tmp1 - tmp2; math::vector r = tmp3 + e; Будет вызван дважды operator * (const math::vector& l, const math::vector& r), который обязан выделять новую память, т. к. не может отобрать её у операндов. Затем будет вызван operator - (const math::vector& l, const math::vector& r), который так же не сможет отобрать память у операндов. Далее, будет вызван operator + (const math::vector& l, const math::vector& r) с той же проблемой. Финальное присваивание обычно убирается оптимизатором (RVO). Move-конструкторы вовсе не вступают в игру, за исключением может быть того, что они подменяют RVO. Что могла бы здесь улучшить move-семантика? С умножениями ничего улучшить нельзя: им по факту неоткуда брать память, придётся выделять новую всё равно. С вычитанием немного лучше: если вы определите перегрузку operator -, принимающую rvalue ref как первый аргумент, то вы сможете отобрать у него память. Для симметрии вам нужно будет сделать ещё одну перегрузку, принимающую rvalue ref как второй аргумент. И судя по всему ещё одну с двумя rvalue ref-аргументами, иначе компилятор не сможет выбрать между предыдущими двумя, если оба аргумента — rvalue ref. То же касается и сложения. Обратите внимание, что вам нужен достаточно серьёзный дубляж кода: все операторы должны быть объявлены по 3, а то и 4 раза. Таким образом, мы имеем два выделения памяти и два отбора памяти у rvalue-объектов. Это лучше, чем без move-семантики, но expression templates, как вы их описали, ещё лучше: там лишнее выделение памяти вовсе не требуется. Вывод: нет, rvalue reference'ы всё ещё не панацея.

Комментариев нет:

Отправить комментарий