在类内使用std :: chrono :: high_resolution_clock播种std :: mt19937的正确方法是什么? [英] What is the proper way of seeding std::mt19937 with std::chrono::high_resolution_clock inside a class?

查看:110
本文介绍了在类内使用std :: chrono :: high_resolution_clock播种std :: mt19937的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,大家好!这是我在这里的第一个问题,所以希望我不要搞砸了。在写这里之前,我在Google上搜索了很多。我是编码,c ++的新手,我自己学习。

First off, hello everyone! This is my first ever question here, so I hope I am not screwing up. I googled a lot before writing here. I am new to coding, to c++ and I am learning it on my own.

考虑到有人告诉我这是一个好习惯(我在这里可能错了) )仅播种一次任何随机引擎,使用类内的随机标准库中的 std :: mt19937 的正确/最佳/效率更高的方法是什么, code> std :: chrono :: high_resolution_clock :: now()。time_since_epoch()。count()来自chrono标准库?

Considering that I was told that it is a good practice (i'm probably wrong here) to only seed any Random Engine once, what is the proper / best / more efficient way of using std::mt19937 from the random standard library inside a class, seeded by std::chrono::high_resolution_clock::now().time_since_epoch().count() from the chrono standard library?

我想使用该chrono值,因为它变化非常快,并且会产生令人毛骨悚然的数字。我从没考虑过 std :: random_device ,因为我认为这有点阴暗。我可能再次错了。

I want to use that chrono value, because it changes really fast and it generates a hell of a creepy number. I never considered std::random_device because I think it is kinda shady. I'm probably wrong again.

编辑:大多数时候,我使用C4Droid IDE在Android Phone上进行编码和学习,因为我不知道没有太多的空闲时间坐在一台合适的计算机上,所以这就是为什么我认为 std :: random_device 并不是很可靠。

Most of the times I code and learn on my Android Phone with C4Droid IDE, because I don't have too much free time to sit on a proper computer, so that's why I think std::random_device is not really reliable.

在知道类是什么之前,我已经成功完成了,但是我现在正在学习类,并且做了很多试验和错误(将static_casts放到任何地方,尝试使用const,static等,因为代码是总是给出错误)来完成此操作:

I've done it successfully before I knew what a class is, but I am now learning classes and did a lot of trial and error (putting static_casts everywhere, trying const, static, etc, because the code was always giving errors) to get this done:

class Deck
{
private:
    std::array<Card, 52> m_card;
    const int m_seed {static_cast<int>(std::chrono::high_resolution_clock::now().time_since_epoch().count())};

    std::mt19937 m_rng {m_seed};

    int rng(int min, int max)
    {
        std::uniform_int_distribution<> rng{min, max};
    return rng(m_rng);
    }

    void swapCard(Card &a, Card &b)
    {
        Card temp {a};
        a = b;
        b = temp;
    }

public:

    Deck()
    {
        int index{0};
        for (int iii {0}; iii < Card::CS_MAX; ++iii)
        {
            for (int jjj {0}; jjj < Card::CR_MAX; ++jjj)
            {
                m_card[index] = Card(static_cast<Card::CardSuit>(iii), static_cast<Card::CardRank>(jjj));
                ++index;
            }
        }
    }

    void printDeck() const
    {
    for (int iii {0}; iii < 52; ++iii)
        {
            m_card[iii].printCard();
            if (((iii + 1) % 13 == 0) && iii != 0)
                std::cout << '\n';
            else
                std::cout << ' ';
        }
    }

    void shuffleDeck(int xTimes = 1)
    {
        for (int iii {0}; iii < xTimes; ++iii)
        {
            for (int jjj {0}; jjj < 52; ++jjj)
            {
                swapCard(m_card[jjj], m_card[rng(0, 51)]);
            }
        }
    }

};

这可行,但是我不知道这是否是正确的方法。另外,有人告诉我,可以将永远不变的变量设为静态,以便在类的所有对象之间共享,但是我不能将m_seed设为静态...

This works, but I don't know if this is the proper way of doing it. Also, I was told that variables that never change can be made static to be shareable between all objects of the class, but I can not make m_seed static...

I我敢肯定,有一种更有效的方法可以做到这一点。你们可以帮忙吗?

I am pretty sure there's a more effective way of doing this. Can you guys help?

推荐答案


有人告诉我,最好只播种任何种子随机引擎一次

I was told that it is a good practice to only seed any Random Engine once

这听起来像是忠告。我想补充一点,您最好每个线程仅具有一个生成器,因为实例化和种子化需要时间,并且标准生成器不是线程安全的。

That sounds like sound advice. I'd like to add that you should preferably have exactly one generator per thread since instantiating and seeding it takes time and the standard generators are not thread safe.


我认为 std :: random_device 并不是真的可靠

它应该能够通过其 entropy()函数告诉您。熵为零表示其熵池为空或什至不存在。在后一种情况下,您会从中获得伪随机数。

It should be able to tell you if it is via its entropy() function. Zero entropy means its entropy pool is empty or doesn't even exist. You'll get pseudo random numbers from it in the latter case.


什么是正确的方法...

What is the proper way ...

通过阅读评论和其他提示中的链接,这是我到目前为止收集的内容:

By reading the links in the comments and some other tips, this is what I've collected so far:


  • 创建一个 SeedSequence 类,创建生成器所需数量的种子值。如果 std :: random_device 中的熵为零,则应将其与其他来源尽可能地结合起来。我认为散列的 time_point 样本需要一些时间才能与 rd()结合使用,因为输入中的1更改了位理想情况下,值应该更改散列值的一半。

  • 创建一个共享的生成器,该生成器在(新)线程请求时会自动实例化并植入种子,因为生成器不是线程安全的。

  • 创建从生成器继承的分发模板,以使一个线程中的所有分发都共享同一生成器。

  • 不要实例化分发超过必要。如果您经常使用相同的发行版,请保留该发行版。

  • Create a SeedSequence class that creates as many seed values as the generator requires. If the entropy in std::random_device is zero, combine it as best you can with some other source. I think hashed time_point samples taken some time apart could work in combination with rd() since 1 changed bit in an input value should ideally change half the bits in the hashed value.
  • Create a shared generator that is automatically instantiated and seeded when requested from a (new) thread since since generators aren't thread safe.
  • Create distribution templates that inherits from the generator so that all distributions in one thread share the same generator.
  • Don't instantiate the distribution more than necessary. If you use the same distribution a lot, keep it.

在此尝试在代码中添加注释:

Here's an attempt at it with comments in the code:

#include <iostream>
#include <chrono>
#include <climits>
#include <functional>
#include <iterator>
#include <random>
#include <thread>
#include <type_traits>
#include <utility>

//----------------------------------------------------------------------------------
// sexmex - A hash function kindly borrowed from Pelle Evensens yet to be published
// work: http://mostlymangling.blogspot.com/
//
// g++ 8.3.1: std::hash<Integer-type> lets the value through as-is (identity)
//            so I'll use this to create proper hash values instead.
template<typename Out = size_t, typename In>
inline std::enable_if_t<sizeof(In) * CHAR_BIT <= 64 &&
                            std::numeric_limits<Out>::is_integer &&
                            std::numeric_limits<In>::is_integer,
                        Out>
sexmex(In v) {
    uint64_t v2 = static_cast<uint64_t>(v); // cast away signedness
    v2 ^= (v2 >> 20) ^ (v2 >> 37) ^ (v2 >> 51);
    v2 *= 0xA54FF53A5F1D36F1ULL; // Fractional part of sqrt(7)
    v2 ^= (v2 >> 20) ^ (v2 >> 37) ^ (v2 >> 51);
    v2 *= 0x510E527FADE682D1ULL; // Fractional part of sqrt(11)
    v2 ^= (v2 >> 20) ^ (v2 >> 37) ^ (v2 >> 51);
    // Discard the high bits if Out is < 64 bits. This particular hash function
    // has not shown any weaknesses in the lower bits in any widely known test
    // suites yet.
    return static_cast<Out>(v2);
}
//----------------------------------------------------------------------------------
class seeder {
public:
    using result_type = std::uint_least32_t;

    // function called by the generator on construction to fill its internal state
    template<class RandomIt>
    void generate(RandomIt Begin, RandomIt End) const noexcept {
        using seed_t = std::remove_reference_t<decltype(*Begin)>;
        std::random_device rd{};

        if(rd.entropy() == 0.) { // check entropy
            // zero entropy, add some
            constexpr auto min = std::chrono::high_resolution_clock::duration::min();
            std::vector<seed_t> food_for_generator(
                static_cast<size_t>(std::distance(Begin, End)));

            for(int stiring = 0; stiring < 10; ++stiring) {
                for(auto& food : food_for_generator) {
                    // sleep a little to ensure a new clock count each iteration
                    std::this_thread::sleep_for(min);
                    std::this_thread::sleep_for(min);
                    auto cc = std::chrono::high_resolution_clock::now()
                                  .time_since_epoch()
                                  .count();
                    food ^= sexmex<seed_t>(cc);
                    food ^= sexmex<seed_t>(rd());
                }
                stir_buffer(food_for_generator);
            }

            // seed the generator
            for(auto f = food_for_generator.begin(); Begin != End; ++f, ++Begin)
                *Begin = *f;

        } else {
            // we got entropy, use random_device almost as-is but make sure
            // values from rd() becomes seed_t's number of bits and unbiased
            // via sexmex.
            //
            // seed the generator
            for(; Begin != End; ++Begin) *Begin = sexmex<seed_t>(rd());
        }
    }

private:
    template<typename SeedType>
    inline void stir_buffer(std::vector<SeedType>& buf) const noexcept {
        for(size_t i = 0; i < buf.size() * 2; ++i) {
            buf[i % buf.size()] += static_cast<SeedType>(
                sexmex(buf[(i + buf.size() - 1) % buf.size()] + i));
        }
    }
};
//----------------------------------------------------------------------------------
struct shared_generator {
    // we want one instance shared between all instances of uniform_dist per thread
    static thread_local seeder ss;
    static thread_local std::mt19937 generator;
};

thread_local seeder shared_generator::ss{};
thread_local std::mt19937 shared_generator::generator(ss);
//----------------------------------------------------------------------------------
// a distribution template for uniform distributions, both int and real
template<typename T>
class uniform_dist : shared_generator {
public:
    uniform_dist(T low, T high) : distribution(low, high) {}

    // make instances callable
    inline T operator()() { return distribution(generator); }

private:
    template<class D>
    using dist_t =
        std::conditional_t<std::is_integral_v<D>, std::uniform_int_distribution<D>,
                           std::uniform_real_distribution<D>>;

    dist_t<T> distribution;
};
//----------------------------------------------------------------------------------
void thread_func() {
    uniform_dist<int> something(0, 10);
    for(int i = 0; i < 10; ++i) std::cout << something() << "\n";
}

int main() {
    // all distributions sharing the same generator:
    uniform_dist<size_t> card_picker(0, 51);
    uniform_dist<int16_t> other(-32768, 32767);
    uniform_dist<float> fd(-1000.f, 1000.f);
    uniform_dist<double> dd(-1., 1.);
    for(int i = 0; i < 10; ++i) std::cout << card_picker() << "\n";
    std::cout << "--\n";
    for(int i = 0; i < 10; ++i) std::cout << other() << "\n";
    std::cout << "--\n";
    for(int i = 0; i < 10; ++i) std::cout << fd() << "\n";
    std::cout << "--\n";
    for(int i = 0; i < 10; ++i) std::cout << dd() << "\n";
    // in the thread function, a new generator will be created and seeded.
    std::thread t(thread_func);
    t.join();
}

这篇关于在类内使用std :: chrono :: high_resolution_clock播种std :: mt19937的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