Shed Skin — экспериментальный транслятор из Python в C++

в 20:10, , рубрики: c++, python

Введение

Shed Skin — это экспериментальный транслятор из Python в C++, разработанный для повышения скорости выполнения программ на Python с интенсивными вычислениями. Он преобразует программы, написанные на ограниченном подмножестве языка Python, в C++. Код C++ можно скомпилировать в исполняемый код, который может быть как отдельной программой, так и модулем расширения, легко импортируемым и используемым в обычной программе на Python.

Shed Skin использует методы сопоставления типов, используемых в программе на Python, чтобы сгенерировать явные декларации типов, необходимые для версии на C++. Поскольку C++ — язык со статической типизацией, Shed Skin требует, чтобы код на Python был написан так, чтобы все переменные имели определенный тип.

Помимо ограничений на типизацию и подмножество языка, поддерживаемые программы не могут свободно использовать стандартную библиотеку Python, хотя поддерживается около 25 общеупотребительных модулей, такие как random и re (см. Ограничения Библиотеки).

Вдобавок, технология определения типов, используемая Shed Skin, в данный момент не очень хорошо масштабируется для программ, размер которых превышает несколько тысяч строк кода (максимальный размер транслируемой программы — около 6,000 строк (sloccount)). В целом, это означает, что Shed Skin на данный момент больше подходит для трансляции небольших программ и модулей расширения, которые не используют интенсивно особенности динамической типизации Python или стандартную и внешние библиотеки. Смотри ниже сборник из 75 нетривиальных программ-примеров.

Поскольку Shed Skin находится всё еще в ранней стадии разработки, он может серьёзно улучшиться. В настоящий момент, Вы, возможно, столкнётесь с какими-либо ошибками в процессе его использования. Пожалуйста, отправьте нам отчет о них, чтобы мы смогли их исправить!

В настоящее время Shed Skin совместим с версиями Python с 2.4 по 2.7, ведёт себя как 2.6, и работает на Windows и большинстве UNIX-платформ, таких как GNU/Linux и OSX.

Ограничения на типизацию

Shed Skin транслирует обычные, но статически типизированные программы, на C++. Ограничение на статическую типизацию означает, что переменные могут иметь только один, неизменный тип. Так, например, код

a = 1
a = '1' # ошибка

недопустим. Однако, как в C++, типы могут быть абстрактными, так, например, код

a = A()
a = B() # правильно

где A и B имеют общий базовый класс, допустим.

Ограничение на типизацию также означает, что элементы одной коллекции (list, set, и т.д.) не могут иметь разные типы (потому что типы их членов также должны быть статическими). Таким образом, код:

a = ['apple', 'b', 'c'] # правильно
b = (1, 2, 3) # правильно
c = [[10.3, -2.0], [1.5, 2.3], []] # правильно

допустим, но код

d = [1, 2.5, 'abc'] # ошибка
e = [3, [1, 2]] # ошибка
f = (0, 'abc', [1, 2, 3]) # ошибка

недопустим. Ключи и значения словарей могут иметь разные типы:

g = {'a': 1, 'b': 2, 'c': 3} # правильно
h = {'a': 1, 'b': 'hello', 'c': [1, 2, 3]} # ошибка

В текущей версии Shed Skin смешанные типы также разрешены в кортежах длины два:

a = (1, [1]) # правильно

В будущем, возможно, будут разрешены смешанные типы в кортежах большей длины.

Тип None можно смешивать только с нескалярными типами (то есть, не с int, float, bool или complex):

l = [1]
l = None # правильно
m = 1
m = None # ошибка
def fun(x = None): # ошибка: используйте конкретное значение для x, например, x = -1
    pass
fun(1)

Целые числа и числа с плавающей точкой (integers и floats) обычно можно смешивать (целые числа становятся числами с плавающей точкой). Если это невозможно, Shed Skin выдаст сообщение об ошибке.

Ограничения на подмножество языка Python

Shed Skin всегда будет поддерживать только подмножество всех особенностей языка Python. В данный момент следующие особенности не поддерживаются:

  • eval, getattr, hasattr, isinstance, всё динамическое
  • арифметика с произвольной точностью (целые числа — int — становятся 32-битными (знаковыми) по умолчанию на большинстве архитектур, см. Опции командной строки)
  • паковка-распаковка аргументов (*args и **kwargs)
  • множественное наследование
  • вложенные функции и классы
  • unicode
  • наследование от встроенных типов (исключая Exception и object)
  • перегрузка функций __iter__, __call__, __del__
  • замыкания

