关于Python的一些背景
Python是一门很棒的语言。 在此之前,我尝试过几种语言:在学校使用Pascal; C,带班级的C,大学的C ++。 后两个(三个)对编程产生了强烈的反感:您不是在解决问题,而是在使用底层基元来思考分配和析构函数(过去的可怕话)。 我的观点是C不适合解决教育和科学问题(无论如何,在数学领域)。 我确信他们会反对我,但我并不是要强加任何人,我只是表达我的意见。
Python曾经是一个启示。 我一生中第一次编写了比C语言中更高的抽象级别。 任务和代码之间的距离空前减少。
如果我不必突然实施NIST统计测试,那么我可能会一辈子都用Python做到这一点。 任务似乎很简单:存在一个长度为几(> = 10)兆字节的数组,必须对此数组进行一组测试。
numpy有什么用?
python中有一个很好的numpy软件包,用于处理数组,从本质上讲,它是LAPACK之类的快速库的高级接口。 似乎可以使用整个绅士科学计算集(Sympy,Numpy,Scipy,Matplotlib),您还想要什么?
每个与numpy打交道的人都知道自己很出色,但并非一无所有。 我们还需要尝试确保操作是矢量化的(与R中一样),即 从某种意义上说,整个阵列是统一的。 如果突然由于某种原因您的问题不适合此范例,则您有问题。
我在说什么样的非向量化任务? 是的,至少要使用相同的NIST:使用Berlekamp-Messi算法计算线性移位寄存器的长度; 计算连续单位的最长子序列的长度,依此类推。 我不排除存在某种巧妙的解决方案将问题简化为矢量化的可能性。
狡猾?作为来自同一NIST的示例:有必要计算“切换”序列的数量,其中“切换”是指将连续单位(... 1111 ...)更改为连续零(... 000 ... ),反之亦然。 为此,可以采用不带最后一个元素(x [:-1])的原始序列,并从中减去移位1(x [2:])的序列,然后计算所得新序列中非零元素的数量。 一起看起来像:
np.count_nonzero(x[:-1] - x[1:])
它可能看起来像是一种有趣的锻炼,但实际上发生了一些不自然的事情,这种技巧在短时间后对读者来说并不明显。 更不用说这仍然很慢-没有人取消内存分配。
Python中的非向量化操作很长一段时间。 如果numpy还不够,该如何处理? 通常,他们提供几种解决方案:
- Numba JIT。 如果她按照Numba标题页上的描述进行工作,那么我认为完成这个故事是值得的。 过去我有点忘记她出了什么问题。 最重要的是,加速度没有我预期的那么可观。
- Cython。 好吧,举起双手,相信cython是一个非常漂亮,优雅的解决方案,并且不会破坏Python的真正含义和精神的人? 我不这么认为; 如果您来到Cython,您已经可以不再自欺欺人,而改用C ++和C之类的较不复杂的软件。
- 用C重写瓶颈,并从您心爱的Python中提取瓶颈。 好的,但是如果我拥有整个程序,那又是关于计算和瓶颈的全部呢? 不报价! 我并不是说在这个解决方案中您不仅需要了解一种语言,而且还需要了解两种语言-Python和C。
JULIA来了!
在考虑使用现有解决方案并且没有发现(无法编程)任何好的方法之后,我决定用“更快”的方法重写它。 实际上,如果您在21世纪使用数组的常规支持编写“数字脱粒机”,则“开箱即用”等矢量化运算等。 等等,那么您的选择不是很密集:
- Fortran 。 而且不要笑,我们当中哪一个没有从我们喜欢的语言中提取BLAS / LAPACK? Fortran是用于科学计算的一种非常好的语言(更好的SI!)。 我之所以没有这样做,是因为自Fortran时代以来,已经发明了许多东西并将其添加到编程语言中。 我希望有一些更现代的东西。
- R遭受与Python(向量化)相同的问题。
- Matlab-可能是的,很遗憾,我没有钱可以检查。
- 朱莉娅 -马是黑的,会起飞,不会起飞(直到稳定版本1.0才是自然的)
朱莉娅的一些明显优势
- 看起来像Python,至少是相同的“高级”代码,具有必要时降为位数的能力。
- 不必对内存分配等大惊小怪。
- 强大的文字系统。 类型是可选规定的,并且剂量很大。 可以在不指定单一类型的情况下编写程序-如果您执行该操作,他们将可以快速执行。 但是有细微差别。
- 编写与内置类型一样快的自定义类型很容易。
- 多次调度。 您可以谈论几个小时,但是在我看来-这是Julia所能做到的最好,它是所有程序设计的基础,并且通常是语言哲学的基础。
由于进行了多次分派,许多事情都非常统一地编写。
多个调度示例 rand() # 0 1 rand(10) # 10 0 1 rand(3,5) # 3 5 .... using Distributions d = Normal() # 0, 1 rand(d) # rand(d, 10) # 10 ...
也就是说,第一个参数可以是Distributions中的任何(一维)分布,但是函数调用在字面上将保持不变。 不仅是分发(可以将RNG本身作为MersenneTwister对象进行传输,甚至更多)。 另一个(在我看来,是说明性的)示例是在不带loc / iloc的DataFrames中进行导航。
- 6.数组是内置的。 开箱即用的矢量化。
保持沉默,这将构成犯罪!
- 新语言。 一方面,当然是减号。 在某种程度上,也许还不成熟。 另一方面,他考虑到了许多过去的语言,站在“巨人的肩膀上”,吸收了很多有趣的新事物。
- 快速的程序不太可能编写。 有些不明显的事情很容易处理:您踩耙,向社区求助,再踩...等等。 这些主要是类型不稳定性,类型不稳定性和全局变量。 一般而言,据我自己所知,想要快速用Julia编写程序的程序员经历了多个阶段:a)用Python编写程序。 很好,也是如此,但是有时会出现性能问题。 b)像C:那样尽可能地手动指定类型。 c)逐渐了解哪些地方需要(非常计量)规定类型以及它们在何处产生干扰。
- 生态系统 有些软件包是原始的,从某种意义上说,某些地方不断脱落。 有些是成熟的,但彼此不一致(例如,必须转换为其他类型)。 一方面,这显然是不好的。 另一方面,Julia有很多有趣而大胆的想法,仅仅是因为“我们站在巨人的肩膀上”-我们已经积累了踩踏耙子和“不该怎么做”的丰富经验,并且包装开发人员(部分地)考虑到了这一点。
- 从1.开始编号数组。哈,开玩笑,这当然是加号! 不,是的,认真的,这是怎么回事,从什么索引开始编号? 您在5分钟内就习惯了。 没有人抱怨整数被称为整数,而不是整数。 这些都是口味的问题。 是的,根据算法,至少要使用相同的Cormen-一切都从那里开始编号,相反,有时在Python中重做有时不方便。
好吧,为什么要速度?
现在该记住为什么一切开始了。
注意:我安全地忘记了Python,因此请在注释中写下您的改进,我将尝试在笔记本电脑上运行它们并查看运行时。
因此,有两个带有微基准的小例子。
向量化的东西问题:向函数的输入提供向量X的0和1,有必要将其转换为向量1和(-1)(1-> 1,0-> -1),并从该向量的傅里叶变换中计算出多少个系数绝对值超出边界。
def process_fft(x, boundary): return np.count_nonzero(np.abs(np.fft.fft(2*x-1)) > boundary) %timeit process_fft(x, 2000) 84.1 ms ± 335 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
function process_fft(x, boundary) count(t -> t > boundary, abs.(fft(2*x.-1))) end @benchmark process_fft(x, 2000) BenchmarkTools.Trial: memory estimate: 106.81 MiB allocs estimate: 61 -------------- minimum time: 80.233 ms (4.75% GC) median time: 80.746 ms (4.70% GC) mean time: 85.000 ms (8.67% GC) maximum time: 205.831 ms (52.27% GC) -------------- samples: 59 evals/sample: 1
我们不会在这里看到任何令人惊讶的地方-一样,他们自己不会考虑它,而是将其传递给经过优化的fortran程序。
不可向量化的东西数组0和1被输入到输入中,求出连续单位的最长子序列的长度。
def longest(x): maxLen = 0 currLen = 0
function longest(x) maxLen = 0 currLen = 0 # This will count the number of ones in the block for bit in x if bit == 1 currLen += 1 maxLen = max(maxLen, currLen) else maxLen = max(maxLen, currLen) currLen = 0 end end return max(maxLen, currLen) end @benchmark longest(x) BenchmarkTools.Trial: memory estimate: 0 bytes allocs estimate: 0 -------------- minimum time: 9.094 ms (0.00% GC) median time: 9.248 ms (0.00% GC) mean time: 9.319 ms (0.00% GC) maximum time: 12.177 ms (0.00% GC) -------------- samples: 536 evals/sample: 1
用肉眼可以明显看出差异。 欢迎使用“完成”和/或矢量化numpy版本的提示。 我还想指出,程序几乎相同。 例如,我没有在Julia中注册一个单一的类型(与上一个相比)-都一样,一切都可以快速进行。
我还想指出的是,所提供的版本并未包含在最终程序中,而是经过了进一步优化。 此处以示例为例,没有不必要的复杂性(在Julia中提供了额外的内存来就地执行rfft等)。
最终结果是什么?
我不会显示Python和Julia的最终代码:这是一个秘密(至少现在是这样)。 当我退出整理python版本时,它在约50秒内对16 MB二进制字符数组进行了所有NIST测试。 目前,在Julia上,所有测试都在相同的音量下运行〜20秒。 很可能我是个傻瓜,并且还有优化的余地,等等。 等 但这就是我本人的所有优点和缺点,我认为,应该考虑的不是编程语言在真空中的球形速度,而是我个人如何编程(没有真正的错误)。
我为什么要写所有这些?
这里的人们开始感兴趣; 我决定在时间出现时写信。 我认为将来会以解决一些典型问题为例,更全面地介绍Julia。 怎么了 更多和不同的语言,让每个想要找到适合他的东西的人。