【发布时间】:2014-10-29 21:38:39
【问题描述】:
我希望在使用 sqlite 和正则表达式模式搜索时达到接近 C 的速度。我知道其他库和 FTS4 会更快或替代解决方案,但这不是我要问的。
我发现只要我不使用 lambda 或定义的方法,或者根本不使用 python 代码,CPython 暴露的某些原语和 C 级函数可以直接作为 sqlite 自定义函数注入,并且在运行时,一个实现了 10 倍的提升,即使除了返回一个常量之外没有进行任何操作。但是,我还没有准备好深入创建扩展,我试图避免使用像 Cython 这样的工具将 C 和 python 混合在一起。
我设计了以下测试代码来揭示这些性能差异,并利用第三方库 cytoolz(compose 方法)提供的一些加速来实现一些函数式逻辑,同时避免使用 lambda。
import sqlite3
import operator
from cytoolz import functoolz
from functools import partial
from itertools import ifilter,chain
import datetime
from timeit import repeat
import re,os
from contextlib import closing
db_path='testdb.sqlite'
existed=os.path.exists(db_path)
re_pat=re.compile(r'l[0-3]+')
re_pat_match=re.compile(r'val[0-3]+')
with closing(sqlite3.connect(db_path)) as co, co as co:
if not existed:
print "creating test data"
co.execute('create table test_table (testval TEXT)')
co.executemany('insert into test_table values (?)',(('val%s'%v,) for v in xrange(100000)))
def count(after_from=''):
print co.execute('select count(*) from test_table %s'%(after_from,)).fetchone()[0]
def python_return_true(v):
return True
co.create_function('python_return_true',1,python_return_true)
co.create_function('python_lambda_true',1,lambda x: True)
co.create_function('custom_lower',1,operator.methodcaller('lower'))
co.create_function('custom_composed_match',1,functoolz.compose(partial(operator.is_not,None),re_pat_match.match))
data=[None,type('o',(),{"group":partial(operator.truth,0)})] # create a working list with a fallback object
co.create_function('custom_composed_search_text',1,functoolz.compose(
operator.methodcaller('group'), # call group() on the final element (read these comments in reverse!)
next, # convert back to single element. list will either be length 1 or 2
partial(ifilter,None), # filter out failed search (is there a way to emulate a conditional method call via some other method??)
partial(chain,data), # iterate list (will raise exception if it reaches result of setitem which is None, but it never will)
partial(data.__setitem__,0), # set search result to list
re_pat.search # first do the search
))
co.create_function('custom_composed_search_bool',1,functoolz.compose(partial(operator.is_not,None),re_pat.search))
_search=re_pat.search # prevent an extra lookup in lambda
co.create_function('python_lambda_search_bool',1,lambda _in:1 if _search(_in) else None)
co.create_function('custom_composed_subn_alternative',1,functoolz.compose(operator.itemgetter(1),partial(re_pat.subn,'',count=1)))
for to_call,what in (
(partial(count,after_from='where 1'),'pure select'),
(partial(count,after_from='where testval'),'select with simple compare'),
(partial(count,after_from='where python_return_true(testval)'),'select with python def func'),
(partial(count,after_from='where python_lambda_true(testval)'),'select with python lambda'),
(partial(count,after_from='where custom_lower(testval)'),'select with python lower'),
(partial(count,after_from='where custom_composed_match(testval)'),'select with python regex matches'),
(partial(count,after_from='where custom_composed_search_text(testval)'),'select with python regex search return text (chain)'),
(partial(count,after_from='where custom_composed_search_bool(testval)'),'select with python regex search bool (chain)'),
(partial(count,after_from='where python_lambda_search_bool(testval)'),'select with python regex search bool (lambda function)'),
(partial(count,after_from='where custom_composed_subn_alternative(testval)'),'select with python regex search (subn)'),
):
print '%s:%s'%(what,datetime.timedelta(0,min(repeat(to_call,number=1))))
使用 Python 2.7.8 32 位(操作系统:windows 8.1 64 位 home)输出,省略打印语句:
pure select:0:00:00.003457
select with simple compare:0:00:00.010253
select with python def func:0:00:00.530252
select with python lambda:0:00:00.530153
select with python lower:0:00:00.051039
select with python regex matches:0:00:00.066959
select with python regex search return text (chain):0:00:00.134115
select with python regex search bool (chain):0:00:00.067687
select with python regex search bool (lambda function):0:00:00.576427
select with python regex search (subn):0:00:00.136042
我可能会选择上面“使用 python regex search bool (chain) 选择”的一些变体。所以我的问题是两部分。
如果 create_function() 调用创建的函数返回除它理解的原语以外的任何内容,Sqlite3 将失败,因此 search() 返回的 MatchObject 需要转换,因此链式“不为空”方法.对于搜索文本返回功能,这变得丑陋(不是很直接),正如您在源代码中看到的那样。有没有比我在尝试使非 python 函数可选地显示 MatchObject 的组时使用的元素到迭代器转换策略更简单的替代方法,前提是它在搜索用于 sqlite3 的正则表达式后返回?
我一直在与 Python 的速度作斗争:是使用数据库函数而不是 python 函数,还是使用列表而不是字典或对象,浪费代码行将变量名称复制到本地命名空间,使用生成器而不是附加方法调用或内联循环和函数,而不是从 Python 可以提供的抽象中受益。我应该考虑哪些其他函数/库可以让我在仍然使用 Python 搭建脚手架的同时获得巨大的效率回报(我说的是至少 10 倍)?我知道实际上加速python代码本身的程序(pypi,cython),但它们似乎使用起来风险更大,或者仍然受到python语言结构如何限制优化的影响,因为假设代码总是被“解释”?也许有一些 ctypes 暴露的方法和策略可以在快速文本处理领域得到回报?我知道专注于科学、统计和数学加速的库,但我对那个领域并不特别感兴趣。
【问题讨论】:
标签: python performance sqlite functional-programming itertools