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

Why is valarray so slow on Visual Studio 2015?

为了加快我库中的计算速度,我决定使用 std::valarray class。 documentation 表示:

std::valarray and helper classes are defined to be free of certain forms of aliasing, thus allowing operations on these classes to be optimized similar to the effect of the keyword restrict in the C programming language. In addition, functions and operators that take valarray arguments are allowed to return proxy objects to make it possible for the compiler to optimize an expression such as v1 = a * v2 + v3; as a single loop that executes v1[i] = a * v2[i] + v3[i]; avoiding any temporaries or multiple passes.

这正是我需要的。当我使用 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?

Am I doing everything right?

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

Why is std::valarray not working as needed on 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 implementation. As you can see, each operator returns the template class _Expr which contains only the operation, but does not contain data. The real computation is performed in the assignment operator and more specifically in the __valarray_copy 函数。因此,在您执行赋值之前,所有操作都是在代理对象 _Expr 上执行的。只有调用 operator= 后,才会在单个循环中执行存储在 _Expr 中的操作。这就是为什么你用 g++ 得到这么好的结果的原因。

How can I solve the problem?

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