避免循环中的条件语句

Avoiding Conditional Statements in Loops

我的 f90 程序有一部分占用了大量的计算时间。我基本上循环遍历三个矩阵(大小相同,维度大至 250×250),并试图确保值保持在区间 [-1.0, 1.0] 内。我知道最好避免循环中的条件语句,但我无法弄清楚如何重写这段代码以获得最佳性能。有没有办法“解开”循环或使用某种内置函数来“向量化”条件语句?

do ind2 = 1, size(u_mat,2)
    do ind1 = 1,size(u_mat,1)
        ! Dot product 1 must be bounded between [-1,1]
        if (b1_dotProd(ind1,ind2) .GT. 1.0_dp) then
            b1_dotProd(ind1,ind2) = 1.0_dp
        else if (b1_dotProd(ind1,ind2) .LT. -1.0_dp) then
            b1_dotProd(ind1,ind2) = -1.0_dp
        end if
        ! Dot product 2 must be bounded between [-1,1]
        if (b2_dotProd(ind1,ind2) .GT. 1.0_dp) then
            b2_dotProd(ind1,ind2) = 1.0_dp
        else if (b2_dotProd(ind1,ind2) .LT. -1.0_dp) then
            b2_dotProd(ind1,ind2) = -1.0_dp
        end if
        ! Dot product 3 must be bounded between [-1,1]
        if (b3_dotProd(ind1,ind2) .GT. 1.0_dp) then
            b3_dotProd(ind1,ind2) = 1.0_dp
        else if (b3_dotProd(ind1,ind2) .LT. -1.0_dp) then
            b3_dotProd(ind1,ind2) = -1.0_dp
        end if
    end do
end do

为了它的价值,我正在编译 ifort

您可以为此使用内部 min and max 函数。

因为它们都是元素,你可以在整个阵列上使用它们,因为

b1_dotProd = max(-1.0_dp, min(b1_dotProd, 1.0_dp))

虽然有 允许 minmax 在没有分支的情况下实现,但这将取决于 min 和 [=12= 的编译器实现]至于这是否真的完成了,如果这实际上更快,但它至少更简洁。

@veryreverie 的回答绝对正确,但是有 有两件事需要考虑。

  1. where语句是另一个明智的选择。因为它仍然是一个条件选择,所以
  2. 的警告相同

whether or not this actually avoids branches and if it's actually any faster, but it is at least a lot more concise

仍然适用。

一个例子是:

    pure function clamp(X) result(res)
        real, intent(in) :: X(:)
        real :: res(size(X))
        where (X < -1.0)
            res = -1.0
        else where (X > 1.0)
            res = 1.0
        else
            res = X
        end where
    end function

  1. 如果你想严格规范化为 1 或 -1,我实际上会考虑将数据类型更改为整数。然后你可以实际使用 a == 1 等而不用考虑浮点相等问题。根据您的代码,我还会考虑点积接近于零的情况。当然这一点只适用于,如果你只对标志感兴趣。
    pure function get_sign(X) result(res)
        real, intent(in) :: X(:)
        integer :: res(size(X))
        ! Or use another appropiate choice to test for near_zero
        where (abs(X) < epsilon(X) * 10.)
            res = 0
        else where (X < 0.0)
            res = -1
        else where (X > 0.0)
            res = +1
        end where
    end function