C++数组运算符开销

C++ array operator overhead

本文关键字:开销 运算符 数组 C++      更新时间:2023-10-16

我记得以前读过一些代码,这些代码允许编译器做一些工作并简化像下面这样的表达式:

// edit: yes the parameters where meant to be passed by reference
//       and maintain constness sorry !
template< typename T >
std::vector<T> operator+( const std::vector<T>& a, const std::vector<T>& b )
{
    assert( a.size() == b.size() );
    std::vector<T> o; o.reserve( a.size() );
    for( std::vector<T>::size_type i = 0; i < a.size(); ++i )
        o[i] = a[i] + b[i];
    return o;
}
// same for operator* but a[i] * b[i] instead
std::vector<double> a, b, c, d, e;
// do some initialization of the vectors
e = a * b + c * d

在通常情况下,会为每个运算符创建并分配一个新的矢量,而编译器只会创建一个副本并对其执行所有操作

这是什么技术?

正如@Agnew很早就提到的,您所描述的技术是表达式模板

这通常是用向量1的数学概念来完成的,而不是std::vector。大致的笔画是:

  1. 不需要对向量进行数学运算即可返回结果。相反,让它们返回一个代表最终需要执行的操作的代理对象。a * b可以返回一个"乘法代理"对象,该对象只包含对应该相乘的两个向量的常量引用。

  2. 也为这些代理编写数学运算,允许它们链接在一起,因此a * b + c * d变成(TempMulProxy) + (TempMulProxy)变成(TempAddProxy),所有这些都不需要进行任何数学运算或复制任何向量。

  3. 编写一个赋值运算符,将代理对象作为右侧对象。这个操作符可以看到整个表达式a * b + c * d,并在知道目的地的情况下对向量有效地执行该操作。全部不创建多个临时矢量对象。

1或矩阵或四元数等…*

这里没有问题。然而,我的水晶球告诉我,你想知道你想出的两种方法中更好的方法,以便对a * b + c * d这样的向量执行分量算术运算,其中abcd是大小相同的向量(std::vector<T>):

  1. 对于要执行的每个操作,在元素上循环,执行计算并返回结果向量。把这些运算放在一个向量公式中。

  2. 对于输入向量中的每个元素,计算整个表达式,并将其写入一个最终结果向量中。

有两件事需要考虑:

  • 性能:这里,第二个选项在前面,因为处理器不会分配不必要的临时向量
  • 可重用性:很明显,实现向量的算法操作并通过简单地在向量上表达目标公式来重用它们是很好的

然而,有一个很好的选项来实现第二个选项,它看起来非常漂亮:

std::vector<int> a, b, c, d, e;
// fill a, b, c, d with data
auto expression = [](int a, int b, int c, int d){ return a * b + c * d; };
assert (a.size() == b.size() && b.size() == c.size() && c.size() == d.size());
e.reserve(a.size());
for(auto _a = a.begin(), _b = b.begin(), _c = c.begin(), _d = d.begin(), _e = e.begin();
    _a != a.end();
    ++_a, ++_b, ++_c, ++_d, ++_e)
{
    *_e = expression(*_a, *_b, *_c, *_d);
}

通过这种方式,您可以将表达式从逻辑中分离出来进行评估:

void componentWise4(std::function<int(int,int,int,int)> f,
                    const std::vector<int> & a,
                    const std::vector<int> & b,
                    const std::vector<int> & c,
                    const std::vector<int> & d,
                    std::vector<int> & result)
{
    assert (a.size() == b.size() && b.size() == c.size() && c.size() == d.size());
    result.reserve(a.size());
    for(auto _a = a.begin(), _b = b.begin(), _c = c.begin(), _d = d.begin(), _result = result.begin();
        _a != a.end();
        ++_a, ++_b, ++_c, ++_d, ++_result)
    {
        *_result = expression(*_a, *_b, *_c, *_d);
    }
}

这就是所谓的

std::vector<int> a, b, c, d, e;
// fill a, b, c, d with data
componentWise4([](int a, int b, int c, int d){ return a * b + c * d; },
               a, b, c, d, e);

我相信这个"表达式计算器"可以使用C++11的新功能"可变模板"进行扩展,以支持表达式中任意数量的参数,甚至支持不同的类型。我无法让它工作(可变模板的事情),你可以尝试在这里完成我的尝试:http://ideone.com/w88kuG(我是变元模板的新手,所以我不知道语法)。

您想要的是"C++编程语言"Bjarne Stroustrup的第三版,22.4.7临时、复制和循环[num.matrix]。这本书总是个好主意。

如果你没有,基本上我们有两个选择:

首先:我们编写了一组函数,用于直接计算一些最期望的组合(例如mul_add_and_assign(&U,&M,&V,&W)来计算U=M*V+W),并引导用户自己选择他最方便的函数。

第二:我们可以引入一些辅助类(例如VxVVplusV等),它们只保留对每个操作的参数的引用,并定义到vector的运算符转换。现在,我们创建运算符+*的重载,它们通过引用获取两个向量,并只返回相应类型的对象。我们可以创建VxVplusVxV类型的类来计算更复杂的运算。现在我们可以将operator=过载到将VxVplusVxV初始化到vector。在上一次重载中,我们使用对辅助类对象中保留的参数的引用进行了所有计算,没有创建临时向量或创建了最小的临时向量。