- PVSM.RU - https://www.pvsm.ru -
В данной статье рассматриваются наиболее интересные преобразования, которые выполняет цепочка из двух транспайлер [1]ов (первый переводит код на языке Python в код на новом языке программирования 11l [2], а второй — код на 11l в C++), а также производится сравнение производительности с другими средствами ускорения/исполнения кода на Python (PyPy, Cython, Nuitka).
Python | 11l |
|
|
Явное указание для индексирования от конца массива s[(len)-2]
вместо просто s[-2]
нужно для исключения следующих ошибок:
s[i-1]
, но при i = 0 такая/данная запись вместо ошибки молча вернёт последний символ строки [и я на практике сталкивался с такой ошибкой — коммит [3]].s[i:]
после i = s.find(":")
будет работать неверно когда символ не найден в строке [вместо ‘‘часть строки начиная с первого символа :
и далее’’ будет взят последний символ строки] (и вообще, возвращать -1
функцией find()
в Python-е я считаю также неправильно [следует возвращать null/None [а если требуется -1, то следует писать явно: i = s.find(":") ?? -1
]]).s[-n:]
для получения n
последних символов строки будет некорректно работать при n = 0.
На первый взгляд выдающаяся черта языка Python, но на практике от неё легко можно отказаться/обойтись посредством оператора in
и диапазонов:
a < b < c |
b in a<..<c |
a <= b < c |
b in a..<c |
a < b <= c |
b in a<..c |
0 <= b <= 9 |
b in 0..9 |
Аналогично, как оказалось, можно отказаться и от другой интересной фичи Python — list comprehensions.
В то время как одни прославляют list comprehension и даже предлагают отказаться от `filter()` и `map()` [4], я обнаружил, что:
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)))
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"))
и следовательно необходимость в list comprehensions в 11l фактически отпадает [замена list comprehension на filter()
и/или map()
выполняется в процессе преобразования Python-кода в 11l автоматически].
В то время как Python не содержит оператора switch, это одна из самых красивых конструкций в языке 11l, и поэтому я решил вставлять switch автоматически:
Python | 11l |
|
|
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;
}
Рассмотрим такую строчку кода на Python:
tag = {'*':'b', '_':'u', '-':'s', '~':'i'}[prev_char()]
Скорее всего, такая форма записи не очень эффективна [с точки зрения производительности], зато очень удобна.
В 11l же соответствующая данной строчке [и полученная транспайлером Python → 11l] запись не только удобная [впрочем, не настолько изящная как в Python], но и быстрая:
var tag = switch prev_char() {‘*’ {‘b’}; ‘_’ {‘u’}; ‘-’ {‘s’}; ‘~’ {‘i’}}
Приведённая строчка странслируется в:
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());
[Вызов лямбда-функции компилятор C++ встроитinline в процессе оптимизации и останется только цепочка операторов ?/:
.]
В том случае, когда производится присваивание переменной, словарь оставляется как есть:
Python |
|
11l |
|
C++ |
|
В Python для указания того, что переменная не является локальной, а должна быть взята снаружи [от текущей функции], используется ключевое слово nonlocal [в противном случае к примеру found = True
будет трактоваться как создание новой локальной переменной found
, а не присваивание значения уже существующей внешней переменной].
В 11l для этого используется префикс @:
Python | 11l |
|
|
C++:
auto writepos = 0;
auto write_to_pos = [..., &outfile, &writepos](const auto &pos, const auto &npos)
{
outfile.write(...);
writepos = npos;
};
Аналогично внешним переменным, если забыть объявить глобальную переменную в Python [посредством ключевого слова global], то получится незаметный баг:
|
|
Код на 11l [справа] в отличие от Python [слева] выдаст на этапе компиляции ошибку ‘необъявленная переменная break_label_index
’.
Я всё время забываю порядок переменных, которые возвращает Python-функция enumerate
{сначала идёт значение, а потом индекс или наоборот}. Поведение аналога в Ruby — each.with_index
— гораздо легче запомнить: with index означает, что index идёт после value, а не перед. Но в 11l логика ещё проще для запоминания:
Python | 11l |
|
|
В качестве тестировочной используется программа преобразования пк-разметки в HTML [5], а в качестве исходных данных берётся исходник статьи по пк-разметке [6] [так как эта статья на данный момент — самая большая из написанных на пк-разметке], и повторяется 10 раз, то есть получается из 48.8 килобайтной статьи файл размером 488Кб.
Вот диаграмма, показывающая во сколько раз соответствующий способ исполнения Python-кода быстрее оригинальной реализации [CPython]:
Я хотел ещё попробовать Shed Skin [7], но он не поддерживает локальные функции.
Numba использовать также не получилось (выдаёт ошибку ‘Use of unknown opcode LOAD_BUILD_CLASS’).
Вот архив [8] с использовавшейся программой для сравнения производительности [под Windows] (требуются установленный Python 3.6 или выше и следующие Python-пакеты: pywin32, cython).
Исходник на Python и вывод транспайлеров Python → 11l и 11l → C++:
Python [9] | Сгенерированный 11l [10] (с ключевыми словами вместо букв) |
11l [11] (с буквами) |
Сгенерированный C++ [12] |
Автор: alextretyak
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/300449
Ссылки в тексте:
[1] транспайлер: https://ru.wikipedia.org/wiki/%D0%A2%D1%80%D0%B0%D0%BD%D1%81%D0%BF%D0%B0%D0%B9%D0%BB%D0%B5%D1%80
[2] новом языке программирования 11l: http://11l-lang.org/ru/
[3] коммит: https://bitbucket.org/pqmarkup/pqmarkup/commits/aad8d2a0d7bf8851371ee28b9d711c4cd476f382
[4] прославляют list comprehension и даже предлагают отказаться от `filter()` и `map()`: https://stackoverflow.com/a/3013722/2692494
[5] программа преобразования пк-разметки в HTML: https://bitbucket.org/pqmarkup/pqmarkup
[6] статьи по пк-разметке: https://habr.ru/post/348218/
[7] Shed Skin: http://shedskin.github.io
[8] Вот архив: https://gist.github.com/alextretyak/5524244126944a4c39f70341ef21b87a/raw/e6cb2f07446ef4a19a3d6cdc1f8c483287ef1308/perf_tests.7z
[9] Python: https://bitbucket.org/pqmarkup/pqmarkup/src/055831213261078276a604c2e1d8e4491897934f/pqmarkup.py
[10] Сгенерированный 11l: https://gist.github.com/alextretyak/5524244126944a4c39f70341ef21b87a/raw/c3217e8745443d2f977b4287f3dfd96a4a4e7e13/pqmarkup.11kw
[11] 11l: https://gist.github.com/alextretyak/5524244126944a4c39f70341ef21b87a/raw/c3217e8745443d2f977b4287f3dfd96a4a4e7e13/pqmarkup.11l
[12] Сгенерированный C++: https://gist.github.com/alextretyak/5524244126944a4c39f70341ef21b87a/raw/c3217e8745443d2f977b4287f3dfd96a4a4e7e13/pqmarkup.cpp
[13] Источник: https://habr.com/post/431318/?utm_campaign=431318
Нажмите здесь для печати.