F2PY - ускоряем вычисления в Питоне в 500 раз
Лирическое отступление:
Сегодня у меня радостное событие, мне подарили новогодний подарок. Как известно лучший подарок это подарок сделанный своими руками, так и поступил Михаил Иткин из Института Метеорологии им. Макса Планка, подарив мне эту статью. Большое тебе, Миша, человеческое спасибо ) Если у кого появится подобное желание по поводу и без повода - пишите на koldunovn@gmail.com
Собственно статья:
Чистый питон очень сильно проигрывает низкоуровневым языкам программирования, длинные циклы могут замедлить выполнение программы на пару порядков. Часто узкие места можно обойти используя пакеты вроде numpy, в них функции написаны с использованием СИ и Фортрана. Функционала numpy или PyNGL хватит для большинства рутинных задач, но если нужно применить вычисление к каждому элементу массива или, не дай бог, к "бегущему окну" - двигающемуся массиву меньшего размера, то придётся писать рутину самому.
F2PY позволяет компилировать фортрановский код в виде модуля, который можно импортировать в скрипт, и обмениваться с ним данными, исполнять фунцкии и т. д. и т. п. F2PY уже пару лет включён в репозиторий numpy, он ставится вместе с пакетом python-numpy.Для компиляции фортран кода вам нужны компилятор и пакет python-dev, apt-get вам в помощь:
Фотрановский модуль состоит из модуля, содержащего подпрограммы. Разберем как эта шутка работает на примере. Напишем скрипт на питоне который будет импортировать фортрановский модуль, сообщать ему некую переменную, а операции над переменной и вывод результатов будет исполнять Фортран.
Так выглядит фортрановский код тестового модуля:
character, allocatable, dimension(:) :: myname
integer, dimension(1) :: sh
integer :: l = 0
contains
subroutine calc_length
integer i
if (allocated(myname)) then
sh = shape(myname)
l = sh(0)
print *, "Your name contains ", l, " characters"
end if
end subroutine calc_length
end module test_module
Переменные, содержимым которых можно обмениваться, объявляются до начала подпрограмм. В подпрограммах включаются только необходимые для внутренннего пользования переменные.Этот простенький модуль считает сколько символов в слове, которое вы ему сообщаете,пишет результат на экран и отдает длину строковой переменной вам обратно. Все переменные которые вы ему подсунете он будет пытаться конвертировать в ранее объявленный формат данных.Компилируем все это дело:
Тут вроде все понятно,
- "-с" означает конфиг,
- "--fcompiler" - ваш любимый компилятор, у меня это gfortran,
- "-m" - имя .so - объекта на выходе, его и будем импортировать в питон.
Если компиляция прошла успешно, то вперед, пишем обертку на Питоне:
Питон:
# -*- coding: utf-8 -*-
import sys
from f_module import test_module as tm
# проверим если фортан уже что-то объявил
# трюк с конверсией в integer нужен чтобы переменная
# была не ссылкой на существующий объект, который в будущем изменится
# а новым объектом.
f_var_1 = int(tm.l)
# передаём данные фортан модулю
tm.myname = sys.argv[1]
# вызываем функцию, которая выполнит все наши прихоти
tm.calc_length()
# наблюдаем вывод результата на экран и
# забираем значение обратно и делаем что-нибудь простенькое с ним
f_var_2 = int(tm.l)
# взглянем, если значение tm.l изменилось
if f_var_1 != f_var_2:
print "Wow, value tm.l has changed! It was %i and now it's %i" % (f_var_1, f_var_2)
else:
print "And nothing happend to tm.l variable."
exit("Now we're done.")
И результат:
Your name contains 21 characters
Wow, value tm.l has changed! It was 0 and now it's 21
We're done.
Большого прироста быстродействия в этом примере нет, но если попробуете поиграть с массивами, то увидите огромную разницу во времени. И в результате имеем все прелести фортрана с удобной оберткой на питоне.