Ответ 1
Мне было интересно, могут ли лямбды вместе с семантикой перемещения или любой другой новой функцией сделать так же хорошо, как и ET. Любые мысли?
Быстрый ответ
Перемещение семантики не является полной панацеей самостоятельно - в С++ 11 все еще нужны технологии, такие как шаблоны выражений (ET), чтобы устранить накладные расходы, такие как перемещение данных! Итак, чтобы быстро ответить на ваш вопрос перед погружением в остальную часть моего ответа, переместить семантику и т.д., Не полностью заменяет ET, как показывает мой ответ ниже.
Подробный ответ
ET обычно возвращают прокси-объекты для отсрочки оценки до более позднего времени, поэтому непосредственное очевидное преимущество языковых возможностей языка С++ 11 до тех пор, пока код, инициирующий вычисление, не станет очевидным. Тем не менее, не хотелось бы писать ET-код, однако, что запускает генерации кода во время создания дерева выражений с помощью прокси. Симпатично, семантика перемещения С++ 11 и совершенная пересылка могут помочь избежать таких накладных расходов, если это произойдет иначе. (Такое было бы невозможно в С++ 03.)
По сути, при написании ETs вы хотите использовать языковые функции для генерации оптимального кода, когда вызываются функции-члены задействованных объектов-прокси. В С++ 11 это будет включать в себя использование совершенной пересылки, перемещение семантики по копированию и т.д., Если такое на самом деле все еще необходимо сверх того, что уже может сделать компилятор. Название игры состоит в том, чтобы свести к минимуму созданный код времени выполнения и/или максимизировать скорость выполнения и/или свести к минимуму накладные расходы времени выполнения.
Я действительно хотел попробовать некоторые ET с функциями С++ 11, чтобы увидеть, могу ли я удалить ВСЕ промежуточные временные типы экземпляров с выражением a = b + c + d;
. (Поскольку это был просто забавный перерыв в моей обычной деятельности, поэтому я не сравнивал его и не писал E-код исключительно с использованием С++ 03. Также я не беспокоился обо всех аспектах полировки кода, которые появляются ниже.)
Для начала я не использовал lambdas - я предпочитал использовать явные типы и функции, поэтому я не буду спорить о/против лямбда в отношении вашего вопроса. Я предполагаю, что они будут похожи на использование функторов и не будут работать лучше, чем не-ET-код ниже (т.е. Потребуются перемещения) - по крайней мере до тех пор, пока компиляторы не смогут автоматически оптимизировать lambdas, используя их собственные внутренние ET для такого. Однако код, который я написал, использует семантику перемещения и совершенную пересылку. Вот что я сделал, начиная с результатов и затем, наконец, представляя код.
Я создал класс math_vector<N>
, где N==3
и он определяет внутренний частный экземпляр std::array<long double, N>
. Члены представляют собой конструктор по умолчанию, копируют и перемещают конструкторы и назначения, конструктор списка инициализаторов, деструктор, элемент swap(), оператор [] для доступа к элементам вектора и оператора + =. Используется без шаблонов выражений, этот код:
{
cout << "CASE 1:\n";
math_vector<3> a{1.0, 1.1, 1.2};
math_vector<3> b{2.0, 2.1, 2.2};
math_vector<3> c{3.0, 3.1, 3.2};
math_vector<3> d{4.0, 4.1, 4.2};
math_vector<3> result = a + b + c + d;
cout << '[' << &result << "]: " << result << "\n";
}
(при компиляции с clang++
3.1 или g++
4.8 с - std=c++11 -O3
):
CASE 1:
0x7fff8d6edf50: math_vector(initlist)
0x7fff8d6edef0: math_vector(initlist)
0x7fff8d6ede90: math_vector(initlist)
0x7fff8d6ede30: math_vector(initlist)
0x7fff8d6edd70: math_vector(copy: 0x7fff8d6edf50)
0x7fff8d6edda0: math_vector(move: 0x7fff8d6edd70)
0x7fff8d6eddd0: math_vector(move: 0x7fff8d6edda0)
0x7fff8d6edda0: ~math_vector()
0x7fff8d6edd70: ~math_vector()
[0x7fff8d6eddd0]: (10,10.4,10.8)
0x7fff8d6eddd0: ~math_vector()
0x7fff8d6ede30: ~math_vector()
0x7fff8d6ede90: ~math_vector()
0x7fff8d6edef0: ~math_vector()
0x7fff8d6edf50: ~math_vector()
т.е. четыре явно построенных экземпляра с использованием списков инициализаторов (т.е. элементов initlist
), переменной result
(т.е. 0x7fff8d6eddd0
) и также позволяет копировать и перемещать еще три объекта.
Чтобы сосредоточиться только на временных и перемещениях, я создал второй случай, который создает только result
как именованную переменную - все остальные значения r:
{
cout << "CASE 2:\n";
math_vector<3> result =
math_vector<3>{1.0, 1.1, 1.2} +
math_vector<3>{2.0, 2.1, 2.2} +
math_vector<3>{3.0, 3.1, 3.2} +
math_vector<3>{4.0, 4.1, 4.2}
;
cout << '[' << &result << "]: " << result << "\n";
}
который выводит это (снова, когда ET не используются):
CASE 2:
0x7fff8d6edcb0: math_vector(initlist)
0x7fff8d6edc50: math_vector(initlist)
0x7fff8d6edce0: math_vector(move: 0x7fff8d6edcb0)
0x7fff8d6edbf0: math_vector(initlist)
0x7fff8d6edd10: math_vector(move: 0x7fff8d6edce0)
0x7fff8d6edb90: math_vector(initlist)
0x7fff8d6edd40: math_vector(move: 0x7fff8d6edd10)
0x7fff8d6edb90: ~math_vector()
0x7fff8d6edd10: ~math_vector()
0x7fff8d6edbf0: ~math_vector()
0x7fff8d6edce0: ~math_vector()
0x7fff8d6edc50: ~math_vector()
0x7fff8d6edcb0: ~math_vector()
[0x7fff8d6edd40]: (10,10.4,10.8)
0x7fff8d6edd40: ~math_vector()
что лучше: создаются только дополнительные объекты перемещения.
Но я хотел лучше: мне нужны нулевые дополнительные временные файлы и иметь код, как если бы я жестко закодировал его с одним стандартным предостережением: все явно созданные экземпляры все равно будут созданы (т.е. четыре конструктора initlist
и result
). Для этого я добавил код шаблона выражения следующим образом:
- был создан класс proxy
math_vector_expr<LeftExpr,BinaryOp,RightExpr>
, чтобы еще не вычисленное выражение, - был создан класс proxy
plus_op
для хранения операции добавления, - конструктор был добавлен в
math_vector
для принятия объектаmath_vector_expr
и - Функции-члены "стартера" были добавлены, чтобы инициировать создание шаблона выражения.
Результаты с использованием ЭП прекрасны: никаких лишних времен в любом случае! Вышеуказанные предыдущие два случая:
CASE 1:
0x7fffe7180c60: math_vector(initlist)
0x7fffe7180c90: math_vector(initlist)
0x7fffe7180cc0: math_vector(initlist)
0x7fffe7180cf0: math_vector(initlist)
0x7fffe7180d20: math_vector(expr: 0x7fffe7180d90)
[0x7fffe7180d20]: (10,10.4,10.8)
0x7fffe7180d20: ~math_vector()
0x7fffe7180cf0: ~math_vector()
0x7fffe7180cc0: ~math_vector()
0x7fffe7180c90: ~math_vector()
0x7fffe7180c60: ~math_vector()
CASE 2:
0x7fffe7180dd0: math_vector(initlist)
0x7fffe7180e20: math_vector(initlist)
0x7fffe7180e70: math_vector(initlist)
0x7fffe7180eb0: math_vector(initlist)
0x7fffe7180d20: math_vector(expr: 0x7fffe7180dc0)
0x7fffe7180eb0: ~math_vector()
0x7fffe7180e70: ~math_vector()
0x7fffe7180e20: ~math_vector()
0x7fffe7180dd0: ~math_vector()
[0x7fffe7180d20]: (10,10.4,10.8)
0x7fffe7180d20: ~math_vector()
i.e, ровно 5 вызовов конструктора и 5 вызовов деструктора в каждом случае. Фактически, если вы попросите компилятор сгенерировать код ассемблера между вызовами конструктора 4 initlist
и выводом result
, получится эта красивая строка кода ассемблера:
fldt 128(%rsp)
leaq 128(%rsp), %rdi
leaq 80(%rsp), %rbp
fldt 176(%rsp)
faddp %st, %st(1)
fldt 224(%rsp)
faddp %st, %st(1)
fldt 272(%rsp)
faddp %st, %st(1)
fstpt 80(%rsp)
fldt 144(%rsp)
fldt 192(%rsp)
faddp %st, %st(1)
fldt 240(%rsp)
faddp %st, %st(1)
fldt 288(%rsp)
faddp %st, %st(1)
fstpt 96(%rsp)
fldt 160(%rsp)
fldt 208(%rsp)
faddp %st, %st(1)
fldt 256(%rsp)
faddp %st, %st(1)
fldt 304(%rsp)
faddp %st, %st(1)
fstpt 112(%rsp)
с g++
и clang++
выводит аналогичный (даже меньший) код. Нет вызовов функций и т.д. - просто куча добавлений, которая ТОЧНО, что нужно!
Для этого следует код С++ 11. Просто #define DONT_USE_EXPR_TEMPL
не использовать ET или вообще не определять его для использования ET.
#include <array>
#include <algorithm>
#include <initializer_list>
#include <type_traits>
#include <iostream>
//#define DONT_USE_EXPR_TEMPL
//===========================================================================
template <std::size_t N> class math_vector;
template <
typename LeftExpr,
typename BinaryOp,
typename RightExpr
>
class math_vector_expr
{
public:
math_vector_expr() = delete;
math_vector_expr(LeftExpr l, RightExpr r) :
l_(std::forward<LeftExpr>(l)),
r_(std::forward<RightExpr>(r))
{
}
// Prohibit copying...
math_vector_expr(math_vector_expr const&) = delete;
math_vector_expr& operator =(math_vector_expr const&) = delete;
// Allow moves...
math_vector_expr(math_vector_expr&&) = default;
math_vector_expr& operator =(math_vector_expr&&) = default;
template <typename RE>
auto operator +(RE&& re) const ->
math_vector_expr<
math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&,
BinaryOp,
decltype(std::forward<RE>(re))
>
{
return
math_vector_expr<
math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&,
BinaryOp,
decltype(std::forward<RE>(re))
>(*this, std::forward<RE>(re))
;
}
auto le() ->
typename std::add_lvalue_reference<LeftExpr>::type
{ return l_; }
auto le() const ->
typename std::add_lvalue_reference<
typename std::add_const<LeftExpr>::type
>::type
{ return l_; }
auto re() ->
typename std::add_lvalue_reference<RightExpr>::type
{ return r_; }
auto re() const ->
typename std::add_lvalue_reference<
typename std::add_const<RightExpr>::type
>::type
{ return r_; }
auto operator [](std::size_t index) const ->
decltype(
BinaryOp::apply(this->le()[index], this->re()[index])
)
{
return BinaryOp::apply(le()[index], re()[index]);
}
private:
LeftExpr l_;
RightExpr r_;
};
//===========================================================================
template <typename T>
struct plus_op
{
static T apply(T const& a, T const& b)
{
return a + b;
}
static T apply(T&& a, T const& b)
{
a += b;
return std::move(a);
}
static T apply(T const& a, T&& b)
{
b += a;
return std::move(b);
}
static T apply(T&& a, T&& b)
{
a += b;
return std::move(a);
}
};
//===========================================================================
template <std::size_t N>
class math_vector
{
using impl_type = std::array<long double, N>;
public:
math_vector()
{
using namespace std;
fill(begin(v_), end(v_), impl_type{});
std::cout << this << ": math_vector()" << endl;
}
math_vector(math_vector const& mv) noexcept
{
using namespace std;
copy(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector(copy: " << &mv << ")" << endl;
}
math_vector(math_vector&& mv) noexcept
{
using namespace std;
move(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector(move: " << &mv << ")" << endl;
}
math_vector(std::initializer_list<typename impl_type::value_type> l)
{
using namespace std;
copy(begin(l), end(l), begin(v_));
std::cout << this << ": math_vector(initlist)" << endl;
}
math_vector& operator =(math_vector const& mv) noexcept
{
using namespace std;
copy(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector op =(copy: " << &mv << ")" << endl;
return *this;
}
math_vector& operator =(math_vector&& mv) noexcept
{
using namespace std;
move(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector op =(move: " << &mv << ")" << endl;
return *this;
}
~math_vector()
{
using namespace std;
std::cout << this << ": ~math_vector()" << endl;
}
void swap(math_vector& mv)
{
using namespace std;
for (std::size_t i = 0; i<N; ++i)
swap(v_[i], mv[i]);
}
auto operator [](std::size_t index) const
-> typename impl_type::value_type const&
{
return v_[index];
}
auto operator [](std::size_t index)
-> typename impl_type::value_type&
{
return v_[index];
}
math_vector& operator +=(math_vector const& b)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] += b[i];
return *this;
}
#ifndef DONT_USE_EXPR_TEMPL
template <typename LE, typename Op, typename RE>
math_vector(math_vector_expr<LE,Op,RE>&& mve)
{
for (std::size_t i = 0; i < N; ++i)
v_[i] = mve[i];
std::cout << this << ": math_vector(expr: " << &mve << ")" << std::endl;
}
template <typename RightExpr>
math_vector& operator =(RightExpr&& re)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] = re[i];
return *this;
}
template <typename RightExpr>
math_vector& operator +=(RightExpr&& re)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] += re[i];
return *this;
}
template <typename RightExpr>
auto operator +(RightExpr&& re) const ->
math_vector_expr<
math_vector const&,
plus_op<typename impl_type::value_type>,
decltype(std::forward<RightExpr>(re))
>
{
return
math_vector_expr<
math_vector const&,
plus_op<typename impl_type::value_type>,
decltype(std::forward<RightExpr>(re))
>(
*this,
std::forward<RightExpr>(re)
)
;
}
#endif // #ifndef DONT_USE_EXPR_TEMPL
private:
impl_type v_;
};
//===========================================================================
template <std::size_t N>
inline void swap(math_vector<N>& a, math_vector<N>& b)
{
a.swap(b);
}
//===========================================================================
#ifdef DONT_USE_EXPR_TEMPL
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N> const& a,
math_vector<N> const& b
)
{
math_vector<N> retval(a);
retval += b;
return retval;
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N>&& a,
math_vector<N> const& b
)
{
a += b;
return std::move(a);
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N> const& a,
math_vector<N>&& b
)
{
b += a;
return std::move(b);
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N>&& a,
math_vector<N>&& b
)
{
a += std::move(b);
return std::move(a);
}
#endif // #ifdef DONT_USE_EXPR_TEMPL
//===========================================================================
template <std::size_t N>
std::ostream& operator <<(std::ostream& os, math_vector<N> const& mv)
{
os << '(';
for (std::size_t i = 0; i < N; ++i)
os << mv[i] << ((i+1 != N) ? ',' : ')');
return os;
}
//===========================================================================
int main()
{
using namespace std;
try
{
{
cout << "CASE 1:\n";
math_vector<3> a{1.0, 1.1, 1.2};
math_vector<3> b{2.0, 2.1, 2.2};
math_vector<3> c{3.0, 3.1, 3.2};
math_vector<3> d{4.0, 4.1, 4.2};
math_vector<3> result = a + b + c + d;
cout << '[' << &result << "]: " << result << "\n";
}
cout << endl;
{
cout << "CASE 2:\n";
math_vector<3> result =
math_vector<3>{1.0, 1.1, 1.2} +
math_vector<3>{2.0, 2.1, 2.2} +
math_vector<3>{3.0, 3.1, 3.2} +
math_vector<3>{4.0, 4.1, 4.2}
;
cout << '[' << &result << "]: " << result << "\n";
}
}
catch (...)
{
return 1;
}
}
//===========================================================================