Некоторые другие особенности поддерживаются только частично:

  • к атрибутам класса можно обращаться только через имя класса:
            self.class_attr # ошибка
            SomeClass.class_attr # правильно
            SomeClass.some_static_method() # правильно
    

  • ссылки на функции можно передавать, но не на методы классов и не на классы, и они не могут содержаться в каком-либо контейнере:
    
            var = lambda x, y: x+y # правильно
            var = some_func # правильно
            var = self.some_method # ошибка, ссылка на метод
            var = SomeClass # ошибка
            [var] # ошибка, находится в контейнере
    

Ограничения на библиотеки

На данный момент, в большой степени поддерживаются следующие 25 модулей. Некоторые из них, такие как os.path, были транслированы на C++ с помощью Shed Skin.

  • array
  • binascii
  • bisect
  • collections (defaultdict, deque)
  • colorsys
  • ConfigParser (без SafeConfigParser)
  • copy
  • csv (без Dialect, Sniffer)
  • datetime
  • fnmatch
  • getopt
  • glob
  • heapq
  • itertools (без starmap)
  • math
  • mmap
  • os (под Windows отсутствует некоторый функционал)
  • os.path
  • random
  • re
  • select (только функция select, под UNIX)
  • socket
  • string
  • struct (без Struct, pack_into, unpack_from)
  • sys
  • time

Заметим, что любой другой модуль, такой, как pygame, pyqt или pickle, можно использовать в совокупности с модулем расширения, сгенерированным с помощью Shed Skin. Примеры такого использования см. среди примеров к Shed Skin.

Установка

Имеется два вида установщиков: сапораспаковывающийся инсталятор Windows и архив UNIX. Но, конечно, лучше, если Shed Skin установлек с помощью менеджера пакетов вашей инсталяции GNU/Linux (Shed Skin доступен по крайней мере на Debian, Ubuntu, Fedora и Arch).

Windows

Чтобы установить Windows-версию, просто загрузите и запустите инсталятор. Если вы используете ActivePython или другой нестандартный дистрибутив Python, или MingW, сначала удалите его. Также имейте в виду, что, вероятно, в 64-битной версии Python недостаёт какого-то файла, так что сборка модулей расширения невозможна. Вместо 64-битной используйте 32-битную версию Python.

UNIX
Инсталяция через менеджер пакетов

Пример команды для Ubuntu:

sudo apt-get install shedskin
Ручная инсталяция

Чтобы установить дистрибутив из архива UNIX вручную, сделайте следующее:

  • загрузите и распакуйте архив
  • запустите команду run sudo python setup.py install
Зависимости

Чтобы скомпилировать и запустить программы, сгенерированные shedskin, необходимы следующие библиотеки:

  • g++, компилятор C++ (версия 4.2 или выше).
  • отладочные файлы pcre
  • отладочные файлы Python
  • сборщик мусора Boehm

Чтобы установить эти библиотеки под Ubuntu, введите:

sudo apt-get install g++ libpcre++dev python-all-dev libgc-dev

Если сборщик мусора Boehm недоступен через ваш менеджер пакетов, используйте такой способ. Загрузите с вебсайта, например, версию 7.2alpha6, распакуйте её, и установите следующим образом:

./configure --prefix=/usr/local --enable-threads=posix --enable-cplusplus --enable-thread-local-alloc --enable-large-config
make
make check
sudo make install

Если библиотека PCRE недоступна через ваш менеджер пакетов, воспользуйтесь следущим способом. Загрузите с вебсайта, например, версию 8.12, распакуйте её и установите следующим образом:

./configure --prefix=/usr/local
make
sudo make install
OSX
Ручная установка

Чтобы установить Shed Skin из архива UNIX на систему OSX, сделайте следущее:

  • загрузите и распакуйте архив
  • запустите команду run sudo python setup.py install
Зависимости

Чтобы cкомпилировать и запустить программы, сгенерированные shedskin, необходимы следующие библиотеки:

  • g++, компилятор C++ (версия 4.2 или выше; поставляется со средой разработки Apple XCode?).
  • отладочные файлы pcre
  • отладочные файлы Python
  • сборщик мусора Boehm

Если сборщик мусора Boehm недоступен через ваш менеджер пакетов, используйте такой способ. Загрузите с вебсайта, например, версию 7.2alpha6, распакуйте её, и установите следующим образом:

