lxmlモジュールでXPathプリコンパイルの効果

前回の記事で使用したPythonのlxmlモジュールについて調べていたら、XMLデータの検索に用いるXPath式をプリコンパイルできるらしいことが分かったので、どのくらい速度が向上するのか試してみた。


テストデータは前回のものと同じ。

$ ls -hl | grep yahoo_list
-rw-rw-r-- 1 hogeuser hogeuser   11K  925 05:46 yahoo_list.rss
-rw-rw-r-- 1 hogeuser hogeuser 1011K  926 14:47 yahoo_list2.rss
※yahoo_list2.rssは、yahoo_list.rssを加工して100倍くらいにサイズを大きくしたもの。


以下、測定用スクリプト

#!/usr/bin/env python
# -*- coding:utf8 -*-
import sys
import timeit
from lxml import etree

COUNT = 10000
if len(sys.argv) > 1:
    print "SOURCE: %s" % sys.argv[1]
    ET = etree.parse(sys.argv[1])

if len(sys.argv) > 2:
    print "XPATH: %s" % sys.argv[2]
    XPATH = sys.argv[2]

def test1():
    '''XPath式の文字列で検索。
    '''
    global finded
    finded = ET.xpath(XPATH)

compiled_xpath = etree.XPath(XPATH) # XPath式をプリコンパイルしておく。
def test2():
    '''コンパイル済みXPathで検索。
    '''
    global finded
    finded = compiled_xpath(ET)

for func in ["test1", "test2"]:
    time_eachcall = timeit.Timer(
        setup = ('from __main__ import %s' % func),
        stmt  = ('%s()' % func)
    )
    print func
    print "  Time = %s" % time_eachcall.timeit(number=COUNT)
    print "  Items = %d" % len(finded)


前回のテストデータとXPath式、それぞれを使って実行してみる。

$ python -V
Python 2.4.3

$ python test_lxml_xpath.py yahoo_list.rss //rss/channel/item/title
SOURCE: yahoo_list.rss
XPATH: //rss/channel/item/title
test1
  Time = 0.740010976791
  Items = 20
test2
  Time = 0.270004034042
  Items = 20

$ python test_lxml_xpath.py yahoo_list2.rss //rss/channel/item/title
SOURCE: yahoo_list2.rss
XPATH: //rss/channel/item/title
test1
  Time = 24.0603630543
  Items = 2000
test2
  Time = 22.6303420067
  Items = 2000

$ python test_lxml_xpath.py yahoo_list.rss /rss/channel/item/title
SOURCE: yahoo_list.rss
XPATH: /rss/channel/item/title
test1
  Time = 0.670010089874
  Items = 20
test2
  Time = 0.21000289917
  Items = 20

$ python test_lxml_xpath.py yahoo_list2.rss /rss/channel/item/title
SOURCE: yahoo_list2.rss
XPATH: /rss/channel/item/title
test1
  Time = 19.6702969074
  Items = 2000
test2
  Time = 18.6202809811
  Items = 2000


測定スクリプトの実行結果をまとめると以下のような感じになった。

XPath //rss/channel/item/title

yahoo_list.rss(11K) yahoo_list2.rss(1011K)
XPath文字列で検索 0.74sec 24.06sec
コンパイルXPathで検索 0.27sec 22.63sec

XPath /rss/channel/item/title

yahoo_list.rss(11K) yahoo_list2.rss(1011K)
XPath文字列で検索 0.67sec 19.67sec
コンパイルXPathで検索 0.21sec 18.62sec

yahoo_list.rss(ファイルサイズが10K程度のXMLデータ)の方は、XPath式をプリコンパイルしたことでそれなりに速くなった。
一方、yahoo_list2.rss(1000K程度)の方は、それほど効果が現れなかったのは何故だろう?
何か他の要因も関係しているのかも。

何にせよ、同じXPath式を繰り返し使うような処理を行う場合は、プリコンパイルしておくと良さそう。


参考
lxml を使用して Python での XML 構文解析をハイパフォーマンスにする
XPath and XSLT with lxml