这个冬天 流浪地球

流浪地球,这个电影这不错,没想到中国的科幻片已经可以达到这样的高度。我才不介意网上那些刷存在感的人,一定要说这部电影这里不好,那里不好。电影么,就是好看就行,想那么多累么。

刘慈欣本来作品就不错,制作团队那么认真,演员也还行,道路千万条的梗也很好笑,很佩服这些电影人,为了梦想而执着,成功的背后是为人不知的艰辛和付出。

这个春节,有流浪地球,让我们耳目一新。让我为了坚持梦想多一个榜样和理由!

丰田产业技术纪念馆之一

因为第一辆车是丰田的,加上对于丰田的精益管理始终有朝圣之心,所以这次去了距离住地较近的丰田产业技术纪念馆。果然很震撼。

之前也是孤陋寡闻了,原来丰田是从纺织机械起家的,从二十世纪初就开始了,经历百多年了,一直到全球汽车制造的翘楚,值得学习。纪念馆里也是无所不用其极,除了常规想得到的以外,竟然还有日本最擅长的动漫,让我们仿佛回到了丰田的创业时代。

2019.2 名古屋之游

2018年的元旦,在名古屋,2019年的春节,又去了名古屋,这次去了丰田产业技术馆和铁道博物馆,感触颇深。然后去了鸟羽,位于伊势弯的一隅,鸟羽水族馆挺不错,日本养殖数量第一的水族馆,以及鸟羽美丽的风景。

感觉还是有些国人对于日本这个国家有着一些误解,如同颜真卿书法事件,我的想法是,学习人家好的地方,而绝大多数事情和政治无关。

今年准备好好用考据的方式将去过的地方记录下来,应该会是一种有趣的经历。

今天,情人节

休假回来,昨天上了一天班,感觉大家还是沉浸在过年的气氛中,挺好的。事情不多,会议不多。估计工作的节奏正式开始要下周一了。

今天,情人节,这个节日记得是学生时代和圣诞节一起开始迅速流行,或许我们的传统节日有点隆重,而这两个节日其实对我们来说没有什么高不可攀的意义,就是热闹,各种气氛。

形式不论,心中有爱。

2019 新春快乐

好像说今年是春晚 36 年了,小时候对 cctv 有个节目还是印象深刻的,叫做九州方圆,当时不懂为什么叫这个名字,后来才知道“九州”就是说中国。

从昨天小年夜起,微信就开始很热闹了,家人、同事、朋友,各类群,响个不停。从今天早上开始,更是各种祝福拜年此起彼伏。好像已经几乎没有人用短信、电话拜年了(也或许我孤陋寡闻)。

支付宝自然不甘人后,抢五福的活动大家玩的不亦乐乎,从去年开始我也投入其中,今年更是复杂,又是 AR 扫,又是浇水答题,终于在昨天搞定了最难找的敬业福。等着晚上开奖,可以中个几毛钱。。。

过年的方式在变,年味或许也不如过去那样浓烈,但是心中还是有一份感激,新春快乐!猪事顺利!

2019 春节将至

今年春节不早不晚,2018年跌宕起伏,圆了一些梦想,也是很多煎熬。

互联网金融的热潮终于过去,经历了最风光的大约 2015 2016 2017 三年,P2P 整个行业几乎一蹶不起。还好,公司始终居安思危,使得我们依然在支付、金融、科技等方面前行,而上市实在不容易,拉开了和竞争对手的差距。只是,前路依然不容易。诚惶诚恐者生存。

明天是春节前最后一个上班日,今天还是有一般的同事在,有些组到今天还有不少项目在开发,很辛苦。

2018年下半年开始,所谓互联网寒冬,而我真的觉得其实无所谓寒冬,或者任何时候都要做好过冬的准备。

新春快乐!大家快乐!

计算缓存、优化算法和加速 Python 执行 第三部分

用列表计算进行优化

Python 性能的确一般,但是 Python 的常规函数的性能还是不错的, Python 解释器是用 C 语言实现的,在基本操作层面,已经优化了很多年,所以虽然有 requests 这样石破天惊的扩展包来取代 Python 内部的函数包,但是基本上没有人会去自己实现 Python 的列表计算等最常规的功能。在我们实践中的大部分场景,包括以 server 服务为主的场景,Python 的性能、稳定性都非常在线,也没有 Java 的 JVM 带来的很多不可控制的问题。我们用程序是去解决更多的业务逻辑,所以易学、易维护这些特性也是要关注的。

