2017-10-09 13 views
0

.Calculatrice de Postfix en Cython

Salut. J'essaye de développer une calculatrice de postfix dans Cython, traduite d'une version de Numpy fonctionnante. C'est ma première tentative. La fonction calculatrice obtient l'expression postfix dans une liste et la matrice des échantillons. Ensuite, il doit retourner le tableau calculé.

exemple d'entrée:

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) 

Configuration:

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' 

Compilation et exécutez:

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. 

Il faut environ secondes pour exécuter 1,25 sur mon i5 1.4Ghz. Cependant, un code C similaire prend 0,13 secondes.

Le code ci-dessus évalue 1000 expressions, mais je vis à 1 000 000. Ainsi, je dois accélérer ce code Cython par une grande marge.

Comme je l'ai écrit au début, la version Numpy fonctionne correctement. Peut-être, dans cette version de Cython, je ne devrais pas utiliser une liste comme une pile? Je ne vérifie toujours pas si les résultats générés par ce code Cython sont corrects, car je suis concentré sur l'amélioration de sa vitesse.

Des suggestions?

Merci.

Répondre

1

Actuellement, la seule opération optimisée est l'indexation samples[:, j]. (Presque) tout le reste est non typé et Cython ne peut donc pas l'optimiser.

Je ne veux pas vraiment réécrire complètement votre programme (assez grand), mais voici quelques idées simples sur la façon de l'améliorer.

  1. corriger une erreur logique de base - vous avez besoin de la ligne Y = numpy.zeros(N) dans votre boucle. stack.append(Y) ne fait pas une copie de Y, donc chaque fois que vous modifiez Y vous modifiez également toutes les autres versions que vous avez mises sur la pile.

  2. Définir un type pour a et b:

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

    Cela permettra d'accélérer considérablement l'indexation des

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

    cependant, Fera la ligne comme a = stack.pop() être un peu plus lent que il doit vous vérifier que le résultat peut être utilisé comme mémoire. Vous devrez également modifier la ligne

    stack.append(float(p)) 
    

    pour vous assurer de mettre quelque chose d'utilisable avec un memoryview sur la pile:

    stack.append(float(p)*np.ones(N)) 
    
  3. Changer la pile à un memoryview 2D. Je vous suggère de le surallouer et de garder le nombre de number_on_stack, puis de réaffecter la pile si nécessaire. Vous pouvez ensuite modifier:

    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 
    

    et

    a = stack.pop() 
    

    à

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

    D'autres changements suivent un schéma similaire. Cette option est la plupart du travail, mais susceptible d'obtenir de meilleurs résultats.


En utilisant cython -a pour générer HTML couleur vous donne une idée raisonnable dont les bits sont bien optimisés (code jaune est généralement pire)