【问题标题】:Why is Mersenne twister faster than the linear congruential generator?为什么 Mersenne twister 比线性同余发生器快?
【发布时间】:2015-11-08 18:13:07
【问题描述】:

我使用 gcc C++ 标准库的 Mersenne twister 实现进行了测试。它优于线性同余生成器和 Crand,这很可能是 LCG。 A boost documentation 似乎也给出了类似的结果,但更偏爱梅森捻线机。谁能解释一下?

#include <cstdlib>
#include <iostream>
#include <chrono>
#include <random>

class Timer
{
private:
  std::chrono::high_resolution_clock::time_point start_time;
  std::chrono::high_resolution_clock::time_point stop_time;

public:
  void start()
  {
    start_time = std::chrono::high_resolution_clock::now();
  }

  void stop()
  {
    stop_time = std::chrono::high_resolution_clock::now();
  }

  double measure()
  {
    using namespace std::chrono;
    return duration_cast<microseconds>
    (stop_time - start_time).count() / 1000000.0;
  }
};

template<typename T>
class Random
{
private:
  T generator;

public:
  Random()
  : generator
  (std::chrono::high_resolution_clock::now().time_since_epoch().count())
  {
  }

  int generate_integer(int begin, int end)
  {
    return std::uniform_int_distribution<int>(begin, end - 1)(generator);
  }
};

int main()
{
  constexpr int n = 300000000;
  Random<std::minstd_rand> mr;
  Random<std::mt19937> mt;
  Timer t;
  for (int j = 0; j < 3; ++j)
  {
    t.start();
    for (int i = 0; i < n; ++i)
    {
      static_cast<volatile void>(mr.generate_integer(0, 10));
    }
    t.stop();
    std::cout << "minstd " << t.measure() << std::endl;
    t.start();
    for (int i = 0; i < n; ++i)
    {
      static_cast<volatile void>(mt.generate_integer(0, 10));
    }
    t.stop();
    std::cout << "mersenne " << t.measure() << std::endl;
    t.start();
    for (int i = 0; i < n; ++i)
    {
      static_cast<volatile void>(std::rand() % 10);
    }
    t.stop();
    std::cout << "rand " << t.measure() << std::endl;
  }
}

结果

minstd 4.70876
mersenne 1.55853
rand 4.11873
minstd 4.53199
mersenne 1.55928
rand 4.15159
minstd 4.5374
mersenne 1.55667
rand 4.13715

【问题讨论】:

  • 您期待不同的结果吗?
  • @zenith 当然,LCG 只是一些算术运算,而 mt19937 是几页代码。
  • 你开启优化了吗?为了获得良好的结果,您应该打开完全优化并强制定时活动产生副作用(累积校验和?),例如在计时器停止之后打印校验和结果,以防止编译器优化定时活动离开。
  • @Galik 测试使用-O3 -fwhole-program 运行。由于static_cast&lt;volatile void&gt;,编译器没有优化掉语句。
  • @xiver77 啊好想知道static_cast&lt;volatile void&gt;这个把戏!

标签: c++ random


【解决方案1】:

Mersenne Twister 算法并不像看起来那么复杂。或者,更准确地说,几乎所有复杂部分的执行频率都不足以严重影响长期平均速度。

如果你看pseudocode implementation on Wikipedia,绝大多数调用只行使extract_number()函数的后半部分;其余的非初始化代码(主要在 twist() 函数中)仅在 625 中的一次调用中运行(在最常见的版本中)。每次运行的部分都非常简单,只需少量移位和其他按位运算,预计在大多数处理器上都非常快。 extract_number() 开头的测试几乎总是错误的,因此可以通过分支预测轻松优化。

将此与线性同余算法进行对比,其中每个调用都会进行整数乘法(昂贵)和模除法(非常昂贵,除非您使用 2 的幂作弊,这会影响随机数的质量)。 LC 和 MT 算法所涉及的算法是如此不同,因此如果它们的相对性能因一个系统而异,我并不感到惊讶,但我毫不犹豫地相信 MT 至少在某些情况下更快。

(如果您仔细观察 MT 算法,乍一看似乎在 twist() 中每次迭代都会执行几个模运算,但这些形式很容易优化掉。)

对于普通的旧 rand(),其实现差异很大,不应期望跨系统保持一致。许多实现使用 16 位算法,自然会比 32 位或 64 位算法更快。

【讨论】:

    【解决方案2】:

    这可能是因为 rand 正在访问线程本地存储以检索其状态。

    我使用 Visual Studio 2015 Community 进行了尝试,得到了类似于 OP 的结果。查看 VS2012 编译器提供的 rand 源代码,rand() 访问线程本地存储以获取前一个值,然后通过 LCRG 数学传递以生成下一个值。

    在没有本地存储访问权限的情况下使用我自己的 rand 版本可以让我的时间更快 - 在 OP 的规模上大约是 0.25。

    【讨论】:

      【解决方案3】:

      我无法重现你的结果,当我尝试它时,看起来速度要快得多

      chris@chris-thinkpad ~/cpp/test5 $ g++ -std=c++11  main.cpp -o main
      chris@chris-thinkpad ~/cpp/test5 $ ./main 
      minstd 18.168
      mersenne 20.7626
      rand 3.13027
      minstd 17.8153
      mersenne 20.8395
      rand 3.19297
      minstd 18.0667
      mersenne 20.7672
      rand 3.13617
      

      编辑:当我使用 -O3 时,rand 仍然更快

      chris@chris-thinkpad ~/cpp/test5 $ g++ -std=c++11 -O3 main.cpp -o main
      chris@chris-thinkpad ~/cpp/test5 $ ./main 
      minstd 7.74432
      mersenne 8.54915
      rand 3.04077
      minstd 7.73824
      mersenne 8.5711
      rand 3.03335
      minstd 7.74818
      mersenne 8.55403
      rand 3.03481
      

      我认为这可能与操作系统/编译器/配置有关? 也许在 Windows 上,隐式调用 std::rand() 必须从操作系统或其他东西中获取时间来播种,或者类似的东西? (编辑:我不确定我是否理解提升结果,我怀疑提升结果会反映这样的问题)

      我的操作系统和编译器:

      chris@chris-thinkpad ~/cpp/test5 $ cat /etc/issue
      Linux Mint 17.1 Rebecca \n \l
      
      chris@chris-thinkpad ~/cpp/test5 $ gcc -v
      Using built-in specs.
      COLLECT_GCC=gcc
      COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
      Target: x86_64-linux-gnu
      Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
      Thread model: posix
      gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04) 
      

      编辑:我用“-fwhole-program”又做了一次,没有太大变化:

      chris@chris-thinkpad ~/cpp/test5 $ g++ -std=c++11 -fwhole-program -O3 main.cpp -o main
      chris@chris-thinkpad ~/cpp/test5 $ ./main 
      minstd 8.15607
      mersenne 8.03688
      rand 2.9622
      minstd 8.17983
      mersenne 7.99626
      rand 2.90655
      minstd 8.16007
      mersenne 7.99331
      rand 2.90902
      

      【讨论】:

        猜你喜欢
        • 2014-02-22
        • 2012-01-23
        • 2013-01-30
        • 2017-04-01
        • 2014-05-20
        • 2023-03-18
        • 2013-04-07
        • 2021-08-19
        相关资源
        最近更新 更多