我遇到了一个基本的类型稳定性问题,即将两个Integer
分开会生成一些具体类型的AbstractFloat
。
typeof(60 * 5 / 60)
> Float64
现在这是安全的事情,但它会导致运行时开销转换为浮动。
如果我们知道除法总是会得到余数为0的数字,即。 Integer
?
我们可以使用:
div(60 * 5 , 60)
fld(60 * 5 , 60)
这为我们提供了一些具体的Integer
类型,但是这种方法仍有开销,我们可以从LLVM IR看到:
@code_llvm div(60 * 5 , 60)
当我们知道结果没有余数时,我们可以做些什么来消除运行时开销?
可能的解决方案路径:
我希望使用Julia构造解决这个问题,即使我们需要创建它,而不是注入LLVM IR ...但是再一次,我们可以将注入包装到Julia函数中......
或许我们需要一个像@inbounds
这样的宏来进行安全整数除法,从而得到一个整数。
或者也许有一些纯粹的数学方法来执行适用于任何语言的方法?
答案 0 :(得分:7)
整数除法是CPU上与缓存无关的最慢操作之一;实际上,大多数CPU上的浮点除法速度更快(自己测试一下)。如果您事先知道要分割的内容(并希望将其除以多次),则可以使用预先计算因子来使用乘法/移位/加法替换整数除法。有很多网站描述了这个基本想法,here's one。
有关朱莉娅的实施,请参阅 https://gist.github.com/simonster/a3b691e71cc2b3826e39
答案 1 :(得分:7)
你是对的 - 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操作相比,它仍然非常慢,所以如果你经常除以相同的数字,你可能想要在他的答案中调查替代方案。 / p>