我有一个程序,使用相同版本的 Julia 在两台不同的电脑上运行。我不想给出程序的具体片段,因为一方面有点复杂,另一方面,我觉得一定有一些东西取决于机器而不是我的程序本身。
以下是我在两台机器上得到的一些结果:
1 1.0000000000582077 15.124999999941792
2 1.9999999999417923 27.536972172616515
3 3.000000000523869 45.722282028989866
Run Code Online (Sandbox Code Playgroud)
1 1.0 15.125
2 2.0 27.53697217302397
3 3.0 45.722282028466
Run Code Online (Sandbox Code Playgroud)
问题是舍入误差,我不知道它们是在哪一点引入的,导致结果略有不同。它不可能是格式错误(我在两种情况下使用完全相同的程序),也不可能是机器 epsilon 错误(两种情况下的机器 epsilon 都是>2.220446049250313e-16)。
目前我只能认为我使用的额外软件包,SparseArrays或LinearAlgebra正在FFTW引入此人为错误,但我不确定。
编辑:
好吧,这里是一个最小的例子(也许数组不需要像 [a,a] 一样,其中 a 本身就是一个稀疏数组)。我必须修改我的程序才能隔离问题,因此我下面将讨论的结果与我上面写的结果略有不同。然而,两者都显示了我提到的不可忽视的错误。
using LinearAlgebra
using SparseArrays
using FFTW
const Nsites = 10000
function Opbuild1(::Val{true})
Up = spzeros(Complex{Float64}, Nsites, Nsites)
N = div(Nsites,2)
ii = 1
for p in 0:(N-1)
Up[ii,ii] = exp(-1.0im*p^2*0.25)
Up[ii + N, ii + N] = exp(-1.0im*(p - N)^2*0.25)
ii += 1
end
return kron(sparse(Matrix(1.0I, 2, 2)), Up)
end
function Opbuild2(::Val{true})
pop = spzeros(Float64, Nsites, Nsites)
N = div(Nsites,2)
ii = 1
for p in 0:(N-1)
pop[ii,ii] = p
pop[ii+N,ii+N] = (p-N)
ii += 1
end
return kron(sparse(Matrix(1.0I, 2, 2)), pop)
end
function initst1(BoolN, irealiz)
psi = spzeros(Complex{Float64}, Nsites)
a = range(680,stop=783,step=1)
x = a[irealiz]
psi[x] = 1.0
psi = kron([1.0,1.0im]/sqrt(2), psi)
return psi, x
end
function ifftcustom(array)
array = Array(array)
array1 = copy(array)
array1[1:Nsites] = ifft(array[1:Nsites])
array1[Nsites+1:end] = ifft(array[Nsites+1:end])
return array1/norm(array1)
end
function fftcustom(array)
array = Array(array)
array1 = copy(array)
array1[1:Nsites] = fft(array[1:Nsites])
array1[Nsites+1:end] = fft(array[Nsites+1:end])
return array1/norm(array1)
end
function main()
boolN = rem(Nsites,2) == 0
Operator = Opbuild1(Val(boolN))
Operator_p = Opbuild2(Val(boolN))
#
psi0_inp, pinit = initst1(boolN, 1) #!
psi0_inp = round.(ifftcustom(Operator*psi0_inp), digits=15) # (*)See below
psi0_inp = round.(fftcustom(psi0_inp), digits=15)
psi0_inp = round.(psi0_inp, digits=15)
@show psi0_inp'*Operator_p*psi0_inp
end
main()
Run Code Online (Sandbox Code Playgroud)
所以区别如下。如果(*)我改为运行行
psi0_inp = round.(ifftcustom(psi0_inp), digits=15), 在@show我679.000000000001在两台机器中获得的部分中。另一方面,如果我按照我在代码中编写的方式运行它,在一台机器上我得到,679.0000000000001 + 0.0im但在另一台机器上得到679.0 + 1.2036944776504516e-16im机器上得到。
我不关心 -16im,因为这是双精度中的“零”,但实际上实部的数量级是 -13,这在双精度中不完全是零。
Ste*_*ski 10
如果机器具有不同的 CPU 架构,则由于不同的 SIMD 指令,特别是不同的并行指令集宽度,结果可能会有所不同。差异是由于以下几点造成的:
与它们近似的纯数学运算不同,浮点运算(如+和 )*是非关联的,即(x + y) + z可以不同于x + (y + z)。
为了利用 SIMD 并行指令,编译器将重新关联操作作为相当重要的优化。Julia 代码需要显式选择这种重新关联,但像 BLAS(用于稠密线性代数)、SuiteSparse(用于稀疏线性代数)和 FFTW 这样的高性能库肯定会以完美再现性为代价实现此类优化。如果这些库不允许 SIMD 优化,它们的速度会慢 2-8 倍,因此它们都这样做。
在实现求和等操作时,数值运算的关联树取决于 SIMD 指令的宽度。如果“车道宽度”为W,则总和被实现为W个并行总和,它们在最后相加在一起。由于不同CPU架构的宽度W可能不同,因此关联性树可能不同,因此数值结果可能不同。
请注意,这些都不是 Julia 特有的,如果程序是使用这些库(BLAS、SuiteSparse 和 FFTW)以任何语言编写的,也会发生这种情况。