【问题标题】:Fast percentile in C++C++ 中的快速百分位数
【发布时间】:2013-01-31 03:43:31
【问题描述】:

我的程序计算了风险价值指标的蒙特卡罗模拟。为了尽可能简化,我有:

1/ simulated daily cashflows
2/ to get a sample of a possible 1-year cashflow, 
   I need to draw 365 random daily cashflows and sum them

因此,每日现金流量是根据经验给出的分布函数,需要进行 365 次抽样。为此,我

 1/ sort the daily cashflows into an array called *this->distro*
 2/ calculate 365 percentiles corresponding to random probabilities

我需要对年度现金流进行此模拟,例如 10K 次,以获取一组模拟的年度现金流。准备好每日现金流的分布函数后,我会像...

for ( unsigned int idxSim = 0; idxSim < _g.xSimulationCount; idxSim++ )
{
    generatedVal = 0.0;
    for ( register unsigned int idxDay = 0; idxDay < 365; idxDay ++ )
    {
        prob = (FLT_TYPE)fastrand();         // prob [0,1]
        dIdx = prob * dMaxDistroIndex;       // scale prob to distro function size
                                             // to get an index into distro array
        _floor = ((FLT_TYPE)(long)dIdx);     // fast version of floor
        _ceil  = _floor + 1.0f;              // 'fast' ceil:)
        iIdx1  = (unsigned int)( _floor );
        iIdx2  = iIdx1 + 1;

        // interpolation per se
        generatedVal += this->distro[iIdx1]*(_ceil - dIdx  );
        generatedVal += this->distro[iIdx2]*(dIdx  - _floor);
    }
    this->yearlyCashflows[idxSim] = generatedVal ;
}

for 循环中的代码执行线性插值。如果说 1000 美元对应概率 = 0.01,10000 美元对应概率 = 0.1,那么如果我没有 p = 0.05 的经验数,我想通过插值获得 5000 美元。

问题:这段代码运行正确,尽管分析器说程序在插值本身上花费了大约 60% 的运行时间。所以我的问题是,我怎样才能使这项任务更快? VTune 报告的特定行的示例运行时如下:

prob = (FLT_TYPE)fastrand();         //  0.727s
dIdx = prob * dMaxDistroIndex;       //  1.435s
_floor = ((FLT_TYPE)(long)dIdx);     //  0.718s
_ceil  = _floor + 1.0f;              //    -

iIdx1  = (unsigned int)( _floor );   // 4.949s
iIdx2  = iIdx1 + 1;                  //    -

// interpolation per se
generatedVal += this->distro[iIdx1]*(_ceil - dIdx  );  //    -
generatedVal += this->distro[iIdx2]*(dIdx  - _floor);  // 12.704s

破折号表示分析器没有报告这些行的运行时间。

任何提示将不胜感激。 丹尼尔

编辑: c.fogelklou 和 MSalters 都指出了很大的改进。符合 c.fogelklou 所说的最好的代码是

converter = distroDimension / (FLT_TYPE)(RAND_MAX + 1)
for ( unsigned int idxSim = 0; idxSim < _g.xSimulationCount; idxSim++ )
{
    generatedVal = 0.0;
    for ( register unsigned int idxDay = 0; idxDay < 365; idxDay ++ )
    {
        dIdx  = (FLT_TYPE)fastrand() * converter;
        iIdx1 = (unsigned long)dIdx);
        _floor = (FLT_TYPE)iIdx1;
        generatedVal += this->distro[iIdx1] + this->diffs[iIdx1] *(dIdx  - _floor);
    }
}

按照 MSalter 的说法,我拥有的最好的是

normalizer = 1.0/(FLT_TYPE)(RAND_MAX + 1);
for ( unsigned int idxSim = 0; idxSim < _g.xSimulationCount; idxSim++ )
{
    generatedVal = 0.0;
    for ( register unsigned int idxDay = 0; idxDay < 365; idxDay ++ )
    {
        dIdx  = (FLT_TYPE)fastrand()* normalizer ;
        iIdx1 = fastrand() % _g.xDayCount;
        generatedVal += this->distro[iIdx1];
        generatedVal += this->diffs[iIdx1]*dIdx;
    }
}

第二个代码大约是。快 30%。现在,在总运行时间的 95 秒中,最后一行消耗了 68 秒。最后一行只消耗 3.2 秒,因此双 * 双乘法一定是魔鬼。我想到了 SSE - 将最后三个操作数保存到一个数组中,然后执行 this->diffs[i]*dIdx[i] 的向量乘法并将其添加到 this->distro[i] 但这段代码运行了 50%慢点。因此,我想我碰壁了。

