如何以最快的方式计算一堆指数函数的乘积?

How to calculate the product of a bunch of exponential functions in the fastest way possible?

这是 Fortran 代码。我想计算一个名为 pYq_i 的函数,它是 mi 指数函数的乘积,见下文,

左边 (LHS) 是 pYq_i,RHS 是它的表达式。在代码中,theta(1) 是 ki,theta(2) 是 Di。然而,ki 和 Di 是随机变量。 sigma, D, 只是常数,它们可以是任何值。 Yji 是数据的值,tj 是时间。例如,t1 - t5 是 0.1,0.2,0.3,0.4,0.5.

这是代码,

function pYq_i(theta,i)
integer(kind=i8) :: i,j
real(kind=r8) :: theta(dim_p),mean,sigma,pYq_i,fact_mean
fact_mean = D/theta(2)
pYq_i = normpdf_factor_pYq_i
do j = 1,mi
    mean = fact_mean*exp(-theta(1)*t(j))
    sigma = abs(sig*mean)   
    pYq_i = pYq_i/sigma*exp(-half*((Yji(j,i)-mean)/sigma)**2)   
enddo  
return
end function pYq_i

我想以最快的速度计算这个 pYq_i。

我首先想到的是我可以做一些事情(不是静态的,这里只是说明),

mean(:) = xxxxxxx
sigma(:) = xxxxxx
pYq(:) = 1.0/sigma(:)*exp(-half*((Yji(:,i)-mean(:))/sigma(:))**2)
pYq_i = product(pYq)

但是上面的代码比do循环代码慢。我想在 do 循环中,事情会被保存在缓存或其他东西中。

所以我做了个小把戏,我重写了do循环中的pYq_i表达式,这样我现在计算指数的对数,然后求和,所以我在循环中保存了一些exp计算.最后我只需要做一次exp。看下面的代码,pYq_i_detail和pYq_i,

完全一样
function pYq_i_detail(theta,i) ! This should be faster than pYq_i.
integer(kind=i8) :: i,j
real(kind=r8) :: theta(dim_p),sigma_inv,pYq_i_detail,product_sigma_inv,log_pYq_i,fact_mean,xk
xk = theta(1)
fact_mean = theta(2)/D
log_pYq_i = zero
product_sigma_inv = normpdf_factor_pYq_i 
do j = 1,mi
    sigma_inv = sig_inv*fact_mean*exp(xk*t(j))
    log_pYq_i = log_pYq_i - (Yji(j,i)*sigma_inv-sig_inv)**2 
    product_sigma_inv = product_sigma_inv*sigma_inv
enddo
pYq_i_detail = abs(product_sigma_inv)*exp(half*log_pYq_i)   
return
end function pYq_i_detail 

顺便说一句,在上面的所有代码中,如果有任何变量没有在函数内定义,那么它就是在函数外定义的。它们只是变量,你可以给它们任何你想要的值。比如normpdf_factor_pYq_i,就是1/((2*pi)**mi)。 sig_inv 也只是一个常数。

单次计算成本低。但是,问题在于必须在代码中多次评估此函数。因此这个功能的评估似乎是一个瓶颈和关键。

现在我想问一下,有没有人对计算这个 pYq_i 的更快版本有建议?谢谢!

正如 kvantour 指出的那样,由于分子中的指数是平方的,因此您可以将术语 abs( D/Vi*exp(-ki*t(j)) ) 替换为 D/Vi*exp(-ki*t(j))。这允许将此项分解出来,给出更简单的指数。

然后,用总和的指数替换指数的乘积,并尽可能多地退出循环,我得到

function p(Yi,t,Vi,ki,D,sigma,mi)
  real(r8), intent(in) :: Yi(:)
  real(r8), intent(in) :: t(:)
  real(r8), intent(in) :: Vi
  real(r8), intent(in) :: ki
  real(r8), intent(in) :: D
  real(r8), intent(in) :: sigma
  integer,  intent(in) :: mi
  real(r8)             :: p
  
  real(r8) :: factor
  
  factor = D/Vi
  
  p = exp( -1/(2*sigma**2) * sum((Yi*exp(ki*t)/factor-1)**2) + ki*sum(t) ) &
  & / (sigma*abs(factor)*sqrt(2*pi))**mi
end function

我认为这与因式分解的速度一样快。为了更快地进行,您需要深入研究优化,并为此提供帮助,我们理想情况下需要 minimum example 典型用例。