./configure --prefix=/usr/local --enable-threads=posix --enable-cplusplus --enable-thread-local-alloc --enable-large-config
make
make check
sudo make install

Если библиотека PCRE недоступна через ваш менеджер пакетов, воспользуйтесь следущим способом. Загрузите с вебсайта, например, версию 8.12, распакуйте её и установите следующим образом:

./configure --prefix=/usr/local
make
sudo make install

Трансляция обычной программы

На Windows сначала выполните (двойным щелчком) файл init.bat в директории, куда Вы установили Shed Skin.

Чтобы скомпилировать следующую простую тестовую программу с именем test.py:

print 'hello, world!'

введите:

shedskin test

Создадутся два файла C++ с именами test.cpp и test.hpp, а также Makefile.

Чтобы создать исполняемый файл с именем test (или test.exe), введите:

make

Создание модуля расширения

Чтобы скомпилировать следующую программу с именем simple_module.py как модуль расширения:

# simple_module.py

def func1(x):
    return x+1

def func2(n):
    d = dict([(i, i*i)  for i in range(n)])
    return d

if __name__ == '__main__':
    print func1(5)
    print func2(10)

введите:

shedskin -e simple_module
make

Чтобы команда 'make' выполнилась успешно на не-Windows системе, убедитесь, что у Вас установлены отладочные файлы Python (на Debian установите python-dev; на Fedora установите python-devel).

Заметьте, что чтобы было возможно определение типов, ваш модуль должен вызывать только свои собственные функции. Такой эффект достигается в примере за счет того, что вызовы помещены внутрь условия if __name__=='__main__', так что они не вызываются, если модуль импортируют. Функции могут вызываться только косвенно, то есть, если func2 вызывает func1, вызов func1 может быть опущен.

Модуль расширения теперь можно просто проимпортировать и использовать как обычно:

>>> from simple_module import func1, func2
>>> func1(5)
6
>>> func2(10)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
Ограничения

Имеются существенные отличия между использованием компилированного модуля расширения и оригиналом.

Можно передавать и возвращать только встроенные скалярные типы и контейнеры (int, float, complex, bool, str, list, tuple, dict, set), а также None и экземпляры классов, определённых пользователем. Так, например, анонимные функции и итераторы в настоящее время не поддерживаются.
Встроенные объекты а также их содержимое полностью преобразуются на каждый вызов/возврат функции из типов Shed Skin в CPython и наоборот. Это означает, что нельзя менять встроенные объекты CPython со стороны Shed Skin и наоборот, и преобразование может быть медленным Экземпляры классов, определённых пользователем можно передавать/возвращать без каких-либо преобразований и менять с любой стороны.
Глобальные переменные преобразуются только один раз, во время инициализации, из Shed Skin в CPython. Это означает, что значения версии CPython и версии Shed Skin могут меняться независимо друг от друга. Этой проблемы можно избежать, используя только константные глабольные переменные, или добавляя функции-геттеры и сеттеры.
Множественные (взаимодействующие) модули расширения в настоящий момент не поддерживаются. Также, импорт и одновременное использование питоновской и транслированной версии может не работать.

Интеграция с Numpy

Shed Skin в настоящее время не имеет прямой поддержки Numpy. Однако, возможно передавть массив Numpy array в транслированный модуль расширения Shed Skin как список (list), используя его метод tolist. Заметьте, что это весьма неэффективно (см. выше), так что это можно использовать, если большое количество времени тратится внутри модуля расширения. Рассмотрим следующий пример:

# simple_module2.py

def my_sum(a):
    """ compute sum of elements in list of lists (matrix) """
    h = len(a) # number of rows in matrix
    w = len(a[0]) # number of columns
    s = 0.0
    for i in range(h):
        for j in range(w):
            s += a[i][j]
    return s

if __name__ == '__main__':
    print my_sum([[1.0, 2.0], [3.0, 4.0]]) 

После трансляции этого модуля как модуля расширения с помощью Shed Skin, мы можем передать массив Numpy следующим образом:

>>> import numpy
>>> import simple_module2
>>> a = numpy.array(([1.0, 2.0], [3.0, 4.0]))
>>> simple_module2.my_sum(a.tolist())
10.0

Распространение бинарных кодов

Windows

Чтобы использовать сгенерированный бинарный код Windows на другой системе или запустить его без запуска init.bat, поместите следующие файлы в директорию вместе с бинарным файлом:

