【问题标题】:C++ array operator overheadC++ 数组运算符开销
【发布时间】:2013-02-07 16:09:43
【问题描述】:

我记得不久前读过一些代码,这些代码允许编译器完成一些工作并简化如下表达式:

// 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

通常会为每个运算符创建和分配一个新向量,而编译器只会创建一个副本并对其执行所有操作。

这是什么技术?

【问题讨论】:

  • 这里没有问题。
  • 问题是……?还是您只是在寻找expression templates
  • This question 可能是你第一次读到这个的地方。
  • 返回值优化?
  • 鉴于您的函数按值获取参数,operator[] 的成本将远低于函数调用本身的开销。

标签: c++ arrays performance operator-overloading hpc


【解决方案1】:

正如@Agnew 很早就提到的那样,您描述的技术是expression templates

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

  1. 不对向量进行数学运算会返回结果。相反,让他们返回代表最终需要完成的操作的proxy objecta * b 可以返回一个“乘法代理”对象,它只保存对应该相乘的两个向量的 const 引用。

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

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

1 或矩阵或四元数等...*

【讨论】:

  • “向量的数学概念*”很烦人(数学中的指针?)。也许你不应该在这里使用星号,而应该使用上标数字。
  • 感谢 Angew,这正是我一直在寻找的东西,也感谢您为我在原始帖子中添加了一个问题:P 至少有人理解我的意思。
  • @leemes 很有趣。我考虑了两者,但随着数学概念的发展,上标数字具有额外的含义,而星号只是一个星号。对不起,你被激怒了。
  • VecExpression 类的最后两行在您提供的链接(表达式模板)上的含义: operator E&() { return static_cast(*this); } ?
  • 啊!抱歉,没有意识到它是转换运算符!
【解决方案2】:

我在这里没有看到任何问题。但是,我的水晶球告诉我,您想知道您想出的两种方法中更好的方法,以便对a * b + c * d 等向量执行逐分量算术运算,其中abcd 是具有相同大小的向量 (std::vector&lt;T&gt;):

  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(我是可变参数模板的新手,所以我不知道语法)。

【讨论】:

  • 这是解决问题的方法,但不是我要寻找的解决方案,因为它要求您为要对多个向量执行的每个操作创建一个新函数。从某种意义上说,它还降低了可读性,因为它不像简单的“a * b + c * d”那样简单或最小。
【解决方案3】:

你想要的是„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。在最后一次重载中,我们使用对保存在辅助类对象中的参数的引用进行了所有计算,没有创建或创建最少的临时向量。

【讨论】:

  • 再次,是的,这是一个解决方案,但它需要我为我可以定义的任何无限数量的数组的每个可能的加法/乘法编写代码。
猜你喜欢
  • 2017-08-26
  • 2016-08-30
  • 1970-01-01
  • 2018-11-27
  • 1970-01-01
  • 2018-10-07
  • 2015-11-25
  • 2012-04-15
相关资源
最近更新 更多