【问题标题】:Python: Compiling regexes in parallelPython:并行编译正则表达式
【发布时间】:2013-08-18 06:26:01
【问题描述】:

我有一个程序需要编译数千个大型正则表达式,所有这些都将被多次使用。问题是,re.compile() 他们需要很长时间(根据cProfiler,113 秒)。 (顺便说一句,实际上使用所有这些正则表达式进行搜索

如果我不预编译,它只会将问题推迟到我实际搜索的时候,因为re.search(expr, text) 隐式编译expr。实际上,情况更糟,因为re 每次我使用它们时都会重新编译整个正则表达式列表。

我尝试使用multiprocessing,但这实际上会减慢速度。这是一个小测试来演示:

## rgxparallel.py ##
import re
import multiprocessing as mp

def serial_compile(strings):
    return [re.compile(s) for s in strings]

def parallel_compile(strings):
    print("Using {} processors.".format(mp.cpu_count()))
    pool = mp.Pool()
    result = pool.map(re.compile, strings)
    pool.close()
    return result

l = map(str, xrange(100000))

还有我的测试脚本:

#!/bin/sh
python -m timeit -n 1 -s "import rgxparallel as r" "r.serial_compile(r.l)"
python -m timeit -n 1 -s "import rgxparallel as r" "r.parallel_compile(r.l)"
# Output:
#   1 loops, best of 3: 6.49 sec per loop
#   Using 4 processors.
#   Using 4 processors.
#   Using 4 processors.
#   1 loops, best of 3: 9.81 sec per loop

我猜并行版本是:

  1. 并行编译和酸洗正则表达式,约 2 秒
  2. 在串行中,取消酸洗,因此重新编译它们,大约 6.5 秒

加上启动和停止进程的开销,multiprocessing 在 4 个处理器上比串行的要慢 25% 以上

我还尝试将正则表达式列表划分为 4 个子列表,并 pool.map-ing 子列表,而不是单个表达式。这稍微提升了性能,但我仍然无法比串行慢约 25%。

有没有比串口编译更快的方法?

编辑: 修正了正则表达式编译的运行时间。

我也尝试使用threading,但由于 GIL,只使用了一个处理器。它比multiprocessing 略好(130 秒对 136 秒),但仍然比串行(113 秒)慢。

编辑 2: 我意识到有些正则表达式可能会重复,所以我添加了一个 dict 来缓存它们。这缩短了约 30 秒。不过,我仍然对并行化感兴趣。目标机器有 8 个处理器,这会将编译时间减少到大约 15 秒。

【问题讨论】:

  • 为什么你有这么多大的正则表达式,却只用它们做这么少的搜索?你能简化它们,也许用普通的旧字符串操作替换它们,或者完全避免运行其中的一些?
  • 搜索时间是对整个列表的一次使用。单个列表搜索的时间很短,这一点非常重要,因为用户(和我的雇主)会期待近乎即时的响应。我尝试尽可能地简化,这是在不削减主要功能的情况下我能得到的最好的。 (实际的搜索词列表约为 200,000 项;我的代码尽可能切换到简单的字符串函数,但仍留下约 5,000 个正则表达式。)
  • 您是否尝试过使用线程?每个 cpu 1 个线程和正则表达式在它们之间划分?正则表达式是在 C 中实现的,因此尽管有 GIL,但您应该获得不错的并行度。
  • 我应该链接那个xkcd.com/1171 =)
  • 我本来打算试试的,但我被线程文档中的这个警告推迟了(我正在使用 CPython):在 CPython 中,由于全局解释器锁,只有一个线程可以执行Python 代码一次(即使某些面向性能的库可能会克服这个限制)。如果您希望您的应用程序更好地利用多核机器的计算资源,建议您使用多处理。但是,如果您想同时运行多个 I/O 密集型任务,线程仍然是一个合适的模型。

标签: python regex parallel-processing multiprocessing


【解决方案1】:

有比多处理更轻的解决方案来获得任务执行的异步性,例如线程和协程。尽管 python2 的同时运行能力有限,但 python3 在其基本类型中主要使用这种异步实现。只需使用 python3 运行您的代码,您就会看到不同之处:

$ python2 --version
Python 2.7.17
$ python2 -m timeit -n 1 -s "import rgxparallel as r" "r.serial_compile(r.l)"
1 loops, best of 3: 3.65 sec per loop
$ python -m timeit -n 1 -s "import multire as r" "r.parallel_compile(r.l)"
1 loops, best of 3: 3.95 sec per loop

$ python3 --version
Python 3.6.9
$ python3 -m timeit -n 1 -s "import multire as r" "r.serial_compile(r.l)"
1 loops, best of 3: 0.72 usec per loop
$ python3 -m timeit -n 1 -s "import multire as r" "r.parallel_compile(r.l)"
...
1 loops, best of 3: 4.51 msec per loop

对于 python3 版本,不要忘记将 xrange 更改为 range

【讨论】:

    【解决方案2】:

    尽管我很喜欢 python,但我认为解决方案是,用 perl (see this speed comparison, for example) 或 C 等来做。

    如果您想将主程序保留在 python 中,您可以使用 subprocess 调用 perl 脚本(只需确保在尽可能少的 subprocess 调用中传递尽可能多的值以避免开销。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-10
      • 1970-01-01
      相关资源
      最近更新 更多