【问题标题】:Why is valarray so slow on Visual Studio 2015?为什么 valarray 在 Visual Studio 2015 上这么慢?
【发布时间】:2019-05-08 23:34:49
【问题描述】:

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

std::valarray 和 helper 类被定义为没有某些 形式的别名,从而允许对这些类进行操作 优化类似于C中关键字restrict的效果 编程语言。此外,函数和运算符采用 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的情况下解决问题?

【问题讨论】:

标签: c++ optimization valarray


【解决方案1】:

我做得对吗?

你做的一切都是对的。问题出在 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)
   }

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

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

我该如何解决这个问题?

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

【讨论】:

  • 我读过一篇文章,说 valarray 在任何编译器中都没有达到预期的性能,因此,MSVC 从来没有费心去优化它,因为它总是很慢无论如何。
  • 我查看了 GNU valarray 实现。在这个实现中,一个模板代理对象被返回,真正的计算只在分配时发生。性能仅略低于显式使用循环。看来还是有可能生效valarray
  • @DmytroDadyka:你误会了。微软声称,即使在 GNU 中进行了优化,valarray 也仅比原始版本快一点,但仍然比使用所需命令的汇编慢得多。
  • quora.com/Why-does-nobody-seem-to-use-std-valarray/answer/… 读到这里,我依稀记得问题是用户会经常不小心复制,这完全抵消了性能提升。现在我们有了移动构造函数,这可能会更好,也可能不会更好。
猜你喜欢
  • 2011-10-14
  • 2010-09-07
  • 2023-03-21
  • 1970-01-01
  • 2011-05-14
  • 2013-11-06
  • 1970-01-01
  • 1970-01-01
  • 2015-10-23
相关资源
最近更新 更多