为什么Julia比Python快?因为天生理念就更先进啊(3)
这就是 Julia 语言所有特性的出发点,所以我们需要花些时间深入研究它。如果函数内部存在类型稳定性,即函数内的任何函数调用也是类型稳定的,那么编译器在每一步都能知道变量的类型。因为此时代码和 C/Fortran 代码基本相同,所以编译器可以使用全部的优化方法编译函数。 我们可以通过案例解释多重分派,如果乘法运算符 * 为类型稳定的函数:它因输入表示的不同而不同。但是如果编译器在调用 * 之前知道 a 和 b 的类型,那么它就知道哪一个 * 方法可以使用,因此编译器也知道 c=a * b 的输出类型。因此如果沿着不同的运算传播类型信息,那么 Julia 将知道整个过程的类型,同时也允许实现完全的优化。多重分派允许每一次使用 * 时都表示正确的类型,也神奇地允许所有优化。 我们可以从中学习到很多东西。首先为了达到这种程度的运行优化,我们必须拥有类型稳定性。这并不是大多数编程语言标准库所拥有的特性,只不过是令用户体验更容易而需要做的选择。其次,函数的类型需要多重分派才能实现专有化,这样才能允许脚本语言变得「变得更明确,而不仅更易读」。最后,我们还需要一个鲁棒性的类型系统。为了构建类型不稳定的指数函数(可能用得上),我们也需要转化器这样的函数。 因此编程语言必须设计为具有多重分派的类型稳定性语言,并且还需要以鲁棒性类型系统为中心,以便在保持脚本语言的句法和易于使用的特性下实现底层语言的性能。我们可以在 Python 中嵌入 JIT,但如果需要嵌入到 Julia,我们需要真的把它成设计为 Julia 的一部分。 Julia 基准 Julia 网站上的 Julia 基准能测试编程语言的不同模块,从而希望获取更快的速度。这并不意味着 Julia 基准会测试最快的实现,这也是我们对其主要的误解。其它编程语言也有相同的方式:测试编程语言的基本模块,并看看它们到底有多快。 Julia 语言是建立在类型稳定函数的多重分派机制上的。因此即使是最初版的 Julia 也能让编译器快速优化到 C/Fortran 语言的性能。很明显,基本大多数案例下 Julia 的性能都非常接近 C。但还有少量细节实际上并不能达到 C 语言的性能,首先是斐波那契数列问题,Julia 需要的时间是 C 的 2.11 倍。这主要是因为递归测试,Julia 并没有完全优化递归运算,不过它在这个问题上仍然做得非常好。 用于这类递归问题的最快优化方法是 Tail-Call Optimization,Julia 语言可以随时添加这类优化。但是 Julia 因为一些原因并没有添加,主要是:任何需要使用 Tail-Call Optimization 的案例同时也可以使用循环语句。但是循环对于优化显得更加鲁棒,因为有很多递归都不能使用 Tail-Call 优化,因此 Julia 还是建议使用循环而不是使用不太稳定的 TCO。 Julia 还有一些案例并不能做得很好,例如 the rand_mat_stat 和 parse_int 测试。然而,这些很大程度上都归因于一种名为边界检测(bounds checking)的特征。在大多数脚本语言中,如果我们对数组的索引超过了索引边界,那么程序将报错。Julia 语言默认会完成以下操作:
然而,Julia 语言允许我们使用 @inbounds 宏关闭边界检测:
这会为我们带来和 C/Fortran 相同的不安全行为,但是也能带来相同的速度。如果我们将关闭边界检测的代码用于基准测试,我们能获得与 C 语言相似的速度。这是 Julia 语言另一个比较有趣的特征:它默认情况下允许和其它脚本语言一样获得安全性,但是在特定情况下(测试和 Debug 后)关闭这些特征可以获得完全的性能。 核心概念的小扩展:严格类型形式 类型稳定性并不是唯一必须的,我们还需要严格的类型形式。在 Python 中,我们可以将任何类型数据放入数组,但是在 Julia,我们只能将类型 T 放入到 Vector{T} 中。为了提供一般性,Julia 语言提供了各种非严格形式的类型。最明显的案例就是 Any,任何满足 T:
(编辑:ASP站长网) |