Ini adalah prometeo, alat pemodelan eksperimental untuk komputasi kinerja tinggi yang tertanam. prometeo menyediakan bahasa khusus domain (DSL) berdasarkan subset bahasa Python yang memungkinkan seseorang dengan mudah menulis program komputasi ilmiah dalam bahasa tingkat tinggi (Python itu sendiri) yang dapat diubah menjadi kode C mandiri berkinerja tinggi dengan mudah dapat diterapkan pada perangkat yang disematkan.
Dokumentasi prometeo dapat ditemukan di Baca Dokumen di https://prometeo.readthedocs.io/en/latest/index.html.
Contoh sederhana hello world yang menunjukkan cara menjalankan program prometeo sepele dari Python atau memindahkannya ke C, membangun dan menjalankannya dapat ditemukan di sini. Outputnya menunjukkan hasil analisis penggunaan heap dan waktu eksekusi (dalam hal ini tidak banyak yang bisa dilihat :p).
Karena program prometeo diubah menjadi kode C murni yang memanggil perpustakaan aljabar linier kinerja tinggi BLASFEO (publikasi: https://arxiv.org/abs/1704.02457, kode: https://github.com/giaf/blasfeo), waktu eksekusi dapat sebanding dengan kode berkinerja tinggi yang ditulis tangan. Gambar di bawah menunjukkan perbandingan waktu CPU yang diperlukan untuk melakukan faktorisasi Riccati menggunakan kode C tulisan tangan yang sangat optimal dengan panggilan ke BLASFEO dan yang diperoleh dengan kode transpilasi prometeo dari contoh ini. Waktu komputasi yang diperoleh dengan NumPy dan Julia juga ditambahkan sebagai perbandingan - namun perhatikan bahwa dua implementasi terakhir dari faktorisasi Riccati ini tidak mudah disematkan seperti kode C yang dihasilkan oleh prometeo dan implementasi C kode tangan. Semua benchmark dijalankan pada Dell XPS-9360 yang dilengkapi dengan CPU i7-7560U yang berjalan pada 2,30 GHz (untuk menghindari fluktuasi frekuensi akibat pelambatan termal).
Selain itu, prometeo sebagian besar dapat mengungguli kompiler Python canggih seperti Nuitka. Tabel di bawah menunjukkan waktu CPU yang diperoleh pada benchmark Fibonacci.
parser/kompiler | Waktu CPU [dtk] |
---|---|
Python 3.7 (CPython) | 11.787 |
Nuitka | 10.039 |
PyPy | 1.78 |
prometeo | 0,657 |
prometeo dapat diinstal melalui PyPI dengan pip install prometeo-dsl
. Perhatikan bahwa, karena prometeo banyak menggunakan petunjuk tipe untuk melengkapi kode Python dengan informasi pengetikan statis, versi minimum Python yang diperlukan adalah 3.6.
Jika Anda ingin menginstal prometeo yang membangun sumber di mesin lokal Anda, Anda dapat melanjutkan sebagai berikut:
git submodule update --init
untuk mengkloning submodul.make install_shared
dari <prometeo_root>/prometeo/cpmt
untuk mengkompilasi dan menginstal perpustakaan bersama yang terkait dengan backend C. Perhatikan bahwa jalur instalasi default adalah <prometeo_root>/prometeo/cpmt/install
.virtualenv --python=<path_to_python3.6> <path_to_new_virtualenv>
.pip install -e .
dari <prometeo_root>
untuk menginstal paket Python. Terakhir, Anda dapat menjalankan contoh di <root>/examples
dengan pmt <example_name>.py --cgen=<True/False>
, dengan tanda --cgen
menentukan apakah kode tersebut dieksekusi oleh interpreter Python atau kode C. dihasilkan dikompilasi dan dijalankan.
Kode Python ( examples/simple_example/simple_example.py
)
from prometeo import *
n : dims = 10
def main () -> int :
A : pmat = pmat ( n , n )
for i in range ( 10 ):
for j in range ( 10 ):
A [ i , j ] = 1.0
B : pmat = pmat ( n , n )
for i in range ( 10 ):
B [ 0 , i ] = 2.0
C : pmat = pmat ( n , n )
C = A * B
pmat_print ( C )
return 0
dapat dijalankan oleh juru bahasa Python standar (diperlukan versi >3.6) dan akan melakukan operasi aljabar linier yang dijelaskan menggunakan perintah pmt simple_example.py --cgen=False
. Pada saat yang sama, kode tersebut dapat diurai oleh prometeo dan pohon sintaksis abstraknya (AST) dianalisis untuk menghasilkan kode C berkinerja tinggi berikut:
#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"
int main () {
___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 ;
struct pmat * A = c_pmt_create_pmat ( n , n );
for ( int i = 0 ; i < 10 ; i ++ ) {
for ( int j = 0 ; j < 10 ; j ++ ) {
c_pmt_pmat_set_el ( A , i , j , 1.0 );
}
}
struct pmat * B = c_pmt_create_pmat ( n , n );
for ( int i = 0 ; i < 10 ; i ++ ) {
c_pmt_pmat_set_el ( B , 0 , i , 2.0 );
}
struct pmat * 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 );
return 0 ;
}
yang mengandalkan paket aljabar linier berkinerja tinggi BLASFEO. Kode yang dihasilkan akan siap dikompilasi dan dijalankan saat menjalankan pmt simple_example.py --cgen=True
.
Meskipun menerjemahkan program yang ditulis dalam suatu bahasa ke bahasa lain dengan tingkat abstraksi yang sebanding bisa jauh lebih mudah daripada menerjemahkan ke program dengan tingkat abstraksi yang sangat berbeda (terutama jika bahasa target berada pada tingkat yang jauh lebih rendah), menerjemahkan program Python ke dalam program C masih melibatkan kesenjangan abstraksi yang cukup besar, hal ini secara umum bukanlah tugas yang mudah. Secara sederhana, tantangannya terletak pada perlunya menerapkan kembali fitur-fitur yang secara asli didukung oleh bahasa sumber dalam bahasa target. Secara khusus, ketika menerjemahkan Python ke C, kesulitannya datang dari tingkat abstraksi yang berbeda dari kedua bahasa tersebut dan dari kenyataan bahwa bahasa sumber dan bahasa target terdiri dari dua tipe yang sangat berbeda: Python adalah sebuah interpretasi , tipe bebek dan sampah. -bahasa yang dikumpulkan dan C adalah bahasa yang dikompilasi dan diketik secara statis .
Tugas mentranspilasi Python ke C menjadi lebih menantang jika kita menambahkan batasan bahwa kode C yang dihasilkan harus efisien (bahkan untuk komputasi skala kecil hingga menengah) dan dapat diterapkan pada perangkat keras yang tertanam. Faktanya, kedua persyaratan ini secara langsung menyiratkan bahwa kode yang dihasilkan tidak dapat menggunakan: i) pustaka runtime yang canggih, misalnya pustaka runtime Python, yang umumnya tidak tersedia pada perangkat keras tertanam ii) alokasi memori dinamis yang akan membuat eksekusi menjadi lambat dan tidak dapat diandalkan (pengecualian dibuat untuk memori yang dialokasikan dalam fase pengaturan dan yang ukurannya diketahui secara apriori).
Karena transformasi atau transpilasi kode sumber-ke-sumber, dan khususnya transpilasi kode Python menjadi kode C bukanlah bidang yang belum dijelajahi, berikut ini kami menyebutkan beberapa proyek yang ada yang mengatasinya. Dalam melakukan hal ini, kami menyoroti di mana dan bagaimana mereka tidak memenuhi salah satu dari dua persyaratan yang diuraikan di atas, yaitu efisiensi (skala kecil) dan kemampuan untuk ditanamkan.
Ada beberapa paket perangkat lunak yang menangani terjemahan Python-ke-C dalam berbagai bentuk.
Dalam konteks komputasi performa tinggi, Numba adalah kompiler just-in-time untuk fungsi numerik yang ditulis dengan Python. Oleh karena itu, tujuannya adalah untuk mengubah fungsi Python yang diberi anotasi dengan benar, bukan seluruh program, menjadi kode LLVM berkinerja tinggi sehingga eksekusinya dapat dipercepat. Numba menggunakan representasi internal dari kode yang akan diterjemahkan dan melakukan inferensi tipe (berpotensi parsial) pada variabel yang terlibat untuk menghasilkan kode LLVM yang dapat dipanggil dari Python atau dari C/C++. Dalam beberapa kasus, yaitu kasus di mana inferensi tipe lengkap dapat berhasil dilakukan, kode yang tidak bergantung pada C API dapat dihasilkan (menggunakan flag nopython ). Namun, kode LLVM yang dipancarkan masih bergantung pada Numpy untuk operasi BLAS dan LAPACK.
Nuitka adalah kompiler sumber-ke-sumber yang dapat menerjemahkan setiap konstruksi Python ke dalam kode C yang terhubung dengan perpustakaan libpython dan oleh karena itu mampu mentranspilasi program Python kelas besar. Untuk melakukan hal ini, hal ini bergantung pada fakta bahwa salah satu implementasi bahasa Python yang paling banyak digunakan, yaitu CPython , ditulis dalam C. Faktanya, Nuitka menghasilkan kode C yang berisi panggilan ke CPython yang biasanya dilakukan oleh pengurai Python. Meskipun pendekatan transpilasinya menarik dan umum, ia tidak dapat dengan mudah diterapkan pada perangkat keras yang tertanam karena ketergantungan intrinsiknya pada libpython . Pada saat yang sama, karena pemetaannya sangat mirip dengan konstruksi Python ke implementasi CPython , sejumlah masalah kinerja dapat terjadi ketika menyangkut komputasi kinerja tinggi skala kecil dan menengah. Hal ini terutama disebabkan oleh fakta bahwa operasi yang terkait dengan, misalnya, pengecekan tipe, alokasi memori, dan pengumpulan sampah yang dapat memperlambat eksekusi juga dilakukan oleh program yang ditranspilasi.
Cython adalah bahasa pemrograman yang tujuannya untuk memfasilitasi penulisan ekstensi C untuk bahasa Python. Secara khusus, ia dapat menerjemahkan (opsional) kode seperti Python yang diketik secara statis ke dalam kode C yang bergantung pada CPython . Serupa dengan pertimbangan yang dibuat untuk Nuitka , hal ini menjadikannya alat yang ampuh kapan pun memungkinkan untuk mengandalkan libpython (dan ketika overhead-nya dapat diabaikan, yaitu ketika menangani komputasi berskala cukup besar), namun tidak dalam konteks kepentingan di sini.
Terakhir, meskipun tidak menggunakan Python sebagai bahasa sumber, kami harus menyebutkan bahwa Julia juga tepat waktu (dan sebagian lebih awal) dikompilasi ke dalam kode LLVM. Namun kode LLVM yang dipancarkan bergantung pada perpustakaan runtime Julia sehingga pertimbangan serupa dengan yang dibuat untuk Cython dan Nuitka berlaku.
Transpilasi program yang ditulis menggunakan subset terbatas bahasa Python ke dalam program C dilakukan menggunakan transpiler prometeo . Alat transformasi sumber-ke-sumber ini menganalisis pohon sintaksis abstrak (AST) yang terkait dengan file sumber yang akan ditranspilasi untuk menghasilkan kode C berkinerja tinggi dan dapat disematkan. Untuk melakukan hal ini, aturan khusus perlu diterapkan pada kode Python. Hal ini membuat tugas yang sangat menantang untuk mentranspilasi bahasa tipe bebek tingkat tinggi yang ditafsirkan menjadi bahasa yang diketik secara statis tingkat rendah yang dikompilasi menjadi mungkin. Dalam melakukannya, kami mendefinisikan apa yang kadang-kadang disebut sebagai DSL tertanam dalam arti bahasa yang dihasilkan menggunakan sintaks bahasa host (Python itu sendiri) dan, dalam kasus prometeo , ini juga dapat dieksekusi oleh penerjemah Python standar .
from prometeo import *
nx : dims = 2
nu : dims = 2
nxu : dims = nx + nu
N : dims = 5
def main () -> int :
# number of repetitions for timing
nrep : int = 10000
A : pmat = pmat ( nx , nx )
A [ 0 , 0 ] = 0.8
A [ 0 , 1 ] = 0.1
A [ 1 , 0 ] = 0.3
A [ 1 , 1 ] = 0.8
B : pmat = pmat ( nx , nu )
B [ 0 , 0 ] = 1.0
B [ 1 , 1 ] = 1.0
Q : pmat = pmat ( nx , nx )
Q [ 0 , 0 ] = 1.0
Q [ 1 , 1 ] = 1.0
R : pmat = pmat ( nu , nu )
R [ 0 , 0 ] = 1.0
R [ 1 , 1 ] = 1.0
A : 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 ] = R
RSQ [ nu : nu + nx , nu : nu + nx ] = Q
# array-type Riccati factorization
for i in range ( nrep ):
pmt_potrf ( Q , Lxx )
M [ nu : nu + nx , nu : nu + nx ] = Lxx
for i in range ( 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 ]
return 0
Demikian pula, kode di atas ( example/riccati/riccati_array.py
) dapat dijalankan oleh penerjemah Python standar menggunakan perintah pmt riccati_array.py --cgen=False
dan prometeo dapat menghasilkan, mengkompilasi, dan menjalankan kode C menggunakan pmt riccati_array.py --cgen=True
.
Agar dapat melakukan transpilasi ke C, hanya sebagian dari bahasa Python yang didukung. Namun, fitur-fitur yang tidak mirip C seperti fungsi yang berlebihan dan kelas didukung oleh transpiler prometeo. Contoh Riccati yang diadaptasi ( examples/riccati/riccati_mass_spring_2.py
) di bawah ini menunjukkan bagaimana kelas dapat dibuat dan digunakan.
from prometeo import *
nm : dims = 4
nx : dims = 2 * nm
sizes : dimv = [[ 8 , 8 ], [ 8 , 8 ], [ 8 , 8 ], [ 8 , 8 ], [ 8 , 8 ]]
nu : dims = nm
nxu : dims = nx + nu
N : dims = 5
class qp_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 )
def factorize ( 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 )
for i in range ( 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 ] = R
M [ nu : nu + nx , nu : nu + nx ] = Q
pmt_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])
return
def main () -> int :
A : pmat = pmat ( nx , nx )
Ac11 : pmat = pmat ( nm , nm )
Ac12 : pmat = pmat ( nm , nm )
for i in range ( nm ):
Ac12 [ i , i ] = 1.0
Ac21 : pmat = pmat ( nm , nm )
for i in range ( nm ):
Ac21 [ i , i ] = - 2.0
for i in range ( nm - 1 ):
Ac21 [ i + 1 , i ] = 1.0
Ac21 [ i , i + 1 ] = 1.0
Ac22 : pmat = pmat ( nm , nm )
for i in range ( nm ):
for j in range ( nm ):
A [ i , j ] = Ac11 [ i , j ]
for i in range ( nm ):
for j in range ( nm ):
A [ i , nm + j ] = Ac12 [ i , j ]
for i in range ( nm ):
for j in range ( nm ):
A [ nm + i , j ] = Ac21 [ i , j ]
for i in range ( nm ):
for j in range ( nm ):
A [ nm + i , nm + j ] = Ac22 [ i , j ]
tmp : float = 0.0
for i in range ( nx ):
tmp = A [ i , i ]
tmp = tmp + 1.0
A [ i , i ] = tmp
B : pmat = pmat ( nx , nu )
for i in range ( nu ):
B [ nm + i , i ] = 1.0
Q : pmat = pmat ( nx , nx )
for i in range ( nx ):
Q [ i , i ] = 1.0
R : pmat = pmat ( nu , nu )
for i in range ( nu ):
R [ i , i ] = 1.0
qp : qp_data = qp_data ()
for i in range ( N ):
qp . A [ i ] = A
for i in range ( N ):
qp . B [ i ] = B
for i in range ( N ):
qp . Q [ i ] = Q
for i in range ( N ):
qp . R [ i ] = R
qp . factorize ()
return 0
Penafian: prometeo masih dalam tahap awal dan hanya beberapa operasi aljabar linier dan konstruksi Python yang didukung untuk saat ini.