你是对的 - div
函数确实有一定的开销,但这并不是因为它可能存在余数。而是因为 div(typemin(Int),-1)
和 div(x, 0)
都会导致错误。所以你在 @code_llvm
中看到的开销只是为了检查这些情况。你需要的 LLVM 指令只是 sdiv i64 %0, %1
... 并且处理器甚至会在这些错误条件下抛出 SIGFPE。我们可以使用 llvmcall
创建自己的“无开销”版本:
julia> unsafe_div(x::Int64,y::Int64) = Base.llvmcall("""
%3 = sdiv i64 %0, %1
ret i64 %3""", Int64, Tuple{Int64, Int64}, x, y)
unsafe_div (generic function with 1 method)
julia> unsafe_div(8,3)
2
julia> @code_llvm unsafe_div(8,3)
define i64 @julia_unsafe_div_21585(i64, i64) {
top:
%2 = sdiv i64 %0, %1
ret i64 %2
}
julia> unsafe_div(8,0)
ERROR: DivideError: integer division error
in unsafe_div at none:1
那么,如果这样有效,为什么Julia坚持要将这些检查插入到LLVM IR中呢?这是因为LLVM认为这些错误情况在其优化过程中属于未定义行为。因此,如果LLVM能够通过静态分析证明它会出错,它就会更改其输出以跳过整个除法(和随后的异常)!这个自定义的div函数确实是不安全的:
julia> f() = unsafe_div(8,0)
f (generic function with 2 methods)
julia> f()
13315560704
julia> @code_llvm f()
define i64 @julia_f_21626() {
top:
ret i64 undef
}
在我的机器上(一台旧的 Nehalem i5),这个不安全的版本可以将 div
的速度提高约 5-10%,因此相对于整数除法的固有成本,这里的开销并不是非常可怕。正如@tholy所指出的那样,与几乎所有其他 CPU 操作相比,它仍然非常慢,因此如果您经常使用相同的数字进行除法运算,则可能需要查看他的答案中提供的替代方案。