【问题标题】:What is the proper way of seeding std::mt19937 with std::chrono::high_resolution_clock inside a class?在类中使用 std::chrono::high_resolution_clock 播种 std::mt19937 的正确方法是什么?
【发布时间】:2019-05-21 16:45:49
【问题描述】:

首先,大家好!这是我在这里的第一个问题,所以我希望我没有搞砸。在这里写之前我google了很多。我是编码新手,不熟悉 c++,我正在自学。

考虑到有人告诉我,只播种任何随机引擎一次是一种很好的做法(我可能在这里错了),从随机标准库中使用std::mt19937 的正确/最佳/更有效的方法是什么在一个类中,由 chrono 标准库中的 std::chrono::high_resolution_clock::now().time_since_epoch().count() 播种?

我想使用那个 chrono 值,因为它变化得非常快,而且会产生一个令人毛骨悚然的数字。我从未考虑过std::random_device,因为我认为它有点阴暗。我可能又错了。

编辑:大多数时候我在我的 Android 手机上使用 C4Droid IDE 编写代码和学习,因为我没有太多空闲时间坐在合适的电脑上,所以这就是我认为的原因std::random_device 不太可靠。

在我知道类是什么之前我已经成功完成了,但是我现在正在学习类并且做了很多试验和错误(到处都放 static_casts,尝试 const、static 等,因为代码总是出错) 来完成这项工作:

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 设为静态...

我很确定有一种更有效的方法可以做到这一点。你们能帮忙吗?

【问题讨论】:

  • “另外,有人告诉我,可以将永不改变的变量设为静态,以便在类的所有对象之间共享” 你确定你真的想要这样吗?在这种情况下,所有类实例都会产生相同的卡片序列。还有你是什么意思你不能做到static,这样做的具体问题是什么?
  • “我认为这有点阴暗。”为什么?我的意思是现代 CPU 有一个生成真正随机数的指令。
  • 利用时间播种可以使您的程序在一定程度上具有可预测性。
  • @Quimby std::random_device 已知在 MinGW 编译器上被破坏。但除此之外,这将是我的首选(如果他们不进行简单的修复,我不会支持 MinGW)。
  • 不相关,std::shuffle(m_card.begin(), m_card.end(), m_rng)。让我们不要重新发明轮子。

标签: c++ class random chrono


【解决方案1】:

有人告诉我,只给任何随机引擎播种一次是一个好习惯

这听起来像是中肯的建议。我想补充一点,您最好每个线程只有一个生成器,因为实例化和播种需要时间,而且标准生成器不是线程安全的。

我觉得std::random_device不太靠谱

它应该能够告诉你它是否通过它的entropy() 函数。零熵意味着它的熵池是空的或者甚至不存在。在后一种情况下,你会从中得到伪随机数。

正确的方法是什么...

通过阅读 cmets 中的链接和其他一些技巧,这是我目前收集到的:

  • 创建一个SeedSequence 类,该类可以根据生成器的需要创建尽可能多的种子值。如果std::random_device 中的熵为零,请尽可能将其与其他来源结合。我认为分开一段时间的散列 time_point 样本可以与 rd() 结合使用,因为输入值中的 1 个更改位理想情况下应该更改散列值中的一半位。
  • 创建一个共享生成器,当从(新)线程请求时自动实例化和播种,因为生成器不是线程安全的。
  • 创建从生成器继承的分发模板,以便一个线程中的所有分发共享同一个生成器。
  • 不要过度实例化分发。如果您经常使用相同的发行版,请保留它。

这是在代码中使用 cmets 的尝试:

#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();
}

【讨论】:

  • 我刚刚反驳了那个否决票。好吧,我还不知道模板是什么。 :) 必须了解更多。我回家后会好好读一读。
  • 谢谢!我现在已经用一个好的散列函数替换了hasher,但是稍后当rd() 中没有熵时,我可能会调整播种。
  • 这对我来说很复杂,但似乎是一个很好的答案,并且付出了很多努力来帮助我!谢谢你。
  • @msmilkshake 谢谢!如果你想让我把更多的 cmets 放在不清楚的地方,我会努力让它变得更好。答案的主要部分不在示例代码中。如果您根据建议集实现某些东西,您只需看一眼熵添加的东西就可以做出适合您的东西。不过,拥有一个好的散列函数是让它工作的关键。上面的那个非常(矫枉过正)很好,但即使我看到它在做什么,它甚至无法开始解释它。
猜你喜欢
  • 2016-09-22
  • 2015-05-14
  • 2017-09-02
  • 2014-03-23
  • 1970-01-01
  • 2015-06-04
  • 1970-01-01
  • 2017-08-31
  • 2019-10-10
相关资源
最近更新 更多