Rantai python transpiler → 11l → C ++ [untuk mempercepat kode Python dan banyak lagi]




Artikel ini membahas konversi yang paling menarik yang dilakukan oleh dua rantai transponder (yang pertama menerjemahkan kode Python menjadi kode dalam bahasa pemrograman baru 11l , dan yang kedua menerjemahkan kode menjadi 11l dalam C ++), dan juga membandingkan kinerja dengan alat akselerasi lainnya / Eksekusi kode python (PyPy, Cython, Nuitka).

Mengganti irisan \ irisan dengan rentang

Python11l
s[-1] s[-2] s[:-1] s[1:] s[:2] s[1:2] s[::2] s[1::2] s[3:10:2] 
 s.last s[(len)-2] s[0..<(len)-1] s[1..] s[0..<2] s[1..<2] s[(0..).step(2)] s[(1..).step(2)] s[(3..<10).step(2)] 
Indikasi eksplisit untuk pengindeksan dari akhir array s[(len)-2] bukan hanya s[-2] diperlukan untuk menghilangkan kesalahan berikut:
  1. Ketika, misalnya, diperlukan untuk mendapatkan karakter sebelumnya dengan s[i-1] , tetapi untuk i = 0 seperti / catatan ini bukannya kesalahan akan diam-diam mengembalikan karakter terakhir dari string [ dan dalam praktiknya saya menemukan kesalahan seperti itu - komit ] .
  2. Ekspresi s[i:] setelah i = s.find(":") akan bekerja secara tidak benar ketika karakter tidak ditemukan dalam string [ bukan '' bagian dari string yang dimulai dari karakter pertama : dan kemudian '' karakter terakhir dari string akan diambil ] (dan umumnya , Saya pikir mengembalikan -1 dengan fungsi find() di Python juga salah [ harus mengembalikan null / Tidak ada [ dan jika -1 diperlukan, itu harus ditulis secara eksplisit: i = s.find(":") ?? -1 ] ] )
  3. Menulis s[-n:] untuk mendapatkan n karakter terakhir dari string tidak akan berfungsi dengan benar ketika n = 0.

Rantai operator pembanding


Sepintas, ini adalah fitur yang luar biasa dari bahasa Python, tetapi dalam praktiknya itu dapat dengan mudah ditinggalkan / dibagikan dengan menggunakan operator dan rentang:
a < b < cb in a<..<c
a <= b < cb in a..<c
a < b <= cb in a<..c
0 <= b <= 9b in 0..9

Daftar pemahaman


Demikian pula, ternyata, Anda dapat menolak fitur menarik lain dari Python - daftar pemahaman.
Sementara beberapa mengagungkan pemahaman daftar dan bahkan menyarankan untuk meninggalkan `filter ()` dan `map ()` , saya menemukan bahwa:
  1. Di semua tempat di mana saya melihat pemahaman daftar Python, Anda dapat dengan mudah bertahan dengan fungsi `filter ()` dan `map ()`.
     dirs[:] = [d for d in dirs if d[0] != '.' and d != exclude_dir] dirs[:] = filter(lambda d: d[0] != '.' and d != exclude_dir, dirs) '[' + ', '.join(python_types_to_11l[ty] for ty in self.type_args) + ']' '[' + ', '.join(map(lambda ty: python_types_to_11l[ty], self.type_args)) + ']' # Nested list comprehension: matrix = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], ] [[row[i] for row in matrix] for i in range(4)] list(map(lambda i: list(map(lambda row: row[i], matrix)), range(4))) 
  2. `filter ()` dan `map ()` dalam 11l terlihat lebih cantik daripada di Python
     dirs[:] = filter(lambda d: d[0] != '.' and d != exclude_dir, dirs) dirs = dirs.filter(d -> d[0] != '.' & d != @exclude_dir) '[' + ', '.join(map(lambda ty: python_types_to_11l[ty], self.type_args)) + ']' '['(.type_args.map(ty -> :python_types_to_11l[ty]).join(', '))']' outfile.write("\n".join(x[1] for x in fileslist if x[0])) outfile.write("\n".join(map(lambda x: x[1], filter(lambda x: x[0], fileslist)))) outfile.write(fileslist.filter(x -> x[0]).map(x -> x[1]).join("\n")) 
    dan akibatnya, kebutuhan untuk pemahaman daftar dalam 11l benar-benar menghilang [ penggantian pemahaman daftar dengan filter() dan / atau map() dilakukan selama konversi kode Python ke 11l secara otomatis ] .

Ubah rantai if-elif-else untuk beralih


Sementara Python tidak mengandung pernyataan switch, ini adalah salah satu konstruksi paling indah di 11l, dan jadi saya memutuskan untuk memasukkan switch secara otomatis:
Python11l
 ch = instr[i] if ch == "[": nesting_level += 1 elif ch == "]": nesting_level -= 1 if nesting_level == 0: break elif ch == "'": ending_tags.append(''') # '' elif ch == "'": assert(ending_tags.pop() == ''') 
 switch instr[i] '[' nesting_level++ ']' if --nesting_level == 0 loop.break "'" ending_tags.append("'") // '' "'" assert(ending_tags.pop() == "'") 
