【问题标题】:C++ random generator is 10x faster in g++ than MSVC?C++ 随机生成器在 g++ 中比 MSVC 快 10 倍?
【发布时间】:2017-11-04 22:58:25
【问题描述】:

在同一台计算机上,使用 Visual C++ 和 MINGW-64 编译的相同代码之间的速度差异大约是 15 到 45 倍。

MSVC 可以加速吗?

最近我在寻找一个可以产生介于 0. 和 1 之间的双精度类型的 C++ 随机数生成器。我使用了 stdlib 中的 erand48() 函数,因为我不需要高质量的 PRN,但它不可用对于 MSVC。我在 MSVC 14 (2015) Community 中编译了代码(如下),并通过 MSYS 使用 MINGW-64。 MINGW 编译的结果比 MSVC 编译快 15 到 45 倍。

两者都在同一台计算机上运行。


g++ 结果(所有时间以毫秒为单位),使用命令行编译

g++ -o RandSpeedTest -std=c++11 -O3 RandSpeedTest.cpp

生产

| ranlux64_base_01                | 169 | ms |
| linear_congruential             | 184 | ms |
| minstd_rand0                    |  78 | ms |
| minstd_rand                     |  68 | ms |
| mt19937 (default_random_engine) |  53 | ms |

MSVC 2015 社区结果 使用 x64 目标编译。使用 /O2 启用优化。我尝试了不同的 /arch:SSE、/arch:SSE2、/arch:AVX 和 /arch:AVX2。它们在效果上都是微不足道的。

| ranlux64_base_01                |  7794 | ms |
| linear_congruential             |  7746 | ms |
| minstd_rand0                    |  1181 | ms |
| minstd_rand                     |  1221 | ms |
| mt19937 (default_random_engine) |  1045 | ms |
| knuth_b                         |  1575 | ms |
| mt19937_64                      |  1009 | ms |
| ranlux24                        |  5639 | ms |
| ranlux48                        | 10687 | ms |

我还没有想出一个聪明的方法来模板引擎,所以代码有大量的重复。对不起。

#ifdef __GNUC__
#include <tr1/random>
//#include <cstdlib>

#elif _MSC_VER
#include <random>
#endif //__GNUC__

#include <iostream>
#include <chrono>
#include <vector>

using namespace std;
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;

void dump_time(
  std::chrono::time_point<std::chrono::high_resolution_clock> t1,
  std::chrono::time_point<std::chrono::high_resolution_clock> t2)
{
  cout << std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count() << " | ms |\n";
}

int main()
{
  const unsigned long numToGen = 10000000;
  std::tr1::uniform_real<double>  dist(0,1.); 
  volatile double f;
  std::chrono::time_point<std::chrono::high_resolution_clock> t1, t2;

  cout << "| ranlux64_base_01 | ";
  dist.reset(); // discard any cached values
  std::tr1::ranlux64_base_01 eng0;
  eng0.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng0));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);
  /*
  // verify that it produces output
  for(int i =0; i < 10; i++)
    {
    cout << dist(eng0) << endl;
    }
  */

  cout << "| linear_congruential | ";
  std::tr1::ranlux64_base_01 eng1;
  eng1.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng1));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);

  cout << "| minstd_rand0 | ";
  std::tr1::minstd_rand0 eng2;
  eng2.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng2));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);

  cout << "| minstd_rand |";
  std::tr1::minstd_rand eng4;
  eng4.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng4));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);

  cout << "| mt19937 (default_random_engine) | ";
  std::tr1::mt19937 eng5;
  eng5.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng5));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);

#ifdef _MSC_VER
  cout << "| knuth_b | ";
  std::tr1::knuth_b eng3;
  eng3.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng3));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);

  cout << "| mt19937_64 | ";
  std::tr1::mt19937_64 eng6;
  eng6.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng6));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);

  cout << "| ranlux24 | ";
  std::tr1::ranlux24 eng7;
  eng7.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng7));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);

  cout << "| ranlux48 |";
  std::tr1::ranlux48 eng8;
  eng8.seed( (unsigned int) 357);
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=dist(eng8));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);

#endif  // _MSC_VER

/*
// Not available in MINGW-64, not available in MSVC
  cout << "erand48" << endl;
  unsigned short int seed16v[3];
  t1 = high_resolution_clock::now();
  for (int i = 0; i < numToGen; i++, f=erand48(seed16v));
  t2 = high_resolution_clock::now();
  dump_time(t1, t2);
*/

  return (0);
}

【问题讨论】:

  • 据我所见,编译器可以删除对 dist() 函数的所有调用,并且可能在一种情况下这样做。
  • 您是否打开了 MSVC 编译器的优化功能?您没有在帖子中明确提及。
  • @Blastfurnace,它来自一个版本。调试结果会慢 50%。
  • @PaulB 我编辑显示 /O2 用于 MSVC 优化。
  • 你需要产生一个副作用,强制编译器调用 dist();例如,您可以累积返回值,然后在每次测试结束时将其输出。还。 volatile 几乎可以肯定不会像你想象的那样做。我在latedev.wordpress.com/2011/10/15/the-joy-of-benchmarks 有一篇关于基准测试问题的简短博客文章

标签: c++ performance visual-c++ random g++


【解决方案1】:

根据@NeilButterworth 评论,手动删除呼叫,即替换,例如

dist(eng6())

eng6()

产生与 g++ 相似的速度结果。如下图。

| ranlux64_base_01                 |  162 | ms |
| linear_congruential              |  127 | ms |
| minstd_rand0                     |   71 | ms |
| minstd_rand                      |   71 | ms |
| mt19937 (default_random_engine)  |   69 | ms |
| knuth_b                          |  115 | ms |
| mt19937_64 (__int64 not divided) |  151 | ms |
| ranlux24                         | 1211 | ms |
| ranlux48 (__int64 not divided)   | 4046 | ms |

其中两个引擎生成 __int64 值,它们不会被转换为 0 到 1 之间的浮点数。但是,其他引擎与 GNU 编译器一致,删除了对 dist 的调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-13
    • 2016-02-21
    • 1970-01-01
    • 1970-01-01
    • 2021-03-25
    • 1970-01-01
    • 2022-01-22
    相关资源
    最近更新 更多