0

Cythonのポストフィックス計算機

こんにちは。

私はCythonでポストフィックス計算機を開発しようとしていますが、動作しているナンシー版から翻訳しました。それは私の最初の試みです。計算機関数は、リスト内の後置式とサンプル行列を取得します。次に、計算された配列を返す必要があります。

入力例:

postfix = ['X0', 'X1', 'add'] 
samples = [[0, 1], 
      [2, 3], 
      [4, 5]] 
result = [1, 5, 9] 

example_cython.pyx

#cython: boundscheck=False, wraparound=False, nonecheck=False 

import numpy 
from libc.math cimport sin as c_sin 

cdef inline calculate(list lst, double [:,:] samples): 
    cdef int N = samples.shape[0] 
    cdef int i, j 
    cdef list stack = [] 
    cdef double[:] Y = numpy.zeros(N) 

    for p in lst: 
     if p == 'add': 
      b = stack.pop() 
      a = stack.pop() 
      for i in range(N): 
       Y[i] = a[i] + b[i] 
      stack.append(Y) 
     elif p == 'sub': 
      b = stack.pop() 
      a = stack.pop() 
      for i in range(N): 
       Y[i] = a[i] - b[i] 
      stack.append(Y) 
     elif p == 'mul': 
      b = stack.pop() 
      a = stack.pop() 
      for i in range(N): 
       Y[i] = a[i] * b[i] 
      stack.append(Y) 
     elif p == 'div': 
      b = stack.pop() 
      a = stack.pop() 
      for i in range(N): 
       if abs(b[i]) < 1e-4: b[i]=1e-4 
       Y[i] = a[i]/b[i] 
      stack.append(Y) 
     elif p == 'sin': 
      a = stack.pop() 
      for i in range(N): 
       Y[i] = c_sin(a[i]) 
      stack.append(Y) 
     else: 
      if p[0] == 'X': 
       j = int(p[1:]) 
       stack.append (samples[:, j]) 
      else: 
       stack.append(float(p)) 
    return stack.pop() 


# Generate and evaluate expressions 
cpdef test3(double [:,:] samples, object _opchars, object _inputs, int nExpr): 
    for i in range(nExpr): 
     size = 2 
     postfix = list(numpy.concatenate((numpy.random.choice(_inputs, 5*size), 
             numpy.random.choice(_inputs + _opchars, size), 
             numpy.random.choice(_opchars, size)), 0)) 
     #print postfix 

     res = calculate(postfix, samples) 

main.py

import random 
import time 
import numpy 
from example_cython import test3 

# Random dataset 
n = 1030 
nDim=10 
samples = numpy.random.uniform(size=(n, nDim)) 

_inputs = ['X'+str(i) for i in range(nDim)] 
_ops_1 = ['sin'] 
_ops_2 = ['add', 'sub', 'mul', 'div'] 
_opchars = _ops_1 + _ops_2 
nExpr = 1000 
nTrials = 3 

tic = time.time() 
for i in range(nTrials): test3(samples, _opchars, _inputs, nExpr) 
print ("TEST 1: It took an average of {} seconds to evaluate {} expressions on a dataset of {} rows and {} columns.".format(str((time.time() - tic)/nTrials), str(nExpr), str(n), str(nDim))) 

setup.py

from distutils.core import setup 
from distutils.extension import Extension 
from Cython.Distutils import build_ext 

ext_modules=[ Extension("example_cython", 
       ["example_cython.pyx"], 
       libraries=["m"], 
       extra_compile_args = ["-Ofast", "-ffast-math"])] 

setup(
    name = "example_cython", 
    cmdclass = {"build_ext": build_ext}, 
    ext_modules = ext_modules) 

構成:

Python 3.6.2 |Anaconda, Inc.| (default, Sep 21 2017, 18:29:43) 
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin 

>>> numpy.__version__ 
'1.13.1' 
>>> cython.__version__ 
'0.26.1' 

コンパイルと実行:それは私のi5 1.4GHzの上で実行するために周りの1,25秒かかり