我们来看看用列表操作实现斐波那契数列的版本:

def list_fib(n):

    list_f = []
    f = 1
    list_f.append(f)
    list_f.append(f)  
    for i in range(n-2):
        f = list_f[-1] + list_f[-2]  
        list_f.append(f)
    return f


if __name__ == "__main__":
    x = 47
    start = time.time()
    res = list_fib(x)
    elapsed = time.time() - start
    print("Python Computed fib(%s)=%s in %0.8f seconds" % (x, res, elapsed))

执行结果就是秒开:

Python Computed fib(47)=2971215073 in 0.00002408 seconds

基本是所有执行方法中最快的之一,因为执行几十次的列表运算对于 Python 来说实在很轻松。

Python 的列表运算我们在《Python 机器学习》一书中有专门章节讨论过,列表是 Python 中非常重要的数据类型,不能小看,像这样一个数学运算的场景,用列表也可以简单而高效的完成。

谈谈 PyPy

CPython,也就是我们平时使用的 Python 解释器,因为这个解释器是用 C 写的,所以我们称之为 CPython,它并不是 Python 的唯一发行版,比如还有 PyPy http://pypy.org ,它通过即时编译器(JIT)来加快代码执行速度。在之前斐波那契数列的计算例子中有过比较,要比使用 CPython 快 5 倍左右。PyPy 完全支持 Python 标准库,但它不是支持所有的第三方扩。其平均可以提升 2-5 倍性能,并且你的应用所使用的扩展包都兼容,那么 PyPy 是可以尝试一下的。

维基百科中文版 https://zh.wikipedia.org/wiki/PyPy 关于 PyPy 的资料比较老,可以参考英文版 https://en.wikipedia.org/wiki/PyPy

PyPy 是 Armin Rigo 开发的,一己之力,能做这么多,真的很厉害!

PyPy 的前端是个严格的 Python 子集,称之为 RPython,这里的 R 是严格(Restricted)的意思。RPython 对 Python 语言做了一些约束,以便在之后编译时可以推断出变量类型等诸多优化的前提。PyPy 项目开发了一个工具链,用于分析 RPython 代码并将其转换为字节代码形式,最后用 C 语言来编译,达到将大部分代码编译成机器代码以提高执行速度的目的。它有设计精良的垃圾收集器和内存管理。最后,它包含了 JIT 引擎,在解释器层面上构造“即时的优化代码”。

概念很多,我们一一道来。

什么是 RPython

RPython 有自己的文档站点:https://rpython.readthedocs.io/en/latest/

你写的 Python 程序首先会经过一个 PyPy 的解释器来生成 RPython 代码,前面说了,生成的 RPython 代码比较严格,这样后面的编译工作就好开展,真正的 CPython 是用 C 写的解释器,但是并没有将你的 Python 代码编译成机器码,所以 Python 一般情况下会被诟病执行速度慢。

所以说 PyPy 实际上是包含两个组件:

  1. Python 解释器,输入是常规的 Python 程序,输出 RPython 代码,且这个 Python 解释器本身使用 RPython 写的。
  2. RPython 编译工具链,包括将 Python 代码转换成字节码和 C 代码,编译成本地二进制代码的一系列工具和步骤。

所以大家理解为什么说 PyPy 对 Python 兼容性很好,但是安装一些复杂的本身通过 C 优化过的扩展包兼容性又不太好的原因了。

RPython 编译过程

在官方文档中,PyPy 的整个工作不叫“编译”,而是叫“翻译”(translate)。RPython 编译器是一组工具链,将 RPython 程序编译到目标语言,比如 C。这个编译器本身是用 Python 写的。流程如下:

编译器读入转换到 RPython 样式的代码。RPython 会对普通的、带有一些动态性能的 Python 代码做一些限制,比如函数不能动态被生成、变量不能够不确定到底存储什么类型等,一些在解释执行时候没有问题的细节,在这里都会被严格对待。

编译器通过一个称之为抽象解释器(abstract interpretation)的工具构建 RPython程序的流程图。这个抽象解释器使用 PyPy 的 Python 解释器来解析 RPython 程序,这里开始的流程非常复杂,涉及到很多编译相关的技术细节,在 RPython 官方文档这里有一个更加详细的流程图:

笔者才疏学浅,就不再逐一展开了,有兴趣的朋友可以参考这些资料:

