在循环中使用normal_distribution
using normal_distribution in a loop
我想知道将normal_distribution
放入循环中是否存在问题。
以下是以这种奇怪方式使用normal_distribution
的代码:
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);
}
将normal_distribution
对象放在循环之外可能比将其放在循环中更有效。当它位于循环内时,normal_distribution
对象每次都可以重新构造,而如果它在循环外部,则只构造一次。
组件的比较。
根据对程序集的分析,在循环外声明distribution
更有效。
让我们看一下两个不同的函数,以及相应的程序集。其中一个在循环内声明distribution
,另一个在循环外声明。为了简化分析,它们在这两种情况下都声明为 sst,因此我们(和编译器)知道发行版不会被修改。
您可以在此处查看完整的程序集。
// 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);
}
}
inside_loop
组装
循环的程序集如下所示(在 O3 优化时使用 gcc 8.3 编译)。
.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
基本上,它 - 构造分布 - 调用foo
与发行版 - 测试它是否应该退出循环
outside_loop
组装
使用相同的编译选项,outside_loop
只是重复调用foo
,而无需重新构造发行版。指令更少,所有内容都保留在寄存器中(因此无需访问堆栈)。
.L12:
mov rdi, rsp
add ebx, 1
call foo(std::normal_distribution<double> const&)
cmp ebp, ebx
jne .L12
有什么理由在循环中声明变量吗?
是的。在循环中声明变量肯定是好时机。如果您在循环中以某种方式修改distribution
,那么每次都通过再次构造来重置它是有意义的。
此外,如果您从未在循环之外使用变量,那么仅出于可读性的目的而在循环中声明它是有意义的。
适合 CPU 寄存器的类型(因此浮点数、整数、双精度型和小型用户定义类型)通常没有与其构造相关的开销,并且在循环中声明它们实际上可以通过简化编译器对寄存器分配的分析来实现更好的组装。
查看正态分布的接口,有一个名为reset
的成员,他:
重置分配的内部状态
这意味着分布可能具有内部状态。如果是这样,那么在每次迭代时重新创建对象时,您肯定会重置它。不按预期使用它可能会产生不正常的分布,或者可能只是效率低下。
会是什么状态?这当然是执行定义的。查看LLVM的一个实现,正态分布在这里定义。更具体地说,operator()
在这里。查看代码,在后续调用之间肯定共享了一些状态。更具体地说,在每次后续调用时,布尔变量_V_hot_
的状态都会被翻转。如果为 true,则执行的计算明显减少,并且使用存储_V_
的值。如果为 false,则从头开始计算_V_
。
我没有深入探讨他们为什么选择这样做。但是,仅查看执行的计算,依赖内部状态应该要快得多。虽然这只是一些实现,但它表明该标准允许使用内部状态,并且在某些情况下它是有益的。
后期编辑:
std::normal_distribution
的GCC libstdc++实现可以在这里找到。请注意,operator()
调用另一个函数__generate_impl
,该函数在此处的单独文件中定义。虽然不同,但此实现具有相同的标志,此处名为_M_saved_available
,可加快每隔一次调用的速度。
- 将一个小的 C 定义重写为"normal" C++函数
- "fast"或"normal"在"free(): invalid next size (fast)"中是什么意思?
- 用户定义文字的每个"normal"使用都是未定义的行为吗?
- 函数模板和函数之间奇怪的不一致"normal"
- 在调试中,如何知道对函数的重复调用中参数的统计数据(max-min,average,distribution..)
- Box muller distribution
- 将"normal"标准::字符串转换为 utf-8
- 调用了基运算符而不是派生的one-Normal行为
- Direct X & HLSL - Normal ReComputing
- normal()总是给出负值
- 如何在 c++ 中的 Normal 或 Singleton 类中 GoogleTest 私有方法/枚举类
- boost::math::p df(distribution, value) 是否使用查找表或在每次调用时计算 exp(
- 犰狳inplace_plus明显慢于"normal"加操作
- Eclipse CDT 将"normal folder"转换为"source folder",反之亦然
- Qt Qml in a normal Qt application
- 为什么派生类中复制/移动函数"normal"实现的行为会有所不同,具体取决于它们的定义方式?
- C++中是否有"normal"一元逻辑运算符
- OpenGL glVertexAttribPointer normal
- Dirichlet distribution with RcppGSL
- C++:标准::矢量::调整大小与 "normal"分配