shedskin-0.9shedskingc.dll
shedskin-0.9shedskin-libpcre-0.dll
shedskin-0.9binlibgcc_s_dw-1.dll
shedskin-0.9binlibstdc++.dll

UNIX

Чтобы использовать сгенерированный бинарный файл на другой системе, убедитесь, что там установлены libgc и libpcre3. Если это не так, и Вы не можете установить их глобально на систему, Вы можете поместить копии этих библиотек в ту же директорию, где лежит бинарь, с помощью следующих команд:

$ ldd test
libgc.so.1 => /usr/lib/libgc.so.1
libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3
$ cp /usr/lib/libgc.so.1 .
$ cp /lib/x86_64-linux-gnu/libpcre.so.3 .
$ LD_LIBRARY_PATH=. ./test

Заметьте, что обе системы должны быть 32- или 64-битными. Если это не так, Shed Skin необходимо установить на другой системе, чтобы пересобрать бинарник.

Multiprocessing

Предположим, мы определили следующую функцию в файле под названием meuk.py:

def part_sum(start, end):
    """ calculate partial sum """
    sum = 0
    for x in xrange(start, end):
        if x % 2 == 0:
            sum -= 1.0 / x
        else:
            sum += 1.0 / x
    return sum

if __name__ == ’__main__’:
    part_sum(1, 10)

Чтобы транслировать этот файл в модуль расширения, введите:

    shedskin -e meuk
    make

Чтобы использовать получившийся модуль расширения с модулем стандартной библиотеки multiprocessing, просто добавьте wrapper на Python:

from multiprocessing import Pool

def part_sum((start, end)):
    import meuk
    return meuk.part_sum(start, end)

pool = Pool(processes=2)
print sum(pool.map(part_sum, [(1,10000000), (10000001, 20000000)]))

Вызов кода на C/C++

Чтобы вызвать код на C/C++, сделайте следующее:

Дайте Shed Skin информацию о типизации с помощью модели типов кода C/C++. Предположим, нам нужно вызвать простую функцию, которая возвращает список n наименьших простых чисел, больших, чем заданное число. Следующая модель типов, содержащаяся в файле stuff.py, достаточна, чтобы Shed Skin произвел сопоставление типов:

#stuff.py

def more_primes(n, nr=10):
    return [1]

Чтобы произвести фактическое сопоставление типов, напишите тестовую программу с названием test.py, которая использует эту модель типов, а затем транслируйте её:

#test.py

import stuff
print stuff.more_primes(100)

shedskin test

Кроме test.py, этот код также транслирует stuff.py на C++. Теперь вы можете писать вручную код C/C++ в файле stuff.cpp. Чтобы избежать его перезаписи во время следующей трансляции файла test.py, переместите stuff.* в директорию lib/ Shed Skin.

Стандартная библиотека

Переместив stuff.* в lib/, мы фактически добавили поддержку произвольного библиотечного модуля к Shed Skin. Другие программы, транслированные Shed Skin, теперь могут импортировать нашу библиотеку и использовать more_primes. Фактически, в директории lib/ находятся модели типов и имплементации всех поддерживаемых модулей. Как Вы можете заметить, некоторые были частично преобразованы в C++ в помощью Shed Skin.

Типы Shed Skin

Shed Skin заново реализует Python встроенные типы с помощью набора своих классов на C++. Они имеют такой же интерфейс, как их Python-овские коллеги, так что их легко использовать (при условии, что у вас есть базовые знания C++). Подробности об определениях классов см. в файле lib/builtin.hpp. Если у вас есть сомнения, преобразуйте аналогичный код на Python в C++ и посмотрите на результат!

Опции командной строки

Команда shedskin поддерживает следующие опции:

  • -a --ann Вывести комментированный исходный код (.ss.py)
  • -b --nobounds Отключить проверку диапазонов
  • -e --extmod Сгенерировать модуль расширения
  • -f --flags Задать флаги для Makefile
  • -g --nogcwarns Отключить предупреждения GC времени выполнения
  • -l --long Использовать целые long long («64-бит»)
  • -m --makefile Указать другое имя Makefile
  • -n --silent Молчаливый режим, показывать только предупреждения
  • -o --noassert Отключить операторы assert
  • -r --random Использовать быстрый генератор случайных чисел (rand())
  • -s --strhash Использовать быстрый алгоритм хеширования строк (murmur)
  • -w --nowrap Отключить проверку wrap-around
  • -x --traceback Печатать traceback для непойманных исключений
  • -L --lib Добавить директорию с библиотеками

