2014-09-05 44 views
0

我想達到接近C的速度來處理sqlite和正則表達式模式搜索。我意識到其他庫和FTS4將會是更快或者更好的解決方案,但這並不是我所要求的。可以使用Python的函數式編程來完全避免解釋器和方法的開銷?

我發現只要我不使用lambda或者定義的方法或python代碼,CPython公開的某些原語和C級函數可以直接作爲sqlite自定義函數注入,並且在運行時,即使除了返回一個常數之外沒有任何操作,也可以實現10倍的提升。但是,我還沒有準備好開始創建擴展,我試圖避免使用像Cython這樣的工具將C和python混合在一起。

我設計了以下測試代碼,揭示了這些性能差異,並利用第三方庫cytoolz(撰寫方法)提供的一些加速來實現某些功能樣式的邏輯,同時避免了lambda表達式。 (:Windows 8.1中的64位家用OS),省去打印報表:

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位輸出

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正則表達式搜索布爾(鏈)「上面。所以我的問題是2部分。

  1. SQLITE3會如果create_function()調用創建返回任何東西,但原始的,它能夠理解的功能失效,所以MatchObject該搜索()返回需要轉換,因此鏈式「不爲空」方法。對於搜索文本返回功能,這會變成醜陋(不是非常簡單),就像您在源代碼中看到的一樣。有沒有一種比元素到迭代器轉換策略更容易的替代方法,當我試圖讓一個非python函數有選擇地顯示MatchObject的組時,只有當它在搜索正則表達式以便與sqlite3一起使用時返回?我一直在與Python的速度作鬥爭:是否使用數據庫函數而不是Python或函數,或者列表而不是字典或對象,浪費了將變量名稱複製到本地名稱空間的代碼行,使用生成器而不是其他方法調用或內聯循環和函數,而不是從Python可以提供的抽象中獲益。我還應該考慮哪些其他函數/庫,這些函數/庫會使我獲得巨大的效率回報(我至少說話10倍),同時仍然使用Python來搭建腳手架?我知道實際上會加速python代碼本身的程序(pypi,cython),但是它們似乎使用起來更危險,或者仍然因python的語言結構限制優化而受到影響,因爲它假設代碼總是被「解釋」?也許有幾種ctypes暴露的方法和策略可以在快速文本處理領域獲得回報?我知道這些圖書館主要關注科學,統計和數學加速,但我對這個領域並不特別感興趣。

回答

0

我最近做了一些測試,並研究了其他方法來加快文本處理任務。一個重大的突破,以及Python能夠勝任的工作是元編程。我編寫的代碼組合並轉換代碼片段,這些代碼片段對文本文件的行進行特定的操作。由於方法全部是一種方法,因此方法開銷被消除,另一個主要好處是將屬性自動重新映射到數組索引查找,或者權衡允許時自動本地映射。代碼片段可以以這樣一種方式編寫,即它們可以按原樣運行和測試,也可以作爲其他片段組合的一部分進行運行和測試。跟蹤每個片段的來源可以作爲註釋添加到編譯後的python源代碼的結果中。

這似乎是最簡單的方式,同時堅持使用Python,並獲得語言所能提供的所有(或大部分)好處,而不用擔心(儘可能多)關於永遠解釋型語言的性能缺陷。

感謝所有54位觀衆的貢獻。 ;)

相關問題