鱼C论坛

 找回密码
 立即注册
查看: 2467|回复: 3

为什么没有一个语言运算起来像C或C++一样快,编写起来像Python一样简单

[复制链接]
发表于 2019-8-17 22:09:12 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
不懂就是聊聊
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2019-8-17 22:11:53 | 显示全部楼层
可以用 PyPy

传送门 --> 点我!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-17 22:16:09 | 显示全部楼层
鱼与熊掌不可兼得,封装越高的语言效率肯定越低,但是越简单,越低级的语言基本没什么封装接近汇编(C语言)效率几乎等于汇编。

评分

参与人数 1荣誉 +5 鱼币 +5 贡献 +3 收起 理由
人造人 + 5 + 5 + 3 无条件支持楼主!

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-17 22:27:03 | 显示全部楼层
迷雾少年 发表于 2019-8-17 22:16
鱼与熊掌不可兼得,封装越高的语言效率肯定越低,但是越简单,越低级的语言基本没什么封装接近汇编(C语言)效率 ...

我赞同但可能以后就有可能鱼和熊掌可兼得意!作者:Coldwings
链接:https://www.zhihu.com/question/62185153/answer/196193958
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这个八皇后的DFS,我的C++代码在不加某些评估性剪枝的情况下对15需要算18s左右(开O2大约8.6秒,与题主描述基本一致),但是可以确定的是你的解决方案里用了循环与递归。接下来需要分析的无非是Python慢在哪个细节,以及能否改进的问题。下面是两段用来测试的代码,首先是Python的:#!/usr/bin/env python3

import time

def calc(n, i=0, cols=0, diags=0, trans=0):
    if i == n:
        return 1
    else:
        rt = 0
        for j in range(n):
            col = 1 << j
            diag = 1 << (i - j + n - 1)
            tran = 1 << (i + j)
            if (col & cols) == 0 and (diag & diags) == 0 and (tran & trans) == 0:
                rt += calc(n, i+1, cols | col, diags | diag, trans | tran)
        return rt

if __name__ == '__main__':
    t = time.time()
    print(calc(13))
    print(time.time() - t)
以及C++代码:#include <chrono>
#include <iostream>

using namespace std;

long calc(int n, int i = 0, long cols = 0, long diags = 0, long trans = 0) {
    if (i == n) {
        return 1;
    } else {
        long rt = 0;
        for (int j = 0; j < n; j++) {
            long col = (1 << j);
            long diag = (1 << (i - j + n - 1));
            long tran = (1 << (i + j));
            if (!(col & cols) && !(diag & diags) && !(tran & trans)) {
                rt += calc(n, i + 1, col | cols, diag | diags, tran | trans);
            }
        }
        return rt;
    }
}

int main() {
    auto t = chrono::system_clock::now();
    cout << calc(13) << endl;
    cout << (chrono::system_clock::now() - t).count() * 1e-6 << endl;
    return 0;
}这里的C++代码没按照OOP去写,怎么简单怎么来吧……测试机器配置是Core i7 4870HQ,编译器用的Clang++ 8.1.0,Python解释器则是CPython 3.6.0。没测试15的数据量只测试一下13,因为15太费时间了……由于这里压根不涉及多线程问题,那基本上就跟GIL没有半毛钱关系了。对于n=13,C++代码跑了0.48秒。为了确保不是编译器悄悄干了活,我特地打成了-O0(实际上开O2能到0.2秒左右)。Python跑了24秒。对于这个例子,最直接的影响其实在于:Python是逐句解释执行的,C++是先编译成本地代码,期间还有编译期的类型检查,不存在动态类型、动态检查,并且可以进行编译器优化。之后应该考虑一下能不能提高一点点效率呢?然后根据一般规律,Python的循环很慢,我们可以考虑改成列表展开:def calc(n, i=0, cols=0, diags=0, trans=0):
    if i == n:
        return 1
    else:
        return sum(
            [
                calc(n, i + 1, cols | (1 << j), diags | (1 << (i - j + n - 1)), trans | (1 << (i + j)))
                for j in range(n)
                if (cols & (1 << j)) == 0 and (diags & (1 << (i - j + n - 1))) == 0 and (trans & (1 << (i + j))) == 0
            ]
        )理应速度更快,实时也验证了:这样的Python代码需要跑18秒左右。仍然存在数量级的差异,并没有解决根本问题,但是说明了一点,CPython中for loop的实现其实一点都不快。而后考虑一下,如果我们使用其它解释器,特别是包含JIT的解释器,它将在执行过程中尝试将代码编译成本地二进制编码并执行,同时还能赋予一些额外优化,会不会好很多?那么单纯地尝试一下PyPy3(5.8.0-beta, Python 3.5.3),代码能有多快?实际上,单纯的只是替换一下解释器,换成PyPy来做的话,原本这个24s的Python源码就只需要1s左右了。单单一个JIT可以使得性能提升一个数量级,充分说明官方的CPython解释器的性能真心很烂……PyPy的JIT比较简单纯粹,并不是很激进,但是同样的代码如果能借助更好的JIT,以及更高性能的库,则可以体现出完全不同的性能差。例如,如果使用llvm做JIT,同时加上能使用一些成熟的数学库做优化。我们知道NumPy这样的C扩展能够很大程度提高Python做数值计算的性能,同样的我们也可以用Cython或者直接用C写Python扩展来强化计算能力。但是人都是懒的,重新写代码实在是有些麻烦。对于Python这种生态强大的玩意来说,如果你的计算代码中只是单纯的使用了numpy的简单结构以及Python自身的标准结构,使用numba可能是最简单快速的办法。#!/usr/bin/env python3

import time


from numba import jit


@jit
def calc(n, i=0, cols=0, diags=0, trans=0):
    if i == n:
        return 1
    else:
        rt = 0
        for j in range(n):
            col = 1 << j
            diag = 1 << (i - j + n - 1)
            tran = 1 << (i + j)

            if (col & cols) == 0 and (diag & diags) == 0 and (tran & trans) == 0:
                rt += calc(n, i+1, cols | col, diags | diag, trans | tran)
        return rt



if __name__ == '__main__':
    t = time.time()
    print(calc(13))
    print(time.time() - t)
这里只是很简单地加入了两行代码:从numba导入jit,用jit装饰我们的计算函数。这段代码的运行时间直接就缩短到了0.4s,和C++版本的O0编译后的程序速度几乎一样。这还是考虑到JIT需要预热的情况在内。这段代码,若是计算15的规模,只需要6.5s左右,甚至优于开O2的C++版本。究其原因,JIT不仅仅在运行过程中将代码转为本地机器码,同时还会尝试进行优化。如果用cProfile之类的玩意分析一下运行过程,可以清楚看到这个优化过程。

评分

参与人数 1荣誉 +5 鱼币 +5 收起 理由
迷雾少年 + 5 + 5 这个有点吊

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 1 反对 0

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-10-18 13:23

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表