摘要:华为云专家从优化布局 / 执行 / 多过程 / 开发心理等20个要点,教你如何开发高性能代码。
高性能计算,是一个十分宽泛的话题,能够从专用硬件/处理器/体系结构/GPU,说到操作系统/线程/过程/并行/并发算法,再到集群/网格计算,最初到天河二号(TH-1)。
咱们这次的分享会从集体的实际我的项目摸索登程,与大家分享本人摸爬滚打得出的心得体会,判若两人的保持原创。其中内容波及到优化布局 / 执行 / 多过程 / 开发心理等约20个要点,其中例子代码片段,应用Python。
高性能计算,在商业软件利用开发过程中,要解决的外围问题,用很文言的形式来说,“在无限的硬件条件下,如何让一段本来跑不动的代码,跑起来,甚至飞起来。”
性能晋升教训
举2个例子,随便感触下。
(1)635万条用户浏览文档的历史行为数据,数据处理工夫,由50小时,优化到15秒。(是的,你没有看错)
(2)基于Mongo的宽表创立,由20小时,优化到进来打杯水的功夫。
在大数据的时代,一个优良的程序员,能够写出性能比其他人的程序高出数百倍,甚至数千倍,具备这样的技能,对产品的奉献无疑是很大的,对集体而言,也是本人履历上亮点和加分项。
聊聊历史
2000年前后,因为PC硬件限度,那一代的程序员,比方,国内的求伯君 / 雷军,国外的比尔盖茨 / 卡马特,都是能够从机器码 / 汇编的角度来晋升程序性能。
到2005年前后,PC硬件性能倒退迅速,高性能优化经常听到,来自嵌入式设施和挪动设施。那个年代的挪动设施支流应用J2ME开发,可用内存128KB。那个年代的程序员,须要对程序大小(OTA下载,有数据流量限度,如128KB),内存应用都精打细算,真的是掐着指头算。比方,通常一个程序,只有一个类,因为新增一个类,会多应用几K内存。数据文件会合并为一个,缩小文件数,这样须要算,比方从第几个字节开始,是什么数据。
2008年前后,第一代iOS / Android智能手机上市,App可用内存达到1GB,App能够通过WIFI下载,App大小也能够达到一百多MB。我方才看了下我的P30,就存储空间而言,QQ应用了4G,而微信应用了10G。设施性能晋升,可用内存和存储空间大了,程序员们终于“解放”了,直到–大数据时代的到来。
在大数据时代下,数据量疯狂增长,一个大的数据集操作,你的程序跑一早晨才出后果,是常有的事。
基础知识
本次分享假如读者曾经理解了线程/过程/GIL这些概念,如果不理解,也没有关系,能够读下以下的摘要,并记住上面3点基础知识小结即可。
什么是过程?什么是线程?两者的差异?
以下内容来自Wikipedia: https://en.wikipedia.org/wiki...
Threads differ from traditional multitasking operating-system processes in several ways:
- processes are typically independent, while threads exist as subsets of a process
- processes carry considerably more state information than threads, whereas multiple threads within a process share process state as well as memory and other resources
- processes have separate address spaces, whereas threads share their address space
- processes interact only through system-provided inter-process communication mechanisms
- context switching between threads in the same process typically occurs faster than context switching between processes
驰名的GIL (Global interpreter lock)
以下内容来自 wikipedia.
A global interpreter lock (GIL) is a mechanism used in computer-language interpreters to synchronize the execution of threads so that only one native thread can execute at a time.[1] An interpreter that uses GIL always allows exactly one thread to execute at a time, even if run on a multi-core processor. Some popular interpreters that have GIL are CPython and Ruby MRI.
基础知识小结:
- 因为驰名的GIL,为了线程平安,Python里的线程,只能跑在同一个CPU核,无奈做到真正的并行
- 计算密集型利用,选用多过程
- IO密集型利用,选用多线程
实际要点
以上都是一些铺垫,从当初开始,咱们进入正题,如何开发高性能代码。
始终以来,我都在思考,如何做无效的分享?首先,我保持原创,如果同样的内容能够在网络上找到,那就没有分享的必要,节约本人和其他人的工夫。其次,对不同的人,采纳不同的办法,讲不同的内容。
所以,这次分享,听众大都是有开发教训的python程序员,所以,咱们不在一些根底的内容上花太多工夫,不理解也没关系,下来自已看看也都能看懂。这次咱们更多来从实际问题登程,我总结了约20个要点和开发技巧,心愿能对大家今后的工作有帮忙。
布局和设计尽可能早,而实现则尽可能晚
接到一个我的项目时,咱们能够先辨认下,哪些局部可能会呈现性能问题,做到心里有数。在设计上,能够早点想着,比方,选用适合的数据结构,把类和办法设计解耦,便于未来做优化。
在咱们以前的我的项目中,见过有些我的项目,因为晚期没有去提前设计,前期想优化,发现改变太大,危险十分高。
然而,这里一个常见的谬误是,上来就优化。在软件开发的世界里,这点始终被常常提起。咱们须要管制本人想早优化的心理,而应优先把大框架搭起来,实现次要性能,而后再思考性能优化。
先简略实现,再评估,做好打算,再优化施行
评估革新老本和收益,比方,一个模块费时一小时,如果优化,须要破费开发和测试工夫3小时,可能节俭30分钟,性能晋升50%;另一模块,费时30秒,如果优化,开发和测试须要破费同样的工夫,能够节俭20秒,性能晋升67%。你会优先优化哪个模块?
咱们倡议优先思考第一个模块,因为收益更大,可节俭30分钟;而第二个模块,费时30秒,不优化也能承受,应该把优化优先级放到最低。
另一个状况,如第2个模块被其它模块高频调用,那咱们又要从新评估优先级。
优化时,咱们要管制咱们可能产生的激动:优化所有能优化的局部。
当咱们没有“锤子”时,咱们遇到问题很苦恼,不足技能和工具;然而,当咱们领有“锤子”时,咱们又很容易看所有事物都像“钉子”。
开发调试时,应用Sampling数据,并配合开关配置
开发时,对费时的计算,能够设置sampling参数,调动时,传入不同的参数,既能够疾速测试,又能够平安治理调试和生产代码。千万不要用正文的形式,来开/关代码。
参考以下示意代码:
# Bad def calculate_bad(): # uncomment for debugging # data = load_sampling_data() data = load_all_data() # Good def calculate(sampling=False): if sampling: data = load_sampling_data() else: data = load_all_data()
梳理分明数据Pipeline,建设性能评估机制
我本人写了个Decorator @timeit 能够很不便地打印代码的用时。
@timeit def calculate(): pass
这样生成的log,菜市场大妈都看的懂。上了生产后,也能够告诉配置来管制是否打印。
[2020-07-09 14:44:09,138] INFO: TrialDataContainer.load_all_data - Start...[2020-07-09 14:44:09,158] INFO: preprocess_demand - Start[2020-07-09 14:44:09,172] INFO: preprocess_demand - End - Spent: 0.012998 s...[2020-07-09 14:44:09,186] INFO: preprocess_warehouse - Start[2020-07-09 14:44:09,189] INFO: preprocess_warehouse - End - Spent: 0.002611 s...[2020-07-09 14:44:09,454] INFO: preprocess_substitution - Start[2020-07-09 14:44:09,628] INFO: preprocess_substitution - End - Spent: 0.178258 s...[2020-07-09 14:44:10,055] INFO: preprocess_penalty - Start[2020-07-09 14:44:20,823] INFO: preprocess_penalty - End - Spent: 10.763566 s[2020-07-09 14:44:20,835] INFO: TrialDataContainer.load_all_data - End - Spent: 11.692677 s[2020-07-09 14:44:20,836] INFO: ObjectModelsController.build - Start[2020-07-09 14:44:20,836] INFO: ObjectModelsController.build_penalties - Start[2020-07-09 14:44:20,836] INFO: ObjectModelsController.build_penalties - End - Spent: 0.000007 s[2020-07-09 14:44:20,837] INFO: ObjectModelsController.build_warehouses - Start[2020-07-09 14:44:20,848] INFO: ObjectModelsController.build_warehouses - End - Spent: 0.011002 s
另外,Python也提供了Profiling工具,能够用于费时函数的定位。
优先解决数据读取性能
一个残缺的我的项目,可能会有很多性能晋升的局部,我倡议,优先解决数据读取,起因是,问题容易定位,批改代码绝对独立,见效快。
举例来说,很多机器学习我的项目,都须要建设数据样本数据,用于模型训练。而数据样本的建设,常通过创立一个宽表来实现。很多DB都提供了很多晋升操作性能的办法。假如咱们应用MongoDB,其提供了pipeline函数,能够把多个数据操作,放在一个语句中,一次传给DB。
如果咱们粗犷地单条解决,在一个我的项目中咱们试过,须要近20个小时,花了半天的工夫来优化,跑起来,来到座位去接杯水,回来就曾经跑完了,费时降为1分钟。
留神,很多时候咱们没有能源去优化数据读取的性能,因为数据读取可能次数并不多,但事实上,特地是在试算阶段,数据读取的次数其实并不少,因为咱们总是没有进行过对数据的扭转,比方加个字段,加个特色什么的,这时候,数据读取的代码就要常常被用到,那么优化的收益就体现进去了。
再思考升高工夫复杂度,思考应用预处理,用空间换工夫
咱们如果把性能优化当做一桌宴席,那么能够把数据读取局部的性能优化,当作开胃小菜。接下来,咱们进入更好玩的局部,优化工夫复杂度,用空间换工夫。
举例来说,如果你的程序的复杂度为O(n^2),在数据很大时,肯定会十分低效,如果能优化为复杂度为O(n),甚至O(1),那就会带来几个数据级的性能晋升。
比方下面提到的,应用倒排表,来做数据预处理,用空间换工夫,达到从50小时到15秒的性能晋升。
因驰名的GIL,应用多过程晋升性能,而非多线程
在Python的世界里,因为驰名的GIL,如果要晋升计算性能,其基本准则为:对于I/O操作密集型利用,应用多线程;对于计算密集型利用,应用多过程。
一个多过程的例子:
咱们筹备了一个长数组,并筹备了一个绝对比拟费时的等差数列求和计算函数。
MAX_LENGTH = 20_000 data = [i for i in range(MAX_LENGTH)] def calculate(num): """Calculate the number and then return the result.""" result = sum([i for i in range(num)]) return result
单过程执行例子代码:
def run_sinpro(func, data): """The function using a single process.""" results = [] for num in data: res = func(num) results.append(res) total = sum(results) return total %%time result = run_sinpro(calculate, data) result
CPU times: user 8.48 s, sys: 88 ms, total: 8.56 sWall time: 8.59 s1333133340000
从这里咱们能够看到,单过程须要 ~9 秒。
接下来,咱们来看看,如何应用多过程来优化这段代码。
# import multiple processing lib import sys from multiprocessing import Pool, cpu_count from multiprocessing import get_start_method, set_start_method, get_all_start_methods def mulp_map(func, iterable, proc_num): """The function using multi-processes.""" with Pool(proc_num) as pool: results = pool.map(func, iterable) return results def run_mulp(func, data, proc_num): results = mulp_map(func, data, proc_num) total = sum(results) return total %%time result = run_mulp(calculate, data, 4) result
CPU times: user 14 ms, sys: 19 ms, total: 33 msWall time: 3.26 s1333133340000
同样的计算,应用单过程,须要约9秒;在8核的机器上,如果咱们应用多过程则只须要3秒,耗时节俭了 66%。
多过程:设计好计算单元,应尽可能小
咱们来构想一个场景,假如你有10名员工,同时你有10项工作,每项工作中,都由雷同的5项子工作组成。你会如何来做安顿呢?天经地义的,咱们应该把这10名员工,别离安顿到这10项工作中,让这10项工作并行执行,没故障,对吧?然而,在咱们的我的项目中,如果这样来设计并行计算,很可能出问题。
这里是一个实在的例子,最初性能晋升的成果很差。起因是什么呢?(此处可按Pause键,思考一下)
次要的起因有2个,并行的计算单元颗粒度不应太大,大了当前,通常会有数据交换或共享问题。其次,颗粒度大了当前,实现工夫会差异比拟大,造成短板效应。也就是,颗粒度大了当前,工作实现工夫可能会差异很大。
在一个实在的例子中,并行计算须要1个小时,最初剖析后才发现,只有一个过程须要1小时,而其余过程的工作都在5分钟内实现了。
另一个益处是,出错了,好定位,代码也好保护。所以,计算单元应尽可能小。
多过程:防止过程间通信或同步
当咱们把计算单元设计的足够小后,应该尽量避免过程间通信或同步,防止造成期待,影响整体执行工夫。
多过程:调试是个问题,除了log外,尝试gdb / pdb
并行计算的公认问题是,难调试。通常的IDE只能够中断一个过程。通过打印log,并加上pid,来定位问题,会是一个比拟好的办法。留神,并行计算时,不要打太多log。如果你依照下面讲的,先调通了单过程的实现,那么这时,最重要是,打印过程的启动点,过程数据和敞开点,就能够了。比方,观测到某个过程拖了大家的后腿,那就要好好看看那个过程对应的数据。
这是个粗疏活,特地是,当多过程启动后,可能跑着数小时,你也不晓得在产生什么?能够应用linux下的top,或windows下的activity等工具来观测过程的状态。也能够应用gdb / pdb这样的工具,进入某个过程中,看看卡在哪里。
多过程:防止大量数据作为参数传输
在实在的我的项目中,咱们设计的计算单元,不会像下面的简略例子一样,通常都会带有不少参数。这时须要留神,当大数据作为参数传输时,会导致内存耗费很大,并且,子过程的创立也会很慢。
多过程:Fork? Spawn?
Python的多过程反对3种模式去启一个过程,别离是,spawn, fork, forkserver。他们之间的差异是启动速度,和继承的资源。spawn只继承必要的资源,而fork和forkserver则与父过程完全相同。
依赖于不同的操作系统,和不同版本的python,其默认模式也不同。对python 3.8,Windows默认spawn;从python 3.8开始,macOS也默认应用spawn;Unix类OS默认fork;fork和forkserver在windows上不可用。
灵魂拷问:多过程肯定比单过程快吗?
讲到这里,咱们的分享根本能够完结了,对吧?依照python multiprocessing API,找几个例子,并参考我下面说的几点,能解决80%以上的问题。够了,毕竟性能优化也不是天天须要。以下内容可能要从事性能优化一年后,才会思考到,这里写进去,供参考,帮忙当前少走些弯路。
比方,多过程肯定更快吗?
正如第一点所说,任何优化都有开销。当多过程解决不了你的问题时,别忘了试试,改回单过程,说不定就解决了。(这也是一个实在的例子,花了2周去优化一个,10过程也须要3小时能力执行完的程序,改回单过程后,间接跑进30分钟内了。)
优化心理:手里有了锤子,所有都长的像钉子
同上要点,有时候须要的,可能是优化数据结构,而不是多过程。
优化心理:不要科学“专家”
置信很多团队都这样,当我的项目遇到重大技术问题,比方性能须要优化,管理者都会招集一些专家来帮忙。依据我的察看,80%的状况下,没有太多帮忙,有时甚至更糟。
起因很简略,用一句话来说,你花了20个小时解决不了的问题,其他人用5分钟,依据你提供的信息,指出问题所在,可能性很低,无论他相干的教训有如许丰盛。如果不信,你能够回忆下本人的教训,或未来留神察看下,再回过头来看这个观点。为什么可能更糟?因为依赖心理。有了专家的依赖,人们是不会真拼的,“反正有专家指引”。就像尼采说过,“人们要实现一件看似不可能的事时,须要鼓胀到超过本人的能力。”,所以,如果这件事真的很难,你“疯狂”地置信,“这件事只有你能解决,只能靠你本人,其他人都无奈解决”,说不定成果更好。
在一个继续近一个月的性能优化我的项目中,我脑海中时常响起《名侦探柯南》中的一句台词:假相只有一个。我动摇无比地置信,解法离我越来越近,哪怕事实是,一次又一次地失败,但这份信念到最初的胜利帮忙很大。
优化心理:优化可能是一个长期过程,每天都在迷茫中挣扎
性能优化的过程,漫长而煎熬,如果能有一个急躁的听众,会帮忙很大。他/她可能不会帮你指出问题的解决办法,只是急躁地听着,只说,“it will be fine.” 但这样的述说,会帮忙理清思路,能灵感爆发也说不定。这跟生存中其它事件的情理,应该也是一样的吧。
优化心理:管理者帮忙争取时间,加重心理压力
比方,有教训的管理者,会跟业务协商,分阶段交付。而有些同学,则会每隔几小时就过去问下,“性能有晋升吗?” 而后脸上露出一种诡异的表情:“真的有那么难?”
目前我所有晓得的一个案例,其性能优化继续了近一年,期间几拨外协人员,来了,又走了,搞得奔溃。
所以,咱们呐喊,我的项目管理者应该多了解开发人员,帮忙开发人员挡住内部压力,而不是间接透传压力,或者甚至增大压力。
References
https://baike.baidu.com/item/...
- https://www.liaoxuefeng.com/w...
- https://en.wikipedia.org/wiki...
- https://en.wikipedia.org/wiki...:~:text=A global interpreter lock (GIL,on%20a%20multi%2Dcore%20processor.
- https://git.huawei.com/x00349...
- https://docs.python.org/3/lib...
点击关注,第一工夫理解华为云陈腐技术~