dak ブログ

python、rubyなどのプログラミング、MySQL、サーバーの設定などの備忘録。レゴの写真も。

python と cython でリストの各要素への繰り返し処理の速度を比較

2018-12-18 00:01:08 | python
python と cython でリストの各要素への繰り返し処理の速度を比較したメモ。

for ループで10万要素のリストの各要素にアクセスし、その実行時間を比較した結果は以下のとおり。
python iterator: 0.0013873577117919922
python index: 0.004186868667602539
cython iterator: 0.0005218982696533203
cython index: 0.0004062652587890625

cython でリストに対してインデックスでアクセスするのが最も速い、という結果になりました。

プログラムは以下の通り。
■test.pyx
# -*- coding:utf-8 -*-
# cython: language_level=3

def c_iterator(list items):
    cdef int a
    cdef int item

    for item in items:
        a = item
    return a

def c_index(list items):
    cdef int i, num
    cdef int a

    num = len(items)
    for i in range(num):
        a = items[i]
    return a

■test_main.py
# -*- coding:utf-8 -*-

import time
import test

def p_iterator(items):
    a = 0
    for item in items:
        a = item
    return a

def p_index(items):
    a = 0
    num = len(items)
    for i in range(num):
        a = items[i]
    return a

def run():
    n = 100000
    items = [x for x in range(n)]

    # python iterator
    from_time = time.time()
    res = p_iterator(items)
    to_time = time.time()
    t = to_time - from_time
    print("python_iterator: %s" % (t))

    # python index
    from_time = time.time()
    res = p_index(items)
    to_time = time.time()
    t = to_time - from_time
    print("python_index: %s" % (t))

    # cython iterator
    from_time = time.time()
    res = test.c_iterator(items)
    to_time = time.time()
    t = to_time - from_time
    print("cython_iterator: %s" % (t))

    # cython index
    from_time = time.time()
    res = test.c_index(items)
    to_time = time.time()
    t = to_time - from_time
    print("cython_index: %s" % (t))

    return 0

if __name__ == '__main__':
    res = run()
    exit(res)


python のリストの実装方法を調べてみた

2018-12-09 20:12:41 | python
python のリストの実装方法を調べたメモ。

・要素へのアクセスは以下の様に配列へのアクセスになっている。(listobject.h)
 ※内部ではいわゆるリストではなく、配列で実装されている。
     #define PyList_SET_ITEM(op, i, v) (((PyListObject *)(op))->ob_item[i] = (v))
     #define PyList_GET_ITEM(op, i) (((PyListObject *)(op))->ob_item[i])

・要素追加時に空き領域がないと、以下の様にサイズを拡張している。(listobject.c)
 ※newsize: リストのサイズ, new_allocated: 空き領域を含めたサイズ
     The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
     new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);

・拡張時にはリストのコピーが行われる。(listobject.c, pymem.h)
  #define PyMem_RESIZE(p, type, n) \
    ( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :        \
          (type *) PyMem_REALLOC((p), (n) * sizeof(type)) )

  if (new_allocated <= (SIZE_MAX / sizeof(PyObject *)))
     PyMem_RESIZE(items, PyObject *, new_allocated);

cython.view.array の使い方

2018-12-09 17:31:01 | python
cython.view.array の使い方のメモ

■cython プログラム(foo.pyx)
from import cython.view cimport array

def create_array(int row, int col):
  arr = array(shape=(row, col), itemsize=sizeof(int), format='i')
  
  for r in range(0, row):
    for c in range(0, col):
      arr[r][c] = r + c


def sum_array(int [:, :] arr):
  cdef int row = arr.shape[0]
  cdef int col = arr.shape[1]
  cdef int r, c
  cdef int sum = 0

  for r in range(0, row):
    for c in range(0, col):
      sum += arr[r][c]

  return sum

array(...) で float、double の配列を使いたいときには、
array(..., itemsize=sizeof(float), format='f')
array(..., itemsize=sizeof(double), format='d')
とします。

■puthon プログラム(呼び出し側)
import foo

arr = foo.create_array(1000, 1000)
sum = foo.sum_array(arr)





Cython でお手軽に python プログラムを高速化

2018-12-02 16:46:59 | python
Cython でお手軽に python プログラムを高速化する方法のメモ。

C/C++ を使わずに、以下だけで高速化します。
・メソッドの引数に型宣言を追加
・メソッド内の変数の型宣言を追加


まず、python プログラム(~.py) に以下の変更を行って、~.pyx を作成します。

※主な型
整数: int
文字列: unicode
リスト: list
辞書: dict

■bar_lib.py
def bar(n, ts, idx):
    nts = ts[idx:idx+n]
    nkey = "\t".join([t['key'] for t in nts])
    return nkey


■bar_lib.pyx
# cython: language_level=3

# メソッドの引数の型を追加
def bar(int n, list ts, int idx):
    # メソッド内の変数の型を定義
    cdef list nts
    cdef dict t
    cdef unicode nkey

    nts = ts[idx:idx+n]
    nkey = u"\t".join([t['key'] for t in nts])
    return nkey


次に、setup.py ファイルを作成します。
from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules=cythonize(['bar_lib.pyx']))

※複数の .pyx がある場合には、リストに .pyx ファイルを追加します

そして、setup.py を実行します。
python setup.py build_ext --inplace

bar_lib を import すれば、bar() を実行できます。

■foo.py
import bar_lib

def run():
  bar_lib.bar()

if __name__== '__main__':
  run()


python で行単位でプロファイルを取得する方法

2018-12-02 16:32:14 | python
python で行単位でプロファイルを取得する方法のメモ。

行単位でプロファイルをとるには line_profiler を使います。

まず、line_profiler をインストールします。
pip install line_profiler


次に、プロファイルをとりたいメソッドに @profile を付与します。
@profile
def foo(x):
  ...

def run():
  ...

if __name__ == '__main__':
  run()


そして、プロファイルを取得するためにプログラムを実行します。
kernprof -l {プログラム.py}


プロファイル結果が プログラム.py.lprof に出力され、以下で閲覧することができます。
python -m line_profiler {プログラム.py.lprof}

Timer unit: 1e-06 s

Total time: 727.632 s
File: foo.py
Function: foo at line 399

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   399                                               @profile
   401                                               def foo():
   402
   403   7719789  176860582.0     22.9     24.3          score1 = calc_score1()
   404   7719789  114940601.0     14.9     15.8          score2 = calc_score2()
   ...


各項目は以下の通り
 Hits: 実行回数
 Time: 合計の実行時間(単位はTimer unitの値)
 Per Hit: 1回あたりの実行時間(単位はTimer unitの値)
 % Time: 実行時間の割合

emacs の拡張子によるモード判定の設定方法

2018-12-01 12:07:49 | emacs
emacs の拡張子によるモード判定の設定方法のメモ。

cython のファイルの拡張子を pyx にしていると、
emacs で自動で python モードにならなかったため、
以下を .emacs に設定しました。

(add-to-list 'auto-mode-alist '("\\.pyx\\'" . python-mode))


M-x python-mode で手動で変更もできますが、毎回は面倒ですし、
変更し忘れると、cython で "Mixed use of tabs and spaces" の
エラーがでることが多いので。

top でプロセスの引数を表示する方法

2018-12-01 11:35:51 | linux
top でプロセスの引数を表示する方法のメモ。

デフォルトではプロセス名しか表示されませんが、
'c' を押すと引数も表示されます。