从1维numpy数组生成分隔字符串的最快方法

41

我有一个程序需要将许多大型的一维numpy浮点数数组转换为分隔符字符串。相对于我的程序中的数学运算,我发现这个操作非常慢,想知道是否有方法可以加速它。例如,考虑以下循环,它将取100,000个随机数字的numpy数组,并将每个数组连接成逗号分隔的字符串。

import numpy as np
x = np.random.randn(100000)
for i in range(100):
    ",".join(map(str, x))

这个循环需要约20秒才能完成(总时间,不是每个周期)。相比之下,考虑到100个元素的乘法运算(x*x)只需要不到1/10秒的时间。显然,字符串连接操作会导致性能瓶颈;在我的实际应用中,它将主导总运行时间。这让我想知道,是否有比",".join(map(str, x))更快的方法?由于map()几乎占据了所有的处理时间,所以问题转化为是否存在更快的方法将大量数字转换为字符串。


1
是将数字转换为字符串需要花费时间吗? - mmmmmm
将两个整数相乘和将100,000个数字转换/连接起来是完全不同的事情 - 你怎么能期望这两个操作在性能方面处于同一水平呢? - Tim Pietzcker
马克 - 是的。 蒂姆 - 确实如此。我的观点只是字符串操作会导致真正的瓶颈,如果有一种加速的方法就太好了。 - Abiel
float.hexstr 快 25%。它可以使用 "%a" 格式在其他语言中读取回来。 - jfs
7个回答

37

有点晚了,但这对我来说更快:

#generate an array with strings
x_arrstr = np.char.mod('%f', x)
#combine to a string
x_str = ",".join(x_arrstr)

我的电脑速度提升了大约1.5倍


14

这篇关于Python中各种字符串拼接技术性能的文章写得非常好:http://www.skymind.com/~ocrow/python_string/

我有点惊讶于后面的一些方法表现得如此之好,但看起来你肯定能够在其中找到比你当前使用的方法更好的东西。

该网站提到的最快方法

方法6:列表推导式

def method6():
  return ''.join([`num` for num in xrange(loop_count)])

这种方法是最短的。我会揭示它也是最快的。它非常紧凑,也相当易懂。使用列表推导式创建一个数字列表,然后将它们全部连接起来。简单到不能再简单了。这实际上只是第四种方法的缩写版本,它消耗的内存基本相同。但由于我们不必每次循环调用list.append()函数,因此它更快。


感谢sblom。不幸的是,我的代码已经与提到的最快解决方案基本相同。也许没有办法让它运行得更快了。 - Abiel
@Abiel 如果你真的想让它更快,那么你应该考虑使用Cython。 - Justin Peel
8
我认为一般人喜欢得到一个解释如何做的答案,而不仅仅是链接到一个可能在未来被删除的页面。 - abcd

3

首先将numpy数组转换为列表。与numpy数组相比,map操作在列表上似乎运行得更快。

例如:

import numpy as np
x = np.random.randn(100000).tolist()
for i in range(100):
    ",".join(map(str, x))

在时间测试中,我发现这个示例的速度提升了15%,并保持了一致性。

我将让其他人解释为什么这可能更快,因为我完全不知道!


2
我认为您可以尝试使用numpy.savetxt,并将cStringIO.StringIO对象作为虚拟文件传递...
或者使用str(x),通过替换空格为逗号(编辑:这种方法不太好用,因为str会省略大数组的部分内容 :-s)。
由于这个目的是将数组发送到网络上,也许有更好的替代方法(在CPU和带宽方面都更有效率)。我在另一个答案的评论中指出的一种方法是将数组的二进制表示编码为Base64文本块。这种方法的主要缺点是读取数据块的客户端应该能够做出像将字节数组重新解释为浮点数数组之类的危险操作,而这通常在类型安全的语言中是不允许的;但是它可以通过C库调用快速完成(大多数语言都提供了这样的手段)。
如果您无法处理位,总是有一种可能性,即逐个处理数字以将解码后的字节转换为浮点数。
哦,还要注意通过网络发送数据时机器的字节序:转换为网络顺序 -> base64编码 -> 发送|接收 -> base64解码 -> 转换为主机顺序

1
感谢 Fortran。不幸的是,使用 savetxt 或 str(x) 我仍无法获得速度提升。str(x) 一开始似乎快得多,但一旦使用 np.set_printoptions(threshold=100000)(请参见我在 unutbu 回答下的评论),这种速度优势就消失了。 - Abiel

1

numpy.savetxt比string.join还要慢。ndarray.tofile()似乎不能与StringIO一起使用。

但是我发现了一种更快的方法(至少适用于在python2.5上使用较低版本的numpy的OP示例):

import numpy as np
x = np.random.randn(100000)
for i in range(100):
    (",%f"*100000)[1:] % tuple(x)

如果您有一个明确定义的格式,例如在这种特定情况下,那么字符串格式似乎比字符串连接更快。但我想知道为什么 OP 需要在内存中存储如此长的浮点数字符串。

较新版本的 numpy 没有显示出速度提升。


@Abiel,timeit显示快了20~30%。如果数据大小不是问题,不确定Fortran的建议是否会提高速度。JSON或XML怎么样?我认为在网络上传输二进制数据不安全解包。 - Dingle
Fortran - 在仔细研究了您的建议后,我对实际上如何在客户端解码数据感到困惑,因为客户端不一定是用Python编写的。例如,客户端可能是用Visual Basic编写的,并且被设计为将数字数组放入电子表格中。在这种情况下,我需要知道如何将numpy数组的二进制表示形式转换为类似于VB Variant的东西。有什么想法吗? - Abiel
@Dingle,这是因为“%f”没有像str()一样写出那么多位数,所以速度更快。 - Justin Peel
@Justin,说得好。我再次使用"%.12f"进行测试(应该等同于默认的str行为),仍然快了10~15%。 - Dingle
@Justin,我正在使用Python 2.5(在Python2.5.2上,字符串格式比OP的版本快得多)。我在Python2.6上进行了测试,发现字符串格式略慢。显然,Python2.6以某种方式优化了字符串连接。 :) - Dingle
显示剩余6条评论

0

在OP的代码中,使用itertools中的imap而不是map给了我大约2-3%的改进,虽然不多,但可能与其他想法结合起来会有更大的改进。

个人认为,如果你想要比这更好的结果,你将不得不使用类似Cython的东西。


-1
','.join(x.astype(str))

比起 as,速度慢了大约 10%。

x_arrstr = np.char.mod('%f', x)
x_str = ",".join(x_arrstr)

但更易读。


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