【问题标题】:Time series stacking - C algorithm optimisation时间序列堆叠——C算法优化
【发布时间】:2020-08-04 05:01:08
【问题描述】:

我正在解决一个问题,我想堆叠在不同位置记录的时间序列并提取相干信号。繁重的工作是在 C 中完成的,使用 Python 包装器来提供更友好的界面。我已经达到了对算法的理论正确性感到满意的地步,并希望尽可能地对其进行优化。我对 C 语言的理解足以编写一些可以与 openMP 并行运行的东西,但仅此而已。

问题的优化很重要,因为我正在处理大型数据集 - 最多可堆叠 200 个时间序列,采样率高达 1000Hz,记录数月至数年。使用合理的计算设施,处理可以持续几天到几周。我在连续时间序列的块上运行此堆叠步骤,以免占用内存。

我有几个问题:

  1. 有什么明显的我遗漏的东西会有所帮助(通过编译器进行优化,简化算法)?

  2. 到目前为止,最显着的收获是优化标志 -Ofast - 我已经阅读并只是想了解更多为什么这更快以及它是否“安全” ' 出于我的目的?

  3. 我应该在哪里(除了通过 SO 拖网)了解有关此类问题的更多信息?我的研究中有其他问题想用 C 来解决!

算法

我将每个位置的时间序列连续堆叠在一个 3-D 网格体中。完成给定单元格的完整堆栈后,我需要对结果取幂并按贡献时间序列的数量进行归一化。

#define MAX(a,b) (((a)>(b))?(a):(b))

EXPORT void migrate(double *sigPt, int32_t *indPt, double *mapPt, int32_t fsmp, int32_t lsmp, int32_t nsamp, int32_t nstation, int32_t avail, int64_t ncell, int64_t threads)
{
    double  *stnPt, *stkPt, *eStkPt;
    int32_t *ttpPt;
    int32_t ttp;
    int32_t to, tm, st;
    int64_t cell;

    #pragma omp parallel for private(cell,stkPt,eStkPt,ttpPt,st,ttp,tm) num_threads(threads)
    for (cell=0; cell<ncell; cell++)
    {
        stkPt = &mapPt[cell * (int64_t) nsamp];
        eStkPt = &mapPt[cell * (int64_t) nsamp];
        ttpPt = &indPt[cell * (int64_t) nstation];
        for(st=0; st<nstation; st++)
        {
            ttp   = MAX(0,ttpPt[st]);
            stnPt = &sigPt[st*(fsmp + lsmp + nsamp) + ttp + fsmp];
            for(tm=0; tm<nsamp; tm++)
            {
                stkPt[tm] += stnPt[tm];
            }
        }
        for(tm=0; tm<nsamp; tm++)
        {
            eStkPt[tm] = exp(stkPt[tm] / avail);
        }
    }
}

我目前正在编译:

gcc -shared -fPIC -std=gnu99 ./source.c -fopenmp -Ofast -lm -o ./output

我已阅读:

Profiling python C extensions

What GCC optimization flags and techniques are safe across CPUs?

等等。如果我重复一个问题/我的问题定义不明确,请道歉。

【问题讨论】:

  • 将这些指针本地化以并行处理并省去私有子句可能会有所帮助。您可能希望确保内部循环经过 simd 优化,并且可能为 exp() 使用 simd 库。
  • 好的,我会调查的。我天真地尝试在指数步骤上使用带有#pragma omp simd 的simd 似乎减慢了速度,所以我会去读一些书。谢谢。

标签: c optimization openmp


【解决方案1】:

有什么明显的我遗漏的东西会有所帮助(通过编译器进行优化,简化算法)?

提议的代码看起来相当不错(GCC 应该能够对其进行矢量化,并且并行化似乎还可以)。但这里有一些可能的建议来提高性能:

  • exp(stkPt[tm] / avail) 可以重写为可能更快的表达式pow(availFactor, stkPt[tm]),其中availFactor 是一个在循环外部定义并设置为exp(1.0 / avail) 的常量。正如@tim18 所建议的那样,您还应该检查此循环是否已矢量化,因为指数计算通常很慢。
  • 您可以尝试使用编译器标志-march=native 以牺牲可移植性较差的二进制文件来加快代码速度(如果您不想降低旧处理器的可移植性,您可以尝试-mtune=native,它是一般不如-march=native)。关于你的处理器,这个选项可以让 GCC 使用 AVX 和 FMA 指令(在相对较新的处理器上可用),这应该可以加速你的代码。您也可以使用-mavx-mfma 手动启用此类功能。
  • 您可以尝试调整您的算法,以便包含stkPt[tm] += stnPt[tm] 的热循环主要用于缓存中的数据。这一点非常重要。事实上,算法中最热门的部分可能是内存受限的。一个好的起点是进行平铺(例如同时处理 2 个或 4 个 nstation)。
  • 如果结果精度足够好,请考虑使用单精度浮点类型而不是双精度。

不要忘记检查结果,因为这些优化可能会影响代码的准确性!

到目前为止,最重要的收获是优化标志 -Ofast - 我已经阅读并只是想更多地了解为什么它会更快以及它是否对我的目的“安全”?

使用-Ofast 是不安全的。实际上,此选项启用-ffast-math,从而启用更多选项,如-funsafe-math-optimizations-ffinite-math-only。因此,您的代码不应处理 NaN 值、无穷大和计算的准确性可能会低得多。关于您的期望,这可能是也可能不是问题。 请注意,此选项有助于 GCC 加快指数计算(感谢矢量化)。

【讨论】:

  • 太好了,好的——这当然给了我一些跳跃点。我研究了对那个内部循环进行矢量化,但老实说,我认为我对 C、simd 等的掌握不够扎实。似乎有一些矢量化库可以执行求幂(例如 MKL),但我无法弄清楚如何将它们包含在我的代码中。如果我平铺的项目(例如 nstations)不能被 2/4 等整除,这有关系吗?我通过在合理值之间进行剪辑来清理我正在堆叠的数组,并且问题的性质也应该排除 NaN 值。
  • 对于 SIMD,您可以使用 #pragma omp simd 帮助编译器对循环进行矢量化(这只是一个提示)。英特尔编译器对此相对较好(包括数学函数)。英特尔 VML 库提供有用的矢量化数学函数,但我不知道 ICC、VML 甚至 MKL 是否可以在不损失准确性的情况下做到这一点。实际上,通过严格的 IEEE754 合规性有效地对手动数学调用进行矢量化是非常困难的。所以,我建议你首先检查使用-Ofast 是否真的是一个问题(因为我认为 GCC 已经在这方面做得很好)。
  • 对于平铺,您可以有两个循环:一个以 4 的步幅迭代 nstations(不包括 (nstations/4)*4),另一个完成剩余工作。对于琐碎的案例#pragma omp simd 可以为您做到这一点。 nstationnsamp 的典型值是多少?如果它们很大,我建议你先关注平铺优化。
猜你喜欢
  • 2015-07-14
  • 2016-10-27
  • 2012-07-28
  • 2011-09-01
  • 2021-06-29
  • 1970-01-01
  • 1970-01-01
  • 2022-01-22
  • 2013-10-06
相关资源
最近更新 更多