一些提高代码性能的建议

这里希望大家可以列出一些提高代码性能的建议,方便新手参考。因为我发先很多人对阅读英文的 performance tips有抵触。

但是也希望新手在阅读这部分之前有心理准备:你要按照Julian的方式,而不是Pythonic的方式写代码,才会比较自然然后也有不错的性能。什么意思呢?举个例子

Python里面我们喜欢直接在全局变量里面写东西

import numpy as np

n = 1000
y = 200

A = np.random.rand(1000)

for i in range(1000):
    A[i] = i%y

# etc.

在Julia里这样是低效的,因为编译器不能正确地对你的代码JIT,你需要用函数为编译器提供代码分块的标记。此外变量也应当尽量减少全局变量,因为全局变量的类型不稳定,而类型的不稳定会造成Julia变量的性能下降,一般来说你只要将它们声明在函数里面,自然就是稳定的。因为一个变量往往只会特指某一些对象,而这些对象就是用类型区分的。

也就是说

function main(params...)
     for i  in 1:1000
          do_something(params...)
     end
end

function do_something(params...)
     # do something
end

# 然后调用开始运行
main(1, rand(1000))

如果一定要使用全局变量,将其声明为 const 会更好,常量标记将会告诉编译器这个变量的类型是一定稳定的,否则代码就是错误的(如果编译器发现你改变了变量的类型,欺骗了它,那么就会报错)。

当然,我们不能说这样的编程方式是Julia好,还是Python好(实际上即便是Python我个人也更加喜欢写可读性更好的函数,而不是全局变量)。多写函数和合理封装的这样的编程方式总的来说可读性更好,在Python里我也会建议这么做。除非只是当配置文件,或者某个随便写的脚本用。

当然从这里你获取也能感受到,为什么我们说,速度快是有一些代价的。

5 个赞

多利用多重派发(multiple dispatch)的特性,而不是写很多的 if else 实际上我经常发现,这也是很多从Python转来的人不会注意的问题。因为在Python里不存在编译一说,所有的事情都是解释执行,你也只能用if else 去执行 is 这样的判断。

但是Julia里面我们是有类型信息的,我们看看下面的例子,Python里面我们有 None

def foo(x):
    if x is None:
        print('none')
    else:
        print(x)

而Julia里,我们更喜欢直接利用多重派发做这些事情,这或许也更自然

foo(x::Nothing) = print("none")
foo(x) = print(x)

而实际上,编译器自己会发现这些类型(因为类型推导在运行前就完成了),所以你在运行的时候实际上就不会执行这个判断。

1 个赞

mark 正在学习中

请问一个题外话,为什么这个特性叫multiple dispatch啊?
C++里面不也可以这样吗,定义的两个函数虽然同名但是传进去参数类型不同,不就可以定义不同的函数吗. 就像加法,整型和浮点型加法的过程虽然不同,但是他们都可以通过" +" 来计算.
Mathematica也是自带这个特点, f[x_?NumberQ]=… 和f[x_?SymbolQ] 也是接受不同类型的参数,从而执行不同的过程

看一下教程吧。。。都解释了

你也可以叫C++那个 static multiple dispatch,不是runtime的

function和method在Julia里和傳統OOP的含義不大一樣。需要慢慢理解。

Thus, the overall behavior of a function is a patchwork of the behaviors of its various method definitions. If the patchwork is well designed, even though the implementations of the methods may be quite different, the outward behavior of the function will appear seamless and consistent.

https://juliacn.github.io/JuliaZH.jl/latest/manual/methods/index.html

我推荐你看一下 这个回答

c++还要考虑class method和继承的问题,所以c++的multi dispatch一般都是通过一些设计模式实现的。

当然静态和动态也是很关键的点。

2 个赞

美國這邊在reddit 直播AMA

https://www.reddit.com/r/IAmA/comments/97jdb9
我截屏兩個有關的討論

深有感受,觉得多重派发很喜欢。写Python的时候感觉就像中毒了一样想回来写Julia。然而很多时候我也只能说这个更符合数学表达式,也讲不清楚为什么喜欢多重派发…

感觉有些歪楼, 不是要说提高性能的建议吗…

推荐一个包, Traceur.jl

刚刚手滑没写完就发出去了。。。我觉得你的这个例子不太恰当:

foo(x::Nothing) = print("none")
foo(x) = print(x)

在一些情况下编译器是可以优化这个例子,如果我们直接写,例如 x=foo(Nothing) 然而假设用Nothing表示一个可能失败的动作(用Python举个例子,例如在python中用正则表达式match一个字符串时,没有找到的时候就会返回None,所以才用if…else语句)在这种情况下,没有可能提前知道结果是否为Nothing,除非你运行了。我觉得最好不要用Nothing举例,换成一些数学计算函数这种一般能提前预知参数的类型的函数会比较好。例如经典的例子:

foo(x::Int) = x+1
foo(x::Float) = y+2.0
a = foo(1)
b = foo(2.0) #都可以静态的推出
#对应于Python的推不出来的版本
def foo(x):
    if isinstance(x,int):
        return x+1
    if isintance(x,float):
        return x+2.0

如果推不出的话那就要动态匹配了。

1 个赞

回复错人了吧…

嗯, 你说的有道理. 如果造成动态多重派发就会比较慢, Julia要从methods table里找合适的method来调用. 我在这里也提到过. jit友好的julia代码有什么特征? - #5,来自 Scheme

嗯,nothing不是一个好例子。不过想表达的意思和你差不多。本身可以依靠类型静态推导的东西不要去写if else.

这个包确实不错,非常感谢您的推荐!

foo(x::Float64) = x+2.0

请问创建一个全局的管道会对速度有什么影响:yum:

什么是全局管道啊??

大概就是在函数外定义一个Channel,然后函数对它进行操作