如题
两个 for 循环分别 6000 次,来嵌套; 中间休息 5 秒钟;
python 代码如下(版本 2.7 )
golang 的代码如下(版本 1.12)
python 在循环的时候,cpu 消耗 70%
golang 在循环的时候,cpu 消耗 0.6%、
这 2 个仅仅是简单的循环,性能差别就有这么大么,还是我姿势哪里不对?
1
ysc3839 2019-07-16 23:29:16 +08:00
你是如何测量 “ cpu 消耗” 的呢?
|
2
Buffer2Disk OP @ysc3839 看 top 啊
|
3
shakespaces 2019-07-16 23:43:53 +08:00 via Android
其实 python 里面是两个 5999 次😂,虽然和性能没关系
|
4
moodasmood 2019-07-16 23:45:30 +08:00 via Android
我虽然说不出哪里不对,到直觉告诉我,这他妈明显有问题啊。至于哪里有问题,请楼下解答
|
5
niubee1 2019-07-17 00:00:56 +08:00 1
for num in xrange(1, 100):
[(lambda _:time.sleep(5))([j for j in xrange(1, 6000)]) for i in xrange(1, 6000)] 试试嘛 |
6
niubee1 2019-07-17 00:04:41 +08:00
事实上你用 xrange 替换 range 就能让 CPU 开销降到 20%, 用列表解析, 就是我这个就只有 0 附近了。开不开心, 意不意外?
|
7
neoblackcap 2019-07-17 00:10:08 +08:00
range 返回列表,耗时在这
|
8
Buffer2Disk OP |
9
Buffer2Disk OP @neoblackcap 对,但是我要循环的,其实就是列表
|
10
Buffer2Disk OP @neoblackcap 你的意思是,range 返回列表的时候,是消耗 cpu 的主要部分嘛?
我怎么感觉是循环的时候消耗的,我另外一份代码循环的时候,是直接 2 个 list 来嵌套循环的,没有 range 然后加了 sleep(0.1) ,cpu 就降价来了,但是这样的话,循环的耗时就大大增加了 |
11
niubee1 2019-07-17 00:21:09 +08:00
你是要判断一个列表里的内容是不是存在与另外一个列表嘛, 设有列表 l1, l2
for i in [idx for idx, flag in enumerate([ i in l2 for i in l1]) if flag]: print "列表 l1 中 %s 位的元素在列表 l2 中存在......" |
12
Buffer2Disk OP |
13
fy 2019-07-17 00:26:20 +08:00 1
看到 2.7,吓得我赶紧看看日历,怀疑自己穿越了
|
14
niubee1 2019-07-17 00:27:25 +08:00 2
你也可以使用 itertools.product
要高效循环, 肯定要用到 itertools, 你基础库都不搞明白就出来浪, 这样子不好 |
15
neoblackcap 2019-07-17 00:31:32 +08:00 1
@Buffer2Disk 你每次调用 range 都要申请内存,创建一个列表啊,能快才奇怪。你用 top 来计算 CPU 耗时的方法本身就不对。
而且你只是判断一个元素是否在另外一个列表里面,你转成集合,用求交集的方法啊。那个才 O(n),你现在这个粗糙的实现方式可是 O(n^2)。 你觉得是循环在耗时,那么请你在 Python 里面创建好两个列表,然后再开始你的循环计时,而且停 5s 有什么用啊?你写日志不好么? |
16
raysonx 2019-07-17 00:32:07 +08:00 via iPad
Go 是编译型语言。内部的两层循环没有动作默认会被编译器优化掉
|
17
guog 2019-07-17 00:36:17 +08:00 via Android
这都不是一个层次的对比,如果仅仅对比循环,你完全可以用 while 啊,不创建任何 list,CPU 也是零。
|
18
aheadlead 2019-07-17 00:39:27 +08:00 via iPhone 1
上次看到这么比较语言性能的,还是在易语言的官网
|
19
niubee1 2019-07-17 00:40:14 +08:00
之前的例子不是很恰当, 重新写了个
两个长度 6000 的列表对比元素 |
20
Buffer2Disk OP @niubee1 好的,谢谢大佬,我研究研究
|
21
Buffer2Disk OP @niubee1 大佬,我的关注点其实是在 cpu 和 运行耗时 一起的
我试了下你的代码 排除掉了 range 的因素,提前创建好 list,cpu 的消耗依然不低啊(相对于 go 的 cpu 占用来说,使用的是 1 核的虚拟机) 当然我是通过 top 粗略的看了下 python 进程的 cpu 占用,可能不是那么的精确,但是能侧面反映出来大概的占用情况 |
22
Buffer2Disk OP <img src="https://i.loli.net/2019/07/17/5d2e09582d18548030.png" alt="5555.png" title="5555.png" />
|
23
iPhoneXI 2019-07-17 01:29:47 +08:00 via Android
为什么用 2.7 这种老古董?
|
24
niubee1 2019-07-17 01:42:25 +08:00
@Buffer2Disk for 没有优化的, 提速用列表解析或者用 itertools 里的函数代替
|
25
niubee1 2019-07-17 01:44:01 +08:00
当然要说优化了就秒天秒地秒空气了那不可能,Python 确实不快, 只是优化好了并不会那么的慢, 而已。还有个办法是用 Cyhon 来优化这种纯跑圈的逻辑
|
26
blless 2019-07-17 01:46:58 +08:00 via Android
解释型语言跟编译型语言 这种对比完全没意义,编译型语言很多预编译的时候会优化一些语句,比如内联优化之类的…你的 go 的空循环可能就被优化成一个连续执行的 cpu 指令了 基本上 0 切换。不过现在晚了 也没空测试,我只是大概猜一下可能性,有兴趣可以自己找找资料
|
27
blless 2019-07-17 01:50:51 +08:00 via Android
要测试的话,我建议 for 循环里面加一些额外业务处理,然后内联优化应该可以关闭
真的想用 python 可以试试 pypy,自带 jit,直接用 docker 跑一个就可以 |
28
ysc3839 2019-07-17 02:33:26 +08:00
@Buffer2Disk 看 top 的话你怎么知道你看的时刻程序是不是在 sleep ?你给出的结果完全有可能是 Python 在循环而 Golang 在 sleep。
|
29
ysc3839 2019-07-17 02:34:54 +08:00
@Buffer2Disk 另外看到附言说是要解决性能问题,那应该测量代码的执行时间。
|
30
ysc3839 2019-07-17 02:38:58 +08:00
@Buffer2Disk 另外你所说的 CPU 消耗是 CPU 使用率,意思是一段时间内 CPU 非空闲的时间占这段时间的百分比。大部分工具是隔几秒测一下 CPU 使用率,没办法估量全部代码的执行效率。
|
31
LokiSharp 2019-07-17 08:34:27 +08:00
Go 没试过,我只知道 Python 整体比 Java 慢 10 倍
|
32
www5070504 2019-07-17 09:14:26 +08:00
猜测是 golang 可能优化到操作高速缓存或者寄存器了 python 你这样写肯定有频繁读或者写内存
|
33
xdeng 2019-07-17 09:23:03 +08:00
go for 里面没写东西可能会被优化掉的
|
34
dongxiaozhuo 2019-07-17 10:31:05 +08:00 via iPhone
如果以 Golang 的写法为基准,应该是 Python 里面使用 while 判断一个变量和一个数字的比较结果,例如 while i < 6000: 这样,不管使用 range 和 xrange 都是一堆数字的合集,只是类型与方式不同。
|
35
ipwx 2019-07-17 11:59:13 +08:00 5
Python 的 for 循环性能是不可优化的,别想了,不可能的。
去考虑如何优化 Python 的 for 循环是没有意义的,因为一个老道的 Python 程序员,不是在 sentence-level 优化 Python 程序性能的。 比如我要算向量 x + y ( for 循环裸写是 O(N)),我们会直接使用 NumPy (它是用 C 语言写的 Python 库): import numpy as np x = np.asarray(...) y = np.asarray(...) out = x + y 矩阵点积( for 循环裸写是 O(N^2)): M = np.asarray(...) N = np.asarray(...) out = np.dot(M, N) 如果是可以转化成这种张量运算的,就尝试转化一下。如果不能,就上 Cython。比如 Scikit Learn 的 Tree Classifier 系列代码: https://github.com/scikit-learn/scikit-learn/blob/7813f7efb5b2012412888b69e73d76f2df2b50b6/sklearn/tree/_tree.pyx 如果 Cython 再不行,就上 C/C++ 语言。比如 TensorFlow 一坨运算: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/ops/ 有时候偷懒,会用 JIT 引擎把 Python 代码直接在内存中即时编译成机器码,比如: Numba: https://numba.pydata.org/ Pytorch: https://pytorch.org/docs/stable/_modules/torch/jit.html TensorFlow: https://www.tensorflow.org/xla/jit ---- 以上所有总而言之,Python 的优化方法不是去一条一条语句优化,而是直接暴力把整个 block 替换成更高效的实现。所以你考虑优化 python for 循环没有意义。作为一个 Python 程序员,你应该先考虑实现业务逻辑,然后找出真正的性能瓶颈,再考虑用高级手段(以上种种)优化它们。 |
36
ipwx 2019-07-17 12:00:50 +08:00
顺便 Cython 是一种特别发明出来的 C/Python 混合语言,即没有完全的 C 语言功能,也没有完全的 Python 功能,会经过编译器编译成本机代码。适用于 Python 写起来太慢,但是又不需要 C 语言全部功能的场景。
|
37
ipwx 2019-07-17 12:01:08 +08:00
Python 写起来太慢 -> Python 写的代码跑起来太慢。
|
38
ipwx 2019-07-17 12:04:52 +08:00
顺便 Python JIT 还有一个 PyPy,适用于网络编程。
|
39
crella 2019-07-17 12:32:44 +08:00 via Android
你应该在最内层循环加个判断或者赋值,比如 if 1 = true ; a = 1,要不然哪家语言都会优化掉。
|
40
crella 2019-07-17 12:39:13 +08:00 via Android
反正 g4560,我做文本按列切割整理保存的工作,单核,perl6 1500 行 /s,perl5 2300 行 /s,vb.net3.5 3300 行 /s,不会用 python
|
41
crella 2019-07-17 12:40:04 +08:00 via Android
都是读取一行,按 tab 取列,修改文字,io 写入一行
|
42
Buffer2Disk OP @neoblackcap 确实,算差集的时间复杂度要小很多
|
43
shuax 2019-07-17 13:28:37 +08:00
用 sleep 测试效率,学到了。
|
44
SeaRecluse 2019-07-17 14:12:50 +08:00
python3.6 未复现
占用大概 8% |
45
encro 2019-07-17 14:23:20 +08:00
python 每次 for 循环都重新 range 生成列表,而 golang 没有,这明显不是同样的对比啊。
|
46
encro 2019-07-17 14:24:42 +08:00
这种比 cpu 没有意义,使用 time 命令比总执行时间还好点。
|
47
mengzhuo 2019-07-17 14:25:51 +08:00
空的 For 循环会被 Go 的 SSA 干掉的
|
48
Buffer2Disk OP @neoblackcap 不过有个疑问就是,为什么 Python 计算集合的差集的时候时间复杂度是 O(n)呢? python 计算差集原理我不太了解
因为 golang 的官方并没有提供计算差集的库,我去网上看了一些第三方 golang 库的源码,实际上也是通过嵌套循环来实 现集合计算差集的,并且这种嵌套循环在数据量比较大的时候,cpu 占用也并不是太高 |
49
neoblackcap 2019-07-17 15:43:09 +08:00
@Buffer2Disk
简单的理解就是只是用其中迭代其中一个集合,然后判断一下这个元素是否在,每次查询是否在集合的操作是 O(1),n 个就是 O(n) |
50
Buffer2Disk OP @neoblackcap 网上那些 golang 的第三方库, 判断一下这个元素是否在 的这个操作(contains),内部实现也是一个循环,所以实际上也就变成了 2 个循环在叠代
python 我不清楚是怎么个实现法儿的 |
51
Buffer2Disk OP @neoblackcap
我尝试了下,golang 里面用 map 这种哈希结构的集合来存储数据,它判断元素是否存在的时间复杂度也是 O(1) 使用之后,cpu 的消耗 比 单纯的用 for 循环来迭代降下来不少 所以伪代码就是这样 for i in list { if map[i] != null { //存在 } } 两个 6000 个元素的集合情况下 map 时间复杂度 O(n) , cpu 消耗 1% for 循环迭代 O(n^2) ,cpu 消耗 4% 在元素比较多的情况,map 这种能够快速检索 key 的结构还是有优势的 |
52
neoblackcap 2019-07-17 18:27:04 +08:00 via iPhone
@Buffer2Disk 本身集合一般就是用哈希表实现
|
53
Buffer2Disk OP @neoblackcap 没有啊,golang 里面的 list 就是双向链表实现的 = =
|
54
way2create 2019-07-17 23:29:00 +08:00
看见这个帖子...我想起曾经某语言某性能网站上的代码拿到本地一测,调换下测试顺序,结果完全不同了...所以严重怀疑那个网站测试的真实性
|