Привет, Хабр!

В прошлой статье я рассуждал о том, почему Fortran в 2025 году всё ещё жив и даже растет в рейтингах. В комментариях справедливо заметили: «Философия — это хорошо, но как это применить современному разработчику? Зачем мне Fortran, если я пишу на Python?».

Это правильный вопрос. Сегодня я хочу ответить на него кодом, а не словами.

Я покажу, как использовать Fortran в качестве «числодробилки» для Python. Мы возьмем задачу, на которой интерпретатор Python гарантированно просядет, и ускорим её в 150 раз, используя инструмент, который уже есть в вашем numpy.

Речь пойдет не о замене Python, а о симбиозе: удобный интерфейс Python + сырая мощь Fortran.

Почему Python тормозит (и это нормально)

Мы любим Python за динамическую типизацию и удобство. Но за это приходится платить. Когда вы пишете вложенные циклы, интерпретатор на каждой итерации проверяет типы данных, выделяет память под новые объекты и ищет методы сложения/умножения.

Если операций тысячи — это незаметно. Если миллионы — скрипт превращается в слайд-шоу.

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

Здесь на сцену выходят компилируемые языки.

Задача: Фрактал Мандельброта

Почему я выбрал именно генерацию фрактала для демонстрации?

  1. Независимость данных: Каждый пиксель считается отдельно (идеально для параллелизма, но сегодня рассмотрим однопоточный вариант для чистоты эксперимента).

  2. Тяжелая математика: Для каждой точки нужно выполнить цикл проверки (до 100-200 итераций) с комплексными числами.

  3. Масштаб: Картинка 2000x2000 — это 4 миллиона точек. Умножаем на 100 итераций — получаем 400 миллионов операций.

Это идеальный «стресс-тест». Это тот случай, когда Python упирается в свой потолок производительности, и никакие ухищрения не помогут кардинально изменить ситуацию без смены инструмента.

Инструмент: F2PY

Внутри пакета numpyживет утилита F2PY (Fortran to Python). Она позволяет взять файл с кодом на Fortran и одной командой скомпилировать его в модуль, который Python импортирует как родную библиотеку.

Реализация

1. Python (Как не надо делать для High Load)

Вот так выглядит наивная реализация на Python. Она понятна, красива, но мучительно медленна.

import time
import matplotlib.pyplot as plt


def mandelbrot_python(height, width, max_iter):
    # Создаем пустую матрицу
    output = [[0 for _ in range(width)] for _ in range(height)]

    for i in range(height):
        # Преобразуем координаты пикселя в координаты на плоскости
        y0 = -1.5 + (i * 3.0) / height
        for j in range(width):
            x0 = -2.0 + (j * 3.0) / width
            x, y = 0.0, 0.0
            iteration = 0

            # Тот самый "тяжелый" цикл
            while (x * x + y * y <= 4.0) and (iteration < max_iter):
                xtemp = x * x - y * y + x0
                y = 2.0 * x * y + y0
                x = xtemp
                iteration += 1

            output[i][j] = iteration
    return output


# --- Запуск и визуализация ---
if __name__ == "__main__":
    height, width = 2000, 2000
    max_iter = 100

    print("Начинаем расчёт фрактала на Python...")
    start = time.time()
    image = mandelbrot_python(height, width, max_iter)
    end = time.time()

    print(f"Расчёт занял: {end - start:.2f} секунд")

    # Отображаем результат
    plt.figure(figsize=(10, 6))
    plt.imshow(image, cmap='hot', origin='upper')
    plt.title("Множество Мандельброта (Python, наивная реализация)")
    plt.axis('off')
    plt.tight_layout()
    plt.show()

На моем ноутбуке расчет кадра 2000x2000 занимает около 30 секунд.

2. Modern Fortran (Ускоритель)

Напишем то же самое на Fortran. Забудьте про стереотипы о перфокартах: современный стандарт языка (Fortran 2008/2018) выглядит чисто.

Создаем файл fractal_mod.f90:

SUBROUTINE compute_mandelbrot(min_x, max_x, min_y, max_y, width, height, max_iter, output)
    IMPLICIT NONE
    ! Строгая типизация. REAL(8) соответствует float64 в Python
    REAL(8), INTENT(IN) :: min_x, max_x, min_y, max_y
    INTEGER, INTENT(IN) :: width, height, max_iter
    INTEGER, INTENT(OUT) :: output(height, width) 
    
    ! --- Директивы для F2PY ---
    ! Эти комментарии видит утилита сборки. Мы говорим: 
    ! "Python, создай массив output сам, используя размеры width и height, 
    ! и верни его как результат функции".
    !f2py intent(in) min_x, max_x, min_y, max_y, width, height, max_iter
    !f2py intent(out) output
    !f2py depend(width, height) output
    
    INTEGER :: i, j, iter
    REAL(8) :: x0, y0, x, y, xtemp, dx, dy
    
    dx = (max_x - min_x) / (width - 1)
    dy = (max_y - min_y) / (height - 1)
    
    DO i = 1, height
        y0 = min_y + (i - 1) * dy
        DO j = 1, width
            x0 = min_x + (j - 1) * dx
            x = 0.0d0
            y = 0.0d0
            iter = 0
            
            ! Математика та же, но выполняется на уровне процессора
            DO WHILE ((x*x + y*y <= 4.0d0) .AND. (iter < max_iter))
                xtemp = x*x - y*y + x0
                y = 2.0d0*x*y + y0
                x = xtemp
                iter = iter + 1
            END DO
            
            output(i, j) = iter
        END DO
    END DO
