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

Posted

技术标签:

【中文标题】在不使用 iostream 的情况下保存 c++11 随机生成器的状态【英文标题】:Save state of c++11 random generator without using iostream 【发布时间】:2014-08-12 03:36:45 【问题描述】:

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

对象的大小实现定义的:

g++ (tdm64-1) 4.7.1 给了sizeof(std::mt19937)==2504 但是 Ideone http://ideone.com/41vY5j 给 2500

我缺少像

这样的成员函数
    size_t state_size(); const size_t* get_state() const; void set_state(size_t n_elems,const size_t* state_new);

(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

【问题讨论】:

您可能会发现this question 很有帮助。除此之外,我认为如果不了解底层实现,就不可能序列化 RNG(或任何对象,真的)。如果可能的话,它可能会涉及到一些……古怪的黑客。 @MoreAxes 提到的问题是不相关的。此外,这并不是真正的性能问题,而是接口兼容性问题:I/O 接口不是从任何 iostream 类派生的,如果不先复制到 stringstream,然后再转换回二进制,最后,我就无法使用提供的方法使用 ChunkIO::Writer::dataWrite 函数编写它。 g++ 和 Ideone 的 sizeof std::mt19937 返回值分别比存储 Mersenne Twister 状态数组所需的值大 8 和 4 个字节。如果这在任何一种情况下都是单个值,那么我敢打赌它是一个指针(我假设您使用的是 64 位系统而 Ideone 不是),您需要在序列化过程中相应地处理它。如果它是一个非指针值(或者在g++ 的情况下是两个),按原样序列化它可能是安全的。 您也可以尝试不同的方法:创建一个包装类来存储种子和调用次数,并仅存储这些。在反序列化期间,使用存储的种子为 RNG 播种,并多次调用它。这可能有点奇怪,因为我不确定 C++11 的发行版是否总是从调用它们的 RNG 请求相同数量的伪随机字节,但似乎值得一试。在反序列化过程中显然会很慢,但序列化会非常快。 @MoreAxes 它是一个大小值:_UIntType _M_x[state_size];size_t _M_p; _M_p 是状态大小,它也被写入序列化。生成1e6个随机数需要多长时间? 【参考方案1】:

我已经为我在 OP 的 cmets 中提到的方法编写了一个简单的 (-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;

【讨论】:

以上是关于在不使用 iostream 的情况下保存 c++11 随机生成器的状态的主要内容,如果未能解决你的问题,请参考以下文章

是否可以从一个字符串本地保存数据,然后在不将其移动到其他地方的情况下进行破坏?

为啥我可以在不使用 std::getline 的情况下调用 getline?

cakephp 在不使用视图的情况下保存 HABTM 关系的数据

C#如何在不知道新数组长度的情况下将数组中的值保存到新数组中

如何在不保存/显示图像的情况下拍照

如何在不使用 Ruby 保存到磁盘的情况下生成 zip 文件?