Untuk kelengkapan, berikut adalah kode C ++ yang dihasilkan
 switch (instr[i]) { case u'[': nesting_level++; break; case u']': if (--nesting_level == 0) goto break_; break; case u''': ending_tags.append(u"'"_S); break; // '' case u''': assert(ending_tags.pop() == u'''); break; } 


Ubah kamus kecil menjadi kode asli


Pertimbangkan baris kode Python ini:
 tag = {'*':'b', '_':'u', '-':'s', '~':'i'}[prev_char()] 
Kemungkinan besar, bentuk rekaman ini tidak terlalu efektif [ dalam hal kinerja ] , tetapi sangat nyaman.

Dalam 11l, entri yang sesuai dengan baris ini [ dan diperoleh oleh transporter Python → 11l ] tidak hanya nyaman [ namun, tidak begitu elegan seperti pada Python ] , tetapi juga cepat:
 var tag = switch prev_char() {'*' {'b'}; '_' {'u'}; '-' {'s'}; '~' {'i'}} 

Baris di atas diterjemahkan ke dalam:
 auto tag = [&](const auto &a){return a == u'*' ? u'b'_C : a == u'_' ? u'u'_C : a == u'-' ? u's'_C : a == u'~' ? u'i'_C : throw KeyError(a);}(prev_char()); 
[ Panggilan fungsi lambda akan dikompilasi oleh kompiler C ++ \ inline selama proses optimasi dan hanya rantai operator yang akan tetap ?/: ]

Dalam kasus ketika suatu variabel ditugaskan, kamus dibiarkan apa adanya:
Python
 rn = {'I': 1, 'V': 5, 'X': 10, 'L': 50, ...} 
11l
 var rn = ['I' = 1, 'V' = 5, 'X' = 10, 'L' = 50, ...] 
C ++
 auto rn = create_dict(dict_of(u'I'_C, 1)(u'V'_C, 5)(u'X'_C, 10)(u'L'_C, 50)...); 

Capture \ Ca pture variabel eksternal


Dalam Python, untuk menunjukkan bahwa suatu variabel bukan lokal, tetapi harus diambil di luar [ dari fungsi saat ini ] , kata kunci nonlokal digunakan [ jika tidak, misalnya, found = True akan diperlakukan sebagai membuat variabel lokal baru yang found , daripada menetapkan nilai yang sudah variabel eksternal yang ada ] .
Di 11l, awalan @ digunakan untuk ini:
Python11l
 writepos = 0 def write_to_pos(pos, npos): nonlocal writepos outfile.write(...) writepos = npos 
 var writepos = 0 fn write_to_pos(pos, npos) @outfile.write(...) @writepos = npos 
C ++:
 auto writepos = 0; auto write_to_pos = [..., &outfile, &writepos](const auto &pos, const auto &npos) { outfile.write(...); writepos = npos; }; 

Variabel global


Mirip dengan variabel eksternal, jika Anda lupa mendeklarasikan variabel global dalam Python [ menggunakan kata kunci global ] , Anda mendapatkan bug yang tidak terlihat:
 break_label_index = -1 ... def parse(tokens, source_): global source, tokeni, token, scope source = source_ tokeni = -1 token = None break_label_index = -1 scope = Scope(None) ... 
 var break_label_index = -1 ... fn parse(tokens, source_) :source = source_ :tokeni = -1 :token = null break_label_index = -1 :scope = Scope(null) ... 
Kode 11l [ kanan ] , tidak seperti Python [ kiri ], akan break_label_index kesalahan 'variabel break_label_index tidak dideklarasikan' pada break_label_index kompilasi.

Indeks / nomor item kontainer saat ini


Saya terus lupa urutan variabel yang mengembalikan fungsi Python enumerate {nilai lebih dulu, lalu indeks, atau sebaliknya}. Perilaku analog di Ruby - each.with_index - lebih mudah diingat: dengan indeks berarti indeks datang setelah nilai, bukan sebelumnya. Tetapi dalam 11l, logika bahkan lebih mudah diingat:
Python11l
 items = ['A', 'B', 'C'] for index, item in enumerate(items): print(str(index) + ' = ' + item) 
 var items = ['A', 'B', 'C'] loop(item) items print(loop.index' = 'item) 

Performa


Program untuk mengubah markup PC ke HTML digunakan sebagai program pengujian, dan kode sumber untuk artikel pada markup PC digunakan sebagai sumber data [ karena artikel ini saat ini yang terbesar dari yang ditulis pada markup PC ] , dan diulang 10 kali, yaitu diperoleh dari file artikel 48,8 kilobyte ukuran 488Kb.

Berikut adalah diagram yang menunjukkan berapa kali cara yang sesuai untuk mengeksekusi kode Python lebih cepat daripada implementasi asli [ CPython ] :

Dan sekarang tambahkan ke diagram implementasi yang dihasilkan oleh Python → 11l → C ++ transpiler:

Waktu konversi file [ 488Kb ] runtime adalah 868 ms untuk CPython dan 38 ms untuk kode C ++ yang dihasilkan [ kali ini termasuk [ i.e. tidak hanya bekerja dengan data dalam RAM ] menjalankan program dengan sistem operasi dan semua input / output [ membaca file sumber [ .pq ] dan menyimpan file baru [ .html ] ke disk ] ] .

Saya juga ingin mencoba Shed Skin , tetapi tidak mendukung fungsi lokal.
Numba juga tidak dapat digunakan (ia melempar kesalahan 'Penggunaan opcode yang tidak diketahui LOAD_BUILD_CLASS').
Berikut adalah arsip dengan program yang digunakan untuk membandingkan kinerja [di bawah Windows ] (membutuhkan Python 3.6 atau lebih tinggi dan paket Python berikut: pywin32, cython).

Kode sumber dalam Python dan output dari transponder Python -> 11l dan 11l -> C ++:
PythonDihasilkan 11l
(dengan kata kunci alih-alih huruf)
11l
(dengan surat)
Dihasilkan C ++

Source: https://habr.com/ru/post/id431318/


All Articles