Cadeia de transpiladores Python → 11l → C ++ [para acelerar o código Python e mais]




Este artigo discute as conversões mais interessantes que uma cadeia de dois transpilers realiza (o primeiro traduz código Python em código na nova linguagem de programação 11l e o segundo traduz código em 11l em C ++) e também compara o desempenho com outras ferramentas de aceleração / Execução de código Python (PyPy, Cython, Nuitka).

Substituindo fatias \ fatias por intervalos

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)] 
A indicação explícita para indexação do final da matriz s[(len)-2] vez de apenas s[-2] necessária para eliminar os seguintes erros:
  1. Quando, por exemplo, é necessário obter o caractere anterior por s[i-1] , mas para i = 0, esse registro / em vez de um erro retornará silenciosamente o último caractere da string [ e, na prática, encontrei esse erro - confirmação ] .
  2. A expressão s[i:] após i = s.find(":") funcionará incorretamente quando o caractere não for encontrado na string [em vez de '' parte da string iniciando no primeiro caractere : e depois '' o último caractere da string será utilizado ]] (e geralmente , Acho que retornar -1 com a função find() em Python também está incorreto [ deve retornar null / None [ e se -1 for necessário, ele deve ser escrito explicitamente: i = s.find(":") ?? -1 ] ] )
  3. Escrever s[-n:] para obter os últimos n caracteres de uma string não funcionará corretamente quando n = 0.

Cadeias de operadores de comparação


À primeira vista, é um recurso excelente da linguagem Python, mas na prática pode ser facilmente abandonado / dispensado usando o operador in e os intervalos:
a < b < cb in a<..<c
a <= b < cb in a..<c
a < b <= cb in a<..c
0 <= b <= 9b in 0..9

Compreensão da lista


Da mesma forma, como se viu, você pode recusar outro recurso interessante das compreensões da lista em Python.
Enquanto alguns glorificam a compreensão da lista e até sugerem o abandono de `filter ()` e `map ()` , eu descobri que:
  1. Em todos os lugares onde eu vi a compreensão da lista do Python, você pode facilmente conviver com as funções `filter ()` e `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 ()` e `map ()` no 11l parecem mais bonitos que no 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")) 
    e conseqüentemente, a necessidade de compreensão de lista em 11l realmente desaparece [a substituição da compreensão de lista por filter() e / ou map() é realizada durante a conversão do código Python em 11l automaticamente ] .

Converta a cadeia if-elif-else para alternar


Embora o Python não contenha uma instrução switch, essa é uma das construções mais bonitas do 11l, então decidi inserir o switch automaticamente:
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() == "'") 
Para completar, aqui está o código C ++ gerado
 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; } 


Converta pequenos dicionários em código nativo


Considere esta linha de código Python:
 tag = {'*':'b', '_':'u', '-':'s', '~':'i'}[prev_char()] 
Provavelmente, essa forma de gravação não é muito eficaz [ em termos de desempenho ] , mas é muito conveniente.

Em 11l, a entrada correspondente a esta linha [ e obtida pelo transportador Python → 11l ] não é apenas conveniente [ no entanto, não é tão elegante quanto em Python ] , mas também é rápida:
 var tag = switch prev_char() {'*' {'b'}; '_' {'u'}; '-' {'s'}; '~' {'i'}} 

A linha acima é traduzida em:
 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()); 
[ A chamada de função lambda será compilada pelo compilador C ++ \ inline durante o processo de otimização e apenas a cadeia de operadores permanecerá ?/: ]

No caso em que uma variável é atribuída, o dicionário é deixado como está:
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 \ Cature variáveis ​​externas


No Python, para indicar que a variável não é local, mas deve ser usada fora [ da função atual ] , a palavra-chave não-local é usada [ caso contrário, por exemplo, found = True será tratada como a criação de uma nova variável local found , em vez de atribuir um valor já variável externa existente ] .
Em 11l, o prefixo @ é usado para isso:
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; }; 

Variáveis ​​globais


Semelhante às variáveis ​​externas, se você esquecer de declarar uma variável global em Python [ usando a palavra-chave global ] , receberá um erro invisível:
 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) ... 
O código 11l [ direita ] , diferentemente do Python [ esquerda ], break_label_index erro 'variável não declarada break_label_index ' no break_label_index compilação.

Índice / número do item atual do contêiner


Eu continuo esquecendo a ordem das variáveis ​​que a função Python enumerate retorna {o valor vem primeiro, depois o índice ou vice-versa}. O comportamento analógico no Ruby - each.with_index - é muito mais fácil de lembrar: com index significa que o índice vem após o valor, não antes. Mas em 11l, a lógica é ainda mais fácil de lembrar:
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) 

Desempenho


O programa para converter a marcação de PC em HTML é usado como um programa de teste e o código-fonte do artigo sobre marcação de PC é usado como dados de origem [ já que este artigo é atualmente o maior dos escritos na marcação de PC ] e é repetido 10 vezes, ou seja, obtido a partir de 48,8 KB de tamanho de arquivo de artigo 488Kb.

Aqui está um diagrama mostrando quantas vezes a maneira correspondente de executar o código Python é mais rápida que a implementação original [ CPython ] :

E agora adicione ao diagrama a implementação gerada pelo transpilador Python → 11l → C ++:

O tempo de execução [ tempo de conversão do arquivo de 488Kb ] foi de 868 ms para o CPython e 38 ms para o código C ++ gerado [ desta vez inclui o pleno direito [ isto é não apenas trabalhando com dados na RAM ] executando o programa pelo sistema operacional e todas as entradas / saídas [ lendo o arquivo de origem [ .pq ] e salvando o novo arquivo [ .html ] no disco ] ] .

Eu também queria experimentar o Shed Skin , mas ele não suporta funções locais.
O Numba também não pôde ser usado (gera um erro 'Uso de código de operação desconhecido LOAD_BUILD_CLASS').
Aqui está o arquivo com o programa usado para comparar o desempenho [ no Windows ] (requer Python 3.6 ou superior e os seguintes pacotes Python: pywin32, cython).

Código-fonte em Python e saída de transpilers de Python -> 11l e 11l -> C ++:
Python11l gerado
(com palavras-chave em vez de letras)
11l
(com letras)
C ++ gerado

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


All Articles