在不使用iostream的情况下保存c ++ 11随机生成器的状态

时间:2014-06-21 15:38:22

标签: c++11 random

在不使用iostream接口的情况下,存储C ++ 11随机生成器状态的最佳方法是什么。我想像这里列出的第一个替代品那样做[1]?但是,此方法要求对象包含PRNG状态且仅包含PRNG状态。在partucular中,如果实现使用pimpl模式,它会失败(至少这可能会在重新加载状态时使应用程序崩溃而不是使用错误数据加载它),或者有更多与PRNG对象关联的状态变量没有与生成的序列有关。

对象的大小是实现定义:

我缺少像

这样的会员功能
  1. size_t state_size();
  2. const size_t* get_state() const;
  3. void set_state(size_t n_elems,const size_t* state_new);
  4. (1)应返回随机生成器状态数组的大小

    (2)应返回指向状态数组的指针。指针由PRNG管理。

    (3)应从state_new

    指向的缓冲区中复制缓冲区std::min(n_elems,state_size())

    这种接口允许更灵活的状态操作。或者是否有任何PRNG:s的状态不能表示为无符号整数数组?

    [1] Faster alternative than using streams to save boost random generator state

1 个答案:

答案 0 :(得分:0)

我已经为OP在评论中提到的方法编写了一个简单的(-ish)测试。它显然没有经过实战考验,但这个想法得到了体现 - 你应该能够从这里开始。

由于读取的字节数远小于序列化整个引擎的字节数,因此两种方法的性能实际上可以相当。测试这个假设以及进一步优化,都是留给读者的练习。

#include <iostream>
#include <random>
#include <chrono>
#include <cstdint>
#include <fstream>

using namespace std;

struct rng_wrap
{
    // it would also be advisable to somehow
    // store what kind of RNG this is,
    // so we don't deserialize an mt19937
    // as a linear congruential or something,
    // but this example only covers mt19937

    uint64_t seed;
    uint64_t invoke_count;
    mt19937 rng;

    typedef mt19937::result_type result_type;

    rng_wrap(uint64_t _seed) :
        seed(_seed),
        invoke_count(0),
        rng(_seed)
    {}

    rng_wrap(istream& in) {
        in.read(reinterpret_cast<char*>(&seed), sizeof(seed));
        in.read(reinterpret_cast<char*>(&invoke_count), sizeof(invoke_count));
        rng = mt19937(seed);
        rng.discard(invoke_count);
    }

    void discard(unsigned long long z) {
        rng.discard(z);
        invoke_count += z;
    }

    result_type operator()() {
        ++invoke_count;
        return rng();
    }

    static constexpr result_type min() {
        return mt19937::min();
    }

    static constexpr result_type max() {
        return mt19937::max();
    }
};

ostream& operator<<(ostream& out, rng_wrap& wrap)
{
    out.write(reinterpret_cast<char*>(&(wrap.seed)), sizeof(wrap.seed));
    out.write(reinterpret_cast<char*>(&(wrap.invoke_count)), sizeof(wrap.invoke_count));
    return out;
}

istream& operator>>(istream& in, rng_wrap& wrap)
{
    wrap = rng_wrap(in);
    return in;
}

void test(rng_wrap& rngw, int count, bool quiet=false)
{
    uniform_int_distribution<int> integers(0, 9);
    uniform_real_distribution<double> doubles(0, 1);
    normal_distribution<double> stdnorm(0, 1);

    if (quiet) {
        for (int i = 0; i < count; ++i)
            integers(rngw);

        for (int i = 0; i < count; ++i)
            doubles(rngw);

        for (int i = 0; i < count; ++i)
            stdnorm(rngw);
    } else {
        cout << "Integers:\n";
        for (int i = 0; i < count; ++i)
            cout << integers(rngw) << " ";

        cout << "\n\nDoubles:\n";
        for (int i = 0; i < count; ++i)
            cout << doubles(rngw) << " ";

        cout << "\n\nNormal variates:\n";
        for (int i = 0; i < count; ++i)
            cout << stdnorm(rngw) << " ";
        cout << "\n\n\n";
    }
}


int main(int argc, char** argv)
{
    rng_wrap rngw(123456790ull);

    test(rngw, 10, true);  // this is just so we don't start with a "fresh" rng
    uint64_t seed1 = rngw.seed;
    uint64_t invoke_count1 = rngw.invoke_count;

    ofstream outfile("rng", ios::binary);
    outfile << rngw;
    outfile.close();

    cout << "Test 1:\n";
    test(rngw, 10);  // test 1

    ifstream infile("rng", ios::binary);
    infile >> rngw;
    infile.close();

    cout << "Test 2:\n";
    test(rngw, 10);  // test 2 - should be identical to 1

    return 0;
}
相关问题