V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
Buffer2Disk
V2EX  ›  程序员

Python 和 Go 在循环时候的性能对比

  •  1
     
  •   Buffer2Disk · Jul 16, 2019 · 7890 views
    This topic created in 2478 days ago, the information mentioned may be changed or developed.

    如题

    两个 for 循环分别 6000 次,来嵌套; 中间休息 5 秒钟;

    python 代码如下(版本 2.7 )

    333.png

    golang 的代码如下(版本 1.12)

    111.png

    python 在循环的时候,cpu 消耗 70%

    golang 在循环的时候,cpu 消耗 0.6%、

    这 2 个仅仅是简单的循环,性能差别就有这么大么,还是我姿势哪里不对?

    Supplement 1  ·  Jul 16, 2019
    看网上说,python 那边,for 循环内部加个 sleep(0.1) , cpu 就降下来了,但是这样的话就牺牲了性能了。。。
    Supplement 2  ·  Jul 16, 2019
    其实原本是这样的,两个 list 互相嵌套循环,然后判断内部的那个 list 的每个元素 是否 在外部的 list 中存在,

    然后发现 python 情景下的性能特别低,最后就抽象成了上面两个 for 循环来对比
    Supplement 3  ·  Jul 17, 2019
    本帖是以探讨问题为主,不是专门为了比较语言优劣势哈
    Supplement 4  ·  Jul 17, 2019
    有人说到是 range 的问题,更新一下 python 的代码,提前组装好 list

    <img src="https://i.loli.net/2019/07/17/5d2e09582d18548030.png" alt="5555.png" title="5555.png" />
    Supplement 5  ·  Jul 17, 2019
    有人说是 go 在编译时候,内部对这种 空循环 做过了优化
    我在 go 的 for 循环里面加了 println 打印后,cpu 确实有变化了,大概 20%的占用,还是没有 python 那么的夸张

    所以 python 的这种大数循环有没有办法优化呢
    Supplement 6  ·  Jul 17, 2019
    目前找到了一个办法
    因为我的需求其实本来是算 2 个 list 的差集,然后抽象成了上面的 2 个嵌套的 for 循环

    所以像下面这么写就可以了

    c = set(a).difference(b) //差集 a-b

    看了下 top,两个大的 list(各 6000 个元素)算差集,cpu 占用不到 4%

    但是我也没太明白这么写为啥就性能提升了,可能是实现方式上和单纯的 for 循环有区别。。。。
    有没有大牛解释下。。。
    Supplement 7  ·  Jul 17, 2019
    网上说用 set 来处理差集比 list 更快,我没太懂这句话的意思,

    是因为 set 去除掉了 list 中的重复元素导致变快还是什么其他原因
    Supplement 8  ·  Jul 17, 2019
    我猜测是因为 set 不需要排序 + 外加去除了重复元素

    所以会比 list 算差集的时候更快
    Supplement 9  ·  Jul 17, 2019
    我猜测是因为 set 不需要排序 + 外加去除了重复元素

    所以会比 list 算差集的时候更快
    Supplement 10  ·  Jul 17, 2019
    晕,v2 最近这么卡,连发了 2 条。。。
    54 replies    2019-07-17 23:29:00 +08:00
    ysc3839
        1
    ysc3839  
       Jul 16, 2019
    你是如何测量 “ cpu 消耗” 的呢?
    Buffer2Disk
        2
    Buffer2Disk  
    OP
       Jul 16, 2019
    @ysc3839 看 top 啊
    shakespaces
        3
    shakespaces  
       Jul 16, 2019 via Android
    其实 python 里面是两个 5999 次😂,虽然和性能没关系
    moodasmood
        4
    moodasmood  
       Jul 16, 2019 via Android
    我虽然说不出哪里不对,到直觉告诉我,这他妈明显有问题啊。至于哪里有问题,请楼下解答
    niubee1
        5
    niubee1  
       Jul 17, 2019   ❤️ 1
    for num in xrange(1, 100):
    [(lambda _:time.sleep(5))([j for j in xrange(1, 6000)]) for i in xrange(1, 6000)]

    试试嘛
    niubee1
        6
    niubee1  
       Jul 17, 2019
    事实上你用 xrange 替换 range 就能让 CPU 开销降到 20%, 用列表解析, 就是我这个就只有 0 附近了。开不开心, 意不意外?
    neoblackcap
        7
    neoblackcap  
       Jul 17, 2019
    range 返回列表,耗时在这
    Buffer2Disk
        8
    Buffer2Disk  
    OP
       Jul 17, 2019
    @niubee1 我要循环的其实是 2 个 list,只不过抽象成了上面的 range,

    list 有办法优化么。。。。
    Buffer2Disk
        9
    Buffer2Disk  
    OP
       Jul 17, 2019
    @neoblackcap 对,但是我要循环的,其实就是列表
    Buffer2Disk
        10
    Buffer2Disk  
    OP
       Jul 17, 2019
    @neoblackcap 你的意思是,range 返回列表的时候,是消耗 cpu 的主要部分嘛?

    我怎么感觉是循环的时候消耗的,我另外一份代码循环的时候,是直接 2 个 list 来嵌套循环的,没有 range

    然后加了 sleep(0.1) ,cpu 就降价来了,但是这样的话,循环的耗时就大大增加了
    niubee1
        11
    niubee1  
       Jul 17, 2019
    你是要判断一个列表里的内容是不是存在与另外一个列表嘛, 设有列表 l1, l2
    for i in [idx for idx, flag in enumerate([ i in l2 for i in l1]) if flag]:
    print "列表 l1 中 %s 位的元素在列表 l2 中存在......"
    Buffer2Disk
        12
    Buffer2Disk  
    OP
       Jul 17, 2019
    @niubee1 我看你这个是用 in 和 not in 来判断元素是否在 list 存在是嘛?

    这个我试过了,cpu 消耗依然很高,估计 in 和 not in 的内部实现也是通过这种循环来实现的
    fy
        13
    fy  
       Jul 17, 2019   ❤️ 1
    看到 2.7,吓得我赶紧看看日历,怀疑自己穿越了
    niubee1
        14
    niubee1  
       Jul 17, 2019   ❤️ 2
    你也可以使用 itertools.product

    要高效循环, 肯定要用到 itertools, 你基础库都不搞明白就出来浪, 这样子不好
    neoblackcap
        15
    neoblackcap  
       Jul 17, 2019   ❤️ 1
    @Buffer2Disk 你每次调用 range 都要申请内存,创建一个列表啊,能快才奇怪。你用 top 来计算 CPU 耗时的方法本身就不对。
    而且你只是判断一个元素是否在另外一个列表里面,你转成集合,用求交集的方法啊。那个才 O(n),你现在这个粗糙的实现方式可是 O(n^2)。
    你觉得是循环在耗时,那么请你在 Python 里面创建好两个列表,然后再开始你的循环计时,而且停 5s 有什么用啊?你写日志不好么?
    raysonx
        16
    raysonx  
       Jul 17, 2019 via iPad
    Go 是编译型语言。内部的两层循环没有动作默认会被编译器优化掉
    guog
        17
    guog  
       Jul 17, 2019 via Android
    这都不是一个层次的对比,如果仅仅对比循环,你完全可以用 while 啊,不创建任何 list,CPU 也是零。
    aheadlead
        18
    aheadlead  
       Jul 17, 2019 via iPhone   ❤️ 1
    上次看到这么比较语言性能的,还是在易语言的官网
    niubee1
        19
    niubee1  
       Jul 17, 2019
    之前的例子不是很恰当, 重新写了个
    两个长度 6000 的列表对比元素
    Buffer2Disk
        20
    Buffer2Disk  
    OP
       Jul 17, 2019
    @niubee1 好的,谢谢大佬,我研究研究
    Buffer2Disk
        21
    Buffer2Disk  
    OP
       Jul 17, 2019
    @niubee1 大佬,我的关注点其实是在 cpu 和 运行耗时 一起的

    我试了下你的代码

    排除掉了 range 的因素,提前创建好 list,cpu 的消耗依然不低啊(相对于 go 的 cpu 占用来说,使用的是 1 核的虚拟机)

    当然我是通过 top 粗略的看了下 python 进程的 cpu 占用,可能不是那么的精确,但是能侧面反映出来大概的占用情况
    Buffer2Disk
        22
    Buffer2Disk  
    OP
       Jul 17, 2019
    <img src="https://i.loli.net/2019/07/17/5d2e09582d18548030.png" alt="5555.png" title="5555.png" />
    iPhoneXI
        23
    iPhoneXI  
       Jul 17, 2019 via Android
    为什么用 2.7 这种老古董?
    niubee1
        24
    niubee1  
       Jul 17, 2019
    @Buffer2Disk for 没有优化的, 提速用列表解析或者用 itertools 里的函数代替
    niubee1
        25
    niubee1  
       Jul 17, 2019
    当然要说优化了就秒天秒地秒空气了那不可能,Python 确实不快, 只是优化好了并不会那么的慢, 而已。还有个办法是用 Cyhon 来优化这种纯跑圈的逻辑
    blless
        26
    blless  
       Jul 17, 2019 via Android
    解释型语言跟编译型语言 这种对比完全没意义,编译型语言很多预编译的时候会优化一些语句,比如内联优化之类的…你的 go 的空循环可能就被优化成一个连续执行的 cpu 指令了 基本上 0 切换。不过现在晚了 也没空测试,我只是大概猜一下可能性,有兴趣可以自己找找资料
    blless
        27
    blless  
       Jul 17, 2019 via Android
    要测试的话,我建议 for 循环里面加一些额外业务处理,然后内联优化应该可以关闭
    真的想用 python 可以试试 pypy,自带 jit,直接用 docker 跑一个就可以
    ysc3839
        28
    ysc3839  
       Jul 17, 2019
    @Buffer2Disk 看 top 的话你怎么知道你看的时刻程序是不是在 sleep ?你给出的结果完全有可能是 Python 在循环而 Golang 在 sleep。
    ysc3839
        29
    ysc3839  
       Jul 17, 2019
    @Buffer2Disk 另外看到附言说是要解决性能问题,那应该测量代码的执行时间。
    ysc3839
        30
    ysc3839  
       Jul 17, 2019
    @Buffer2Disk 另外你所说的 CPU 消耗是 CPU 使用率,意思是一段时间内 CPU 非空闲的时间占这段时间的百分比。大部分工具是隔几秒测一下 CPU 使用率,没办法估量全部代码的执行效率。
    LokiSharp
        31
    LokiSharp  
       Jul 17, 2019
    Go 没试过,我只知道 Python 整体比 Java 慢 10 倍
    www5070504
        32
    www5070504  
       Jul 17, 2019
    猜测是 golang 可能优化到操作高速缓存或者寄存器了 python 你这样写肯定有频繁读或者写内存
    46fo
        33
    46fo  
       Jul 17, 2019
    go for 里面没写东西可能会被优化掉的
    dongxiaozhuo
        34
    dongxiaozhuo  
       Jul 17, 2019 via iPhone
    如果以 Golang 的写法为基准,应该是 Python 里面使用 while 判断一个变量和一个数字的比较结果,例如 while i < 6000: 这样,不管使用 range 和 xrange 都是一堆数字的合集,只是类型与方式不同。
    ipwx
        35
    ipwx  
       Jul 17, 2019   ❤️ 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 程序员,你应该先考虑实现业务逻辑,然后找出真正的性能瓶颈,再考虑用高级手段(以上种种)优化它们。
    ipwx
        36
    ipwx  
       Jul 17, 2019
    顺便 Cython 是一种特别发明出来的 C/Python 混合语言,即没有完全的 C 语言功能,也没有完全的 Python 功能,会经过编译器编译成本机代码。适用于 Python 写起来太慢,但是又不需要 C 语言全部功能的场景。
    ipwx
        37
    ipwx  
       Jul 17, 2019
    Python 写起来太慢 -> Python 写的代码跑起来太慢。
    ipwx
        38
    ipwx  
       Jul 17, 2019
    顺便 Python JIT 还有一个 PyPy,适用于网络编程。
    crella
        39
    crella  
       Jul 17, 2019 via Android
    你应该在最内层循环加个判断或者赋值,比如 if 1 = true ; a = 1,要不然哪家语言都会优化掉。
    crella
        40
    crella  
       Jul 17, 2019 via Android
    反正 g4560,我做文本按列切割整理保存的工作,单核,perl6 1500 行 /s,perl5 2300 行 /s,vb.net3.5 3300 行 /s,不会用 python
    crella
        41
    crella  
       Jul 17, 2019 via Android
    都是读取一行,按 tab 取列,修改文字,io 写入一行
    Buffer2Disk
        42
    Buffer2Disk  
    OP
       Jul 17, 2019
    @neoblackcap 确实,算差集的时间复杂度要小很多
    momo1999
        43
    momo1999  
       Jul 17, 2019
    用 sleep 测试效率,学到了。
    SeaRecluse
        44
    SeaRecluse  
       Jul 17, 2019
    python3.6 未复现
    占用大概 8%
    encro
        45
    encro  
       Jul 17, 2019
    python 每次 for 循环都重新 range 生成列表,而 golang 没有,这明显不是同样的对比啊。
    encro
        46
    encro  
       Jul 17, 2019
    这种比 cpu 没有意义,使用 time 命令比总执行时间还好点。
    mengzhuo
        47
    mengzhuo  
       Jul 17, 2019
    空的 For 循环会被 Go 的 SSA 干掉的
    Buffer2Disk
        48
    Buffer2Disk  
    OP
       Jul 17, 2019
    @neoblackcap 不过有个疑问就是,为什么 Python 计算集合的差集的时候时间复杂度是 O(n)呢? python 计算差集原理我不太了解

    因为 golang 的官方并没有提供计算差集的库,我去网上看了一些第三方 golang 库的源码,实际上也是通过嵌套循环来实

    现集合计算差集的,并且这种嵌套循环在数据量比较大的时候,cpu 占用也并不是太高
    neoblackcap
        49
    neoblackcap  
       Jul 17, 2019
    @Buffer2Disk
    简单的理解就是只是用其中迭代其中一个集合,然后判断一下这个元素是否在,每次查询是否在集合的操作是 O(1),n 个就是 O(n)
    Buffer2Disk
        50
    Buffer2Disk  
    OP
       Jul 17, 2019
    @neoblackcap 网上那些 golang 的第三方库, 判断一下这个元素是否在 的这个操作(contains),内部实现也是一个循环,所以实际上也就变成了 2 个循环在叠代

    python 我不清楚是怎么个实现法儿的
    Buffer2Disk
        51
    Buffer2Disk  
    OP
       Jul 17, 2019
    @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 的结构还是有优势的
    neoblackcap
        52
    neoblackcap  
       Jul 17, 2019 via iPhone
    @Buffer2Disk 本身集合一般就是用哈希表实现
    Buffer2Disk
        53
    Buffer2Disk  
    OP
       Jul 17, 2019
    @neoblackcap 没有啊,golang 里面的 list 就是双向链表实现的 = =
    way2create
        54
    way2create  
       Jul 17, 2019
    看见这个帖子...我想起曾经某语言某性能网站上的代码拿到本地一测,调换下测试顺序,结果完全不同了...所以严重怀疑那个网站测试的真实性
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5897 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 118ms · UTC 02:57 · PVG 10:57 · LAX 19:57 · JFK 22:57
    ♥ Do have faith in what you're doing.