29

我有一些代码可以执行一些非常占用 CPU 的字符串操作,并且我正在寻找提高性能的方法。

(编辑:我正在做一些事情,比如找到最长的公共子字符串,运行许多可能更好地表示为 c 中的状态机的正则表达式,从 HTML 中剥离注释,诸如此类。)

在听到很多关于它的好消息后,我目前正在考虑将一些代码移植到Cython 。然而,Cython 的主要关注点似乎是数值计算,并且几乎没有记录使用字符串。

Unicode 也可能是一个大问题。

我的问题是:

  1. 我什至应该为字符串的东西烦恼 Cython 吗?有没有人在 cython 中有这种类型的处理经验并可以分享?
  2. 我在 Cython 文档中遗漏了什么吗?有谁知道有关在 Cython 中使用字符串的教程/参考/文档?
4

6 回答 6

12

我投了“profile it”的答案,但想补充一点:在可能的情况下,您可以进行的最佳优化是使用 Python 标准库或内置函数来执行您想要的任务。这些通常在 C 中实现,并将提供与任何扩展大致相当的性能,包括用 Cython 编写的扩展。如果您的算法在 Python 中逐个字符地执行循环,那么如果可能的话,这些应该是首先要做的事情。

但是,如果您的算法无法根据内置或其他现有标准库进行修改,那么 Cython 似乎是一种合理的方法。它只是将伪 Python 编译为本机代码,并且真的像任何其他操作一样适合字符串操作。但我不相信如果你只是将惯用的 Python 代码交给 Cython,你会看到使用 Cython 的巨大好处。如果您能够在 C 中重写每个算法的部分或全部,从而使低级操作不会不断地跨 Python/C 障碍转换变量,那么最大的好处就会出现。

最后,Unicode——你暗示它可能是一个“大问题”,但没有具体说明你是如何使用它的。Cython 可能会生成调用处理 Unicode 的相关 Python API 的 C 代码,因此功能不太可能受到限制。然而,在 C 中处理 Unicode 字符串并非易事,并且可能意味着在 C 中重写一些算法以获得更好的性能的想法是不值得的。许多经典的字符串算法根本不适用于许多 Unicode 编码,它们不是传统意义上的“字符串”,即每个字符有 1 个存储单元。

于 2009-06-03T12:41:38.797 回答
9

只是为了完整起见,我最终做的只是用 C 编写(一些)字符串操作代码。

事实证明,开始为 python 编写 c 扩展非常容易。Unicode 字符串只是 Py_UNICODE 的数组,它是 int 或 short 取决于 python 构建。

我得到了 x20 改进转换代码,例如

s = re.sub(r' +', ' ', s)

到 c. 我用更复杂的正则表达式得到了类似的改进,但是 c 代码很快变得非常复杂。

总体而言,重写后我的吞吐量提高了 20%。我现在正在寻找更多要重写的东西...

于 2009-11-26T11:36:11.560 回答
8

“非常容易”是一个非常相对的术语。“开始”就是这样。用 C 语言编写健壮的扩展需要非常小心地注意引用计数、内存分配/释放和错误处理等问题。Cython 为你做了很多事情。

Cython 中的非 unicode 字符串可以是 Python str 对象,也可以是 char 数组,如在 C 中。您认为需要哪些 Cython 特定文档?

我建议您自己尝试 Cython。但在你这样做之前,我强烈建议你检查你的 Python 代码是否效率低下。有时你可以很容易地获得巨大的加速。

例如,压缩空格字符的运行......使用

re.sub(' +', ' ', s) # one space in pattern

意味着在运行长度为 1 的可能并不少见的情况下,它将用空格替换空格。如果所有运行的长度都为 1,它将创建一个新的替换字符串,当它可以轻松地增加(或不减少,或其他)输入字符串的引用计数并将其传回时。

re.sub('  +', ' ', s) # two spaces in pattern

产生完全相同的结果并且可能运行得更快......让我们看看:

所有运行长度 1:它以 3.4 倍的速度运行。未显示:输入字符串越长越好。

\python26\python -mtimeit -s"s='now is the winter of our discontent'; import re; x = re.compile(' +').sub" "x(' ', s)"
100000 loops, best of 3: 8.26 usec per loop

\python26\python -mtimeit -s"s='now is the winter of our discontent'; import re; x = re.compile('  +').sub" "x(' ', s)"
100000 loops, best of 3: 2.41 usec per loop

对于长度为 2 的一次运行,速比为 2.5。对于所有长度为 2 的运行,速比为 1.2。考虑到所有因素,1 次击键的投资回报还不错。

于 2009-11-26T23:57:05.400 回答
6

我最近被介绍给 Cython,并在包装​​大型 C 和 C++ 库以用于重要项目中取得了巨大成功。一些生成的 Python 扩展实际上已经在我们的生产环境中运行。所以,首先,Cython 绝对是一个不错的选择。

话虽如此,您应该考虑是否真的想用 Cython 编写所有代码,或者是否想编写 C/C++ 代码并简单地使这些函数可以从 Cython 访问。显然,这部分取决于您对 C 和/或 C++ 的熟悉程度。

当您使用字符串时,使用std::stringfrom C++ 而不是char*. 它可以很容易地导入到 cython 中,from libcpp.string cimport string然后可以通过标准 cython 用字符串类型声明变量cdef string ...

于 2012-02-17T20:37:35.280 回答
5

这是一个非常有趣的问题。Cython 的核心是将 python 与 C 数据类型集成的工具。它不提供任何帮助处理字符串的功能,可能是因为对它的需求不如对特定 Numpy 功能的需求多。

话虽如此,您可以很好地使用 Cython 与旨在处理您描述的问题类型的现有 C/C++ 库进行交互。例如,对于处理 HTML/XML,您可能需要查看libxml。但是,(当然)已经有现成的 python 绑定可用于此。我已经广泛使用 lxml 来处理 HTML,它可以满足我的所有需求并且速度很快,而且它可以很好地处理 unicode。

在您的情况下,我认为 lxml 和定制的 C 函数的组合是最好的。例如,您可以“轻松”创建一个快速函数来查找 C 中最长的子字符串,因为这可以在字节级别完成(回想一下,C 中的字符串只是一个 char*,它是一个字节数组)。然后你可以将它们映射回 python(Cython 会让你很容易)并在 unicode 抽象天堂中继续进行:)。当然不是微不足道的,但如果您的应用程序的性能依赖于它,则可能值得付出努力。

当然,在 C/C++ 中使用 unicode 也有一些不错的(尽管不是很重要的)方法。Evan Jones 的这篇文章可能会帮助您确定是否值得付出努力。

于 2009-10-13T15:02:17.597 回答
4

请注意,Cython 实际上支持 CPython 的 Py_UNICODE 类型,因此,例如,您可以直接迭代 unicode 字符串或以 C 速度比较字符。看

http://docs.cython.org/src/tutorial/strings.html

于 2011-04-16T14:15:20.443 回答