【问题标题】:Can Python's functional programming be used to completely avoid interpreter and method overhead?Python的函数式编程能不能完全避免解释器和方法的开销?
【发布时间】: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) 选择”的一些变体。所以我的问题是两部分。

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

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

【问题讨论】:

    标签: python performance sqlite functional-programming itertools


    【解决方案1】:

    我最近做了一些测试,并研究了加快文本处理任务的其他方法。一个重大的突破,也就是 Python 具备的能力,就是元编程。我编写的代码组合和转换对文本文件行执行特定操作的代码 sn-ps。消除了方法开销,因为它都是一种方法,另一个主要好处是将属性自动重新映射到数组索引查找,或者在权衡允许时,自动本地映射。可以编写代码 sn-ps,使其既可以按原样运行和测试,也可以作为其他 sn-ps 组合的一部分。跟踪每个 sn-p 的来源可以作为 cmets 添加到编译的 python 源代码的结果中。

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

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

    【讨论】:

      猜你喜欢
      • 2021-02-08
      • 2015-08-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-25
      • 1970-01-01
      • 1970-01-01
      • 2021-09-09
      • 1970-01-01
      相关资源
      最近更新 更多