Python中快速的字符串转整数方法

6
一个简单的问题:你有10亿(1e+9)个无符号32位整数,以十进制ASCII字符串的形式存储在TSV(制表符分隔值)文件中。与在相同数据集上工作的其他工具相比,使用int()进行转换非常缓慢。为什么?更重要的是:如何使它更快?
因此,问题是:在Python中将字符串转换为整数的最快方法是什么?
我真正考虑的是一些半隐藏的Python功能,可以(滥用)用于此目的,类似于Guido在他的{{link1:“Optimization Anecdote”}}中使用array.array
示例数据(制表符展开为空格)
38262904        "pfv"              2002-11-15T00:37:20+00:00
12311231        "tnealzref"        2008-01-21T20:46:51+00:00
26783384        "hayb"             2004-02-14T20:43:45+00:00
812874          "qevzasdfvnp"      2005-01-11T00:29:46+00:00
22312733        "bdumtddyasb"      2009-01-17T20:41:04+00:00

这里读取数据所需的时间并不重要,处理数据才是瓶颈。
微基准测试
以下所有语言都是解释型语言。主机运行64位Linux。
Python 2.6.2与IPython 0.9.1,每秒约214k次转换(100%):
In [1]: strings = map(str, range(int(1e7)))

In [2]: %timeit map(int, strings);
10 loops, best of 3: 4.68 s per loop

REBOL 3.0 版本 2.100.76.4.2, ~231kcps (108%):

>> strings: array n: to-integer 1e7 repeat i n [poke strings i mold (i - 1)]
== "9999999"

>> delta-time [map str strings [to integer! str]]
== 0:00:04.328675

REBOL 2.7.6.4.2 (2008年3月15日),~523kcps(261%):

正如John在评论中指出的那样,此版本不会构建转换后整数的列表,因此给出的速度比是相对于Python运行for str in strings: int(str)的4.99秒。

>> delta-time: func [c /local t] [t: now/time/precise do c now/time/precise - t]

>> strings: array n: to-integer 1e7 repeat i n [poke strings i mold (i - 1)]
== "9999999"

>> delta-time [foreach str strings [to integer! str]]
== 0:00:01.913193

KDB+ 2.6t 2009.04.15,~2016kcps(944%):

q)strings:string til "i"$1e7

q)\t "I"$strings
496

4
尝试使用numpy.fromfile来读取“十亿个正整数”(顺便问一下,“十亿”是什么意思(在美国是10的9次方,在英国可能是10的12次方)? - jfs
1
你尝试编译代码了吗? - João Silva
1
(1)请具体说明“作为文本文件中的ASCII字符串存储”。是固定列还是分隔符?文件中只有这一类型的数据吗?请展示几行示例。 (2)如果您希望我们相信int()存在问题,并且这不是一个作业问题,请向我们展示您目前正在使用的代码。 (3)请将速度表达为国际单位制(SI单位),而不是“极慢”。 (4)还有哪些工具可供选择? (5)使用的平台和Python版本是什么? - John Machin
1
(6) 一个整数的平均位数是多少? (7) 这些数字是十进制/十六进制/八进制/其他什么? - John Machin
KDB和最新的REBOL 3代码可以,但REBOL 2代码不行。我更新了测量数据以反映这一点。 - earl
显示剩余3条评论
7个回答

4
以下是最简单的C扩展,相比内置的扩展已经有了很大的提升,每秒可以转换三倍以上的字符串(650kcps vs 214kcps):
static PyObject *fastint_int(PyObject *self, PyObject *args) {
    char *s; unsigned r = 0;
    if (!PyArg_ParseTuple(args, "s", &s)) return NULL;
    for (r = 0; *s; r = r * 10 + *s++ - '0');
    return Py_BuildValue("i", r);
}

这显然不能处理任意长度的整数和其他特殊情况,但在我们的场景中这不是问题。

1
有没有不使用C标准库函数(例如strtoul())的理由? - jfs

3

通过确保在最紧密的循环中仅使用“本地”变量,您将获得一定比例的速度。 int 函数是全局的,因此查找它将比查找本地变量更加昂贵。

您真的需要始终在内存中拥有所有十亿个数字吗? 考虑使用一些迭代器,每次仅提供少量值。 十亿个数字将需要一些存储空间。 逐个将其附加到列表中将需要进行几次大型重新分配。

如果可能,请完全将循环功能移出Python。在这里,map函数可能是您的好朋友。 我不确定数据如何存储。 如果每行只有一个数字,则可以将代码简化为

values = map(int, open("numberfile.txt"))

如果每行有多个以空格分隔的值,请使用itertools深入挖掘,以避免循环代码进入Python。此版本的附加好处是创建数字迭代器,因此您可以一次仅从文件中读取一个或几个数字,而不是一次读取十亿个数字。
numfile = open("numberfile.txt")
valIter = itertools.imap(int, itertools.chain(itertools.imap(str.split, numfile)))

2
我建议,为了获得更快的速度,Python 不是这个任务的最佳工具。手写的 C 代码实现将轻松击败 Python。

3
我完全同意,但那并不是我问题的要点。我添加了一个段落来说明我的需求。一个定制的Python扩展可能是一个选择。 - earl

1

同意Greg的观点;Python作为一种解释性语言,通常速度较慢。您可以尝试使用Psyco库即时编译源代码,或者使用低级语言如C/C++编写应用程序。


2
在解释上出现-1会导致速度变慢。在这种情况下,C实现会更快,但是你的概括是错误的。 - Yuval Adam
一种解释性语言必须在执行时被翻译成机器代码,这比执行已编译的目标代码慢。仍然不明白您为什么要踩我的评论。请解释一下为什么您认为我的“概括”是错误的。 - ramosg
2
解释性语言可以在运行时对字节码进行优化,有时会比本机机器代码具有更好的性能。查一下吧,这个话题已经被讨论烂了。 - Yuval Adam
嗯,我想90%的情况并不足以概括,所以进行了编辑。 - ramosg
将尽可能多的内容移出内部循环,并在1e7次迭代上使用psyco.full()运行,需要27秒。因此,在我的机器上执行1e9大约需要45分钟左右。我倾向于相信C/C++/C#会更快,尽管我没有对它们进行基准测试。 - hughdbrown
实际上,目前的情况是90%的代码是解释执行的,从技术上讲,Java和.NET代码都在字节码级别上进行解释执行。这就是为什么整个JIT编译器行业崛起了,它不仅可以针对特定平台进行优化,而且还可以在编译器手边获得有关代码行为的完整运行时信息,带来了意想不到的好处。因此,一个古老的格言——静态编译语言比解释器更快——已经不再正确。 - Kaerber

1

正如其他人所说,您可以编写自己的C模块来进行解析/转换。然后,您只需导入它并调用即可。您可能可以使用Pyrex或其Cython衍生版本从Python生成C代码(通过向Python添加一些类型约束提示)。

您可以阅读有关Cython的更多信息,看看它是否有所帮助。

另一个问题是...您将用这十亿个整数做什么?是否可能将它们作为字符串加载,作为字符串搜索,并根据需要执行惰性转换?或者您可以使用threadingmultiprocessing模块和队列并行化转换和其他计算吗? (让一个或多个线程/进程执行转换并从中提供队列,您的处理引擎从中获取)。换句话说,生产者/消费者设计是否可以缓解问题?


0

也许这对你来说不是一个选项,但我会认真考虑使用二进制文件而不是文本。它是否经常更改?如果不是,你可以预处理它。


0

这是NumPy非常擅长的事情:

np.fromstring(line, dtype=np.float, sep=" ")


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接