我编写了一个 IIR 滤波器类,它具有内部滤波器状态和元素级函数float filter(float x)
。当然,由于内部滤波器状态,filter()
只能按照序列中排列的顺序对样本调用。
如果我现在将该函数应用于特征数组,例如
input.unaryExpr(filter);
的执行顺序是否unaryExpr()
总是严格按照数组中值的顺序,还是可能会出现乱序执行甚至并行化的情况?
明确地编写循环以确保顺序始终符合预期,这样会更安全吗?
现在它似乎运行正常,但我找不到有关其行为的任何明确文档。
一元表达式,就像所有 Eigen 表达式一样,实际上只是一个行为类似于
Eigen::Matrix
或 的奇特函子Array
,它允许某种自省,并且可以被请求获取各个行和列索引的值。这些值的请求顺序由赋值的对象决定;本质上,就是赋值的左侧。我们可以在二维空间中演示这一点:第一个赋值语句打印
0 3 1 4 2 5
,其他两个赋值语句打印0 1 2 3 4 5
。原因是out1
使用了默认的列主顺序。因此,最有效的填充顺序是:第 0 列第 0 行,第 0 列第 1 行,第 1 列第 0 行,第 1 列第 1 行,…… 其他两个输出数组的内存顺序已转置,因此也使用转置的赋值顺序。还要注意,没有什么可以阻止我进行两次求值。当然,这种情况并不常见,但它可能会出现在其他良性代码中,比如下面的模板:
如果我调用
derivative(in.unaryExpr(…))
,它将打印0 1 3 4 1 2 4 5
,对大多数条目进行两次评估。长话短说:我认为把表达式写成你想要的状态化不是一个好主意,至少一般来说是这样。理想情况下,表达式应该是幂等的。但如果代码最短,你知道自己在做什么,并且在代码周围添加一些警告,以便下一位程序员也能理解这些限制,那么应该没问题。
一般来说,除了矩阵乘法和类似的已知复杂运算外,Eigen 不会自动并行化。其他所有操作都被假设为小巧快速,并且主要受内存带宽限制。我无法想象除了新的主要版本 Eigen4 之外,这一假设会发生变化。即使到了那个时候,我也不会假设它是自定义表达式的默认行为。启动和停止 OpenMP 并行区域的成本太高,除非充分了解其合理性。而且根据定义,Eigen 根本不知道自定义表达式的计算复杂度。
一般来说,求值的顺序也会按照输出在内存中的存储顺序从前到后进行,至少在简单的情况下是这样。这样计算速度更快,编程也更简单。
就我个人而言,我建议使用普通的迭代器
std::transform
或循环来明确操作顺序。一维数组和向量的迭代器速度很快,colwise()
二维迭代器也一样。