Python 3.14 会比 C++ 更快!
对最新的、令人印象深刻的 Python 3.11 的基准测试
知乎@春阳CYang
Python是数据科学(DS)和机器学习(ML)中最常用的脚本语言之一。根据PopularitY of Programming Languages,Python 是谷歌搜索最多的语言。除了它是一种将各种 DS/ML 解决方案整合在一起的优秀胶水语言之外,它还有许多其他的库可以对数据做各种各样的事情。
大约在一个月前,我们得到了新的 Python 年度发行版 - 3.11版。我对这个新版本感到非常兴奋,因为这个版本的主要特点是速度显著提高。
在各种社交媒体上,我们已经看到很多人测试新版本的帖子,他们的测试结果令人震惊。但了解 Python3.11 真正有多快的最好方法是自己运行测试。
在这篇文章中,我将分享我对 Python3.11 的逐步分析。所有代码都可以在 我的 github 上找到。
对编程语言进行基准测试一点也不简单。当你觉得 x 比 y 快时,你应该对结果持怀疑态度。算法的一个实现可能比 x 好,而另一个实现比 y 好。对于我们的基准测试来说,我们尽可能的希望它能简单一些,因为我们正在用 Python 测试 Python,但我们可能已经从语言中选择了一些影响不大的元素。考虑到这一点,我想介绍我用于基准测试的算法:基因组组装算法 DNA K-mers。
基因组组装算法 DNA K-mers。
这个算法的思想很简单,DNA 是一个长串序列,称为核苷酸。在 DNA 中,有 4 种核苷酸以字母 A、C、G 和 T 表示。人类(或者更准确地说是智人)有 30 亿个核苷酸对。例如,人类 DNA 的一小部分可能是:
ACTAGGGATCATGAAGATAATGTTGGTGTTTGTATGGTTTTCAGACAATT
在这个例子中,如果想从这个字符串中选择任何 4 个连续的核苷酸(即字母),它将是一个长度为 4 的k-mer(我们称之为4-mer)。以下是从示例中导出的一些 4-mers 示例。
ACTA, CTAG, TAGG, AGGG, GGGA, etc.
对于本文,让我们生成所有可能的 13-mers。从数学上讲,这是一个置换问题。因此,我们有 4¹³(=67108864)可能的 13-mers。我用一个简单的算法在 C++ 和 Python 中生成结果。让我们看看不同 Python 版本和 C++ 的比较结果。
对比 Python 和 C++ 的结果
Python 不同版本的测试
def K_mer(k: int) -> None:
def convert(c):
if c == "A":
return "C"
if c == "C":
return "G"
if c == "G":
return "T"
if c == "T":
return "A"
print("开始")
opt = "ACGT"
s = ""
s_last = ""
len_str = k
for i in range(len_str):
s += opt[0]
for i in range(len_str):
s_last += opt[-1]
pos = 0
counter = 1
while s != s_last:
counter += 1
# 你可以取消下一行的注释来查看所有的 k-mers.
# print(s)
change_next = True
for i in range(len_str):
if change_next:
if s[i] == opt[-1]:
s = s[:i] + convert(s[i]) + s[i + 1 :] # type: ignore
change_next = True
else:
s = s[:i] + convert(s[i]) + s[i + 1 :] # type: ignore
break
# 你可以取消下一行的注释来查看所有的 k-mers.
# print(s)
print("生成 k-mers 的数量: {}".format(counter))
print("完成!")
上面这个脚本已经可以运行了,但是我们希望使用它来测试 Python 的各种版本,而不仅仅是当前安装(或激活)的版本。测试多个 Python 版本的最简单方法是使用 Docker。Python 维护许多 docker 镜像。当然,所有受支持的版本,也包括一些生命周期结束(EOL)的版本,如 2.7 或 3.2。要使用 Docker,您需要安装它。在 Linux 和 Mac 上相对容易,在 Windows 上我不太确定,但可能也不难。我建议只安装 docker CLI,桌面对我来说太臃肿了。要在容器化的 Python 环境中运行本地脚本,请运行:
docker run -it --rm \
-v $PWD/your_script.py:/your_script.py \
python:3.11-rc-slim \
python /yourscript.py
为了自动化各种版本的测试,我们当然也会使用 Python。这个脚本只需启动一个子进程,以启动具有特定 Python 版本的容器,然后收集结果。没什么特别的:
import time
import argparse
def K_mer(k: int) -> None:
def convert(c):
if c == "A":
return "C"
if c == "C":
return "G"
if c == "G":
return "T"
if c == "T":
return "A"
print("开始")
opt = "ACGT"
s = ""
s_last = ""
len_str = k
for i in range(len_str):
s += opt[0]
for i in range(len_str):
s_last += opt[-1]
pos = 0
counter = 1
while s != s_last:
counter += 1
# 你可以取消下一行的注释来查看所有的 k-mers.
# print(s)
change_next = True
for i in range(len_str):
if change_next:
if s[i] == opt[-1]:
s = s[:i] + convert(s[i]) + s[i + 1 :] # type: ignore
change_next = True
else:
s = s[:i] + convert(s[i]) + s[i + 1 :] # type: ignore
break
# 你可以取消下一行的注释来查看所有的 k-mers.
# print(s)
print("生成 k-mers 的数量: {}".format(counter))
print("完成!")
def run_test(k: int) -> None:
start_time = time.time()
K_mer(k)
print(f"{(time.time() - start_time):.4f}秒")
def main():
"""Main loop in arg parser."""
parser = argparse.ArgumentParser()
parser.add_argument(
"-k",
"--k_mer",
help="DNA 序列长度",
type=int,
default=13,
)
args = parser.parse_args()
run_test(args.k_mer)
if __name__ == "__main__":
main()
运行这些测试时,根据处理器的不同,绝对值会因机器而异(它占用大量 CPU)。以下是最近 7 个主要 Python 版本的结果:
新版本 Python 3.11 花费了 31.9501 秒.
Python 3.5 花费了 63.3502 秒.(Python 3.11 比它快 98.3% )
Python 3.6 花费了 62.9635 秒.(Python 3.11 比它快 97.1% )
Python 3.7 花费了 57.5806 秒.(Python 3.11 比它快 80.2% )
Python 3.8 花费了 59.0129 秒.(Python 3.11 比它快 84.7% )
Python 3.9 花费了 54.4991 秒.(Python 3.11 比它快 70.6% )
Python 3.10 花费了 47.7407 秒.(Python 3.11 比它快 49.4% )
Python 3.11 的基准测试平均耗时 6.46 秒。与之前的版本(3.10)相比,这几乎快了 37%。非常令人印象深刻!版本 3.9 和 3.10 之间的差异大致相同,使 3.11 几乎快了 70%!把所有的点在下图中画出来。
C++ 的测试
当谈到速度时,我们总是有一个人说:如果你想要速度,为什么不使用 C。
C is much faster that Python! — that one guy
下面我们用 C++ 实现上面的 K-mers 算法。
#include <iostream>
#include <string>
using namespace std;
char convert(char c)
{
if (c == 'A') return 'C';
if (c == 'C') return 'G';
if (c == 'G') return 'T';
if (c == 'T') return 'A';
return ' ';
}
int main()
{
cout << "开始" << endl;
string opt = "ACGT";
string s = "";
string s_last = "";
int len_str = 13;
bool change_next;
for (int i=0; i<len_str; i++)
{
s += opt[0];
}
for (int i=0; i<len_str; i++)
{
s_last += opt.back();
}
int pos = 0;
int counter = 1;
while (s != s_last)
{
counter ++;
// You can uncomment the next line to see all k-mers.
// cout << s << endl;
change_next = true;
for (int i=0; i<len_str; i++)
{
if (change_next)
{
if (s[i] == opt.back())
{
s[i] = convert(s[i]);
change_next = true;
} else {
s[i] = convert(s[i]);
break;
}
}
}
}
// You can uncomment the next line to see all k-mers.
// cout << s << endl;
cout << "生成 k-mers 的数量: " << counter << endl;
cout << "完成!" << endl;
return 0;
}
众所周知,C++ 是一种编译语言,因此,我们需要先编译源代码,然后才能使用它。安装了 C++ 的基础软件之后,可以键入:
g++ -o k_mer k_mer.c
编译后,只需运行构建可执行文件。输出应如下所示:
生成 k-mers 的数量: 67108864 花费时间: 8.92218 秒
生成 k-mers 的数量: 67108864 花费时间: 9.01176 秒
生成 k-mers 的数量: 67108864 花费时间: 8.88735 秒
生成 k-mers 的数量: 67108864 花费时间: 8.9754 秒
每个循环平均需要 8.945 秒来计算。
我们必须同意这一点,因为它真的很快。只花了 8.945 秒就完成了我们以前用 Python 编程的相同循环。让我们把它作为一条线添加到前面的绘图中,如下图所示。
现在,在对前面的数字进行了长时间的分析之后,我们清楚地看到了Python的发展势头。自版本 3.9 以来,Python 的速度提高了约 35%。Python开发人员提到,接下来的两个版本将有显著的速度提升,因此,我们可以假设这个速度将保持不变(是的,超级安全的假设)。
现在的问题是,在这种势头得到修正的情况下,Python 何时才能超越 C++ 时代。为此,我们当然可以使用推断来预测下一个 Python 版本的运行时间。这些可以在下图中看到:
结果真是太棒了!如果保持这种迭代的速度,Python 3.14 将比 C++ 更快。
声明
虽然这些 Python3.5 到 Python3.11 的基准测试是有效的,但这种推断当然只是一个玩笑。XKCD 风格的画图也是另一种提醒;-)
如果您想在各种 Python 版本上运行这些测试或您自己的测试,请在我的 Github 页面上下载代码。
如果您有任何意见,请告诉我!
本文由mdnice多平台发布