避免循环中的条件语句
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
。
因为它们都是元素,你可以在整个阵列上使用它们,因为
b1_dotProd = max(-1.0_dp, min(b1_dotProd, 1.0_dp))
虽然有 允许 min
和 max
在没有分支的情况下实现,但这将取决于 min
和 [=12= 的编译器实现]至于这是否真的完成了,如果这实际上更快,但它至少更简洁。
@veryreverie 的回答绝对正确,但是有
有两件事需要考虑。
where
语句是另一个明智的选择。因为它仍然是一个条件选择,所以 的警告相同
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,我实际上会考虑将数据类型更改为整数。然后你可以实际使用
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
我的 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
。
因为它们都是元素,你可以在整个阵列上使用它们,因为
b1_dotProd = max(-1.0_dp, min(b1_dotProd, 1.0_dp))
虽然有 min
和 max
在没有分支的情况下实现,但这将取决于 min
和 [=12= 的编译器实现]至于这是否真的完成了,如果这实际上更快,但它至少更简洁。
@veryreverie 的回答绝对正确,但是有 有两件事需要考虑。
where
语句是另一个明智的选择。因为它仍然是一个条件选择,所以 的警告相同
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,我实际上会考虑将数据类型更改为整数。然后你可以实际使用
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