C++ 在循环中使用正态分布

C++ 在循环中使用正态分布,c++,C++,我想知道把正态分布放在一个循环中是否会有问题 下面是以这种奇怪的方式使用正态分布的代码: std::default_random_engine generator; //std::normal_distribution<double> distribution(5.0,2.0); for (int i=0; i<nrolls; ++i) { std::normal_distribution<double> distribution(5.0,2.0);

我想知道把
正态分布
放在一个循环中是否会有问题

下面是以这种奇怪的方式使用
正态分布的代码:

std::default_random_engine generator;
//std::normal_distribution<double> distribution(5.0,2.0);

for (int i=0; i<nrolls; ++i) {
    std::normal_distribution<double> distribution(5.0,2.0);
    float x = distribution(generator);

}
std::默认随机引擎生成器;
//标准:正态分布(5.0,2.0);

对于(int i=0;i将
正态分布
对象放在循环外可能比放在循环内稍微有效。当它在循环内时,
正态分布
对象每次都可以重新构造,而如果它在循环外,则只构造一次

组件的比较。 根据对程序集的分析,在循环外声明
分发
更有效。

让我们看看两个不同的函数,以及相应的程序集。其中一个在循环内声明
分发
,另一个在循环外声明它。为了简化分析,在这两种情况下,它们都声明为const,因此我们(和编译器)知道分发不会被修改

你可以看到

基本上,它 -构建分布 -使用发行版调用
foo
-测试它是否应该退出循环

外环
总成 使用相同的编译选项,
outside\u loop
只需重复调用
foo
,而无需重新构建分发。指令更少,所有内容都保留在寄存器中(因此无需访问堆栈)

.L12:
移动rdi,rsp
添加ebx,1
调用foo(std::normal\u distribution const&)
cmp-ebp,ebx
jne.L12
是否有任何理由在循环中声明变量? 是。在循环中声明变量肯定是一个好时机。如果您在循环中以某种方式修改
分布,那么每次重新构造它都是有意义的

此外,如果您从未在循环外部使用过变量,那么在循环内部声明变量就很有意义,只是为了可读性

适合CPU寄存器的类型(因此浮点型、整数型、双精度型和小型用户定义类型)通常没有与它们的构造相关的开销,通过简化编译器对寄存器分配的分析,在循环中声明它们实际上可以导致更好的汇编。

查看正态分布的定义,有一个名为
reset
的成员:

重置分发的内部状态

这意味着分发可能有一个内部状态。如果有,那么在每次迭代中重新创建对象时,您肯定会重置该状态。如果不按预期使用它,可能会产生一个不正常或效率低下的分发

它可能是什么状态?这当然是实现定义的。从LLVM的一个实现来看,正态分布是围绕它定义的。更具体地说,
操作符()
是。查看代码,后续调用之间肯定会共享一些状态。更具体地说,在每次后续调用中,布尔变量
\u V\u hot\u
的状态都会被翻转。如果为真,则执行的计算会显著减少,并且使用存储的
\u V
的值。如果为假,则
是从头开始计算的

我没有深入探讨他们为什么选择这样做。但是,仅从执行的计算来看,依赖内部状态应该更快。虽然这只是一些实现,但它表明该标准允许使用内部状态,在某些情况下是有益的

稍后编辑:


可以找到
std::normal_distribution
的GCC libstdc++实现
调用另一个函数,
\uuuu generate\u impl
,该函数在一个单独的文件中定义。虽然不同,但此实现具有相同的标志,在这里命名为
\u M\u saved\u available
,它加快了每一次调用的速度。

分发版具有内部状态,通过将其放入循环中,可以重置它。我不知道结果是否uld可能是错误的,但可能有所不同。为什么不将初始化移到循环之外?这里的“奇怪”可能意味着“低效且错误”。我正在阅读其他人以这种奇怪方式编写的代码。随机数的分布仍然是高斯分布,仍然是具有正确平均值和宽度的高斯分布。我只是想知道是否可能存在潜在问题。对于哪些编译器或标准库实现,这是真的?我上面提到的实现是LLVM库cxx:,在clang.@J.AntonioPerez中使用。我也研究了gcc。提供的libstdc++有一个非常类似的实现,具有类似的行为。如果您想看一看,我已经更新了我的答案。
// This function is here to prevent the compiler from optimizing out the
// loop entirely
void doSomething(std::normal_distribution<double> const& d) noexcept;

void inside_loop(double mean, double sd, int n) {
    for(int i = 0; i < n; i++) {
        const std::normal_distribution<double> d(mean, sd); 
        doSomething(d); 
    }
}
void outside_loop(double mean, double sd, int n) {
    const std::normal_distribution<double> d(mean, sd);
    for(int i = 0; i < n; i++) {
        doSomething(d); 
    }
}
.L3:
        movapd  xmm2, XMMWORD PTR [rsp]
        lea     rdi, [rsp+16]
        add     ebx, 1
        mov     BYTE PTR [rsp+40], 0
        movaps  XMMWORD PTR [rsp+16], xmm2
        call    foo(std::normal_distribution<double> const&)
        cmp     ebp, ebx
        jne     .L3
.L12:
        mov     rdi, rsp
        add     ebx, 1
        call    foo(std::normal_distribution<double> const&)
        cmp     ebp, ebx
        jne     .L12