END SUBROUTINE compute_mandelbrot

3. Сборка

Если у вас установлен компилятор (например, бесплатный gfortran), сборка занимает одну строку в терминале:

python -m numpy.f2py -c fractal_mod.f90 -m fractal_lib

Мы получаем бинарный файл (.pyd или .so), который Python воспринимает как родной модуль.

4. Результат

Теперь напишем небольшой Python-скрипт, который:

·      импортирует наш Fortran-модуль,

·      запускает расчёт,

·      замеряет время,

·      по желанию рисует картинку.

import time

import numpy as np
import fractal_lib  # модуль, собранный через F2PY

try:
    import matplotlib.pyplot as plt
except ImportError:
    plt = None


if __name__ == "__main__":
    width = 2000
    height = 2000
    max_iter = 100

    start = time.time()
    # Вызываем Fortran как обычную функцию из Python
    image = fractal_lib.compute_mandelbrot(
        -2.0, 1.0,     # min_x, max_x
        -1.5, 1.5,     # min_y, max_y
        width,
        height,
        max_iter,
    )
    end = time.time()

    print(f"Время расчета через Fortran: {end - start:.4f} сек")
    print(f"Тип возвращаемого объекта: {type(image)}")  # это будет numpy.ndarray

    if plt is not None:
        plt.figure(figsize=(8, 8))
        plt.imshow(image, cmap="magma", extent=(-2.0, 1.0, -1.5, 1.5))
        plt.title("Фрактал Мандельброта (Fortran + Python)")
        plt.xlabel("Re")
        plt.ylabel("Im")
        plt.tight_layout()
        plt.show()
    else:
        print("matplotlib не установлен, пропускаю визуализацию.")

Время выполнения: ~0.2 секунды.

Мы получили ускорение в 150 раз. Просто переписав "узкое горлышко" на языке, предназначенном для математики.

Фрактал — это красиво, но где это применить в продакшене? Эта связка (Python + Fortran) работает везде, где есть сложные численные методы:

  1. Финансовое моделирование (Monte Carlo): Когда нужно прогнать миллион сценариев поведения рынка для оценки рисков (VaR). Python управляет данными, Fortran генерирует сценарии.

  2. Обработка сигналов и изображений: Если вам нужно применить нестандартный фильтр к картинке 4K или обработать поток с датчиков вибрации по хитрому алгоритму, стандартные функции OpenCV могут не подойти. Свой цикл на Fortran решит проблему.

  3. Моделирование физики (CFD, Теплопроводность): Инженеры пишут на Fortran решатели (сольверы) дифференциальных уравнений, а на Python делают интерфейс и визуализацию.

  4. Legacy-код: В мире науки (геофизика, климат) существуют терабайты проверенного, отлаженного кода на Fortran. Переписывать его на C++ дорого и опасно (можно внести ошибки). Проще обернуть его через F2PY и использовать в современном стеке.

Вывод

Старое не значит бесполезное. Fortran в 2025 году занял уникальную нишу — это "второй пилот" для Python в задачах High Performance Computing. Он безопаснее C++ (благодаря строгой типизации и отсутствию ручного управления памятью в простых случаях), его синтаксис заточен под математику, а интеграция с Python работает "из коробки".

Если ваш код упирается в процессор, возможно, стоит не оптимизировать Python, а просто делегировать работу профессионалу.

P.S. Тема гибридного программирования (Python + нативные языки) обширная, и в одну статью всё не уместить. Я продолжаю систематизировать свой опыт и методические материалы по этой теме. Если вам интересно следить за обновлениями или задать вопрос — заглядывайте в мой профиль, там есть контакты и ссылки на мои проекты.

Комментарии (5)


  1. MAXH0
    10.12.2025 06:46

    Старый добрый Фортран. Один из моих первых языков программирования. Помню, как писал на нем первые лабы.


  1. Tzimie
    10.12.2025 06:46

    Лучше дружить питон с Julia


  1. Vitvitsky
    10.12.2025 06:46

    Код из вашего примера(без fortran)
    Код из вашего примера(без fortran)

    за статью спасибо, если когда-нибудь пригодится, попробую что-то написать на fortran


  1. lopppka
    10.12.2025 06:46

    Или ноутбук какой то очень старый, или его совсем замусорили, но на телефоне фрактал генерировался меньше 5 секунд.


  1. black_warlock_iv
    10.12.2025 06:46

    REAL(8) непереносимо и не рекомендуется к употреблению. Если почему-то нет уверенности в дефолтном DOUBLE PRECISION, то лучше использовать REAL64 из ISO_FORTRAN_ENV.