非常感谢大家。 D.

【问题讨论】:

  • 你有没有解释为什么你的最后一行需要这么多时间,而前一行没有报告时间?除了编译器将它们混合成一个或类似的东西之外,它们应该花费完全相同的时间。
  • Philipp:我只是在转发 VTune 告诉我的内容。我敢打赌,真正的效果是每行分别为 6.3 秒。
  • 对于您的标题问题(!= 您的 fastrand 问题),请参阅 Numerical Recipes 第 435-438 页中的“单次分位数估计”,带有绘图和 c++ 代码。

标签: c++ performance percentile


【解决方案1】:

这是一个小优化的建议,消除了对 ceil、两个强制转换和一个乘法的需要。如果您在定点处理器上运行,那就可以解释为什么 float 和 int 之间的乘法和强制转换需要这么长时间。在这种情况下,如果 CPU 支持,请尝试使用定点优化或在编译器中打开浮点!

for ( unsigned int idxSim = 0; idxSim < _g.xSimulationCount; idxSim++ )
{
    generatedVal = 0.0;
    for ( register unsigned int idxDay = 0; idxDay < 365; idxDay ++ )
    {
        prob = (FLT_TYPE)fastrand();         // prob [0,1]
        dIdx = prob * dMaxDistroIndex;       // scale prob to distro function size
                                             // to get an index into distro array
        iIdx1  = (long)dIdx;
        _floor = (FLT_TYPE)iIdx1;     // fast version of floor
        iIdx2  = iIdx1 + 1;

        // interpolation per se
        {
           const FLT_TYPE diff = this->distro[iIdx2] - this->distro[iIdx1];
           const FLT_TYPE interp = this->distro[iIdx1] + diff * (dIdx - _floor);
           generatedVal += interp;
        }
    }
    this->yearlyCashflows[idxSim] = generatedVal ;
}

【讨论】:

  • 嗨 c.fogelklou,非常感谢。我在这种低级编程方面经验不足,所以请原谅我的愚蠢问题:什么是定点处理器?在家里,我在 i7 860 上运行它,在 Athlon X2 上工作。其他一些小的改进可以通过例如获得。 iIdx2 = (iIdx1 = ((long)dIdx) + 1) 等。至于 const FLT_TYPE 差异,那太棒了。这些差异可以预先计算,这也应该节省一些周期。
  • 如果您在 I7 上运行,请不要担心...您肯定支持浮点。定点优化将用整数乘法和除法替换浮点乘法和除法。例如,您可以通过将浮点数乘以范围 -1..1 乘以 32768,将其作为范围为 -32768 到 32768 的整数来处理。但是,它使代码更加复杂,所以很高兴它是没必要!
  • 这确实是正确的想法;使用diff 可以节省浮点乘法。
  • 嗨 c.fogelklou,移除额外的(不必要的)转换带走了 10.7 秒中的 2.5 秒,这是一个巨大的收益。预先计算差异和一些小的变化将另一个时间缩短到 6.1 秒。非常感谢!
【解决方案2】:

我建议修复fastrand。浮点代码并不是世界上最快的,但特别慢的是浮点代码和整数代码之间的切换。由于需要整数索引,所以使用整数随机函数。

在循环中预先生成所有 365 个随机值甚至可能是有利的。由于每个值只需要 log2(dMaxDistroIndex) 位随机性,因此您可以减少 RNG 调用的数量。

您随后会选择一个介于 0 和 1 之间的随机数作为插值分数。

【讨论】:

  • 我的代码是normalizer = 1.0/(FLT_TYPE)(RAND_MAX+1); dIdx = (FLT_TYPE)fastrand()* normalizer ; iIdx1 = fastrand() % _distroDimension; generatedVal += this-&gt;distro[iIdx1] + this-&gt;diffs[iIdx1]*dIdx,显然,现在最后一次操作花费的时间最多。与 c.fogelklou 的解决方案相比,我保存了一个双减双操作并添加了一个 uint 模数。从software.intel.com/en-us/articles/… 开始,fastrand() 函数既简单又快速,因此没有任何收获。这是你的意思吗?非常感谢。
猜你喜欢
  • 2016-01-26
  • 1970-01-01
  • 1970-01-01
  • 2011-04-13
  • 2021-10-20
  • 2014-12-10
  • 2017-09-28
  • 1970-01-01
  • 2011-04-16
相关资源
最近更新 更多