running build_ext 
skipping 'example_cython.c' Cython extension (up-to-date) 
building 'example_cython' extension 
/usr/bin/clang -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -fwrapv -O2 -Wall -Wstrict-prototypes -march=core2 -mtune=haswell -mssse3 -ftree-vectorize -fPIC -fPIE -fstack-protector-strong -O2 -pipe -march=core2 -mtune=haswell -mssse3 -ftree-vectorize -fPIC -fPIE -fstack-protector-strong -O2 -pipe -I/Users/vmelo/anaconda3/include/python3.6m -c example_cython.c -o build/temp.macosx-10.9-x86_64-3.6/example_cython.o -Ofast -ffast-math 
example_cython.c:2506:15: warning: code will never be executed [-Wunreachable-code] 
    if (0 && (__pyx_tmp_idx < 0 || __pyx_tmp_idx >= __pyx_tmp_shape)) { 
       ^~~~~~~~~~~~~ 
example_cython.c:2506:9: note: silence by adding parentheses to mark code as explicitly dead 
    if (0 && (__pyx_tmp_idx < 0 || __pyx_tmp_idx >= __pyx_tmp_shape)) { 
     ^
     /* DISABLES CODE */ () 
example_cython.c:2505:9: warning: code will never be executed [-Wunreachable-code] 
     __pyx_tmp_idx += __pyx_tmp_shape; 
     ^~~~~~~~~~~~~ 
example_cython.c:2504:9: note: silence by adding parentheses to mark code as explicitly dead 
    if (0 && (__pyx_tmp_idx < 0)) 
     ^
     /* DISABLES CODE */ () 
2 warnings generated. 
/usr/bin/clang -bundle -undefined dynamic_lookup -Wl,-pie -Wl,-headerpad_max_install_names -Wl,-rpath,/Users/vmelo/anaconda3/lib -L/Users/vmelo/anaconda3/lib -Wl,-pie -Wl,-headerpad_max_install_names -Wl,-rpath,/Users/vmelo/anaconda3/lib -L/Users/vmelo/anaconda3/lib -arch x86_64 build/temp.macosx-10.9-x86_64-3.6/example_cython.o -L/Users/vmelo/anaconda3/lib -lm -o /Users/vmelo/Dropbox/SRC/python/random_equation/cython_v2/example_cython.cpython-36m-darwin.so 
ld: warning: -pie being ignored. It is only used when linking a main executable 

TEST 1: It took an average of 1.2609198093414307 seconds to evaluate 1000 expressions on a dataset of 1030 rows and 10 columns. 

。しかし、同様のCコードには0.13秒かかります。

上記のコードは1000個の式を評価しますが、私は1,000,000を目指しています。したがって、このCythonコードを大幅に高速化する必要があります。

冒頭に書いたように、Numpyのバージョンは正常に動作しています。 おそらく、このCythonバージョンでは、リストをスタックとして使用しないでください。私は速度を改善することに焦点を当てているので、このCythonコードによって生成された結果が正しいかどうかはまだチェックしていません。

提案がありますか?

ありがとうございました。

答えて

1

現在、最適化されている唯一の操作は、インデックスsamples[:, j]です。 (ほぼ)他のものは型がないので、Cythonはそれをあまり最適化できません。

私はあなたの(かなり大きな)プログラムを完全に書き直したいとは思っていませんが、改善するための簡単なアイディアがあります。

  1. は、基本的な論理エラーを修正 - あなたはあなたのループ内の行Y = numpy.zeros(N)を必要としています。 stack.append(Y)Yのコピーを作成しないので、Yを変更するたびに、スタックに置いた他のすべてのバージョンも変更されます。

    cdef double[:] a, b # at the start of the program 
    

    これはかなり

    Y[i] = a[i] * b[i] 
    

    のインデックス作成をスピードアップしますが、それはa = stack.pop()のようなラインは、わずかに遅いことになります:abのためのタイプを設定し

  2. 結果をメモリビューとして使用できるかどうかチェックする必要があります。

    stack.append(float(p)*np.ones(N)) 
    
  3. 変更2D memoryviewにスタック:また、あなたがスタック上memoryviewで使用可能な何かを置くことを確認するためにライン

    stack.append(float(p)) 
    

    を変更する必要があります。私はそれを過剰割り当てし、ちょうどnumber_on_stackのカウントを保持し、必要に応じてスタックを再割り当てすることをお勧めします。その後、変更することができます。

    stack.append(samples[:, j]) 
    

    へ:

    if stack.shape[1] < number_on_stack+1: 
        # resize numpy array 
        arr = stack.base 
        arr.resize(... larger shape ..., refcheck=False) 
        stack = arr # re-link stack to resized array (to ensure size is suitably updated) 
    stack[:,number_on_stack+1] = samples[:,j] 
    number_on_stack += 1 
    

    a = stack.pop() 
    

    a = stack[:,number_on_stack] 
    number_on_stack -= 1 
    

    に対するその他の変更は、同様のパターンに従ってください。このオプションはほとんどの場合有効ですが、最良の結果が得られる可能性があります。 HTMLはあなたにビットが十分に最適化されているの合理的なアイデアを提供します色の生成にcython -aを使用し


(黄色のコードは、一般的に悪いです)