为什么在Visual Studio 2015上valarray这么慢?

dil*_*ert 16 c++ optimization valarray

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

std :: valarray和helper类被定义为不包含某些形式的别名,因此可以优化对这些类的操作,类似于C编程语言中关键字limit的效果。此外,允许使用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;
}
Run Code Online (Sandbox Code Playgroud)

在g ++上,我得到了:

Valarr optimized case: 1484215
Optimal case: 1472202
Run Code Online (Sandbox Code Playgroud)

似乎所有操作d = a + b * c;实际上都放在一个周期中,这在保持性能的同时简化了代码。但是,当我使用Visual Studio 2015时,这不起作用。对于相同的代码,我得到:

Valarr optimized case: 6652402
Optimal case: 1766699
Run Code Online (Sandbox Code Playgroud)

差异几乎是四倍。没有优化!为什么std::valarray在Visual Studio 2015上无法按需工作?我做对了吗?我如何不放弃就解决问题std::valarray

Dmy*_*yka 21

我做对了吗?

您做对了所有事情。问题出在Visual Studio std::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)
   }
Run Code Online (Sandbox Code Playgroud)

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

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

我该如何解决这个问题?

您需要std::valarray在互联网上找到合适的 实施方式,也可以编写自己的实施方式。您可以使用GNU实现作为示例。

  • 我读了一篇有关valarray如何在任何编译器中都无法达到预期性能的文章,因此,MSVC从来没有费心去优化它,因为无论如何它总是很慢。 (5认同)
  • 我查看了GNU`valarray`实现。在此实现中,将返回模板代理对象,并且仅在分配时才进行实际计算。性能仅比明确使用周期略低。看起来仍然有可能获得有效的`valarray`。 (3认同)
  • https://developercommunity.visualstudio.com/content/problem/308961/stdvalarray.html,用于MS对错误报告的回复。 (3认同)