Например, чтобы транслировать файл test.py как модуль расширения, введите shedskin –e test или shedskin ––extmod test.

Опция -b или --nobounds очень часто используется, потому что она отключает исключения выхода за диапазон (IndexError), что может в большой степени влиять на производительность.

    a = [1, 2, 3]
    print a[5] # invalid index: out of bounds

Советы и приёмы по улучшению производительности

Советы

Небольшие аллокации памяти (например, создание нового кортежа, списка или экземпляра класса) обычно сильно не замедляют программу на Python. Однако, после трансляции в C++, часто они становятся узким местом. Это происходит потому что на каждую аллокацию памяти, память запрашивается у системы, она должна быть очищена сборщиком мусора, и большое количество последующих аллокаций памяти скорее всего вызовет отсутствие в кэше. Ключевой подход к высокой производительности — часто уменьшение числа мелких аллокаций, например, заменой небольшого генераторного выражения на цикл или устранение промежуточных кортежей в некоторых вычислениях.

Однако, заметьте, что для идиоматических for a, b in enumerate(..), for a, b in enumerate(..) и for a, b in somedict.iteritems(), промежуточные маленькие объекты выкидываются оптимизатором, и строки длины 1 кешируются.

Некоторые особенности Python (которые могут замедлить сгенерированный код) не всегда необходимы, и их можно выключить. См. раздел «Опции командной строки» для подробностей. Выключение проверки диапазонов обычно является очень безопасной оптимизацией и могут существенно помочь в случае кода, где часто используется операция взятия по индексу.

Доступ через атрибут в сгенерированном коде быстрее, чем взятие по индексу. Например, v.x * v.y * v.z быстрее, чем v[0] * v[1] * v[2].

Shed Skin берет флаги для компилятора C++ из файлов FLAGS* в директории, куда установлен Shed Skin. Эти флаги можно изменить, либо модифицировать с помощью локального файла с именем FLAGS.

При большом количестве вычислений с плавающей точкой, не всегда обязательно следовать спецификациям IEEE о плавающей точке. Добавление флага -ffast-math может существенно повысить производительность.

За счет профилирования можно выжать еще большую производительность. В последних версиях GCC сначала скомпилируйте и выполните сгенерированный код с помощью -fprofile-generate, а затем с помощью fprofile-use.

Для наилучших результатов, сконфигурируйте последнюю версию Boehm GC с помощью CPPFLAGS="-O3 -march=native" ./configure --enable-cplusplus --enable-threads=pthreads --enable-thread-local-alloc --enable-large-config --enable-parallel-mark. Последняя опция позволяет GC использовать несколько ядер процессора.

При оптимизации очень полезно знать, сколько времени расходуется в каждой части вашей программы. Программу Gprof2Dot можно использовать для создания красивых трафиков как для отдельной программы, так и для оригинального кода на Python. Программу OProfile можно использовать для профилирования модуля расширения.

Чтобы использовать Gprof2dot, загрузите файл gprof2dot.py с вебсайта и проинсталируйте Graphviz. Затем:

shedskin program
make program_prof
./program_prof
gprof program_prof | gprof2dot.py | dot -Tpng -ooutput.png

Чтобы использовать OProfile, установите его и используйте следующим образом.

shedskin -e extmod
make
sudo opcontrol --start
python main_program_that_imports_extmod
sudo opcontrol --shutdown
opreport -l extmod.so
Приёмы

Следующие два фрагмента кода работают одинаково, но поддерживается только второй:

statistics = {'nodes': 28, 'solutions': set()}

class statistics: pass
s = statistics(); s.nodes = 28; s.solutions = set()

Порядок вычисления аргументов функции или оператора print меняется при трансляции в C++, так что лучше всего не рассчитывать на него:

print 'hoei', raw_input() # raw_input вызывается до вывода 'hoei'!

Кортежи с разным типом элементов и длиной > 2 в настоящее время не поддерживаются. Однако, их можно эмулировать:

class mytuple:
    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c

Блочные комментарии, окружённые #{ и #}, Shed Skin игнорирует. Эту особенность можно использовать для комментирования кода, который не может быть скомпилирован. Например, следующий фрагмент выведет точку только при запуске под CPython:

print "x =", x
print "y =", y
#{
import pylab as pl
pl.plot(x, y)
pl.show()
#}

Версия 0.9.4, 16 июня 2013, Mark Dufour и James Coughlan

Автор: mike1

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js