samedi 4 novembre 2017

C++ random generator is 10x faster in g++ than MSVC?

I'm getting about 15 to 45 times speed difference between the same code compiled with Visual C++ and MINGW-64, on the same computer.

Can the MSVC be sped up?

Recently I was looking for a C++ random number generator that can produce a double type between 0. and 1. I used the erand48() function from stdlib because I don't need a high quality PRN, but it is not available for MSVC. I compiled the code (below) in MSVC 14 (2015) Community, and with MINGW-64 via MSYS. The results for the MINGW compile are about 15 to 45 times faster than for the MSVC build.

Both run on the same computer.


g++ Results (all times in ms), as compiled with command line

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

Produces

| 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 Community Results Compiled with x64 target. I experimented with varies /arch:SSE, /arch:SSE2, /arch:AVX, and /arch:AVX2. They were all significant

| 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 |


I have not figured out a clever way to template the engines, so the code has a huge amount of repetition. Sorry.

#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);
}




Aucun commentaire:

Enregistrer un commentaire