16

为了加快库中的计算速度,我决定使用std::valarray该类。文档说:

std::valarray 和辅助类被定义为没有某些形式的别名,因此允许对这些类的操作进行优化,类似于 C 编程语言中关键字限制的效果。此外,允许接受 valarray 参数的函数和运算符返回代理对象,以使编译器可以优化表达式,例如 v1 = a * v2 + v3; 作为执行 v1[i] = a * v2[i] + v3[i] 的单个循环;避免任何临时或多次通行证。

这正是我所需要的。当我使用 g++ 编译器时,它按照文档中的描述工作。我开发了一个简单的示例来测试std::valarray性能:

void check(std::valarray<float>& a)
{
   for (int i = 0; i < a.size(); i++)
      if (a[i] != 7)
         std::cout << "Error" << std::endl;
}

int main()
{
   const int N = 100000000;
   std::valarray<float> a(1, N);
   std::valarray<float> c(2, N);
   std::valarray<float> b(3, N);
   std::valarray<float> d(N);

   auto start = std::chrono::system_clock::now();
   d = a + b * c;
   auto end = std::chrono::system_clock::now();

   std::cout << "Valarr optimized case: "
      << (end - start).count() << std::endl;

   check(d);

   // Optimal single loop case
   start = std::chrono::system_clock::now();
   for (int i = 0; i < N; i++)
      d[i] = a[i] + b[i] * c[i];
   end = std::chrono::system_clock::now();
   std::cout << "Optimal case: " << (end - start).count() << std::endl;

   check(d);
   return 0;
}

在 g++ 上,我得到:

Valarr optimized case: 1484215
Optimal case: 1472202

看起来所有的操作d = a + b * c;真的都放在了一个循环中,这样在保持性能的同时简化了代码。但是,当我使用 Visual Studio 2015 时,这不起作用。对于相同的代码,我得到:

Valarr optimized case: 6652402
Optimal case: 1766699

差值几乎是四倍;没有优化!为什么std::valarray在 Visual Studio 2015 上无法按需要工作?我做的一切都正确吗?如何在不放弃的情况下解决问题std::valarray

4

1 回答 1

21

我做的一切都正确吗?

你做的一切都是对的。问题出在 Visual Studiostd::valarray实现中。

为什么std::valarray在 Visual Studio 2015 上无法按需要工作?

只需打开任何valarray运算符的实现,例如operator+. 你会看到类似的东西(在宏扩展之后):

   template<class _Ty> inline
      valarray<_Ty> operator+(const valarray<_Ty>& _Left,
         const valarray<_Ty>& _Right)
   {
      valarray<TYPE> _Ans(_Left.size());
      for (size_t _Idx = 0; _Idx < _Ans.size(); ++_Idx)
         _Ans[_Idx] = _Left[_Idx] + _Right[_Idx];
      return (_Ans)
   }

如您所见,创建了一个新对象,其中复制了操作结果。真的没有优化。我不知道为什么,但这是事实。它看起来像在 Visual Studio 中,std::valarray只是为了兼容性而添加的。

为了比较,考虑GNU 实现。如您所见,每个运算符都返回模板类_Expr,其中仅包含操作,但不包含数据。真正的计算是在赋值运算符中执行的,更具体地说是在__valarray_copy函数中。因此,在您执行分配之前,所有操作都在代理对象上执行_Expr。仅operator=调用一次,存储的_Expr操作在单个循环中执行。这就是使用 g++ 获得如此好的结果的原因。

我该如何解决这个问题?

您需要 std::valarray在 Internet 上找到合适的实现,或者您可以自己编写。您可以使用 GNU 实现作为示例。

于 2019-05-08T23:57:10.527 回答