https://rpython.readthedocs.io/en/latest/translation.html

https://www.aosabook.org/en/pypy.html

有更加完整的说明和解释。

编译器一步步通过生成的流程图,最后生成基于 C 语言的代码,当然,在生成 C 代码的时候还要加入很多异常处理、内存管理等。通过下面这一连串的动作,终于可以将 Python 代码转换到 C 代码了。

什么是 JIT

什么是即时编译 JIT,Wiki 的定义(https://zh.wikipedia.org/wiki/即时编译) 很清晰:即时编译是动态编译的一种形式,是一种提高程序运行效率的方法。通常,程序有两种运行方式:静态编译与动态解释。静态编译的程序在执行前全部被翻译为机器码,而动态解释执行则是一句一句边运行边翻译。

即时编译器则混合了这二者,一句一句编译源代码,但是会将编译过的代码缓存起来以提高性能。相对于静态编译代码,即时编译的代码可以处理延迟绑定并增强安全性。

即时编译器有两种类型,一是字节码翻译,二是动态编译翻译。微软的.NET Framework,还有绝大多数的 Java 实现,都依赖即时编译以提供高速的代码执行。Ruby 的第三方实现 Rubinius 和 Python 的第三方实现 PyPy 也都通过 JIT 来明显改善了解释器的性能。

PyPy 的 JIT 机制比较与众不同的地方在于,别的编译器直接执行程序代码,其编译器中支持 JIT 特性,而 PyPy 则是在将 Python 代码转换为 RPython 代码的时候加入的,然后将其编译成可执行代码后就自带 JIT 了。这样的方式也算是比较巧妙的设计了,我理解,PyPy 对性能提升很大程度就在这里了。只是,通过这么复杂的两次转换,Python 代码的性能虽然有了几倍的提升,但和 Java、C 等还是有不小的差距。而随着硬件服务器资源的摩尔定律,引入 PyPy 本身增加的项目复杂度以及其对于第三方扩展包的支持问题,似乎还是使用 Python 标准的 CPython 解释器比较好,综合使用成本低很多。PyPy 解决问题的思路非常不错,但还是有很长的路要走。

fishbase v1.1.6 发布

昨天,我的同事们发布了 fishbase v1.1.6。可能是一种习惯,将项目开发中的所有公用函数都封装起来,Python 的包是一种非常好用的格式,加上有 github 和 CI 工具,都可以提升开发这些公用函数的质量。同时对于我们整理思路、学习 Python 知识、单元测试、文档编写、项目管理等都有不少提升。写一个可以放在 github 上的项目和自己在项目组里折腾,压力还是略有不同的。

现在 fishbase 还是比较简单,坚持,用心,就会越来越好。

自 2016/3 初次发布以来,我们坚持不断更新,先后发布了 20 余个版本。近一年来,我们逐步形成每月更新 1 到 2 个版本的频率,抽象出了很多通用的方法,主要分为以下模块:

模块功能函数
fish_common基本函数包
fish_crypt加密数据函数包
fish_csvcsv 处理增强函数包
fish_data数据信息处理函数包,含银行卡、身份证等
fish_date日期处理增强函数包
fish_file文件处理增强函数包
fish_logger日志记录增强函数包
fish_projectproject 目录结构生成函数包
fish_random随机数据生成函数包
fish_system系统增强函数包

可以到通过以下单元中查找具体的函数列表和使用说明。


fishbase 扩展包 for python:
源代码:https://github.com/chinapnr/fishbase
文档:https://fishbase.readthedocs.io/en/latest/
pypi :https://pypi.org/project/fishbase/ 

计算缓存、优化算法和加速 Python 执行 第二部分

引入 Nim 语言

我们可以将计算密集型的函数移动到 C 扩展模块,来弥补 Python 这方面作为解释型脚本语言的不足,但是用 C 编写 Python 扩展模块并不容易,大多数人都选择使用Python,就是因为它不是 C。虽然为了性能提升引入一种新技术(语言)的代价其实有点大,但是如果这个语言和 Python 很接近,但是性能几乎和 C 语言一样,并且可以很方便的为 Python 提供扩展包,这样的话,这个引入还是值得一试的。

Nim 语言似乎还是比较小众,在它的官网网站,https://nim-lang.org/ 是这样介绍自己的:

Nim是一种系统和应用程序编程语言,支持 静态类型和编译,它通过优雅的方式提供无与伦比的性能。

Nim 具有以下特性:

  • 高性能,自动垃圾回收
  • 可以编译成 C、C++ 或者 JavaScript 语言
  • 生成无依赖的二进制代码
  • 可以运行在 Windows、macOS 和 Linux 等

是不是听上去很神奇,可以编译成二进制代码不算稀奇,可以编译成 JavaScript 还是很不错的,这样就可以全栈通吃了。(这一点很像 Kotlin 语言)我猜测 Nim 的工作原理可能也是将自己编译成 C 语言,然后再将 C 语言编译成二进制代码,同样,也可以将自己转换为 C++ 或者 JavaScript 的等价语言,当然通过 lex 和 yacc 的分析加上应该非常复杂的模板和优化技术。

Nim 语言的安装

Nim 的安装看上去有点繁琐,可以参考 Nim 的官方文档:https://nim-lang.org/install_unix.html

在 macOS 上,最容易的安装方法是 brew install nim

然后推荐几乎万能的 Visual Studio Code ,免费好用。可以到这里下载:https://code.visualstudio.com/。微软这几年还是做出了很多非常优秀的产品,且有开放的心态,才会有这样一个功能超强、支持很多种开发语言,并且还是开源免费的优秀开发工具。

打开 Visual Studio Code 后,安装 Nim 扩展,以及 Code Runner 扩展。

Nim 扩展使得 VS Code 具备支持 Nim 语法高亮。

而 Code Runner 扩展则功能强大,支持在 VS Code 中运行几乎所有主流语言。

按照 Nim 官方介绍,你可以运行一段最简单的 Hello World 程序来测试一下。输入 echo "Hello World!",保存为 helloworld.nim,然后点击运行按钮,就会看到 Hello World!,如果没有出来正确结果的话,可以参考 Nim 官方文档。

我们可以运行一个复杂的,其实也就是刚才那个斐波那契数列的 Nim 程序版本:

import math, strformat, times

proc fib(n: int): int =
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

when isMainModule:
    let x = 47
    let start = epochTime()
    let res = fib(x)
    let elapsed = (epochtime() - start).round(2)
    stderr.writeLine(&"Nim Computed fib({x})={res} in {elapsed} seconds")

用 Nim 制作 Python 扩展包

好,重点来了,我们通过 Nim 的扩展程序安装一个 nimpy 的包,就可以将 nim 编译后的程序作为 C 的二进制程序给 Python 程序使用了。

安装 nimpy 包:nimble install nimpy

下面 Nim 程序实现了算法:

import nimpy

proc fib(n: int): int {.exportpy.} =
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

假设这个程序存盘文件名为 fib_nimpy.nim

然后我们执行 nim c -d:release --app:lib --gc:regions --out:fib_nimpy.so fib_nimpy.nim ,

稍等片刻,看到一堆提示信息和 operation successful (29124 lines compiled; 1.862 sec total; 47.984MiB peakmem; Release Build) [SuccessX] ,编译已经成功,我们会看到一个 fib_nimpy.so 文件。so 文件是 unix 的动态连接库,是二进制文件,作用相当于windows下的 .dll 文件。我没有测试,估计在 Windows 环境下应该可以生成 dll 文件。

然后将 so 文件复制到和 Python 程序同样的路径,import 刚才生成的 so 文件。

import time
from fib_nimpy import fib

if __name__ == "__main__":
    x = 47
    start = time.time()
    res = fib(x)
    elapsed = time.time() - start
    print("Py3+Nim Computed fib(%s)=%s in %0.8f seconds" % (x, res, elapsed))

我们可以看到 from fib_nimpy 就是刚才的 so 文件,其中的 fib 是我们在前面 Nim 语言中具体定义的函数。

执行上述代码,也就是没有任何优化的直接用递归硬算,性能是 Python 的 1000 倍,因为最慢的计算代码已经是和 C 语言同样速度的代码了。

沿袭这个思路,我们可以将所有消耗 CPU 资源的函数,特别是涉及到复杂计算的函数,都可以用 Nim 实现函数功能,然后被 import 到主 Python 程序来获得性能的大大提升。从前面的例子可以看到 Nim 语言可比 C 要简单好学得多。不过 Nim 语言还比较小众,甚至注定一直比较小众,没问题,聪明勤奋的你,为了性能优化是值得去学习的,再说 Nim 语言的优势还远远不止这点。