Julia / LLVM使用整数结果对整数进行有效划分

时间:2016-01-19 01:20:50

标签: llvm julia mathematical-optimization

我遇到了一个基本的类型稳定性问题,即将两个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这样的宏来进行安全整数除法,从而得到一个整数。

或者也许有一些纯粹的数学方法来执行适用于任何语言的方法?

2 个答案:

答案 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>