Package: prometeo-dsl
This is prometeo, an experimental modeling tool for embedded high-performance computing. prometeo provides a
domain specific language (DSL) based on a subset of the Python language that allows
one to conveniently write scientific computing programs in a high-level language (Python itself) that can be transpiled
to high-performance self-contained C code easily deployable on embedded devices.featuresPython compatible syntax: prometeo is a DSL embedded into the Python language.
prometeo programs can be executed from the Python interpreter.efficient: prometeo programs transpile to high-performance C code.statically typed: prometeo uses Python's native type hints to strictly enforce static typing.deterministic memory usage: a specific program structure is required and enforced through static analysis. In this way
prometeo transpiled programs have a guaranteed maximum heap usage.fast memory management: thanks to its static analysis, prometeo can avoid allocating
and garbage-collecting memory, resulting in faster and safer execution.self-contained and embeddable: unlike other similar tools and languages, prometeo targets specifically embedded applications and programs
written in prometeo transpile to self-contained C code that does not require linking against
the Python run-time library.documentation (preliminary)prometeo's documentation can be found on Read the Docs athttps://prometeo.readthedocs.io/en/latest/index.html.hello world!A simple hello world example that shows how to either run a trivial prometeo program from Python or transpile it to C, build it and run it can be foundhere. The output shows the outcome of the heap usage analysis and the execution time (in this case there is not much to see :p).performanceSince prometeo programs transpile to pure C code that calls the high performance linear algebra library BLASFEO (publication:https://arxiv.org/abs/1704.02457, code:https://github.com/giaf/blasfeo), execution time can be comparable to hand-written high-performance code. The figure below shows a comparison of the CPU time necessary to carry out a Riccati factorization using highly optimized hand-written C code with calls to BLASFEO and the ones obtained with prometeo transpiled code fromthis example. The computation times obtained with NumPy and Julia are added too for comparison - notice however that these last two implementations of the Riccati factorization arenot as easily embeddableas the C code generated by prometeo and the hand-coded C implementation. All the benchmarks have been run on a Dell XPS-9360 equipped with an i7-7560U CPU running at 2.30 GHz (to avoid frequency fluctuations due to thermal throttling).Moreover, prometeo can largely outperform state-of-the-art Python compilers such as Nuitka. The table below shows the CPU times obtained on a Fibonacci benchmark.parser/compilerCPU time [s]Python 3.7 (CPython)11.787Nuitka10.039PyPy1.78prometeo0.657PyPI installationprometeo can be installed through PyPI withpip install prometeo-dsl. Notice that, since prometeo makes extensive use oftype hintsto equip Python code with static typing information, the minimum Python version required is 3.6.manual installationIf you want to install prometeo building the sources on your local machine you can proceed as follows:Rungit submodule update --initto clone the submodules.Runmake install_sharedfrom<prometeo_root>/prometeo/cpmtto compile and install the shared library associated with the C backend. Notice that the default installation path is<prometeo_root>/prometeo/cpmt/install.You need Python 3.6. or later.Optional: to keep things clean you can setup a virtual environment withvirtualenv --python=<path_to_python3.6> <path_to_new_virtualenv>.Runpip install -e .from<prometeo_root>to install the Python package.Finally, you can run the examples in<root>/exampleswithpmt <example_name>.py --cgen=<True/False>, where the--cgenflag determines whether the code is executed by the Python interpreter or C code is generated compiled and run.a simple exampleThe Python code (examples/simple_example/simple_example.py)fromprometeoimport*n:dims=10defmain()->int:A:pmat=pmat(n,n)foriinrange(10):forjinrange(10):A[i,j]=1.0B:pmat=pmat(n,n)foriinrange(10):B[0,i]=2.0C:pmat=pmat(n,n)C=A*Bpmat_print(C)return0can be run by the standard Python interpreter (version >3.6 required) and it
will perform the described linear algebra operations using the commandpmt simple_example.py --cgen=False.
At the same time, the code can be parsed by prometeo and its abstract syntax tree (AST) analyzed in order
to generate the following high-performance C code:#include"stdlib.h"#include"simple_example.h"void*___c_pmt_8_heap;void*___c_pmt_64_heap;void*___c_pmt_8_heap_head;void*___c_pmt_64_heap_head;#include"prometeo.h"intmain(){___c_pmt_8_heap=malloc(10000);___c_pmt_8_heap_head=___c_pmt_8_heap;char*pmem_ptr=(char*)___c_pmt_8_heap;align_char_to(8,&pmem_ptr);___c_pmt_8_heap=pmem_ptr;___c_pmt_64_heap=malloc(1000000);___c_pmt_64_heap_head=___c_pmt_64_heap;pmem_ptr=(char*)___c_pmt_64_heap;align_char_to(64,&pmem_ptr);___c_pmt_64_heap=pmem_ptr;void*callee_pmt_8_heap=___c_pmt_8_heap;void*callee_pmt_64_heap=___c_pmt_64_heap;structpmat*A=c_pmt_create_pmat(n,n);for(inti=0;i<10;i++){for(intj=0;j<10;j++){c_pmt_pmat_set_el(A,i,j,1.0);}}structpmat*B=c_pmt_create_pmat(n,n);for(inti=0;i<10;i++){c_pmt_pmat_set_el(B,0,i,2.0);}structpmat*C=c_pmt_create_pmat(n,n);c_pmt_pmat_fill(C,0.0);c_pmt_gemm_nn(A,B,C,C);c_pmt_pmat_print(C);___c_pmt_8_heap=callee_pmt_8_heap;___c_pmt_64_heap=callee_pmt_64_heap;free(___c_pmt_8_heap_head);free(___c_pmt_64_heap_head);return0;}which relies on the high-performance linear algebra package BLASFEO. The generated code will be readily compiled and run with when runningpmt simple_example.py --cgen=True.how does it work?Although translating a program written in a language into another with
a comparable level of abstraction can be significantly easier than
translating to one with a very different level of abstraction
(especially if the target language is of much lower level),
translating Python programs into C programs still involves a
considerable abstraction gap it is not an easy task
in general. Loosely speaking, the challenge lies in the necessity to
reimplement features that are natively supported by the source
language in the target language. In particular, when translating
Python to C, the difficulty comes both from the different level of
abstraction of the two languages and from the fact that the source and
target language are of two very different types: Python is aninterpreted,duck-typedandgarbage-collectedlanguage and C is acompiledandstatically typedlanguage.The task of transpiling Python to C becomes even more challenging if we add the
constraint that the generated C code must be efficient (even for small
to medium scale computations) and deployable
on embedded hardware. In fact these two requirements directly imply
that the generated code cannot make use of:i)sophisticated runtime
libraries, e.g., the Python runtime library, which are generally not available on
embedded hardwareii)dynamic memory allocation that would make the execution
slow and unreliable (exception made for memory that is allocated in a
setup phase and whose size is known a priori).Since source-to-source code transformation, or transpilation, and in
particular transpilation of Python code into C code is not an
unexplored realm, in the following, we mention a few existing projects
that address it. In doing so, we highlight where and how they do not satisfy one of
the two requirements outlined above, namely (small scale) efficiency
and embeddability.related workSeveral software packages exist that address Python-to-C translation in
various forms.In the context of high-performance computing,Numbais a just-in-time compiler for numerical
functions written in
Python. As such, its aim is to convert properly annotated Python
functions, not entire programs, into high-performance LLVM code such
that their execution can be sped
up.Numbauses an internal representation of the code to be
translated and performs a (potentially partial) type inference on the
variables involved in order to generate LLVM code that can be called
either from Python or from C/C++. In some cases, namely the
ones where a complete type inference can
be carried out successfully, code that does not rely on the C API can be
generated (using thenopythonflag). However, the emitted LLVM code
would still rely onNumpyfor BLAS and LAPACK  operations.Nuitkais a source-to-source compiler that can translate
every Python construct into C code that links against thelibpythonlibrary and it is therefore able to transpile a
large class of Python programs. In order to do so, it relies on the fact
that one of the most used implementations of the Python language,
namelyCPython, is written in C. In fact,Nuitkagenerates C code that contains calls toCPythonthat would
normally be carried out by the Python parser. Despite its
attractive and general transpilation approach, it cannot be easily
deployed on embedded hardware due to its intrinsic dependency onlibpython. At the same time, since it maps rather closely
Python constructs to theirCPythonimplementation, a number
of performance issues can be expected when it comes to small to medium
scale high-performance computing. This is particularly due to the fact
that operations associated with,
for example, type checking, memory allocation and garbage collection
that can slow down the execution are carried out by the transpiled
program too.Cythonis a programming language whose goal is to
facilitate writing C extensions for the Python language. In
particular, it can translate (optionally) statically typed Python-like
code into C code that relies onCPython. Similarly to the
considerations made forNuitka, this makes it a powerful tool
whenever it is possible to rely onlibpython(and when its
overhead is negligible, i.e., when dealing with sufficiently large
scale computations), but not in the context of interest here.Finally, although it does not use Python as source language, we should
mention thatJuliatoo is just-in-time (and partially
ahead-of-time) compiled into LLVM
code. The emitted LLVM code relies however on theJuliaruntime library such that considerations similar to the one made forCythonandNuitkaapply.prometeo's transpilerTranspilation of programs written using a restricted subset of the
Python language into C programs is carried out usingprometeo's
transpiler. This source-to-source transformation tool analyzes abstract
syntax trees (AST) associated with the source files to be transpiled
in order to emit high-performance and embeddable C code. In order to
do so, special rules need to be imposed on the Python code. This makes
the otherwise extremely challenging task of transpiling an interpreted high-level
duck-typed language into a compiled low-level statically typed one
possible. In doing so, we define what is sometimes referred to as anembeddedDSL in the sense the resulting language uses the syntax of a host language
(Python itself) and, inprometeo's case, it can also be executed by
the standard Python interpreter.a more advanced example (Riccati factorization)fromprometeoimport*nx:dims=2nu:dims=2nxu:dims=nx+nuN:dims=5defmain()->int:# number of repetitions for timingnrep:int=10000A:pmat=pmat(nx,nx)A[0,0]=0.8A[0,1]=0.1A[1,0]=0.3A[1,1]=0.8B:pmat=pmat(nx,nu)B[0,0]=1.0B[1,1]=1.0Q:pmat=pmat(nx,nx)Q[0,0]=1.0Q[1,1]=1.0R:pmat=pmat(nu,nu)R[0,0]=1.0R[1,1]=1.0A:pmat=pmat(nx,nx)B:pmat=pmat(nx,nu)Q:pmat=pmat(nx,nx)R:pmat=pmat(nu,nu)RSQ:pmat=pmat(nxu,nxu)Lxx:pmat=pmat(nx,nx)M:pmat=pmat(nxu,nxu)w_nxu_nx:pmat=pmat(nxu,nx)BAt:pmat=pmat(nxu,nx)BA:pmat=pmat(nx,nxu)pmat_hcat(B,A,BA)pmat_tran(BA,BAt)RSQ[0:nu,0:nu]=RRSQ[nu:nu+nx,nu:nu+nx]=Q# array-type Riccati factorizationforiinrange(nrep):pmt_potrf(Q,Lxx)M[nu:nu+nx,nu:nu+nx]=Lxxforiinrange(1,N):pmt_trmm_rlnn(Lxx,BAt,w_nxu_nx)pmt_syrk_ln(w_nxu_nx,w_nxu_nx,RSQ,M)pmt_potrf(M,M)Lxx[0:nx,0:nx]=M[nu:nu+nx,nu:nu+nx]return0Similarly, the code above (example/riccati/riccati_array.py) can be run by the standard Python interpreter using the commandpmt riccati_array.py --cgen=Falseand prometeo can generate, compile and run C code using insteadpmt riccati_array.py --cgen=True.supported Python constructsIn order to be able to transpile to C, only a subset of the Python language is supported. However, non C-like features such as function overload and classes are supported by prometeo's transpiler. The adapted Riccati example (examples/riccati/riccati_mass_spring_2.py) below shows how classes can be created and used.fromprometeoimport*nm:dims=4nx:dims=2*nmsizes:dimv=[[8,8],[8,8],[8,8],[8,8],[8,8]]nu:dims=nmnxu:dims=nx+nuN:dims=5classqp_data:A:List=plist(pmat,sizes)B:List=plist(pmat,sizes)Q:List=plist(pmat,sizes)R:List=plist(pmat,sizes)P:List=plist(pmat,sizes)fact:List=plist(pmat,sizes)deffactorize(self)->None:M:pmat=pmat(nxu,nxu)Mxx:pmat=pmat(nx,nx)L:pmat=pmat(nxu,nxu)Q:pmat=pmat(nx,nx)R:pmat=pmat(nu,nu)BA:pmat=pmat(nx,nxu)BAtP:pmat=pmat(nxu,nx)pmat_copy(self.Q[N-1],self.P[N-1])pmat_hcat(self.B[N-1],self.A[N-1],BA)pmat_copy(self.Q[N-1],Q)pmat_copy(self.R[N-1],R)foriinrange(1,N):pmat_fill(BAtP,0.0)pmt_gemm_tn(BA,self.P[N-i],BAtP,BAtP)pmat_fill(M,0.0)M[0:nu,0:nu]=RM[nu:nu+nx,nu:nu+nx]=Qpmt_gemm_nn(BAtP,BA,M,M)pmat_fill(L,0.0)pmt_potrf(M,L)Mxx[0:nx,0:nx]=L[nu:nu+nx,nu:nu+nx]# pmat_fill(self.P[N-i-1], 0.0)pmt_gemm_nt(Mxx,Mxx,self.P[N-i-1],self.P[N-i-1])# pmat_print(self.P[N-i-1])returndefmain()->int:A:pmat=pmat(nx,nx)Ac11:pmat=pmat(nm,nm)Ac12:pmat=pmat(nm,nm)foriinrange(nm):Ac12[i,i]=1.0Ac21:pmat=pmat(nm,nm)foriinrange(nm):Ac21[i,i]=-2.0foriinrange(nm-1):Ac21[i+1,i]=1.0Ac21[i,i+1]=1.0Ac22:pmat=pmat(nm,nm)foriinrange(nm):forjinrange(nm):A[i,j]=Ac11[i,j]foriinrange(nm):forjinrange(nm):A[i,nm+j]=Ac12[i,j]foriinrange(nm):forjinrange(nm):A[nm+i,j]=Ac21[i,j]foriinrange(nm):forjinrange(nm):A[nm+i,nm+j]=Ac22[i,j]tmp:float=0.0foriinrange(nx):tmp=A[i,i]tmp=tmp+1.0A[i,i]=tmpB:pmat=pmat(nx,nu)foriinrange(nu):B[nm+i,i]=1.0Q:pmat=pmat(nx,nx)foriinrange(nx):Q[i,i]=1.0R:pmat=pmat(nu,nu)foriinrange(nu):R[i,i]=1.0qp:qp_data=qp_data()foriinrange(N):qp.A[i]=Aforiinrange(N):qp.B[i]=Bforiinrange(N):qp.Q[i]=Qforiinrange(N):qp.R[i]=Rqp.factorize()return0Disclaimer: prometeo is still at a very preliminary stage and only a few linear algebra operations and Python constructs are supported for the time being.