在 c++ 中使用迭代器加速并行 std::vector 计算



我有一个小函数,它根据对参数列表进行逐元素数学来计算参数,这些参数列表是根据l,t,d,n l,t,d,n std::vector<double>std::vector实例计算的。 这是我程序速度的关键点 - 我已经分析过,我确信。

下面是使用 [] 运算符的工作片段。 我正在C++在Core i7,8GB RAM,Windows 7上以Visual C++ 2008 Express进行开发,在发布模式下进行/O2优化。 最终,这是用SWIG编译成Python扩展,但我们不要超越自己。

我还使用 C

数组编写了一个解决方案(见下文)(我曾经在 C 中使用的解决方案,但我在 C++ 中转向了面向对象的解决方案,这需要(对于我的应用程序)使用 std::vector s 以避免内存泄漏。

所有三种解决方案都如下所示。 我在 SO 和其他地方听到了很多关于 std::vector 迭代器解决方案应该如何(总是??)与数组一样快的讨论,但我的结果表明 100 万次调用需要以下时间:

  • std::vector[]运算符:2.53 秒
  • 带迭代器的std::vector:2.69 秒
  • C 阵列 : 0.58 s

很明显,阵列解决方案要快得多。 我在 std::vector 解决方案的编码中是否遗漏了一些明显的东西?

编辑

因此,似乎我的部分问题在于分析。 优化优化了我的大部分 c 数组代码,这就是为什么它比任何 std::vector 选项都快得多。 我认为我从根本上受到执行所有 exp() 和 pow() 调用的吞吐量的限制。 谢谢大家的所有建议,我认为对于我的应用程序,我只是在与处理器速度对接。 我想 2*19 次 pow 调用大约 6 微秒并不是那么糟糕。 但这对我来说仍然太慢了。 C'est la vie...

std::vector<double>将索引与[]运算符结合使用

double phir_power::base(double tau, double delta) throw()
{
    double summer=0;
    for (unsigned int i=iStart;i<=iEnd;i++)
    {
        if (l[i]>0)
            summer+=n[i]*pow(delta,d[i])*pow(tau,t[i])*exp(-pow(delta,l[i]));
        else
            summer+=n[i]*pow(delta,d[i])*pow(tau,t[i]);
    }
    return summer;
}

使用迭代器std::vector<double>

std::vector<double>::const_iterator n_begin=n.begin(), n_end = n.end(), n_iter = n_begin;
std::vector<double>::const_iterator d_begin=d.begin(), d_end = d.end(), d_iter = d_begin;
std::vector<double>::const_iterator t_begin=t.begin(), t_end = t.end(), t_iter = t_begin;
std::vector<double>::const_iterator l_begin=l.begin(), l_end = l.end(), l_iter = l_begin;
for (unsigned int uuu=0;uuu<1e6;uuu+=1)
{
    double summer=0;
    //Bring the iterators back to the first element
    l_iter = l_begin;
    d_iter = d_begin;
    t_iter = t_begin;
    n_iter = n_begin;
    for (; l_iter != l_end; ++l_iter,++t_iter,++d_iter,++n_iter)
    {
        if ((*l_iter)>0)
            summer+=(*n_iter)*pow(delta,(*d_iter))*pow(tau,(*t_iter))*exp(-pow(delta,(*l_iter)));
        else
            summer+=(*n_iter)*pow(delta,(*d_iter))*pow(tau,(*t_iter));
    }
    rrrrrrrr += summer;
}
t2 = clock();
printf("Time for 1 million calls  %g [s] val %g n",((double)(t2-t1))/CLOCKS_PER_SEC,rrrrrrrr);

C 阵列

double r=0;
t0 = clock();
unsigned int qwe;
double ttte = 0;
double term_;
for (unsigned int j=1;j<19;j++)
{
    t1=clock();
    r=0;
    for (unsigned int i=0; i<1e6; i++)
    {
        term_ = n[j]*pow(delta,d[j])*pow(tau,t[j]);
        if (l[j]>0)
            term_ *= exp(-pow(delta,l[j]));
        r+=term_;
    }
    ttte+=r/1e6;
    t2=clock();
    printf("Index %d time %g [s] val %gn",j,((double)(t2-t1))/CLOCKS_PER_SEC,r/1e6);
}
t3=clock();
printf("Time for 1 million calls %g [s] val is %gn",((double)(t3-t0))/CLOCKS_PER_SEC,ttte);

不同之处在于,在你提供的C代码中,你在一个小循环中有一个大循环,其中没有任何变化,只是做了一百万次。在带有迭代器的代码中,大循环中有一个小循环,这意味着它必须一直更改迭代器。这可能会花费额外的时间。我不确定这一点,但如果您可以测试一下:试一试!

基于迭代器的代码的问题在于,在 operator[] 版本中,您需要针对一个索引 i 进行四个迭代器,因为您有四个包含 1 个双精度的向量。维护四个迭代器的成本很高。使用包含包含四个双精度的结构的单个向量的迭代器,您将获得更好的性能。实际上,对于 operator[] 版本来说,它可能更快,因为指针计算更少,数据位置更好,这应该可以提高 CPU 的吞吐量。

std::vector保证被分配为一个连续的内存块,因此如果您不想,则不必使用其[]运算符。

double phir_power::base(double tau, double delta) throw()
{
    double summer=0;
    double *pl = &l[0];
    double *pn = &n[0];
    double *pd = &d[0];
    double *pt = &t[0];
    for (unsigned int i = iStart; i <= iEnd; i++)
    {
        if (pl[i] > 0)
            summer += pn[i] * pow(delta, pd[i]) * pow(tau, pt[i]) * exp(-pow(delta, pl[i]));
        else
            summer += pn[i] * pow(delta, pd[i]) * pow(tau, pt[i]);
    }
    return summer;
}

最新更新