可以使用Python的函数式编程来完全避免解释器和方法开销吗?

时间:2014-09-05 01:16:36

标签: python performance sqlite functional-programming itertools

我希望使用近C速度来处理sqlite和regex模式搜索。我知道其他库和FTS4将是更快或替代解决方案,但这不是我要问的。

我发现只要我不使用lambda或定义的方法或python代码,CPython公开的某些原语和C级函数就可以直接作为sqlite自定义函数注入,并且在运行时,即使除了返回常数之外没有完成任何操作,也可以实现10倍的提升。但是,我还没准备好开始创建扩展,我试图避免使用像Cython这样的工具将C和python混合在一起。

我设计了以下测试代码来揭示这些性能差异,并利用第三方库提供的一些加速,cytoolz(compose方法)来实现一些功能风格的逻辑,同时避免使用lambdas。

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位输出(OS:Windows 8.1 64位主页),省略打印语句:

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正则表达式搜索bool(链)选择”。所以我的问题是2部分。

  1. 如果create_function()调用创建的函数返回除了它理解的基元以外的任何函数,则Sqlite3将失败,因此需要转换search()返回的MatchObject,因此链接的“非空”方法。对于搜索文本返回函数,这会变得很丑陋(不是很直接),正如您在源代码中看到的那样。有没有比我尝试使用非python函数时使用的元素到迭代器转换策略更容易的替代方法,只有在搜索正则表达式与sqlite3一起使用后返回时才会显示MatchObject的组?

  2. 我一直在与Python的速度作斗争:是否使用数据库函数而不是python函数,或列表而不是dicts或对象,浪费代码行将变量名称复制到本地命名空间,使用生成器而不是额外的方法调用,或内联循环和函数,而不是受益于Python可以提供的抽象。我应该考虑哪些其他功能/库可以让我实现巨大的效率支付(我说话至少10倍),同时仍然使用Python作为脚手架?我知道实际上加速python代码本身的程序(pypi,cython),但是它们看起来风险更大或者仍然受到python的语言结构如何限制优化的影响,因为假设代码总是被“解释”?也许有一些ctypes暴露的方法和策略可以在快速文本处理领域获得回报?我知道图书馆专注于科学,统计和数学加速,但我对这个领域并不特别感兴趣。

1 个答案:

答案 0 :(得分:0)

我最近做了一些测试,并研究了其他加速文本处理任务的方法。元编程是一项重大突破,也是Python有能力做的事情。我编写的代码组合并转换了对文本文件行执行特定操作的代码片段。消除了方法开销,因为它只是一种方法,另一个主要好处是自动将属性重新映射到数组索引查找,或者当权衡允许时,自动本地映射。代码片段可以这样的方式编写,即它们可以按原样运行和测试,也可以作为其他代码片段组合的一部分进行运行和测试。跟踪每个片段的来源可以作为注释添加到已编译的python源代码的结果中。

这似乎是最简单的方法,同时保持对Python的忠诚,并获得语言所能提供的所有(或大部分)好处,而不必担心(尽可能多)关于总是被解释的语言的性能缺陷。

感谢所有54位观众的贡献。 ;)