C++11 保存c+的状态+;11不使用iostream的随机生成器
不使用iostream接口存储C++11随机生成器状态的最佳方法是什么。我想要这里列出的第一个备选方案[1]?但是,这种方法要求对象包含PRNG状态,并且仅包含PRNG状态。在partucular中,如果实现使用pimpl模式(至少在重新加载状态而不是用坏数据加载状态时,这可能会使应用程序崩溃),或者有更多与PRNG对象关联的状态变量与生成的序列无关,则会失败 对象的大小由实现定义:C++11 保存c+的状态+;11不使用iostream的随机生成器,c++11,random,C++11,Random,不使用iostream接口存储C++11随机生成器状态的最佳方法是什么。我想要这里列出的第一个备选方案[1]?但是,这种方法要求对象包含PRNG状态,并且仅包含PRNG状态。在partucular中,如果实现使用pimpl模式(至少在重新加载状态而不是用坏数据加载状态时,这可能会使应用程序崩溃),或者有更多与PRNG对象关联的状态变量与生成的序列无关,则会失败 对象的大小由实现定义: g++(tdm64-1)4.7.1给出了sizeof(std::mt19937)==2504 Ideone25
给出了g++(tdm64-1)4.7.1
sizeof(std::mt19937)==2504
2500Ideone
size\u t state\u size()代码>
const size\u t*get\u state()const代码>
void set\u state(大小元素,常量大小*状态新)代码>
std::min(n元素,state\u size())
从state\u new指向的缓冲区复制
这种接口允许更灵活的状态操作。或者是否存在状态不能表示为无符号整数数组的PRNG:s
[1] 我已经为我在评论中提到的方法编写了一个简单的(-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;
}
#包括
#包括
#包括
#包括
#包括
使用名称空间std;
结构rng_包装
{
//还建议以某种方式
//存储这是什么类型的RNG,
//所以我们不反序列化mt19937
//作为一个线性同余或什么的,
//但本例仅涵盖mt19937
uint64_t种子;
uint64\u t调用\u计数;
mt19937 rng;
typedef mt19937::result_type result_type;
rng_包装(uint64_t_种子):
种子,
调用_计数(0),
rng(_种子)
{}
rng_包裹(istream&in){
in.read(重新解释类型(&seed)、sizeof(seed));
in.read(reinterpret_cast(&invoke_count))、sizeof(invoke_count));
rng=mt19937(种子);
废弃(调用计数);
}
无效放弃(无符号长z){
废弃(z);
调用_count+=z;
}
结果类型运算符(){
++调用u计数;
返回rng();
}
静态constexpr结果_type min(){
返回mt19937::min();
}
静态constexpr结果_type max(){
返回mt19937::max();
}
};
ostream和操作员(istream和in、rng_包裹和包裹)
{
包裹=rng_包裹(英寸);
返回;
}
无效测试(rng\U包裹和rngw,整数计数,布尔安静=假)
{
均匀分布整数(0,9);
均匀实双分布(0,1);
正态分布stdnorm(0,1);
如果(安静){
对于(int i=0;i 除此之外,我认为序列化一个RNG(或者任何对象,真的)是不可能的没有关于底层实现的一些知识。如果可能的话,它可能会涉及一些……古怪的黑客行为。@MoreAxes提到的问题是不相关的。此外,这实际上不是一个性能问题,而是一个接口兼容性问题:I/O接口不是从任何iostream类派生的,我不能使用提供的方法没有首先复制到stringstream,然后转换回二进制,最后使用ChunkIO::Writer::dataWrite函数写入。g++
和Ideone的sizeof std::mt19937
返回的值分别比存储Mersene Twister的状态数组所需的值大8字节和4字节。如果在任何一种情况下都是一个值,那么我敢打赌它是一个指针(我假设您使用的是64位系统,而Ideone不是),您需要在序列化过程中对其进行相应处理,可以安全地按原样序列化。您也可以尝试另一种方法:创建一个包装类来存储种子和调用次数,并仅存储这些。在反序列化过程中,使用存储的种子为RNG种子,并多次调用它。这可能有点不可靠,因为我不确定C++11的发行版是否总是请求调用RNG时使用的伪随机字节数与调用RNG时相同,但似乎值得一试。在反序列化过程中显然会非常慢,但序列化会非常快。@此外,这是一个大小值:\u UIntType\u M\u x[状态大小];size_t_M_p;
\u M_p
是状态大小,也会写入序列化。生成1e6随机数需要多长时间?