惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
博客园 - 【当耐特】
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
D
Docker
GbyAI
GbyAI
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
The Cloudflare Blog
雷峰网
雷峰网
A
About on SuperTechFans
小众软件
小众软件
博客园 - Franky
博客园 - 聂微东
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
MongoDB | Blog
MongoDB | Blog
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
V2EX
Engineering at Meta
Engineering at Meta
宝玉的分享
宝玉的分享
aimingoo的专栏
aimingoo的专栏
量子位
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
罗磊的独立博客
Martin Fowler
Martin Fowler
D
DataBreaches.Net
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Secure Thoughts
Project Zero
Project Zero
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tailwind CSS Blog
S
Schneier on Security
Blog — PlanetScale
Blog — PlanetScale
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Security Latest
Security Latest
NISL@THU
NISL@THU
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
CXSECURITY Database RSS Feed - CXSecurity.com
J
Java Code Geeks

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Ускоряем и оптимизируем numpy, pandas, scipy и sklearn
mike · 2026-05-27 · via Все публикации подряд на Хабре

Средний

10 мин

13K

С момента публикации статьи на Хабре «Импортозамещаем numpy, pandas, scipy и sklearn» прошло почти три года. В течение этого времени я приостановил работу над проектом из-за нехватки времени, ресурсов и сил. К тому же, меня расстроило, что не смог выполнить просьбу пользователя @N-Cube, который активно интересовался моей библиотекой и хотел ускорить работу своего Jupyter Notebook.

В самый критический момент на помощь пришел волшебный AI, который, хоть и иногда проявлял недостаток гибкости, с готовностью исполнял все пожелания своего хозяина. Благодаря этому проект начал продвигаться вперед.

За это время в библиотеки были добавлены поддержка CUDA, множество ручных SIMD-оптимизаций с динамическим выбором SIMD, несколько реализаций линейной регрессии и многое другое.

Давайте рассмотрим, что на сегодняшний день позволяет сделать моя библиотека.

Я представлю несколько тестовых примеров в двух вариантах: с использованием AVX-2 на процессоре Intel® Core™ i7-4790K и AVX-512 на Intel® Xeon. Также покажу результаты замеров для каждого из них. Все тесты проводились без использования GPU, исключительно на процессоре. Это позволяет сравнивать производительность Python и моей библиотеки на равных условиях. Операционная система – Ubuntu 24.04, компилятор – GNU 13.3.0.

Метод Монте-Карло для вычисления числа π

Скрытый текст
  • Генерация координат: Программа создает два вектора случайных координат: rx и ry, которые находятся в диапазоне от 0 до 1. Эти координаты представляют собой точки на плоскости.

  • Проверка попадания: Точка считается находящейся внутри круга, если расстояние dist от начала координат (0, 0) меньше радиуса, равного 1. Это условие можно проверить с помощью следующей формулы: rx² + ry² < 1.

  • Вычисление: Итоговая оценка числа π (pi_est) вычисляется как отношение количества точек, попавших внутрь круга, к общему количеству сгенерированных точек.

Бенчмарк: https://github.com/mgorshkov/np/blob/main/samples/monte-carlo/compare_python_monte_carlo.py

Оригинальный питоновский код

rx = np.random.rand(size)
ry = np.random.rand(size)
dist = rx * rx + ry * ry
inside = np.sum(dist < 1.0)
pi_est = 4.0 * inside / size

Код из библиотеки

auto rx = random::rand(size);
auto ry = random::rand(size);
auto dist = rx * rx + ry * ry;
auto inside = sum("dist<1", dist);
double pi_est = 4 * static_cast<double>(inside) / size;

Результаты бенчмарка на AVX-2

Size

Py time (us)

Py mem (MiB)

C++ time (us)

C++ mem (MiB)

Speedup

Mem ratio

100000

4222

2.3

638

1.5

6.62x

1.5x

1000000

19760

22.9

3386

15.3

5.84x

1.5x

10000000

181804

228.9

29889

152.6

6.08x

1.5x

100000000

1770601

2288.8

313803

1525.9

5.64x

1.5x

Результаты бенчмарка на AVX-512

Size

Py time (us)

Py mem (MiB)

C++ time (us)

C++ mem (MiB)

Speedup

Mem ratio

100000

7538

2.3

2371

1.5

3.18x

1.5x

1000000

30011

22.9

3782

15.3

7.94x

1.5x

10000000

235035

228.9

23761

152.6

9.89x

1.5x

100000000

6192049

2288.8

285586

1525.9

21.68x

1.5x

Неполная бета-функция

Скрытый текст

Что такое полная бета-функция?

Чтобы понять неполную версию, нужно вспомнить полную. Полная бета-функция $\(B(a, b)\)$ — это определенный интеграл от нуля до единицы, который зависит от двух параметров $\(a\)$ и $\(b\)$:

$$\(B(a, b) = \int_{0}^{1} t^{a-1} (1-t)^{b-1} \, dt\)$$

Что такое неполная бета-функция?

В неполной бета-функции верхний предел интеграла заменяется на переменную $\(x\)$ (где $\(0 \le x \le 1\))$. Это значит, что мы интегрируем функцию не до конца, а только на отрезке от $\(0\)$ до $\(x\)$.

Обозначается она как $\(B_x(a, b)\)$ и определяется следующим образом:

$$\(B_x(a, b) = \int_{0}^{x} t^{a-1} (1-t)^{b-1} \, dt\)$$

Бенчмарк: https://github.com/mgorshkov/scipy/tree/main/benchmarks/betainc

Оригинальный код на Python

#!/usr/bin/env python3
"""
Python scipy betainc benchmark - called by the C++ comparison benchmark.
Uses the same test parameters as the C++ benchmark for fair comparison.
"""
import time
import sys
import scipy.special


def benchmark_python_scipy():
    a = 0.5 * 99997
    b = 0.5 * 99997
    x = 0.4
    count = 0
    res = 0.0

    start = time.perf_counter_ns()

    while x < 0.6:
        count += 1
        res += scipy.special.betainc(a, b, x)
        x += 0.000001

    stop = time.perf_counter_ns()

    diff = stop - start
    print(f"Result = {res}")
    print(f"Time = {diff} ns")
    print(f"Loops = {count}")


if __name__ == "__main__":
    benchmark_python_scipy()

Код из библиотеки

timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);

np::float_ a = 0.5 * 99997;
np::float_ b = 0.5 * 99997;

np::float_ x = 0.4;
int count = 0;
np::float_ res = 0;

while (x < 0.6) {
    ++count;
    res += scipy::special::betainc(a, b, x);
    x += 0.000001;
}

timespec stop;
clock_gettime(CLOCK_MONOTONIC, &stop);

std::uint64_t diff = 1000000000L * (stop.tv_sec - start.tv_sec) + stop.tv_nsec - start.tv_nsec;

std::cout << "Result = " << res << std::endl;
std::cout << "Time = " << diff << " ns" << std::endl;
std::cout << "Loops = " << count << std::endl;

Результаты бенчмарка на AVX-2

Implementation

Time (ns)

Loops

Speedup vs Python

C++ scipy (AVX2)

115882110

200000

2.26x

Python scipy

262307821

200000

1.00x

Результаты бенчмарка на AVX-512

Implementation

Time (ns)

Loops

Speedup vs Python

C++ scipy (AVX512)

113440191

200000

2.75x

Python scipy

311787699

200000

1.00x

Большой фрагмент оптимизированного Jupyter Notebook (основные компоненты - неполная бета-функция и линейная регрессия)

Оригинальный код на Python из комментария к предыдущей статье: https://habr.com/ru/articles/752762/#comment_25829022

Бенчмарк: https://github.com/mgorshkov/sklearn/blob/main/samples/gmt_trend_2d/benchmark.cpp

Код на Python

Скрытый текст
from tabulate import tabulate
import numpy as np

def generate_data(rank, num_points, noise_level):
    np.random.seed(42)
    x = np.linspace(-10, 10, num_points)
    y = np.linspace(-10, 10, num_points)
    if rank == 1:
        z = 3 * x + 5 + noise_level * np.random.randn(num_points)
        data = np.column_stack((x, y, z))
    elif rank == 2:
        z = 2 * x + 3 * y + 5 + noise_level * np.random.randn(num_points)
        data = np.column_stack((x, y, z))
    elif rank == 3:
        z = 2 * x**2 + 3 * y**2 + 5 + noise_level * np.random.randn(num_points)
        data = np.column_stack((x, y, z))
    return data

def GMT_trend2d(data, rank):
    import numpy as np
    from sklearn.linear_model import LinearRegression
    # scale factor for normally distributed data is 1.4826
    # https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.median_abs_deviation.html
    MAD_NORMALIZE = 1.4826
    # significance value
    sig_threshold = 0.51

    if rank not in [1,2,3]:
        raise Exception('Number of model parameters "rank" should be 1, 2, or 3')

    #see gmt_stat.c
    def gmtstat_f_q (chisq1, nu1, chisq2, nu2):
        import scipy.special as sc

        if chisq1 == 0.0:
            return 1
        if chisq2 == 0.0:
            return 0
        return sc.betainc(0.5*nu2, 0.5*nu1, chisq2/(chisq2+chisq1))

    if rank in [2,3]:
        x = data[:,0]
        x = np.interp(x, (x.min(), x.max()), (-1, +1))
    if rank == 3:
        y = data[:,1]
        y = np.interp(y, (y.min(), y.max()), (-1, +1))
    z = data[:,2]
    w = np.ones(z.shape)

    if rank == 1:
        xy = np.expand_dims(np.zeros(z.shape),1)
    elif rank == 2:
        xy = np.expand_dims(x,1)
    elif rank == 3:
        xy = np.stack([x,y]).transpose()

    # create linear regression object
    mlr = LinearRegression()

    chisqs = []
    coeffs = []
    while True:
        # fit linear regression
        mlr.fit(xy, z, sample_weight=w)

        r = np.abs(z - mlr.predict(xy))
        chisq = np.sum((r**2*w))/(z.size-3)
        chisqs.append(chisq)
        k = 1.5 * MAD_NORMALIZE * np.median(r)
        w = np.where(r <= k, 1, (2*k/r) - (k * k/(r**2)))
        sig = 1 if len(chisqs)==1 else gmtstat_f_q(chisqs[-1], z.size-3, chisqs[-2], z.size-3)
        # Go back to previous model only if previous chisq < current chisq
        if len(chisqs)==1 or chisqs[-2] > chisqs[-1]:
            coeffs = [mlr.intercept_, *mlr.coef_]

        #print ('chisq', chisq, 'significant', sig)
        if sig < sig_threshold:
            break

    # get the slope and intercept of the line best fit
    return (coeffs[:rank])

def calculate_mse(data, coeffs, rank):
    z_actual = data[:, 2]

    if rank == 1:
        z_predicted = coeffs[0]
    elif rank == 2:
        # Interpolate x the same way as in GMT_trend2d
        x = data[:, 0]
        x_interp = np.interp(x, (x.min(), x.max()), (-1, +1))
        z_predicted = coeffs[0] + coeffs[1] * x_interp
    elif rank == 3:
        # Interpolate x and y the same way as in GMT_trend2d
        x = data[:, 0]
        x_interp = np.interp(x, (x.min(), x.max()), (-1, +1))
        y = data[:, 1]
        y_interp = np.interp(y, (y.min(), y.max()), (-1, +1))
        z_predicted = coeffs[0] + coeffs[1] * x_interp + coeffs[2] * y_interp

    mse = np.mean((z_actual - z_predicted) ** 2)
    return mse

def test_mse(num_points = 100*1000, ranks = [1, 2, 3], noise_levels = [0, 1, 10, 50]):
    import warnings

    results = []
    # Suppress the specific warning
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category=RuntimeWarning)
        for rank in ranks:
            for noise_level in noise_levels:
                data = generate_data(rank, num_points, noise_level)
                # round the output
                coeffs_gmt = [v.round(8) for v in GMT_trend2d(data, rank)]

                mse_gmt = np.round(calculate_mse(data, coeffs_gmt, rank), 0)

                results.append([rank, noise_level, mse_gmt])

    headers = ["Rank", "Noise Level", "GMT_trend2d, MSE"]
    print(tabulate(results, headers=headers))

test_mse()

Код из библиотеки

Скрытый текст
using namespace np;
using namespace scipy;
using namespace sklearn;

auto generate_data(auto rank, auto num_points, auto noise_level) {
    random::seed(42);
    auto x = linspace(-10.0, 10.0, num_points);
    auto y = linspace(-10.0, 10.0, num_points);
    if (rank == 1) {
        auto z = 3 * x + 5 + noise_level * random::randn(num_points);
        return column_stack(x, y, z);
    }
    if (rank == 2) {
        auto z = 2 * x + 3 * y + 5 + noise_level * random::randn(num_points);
        return column_stack(x, y, z);
    }
    auto z = 2 * x * x + 3 * y * y + 5 + noise_level * random::randn(num_points);
    return column_stack(x, y, z);
}

auto GMT_trend2d(const Array<float_> &data, int rank) {
    float_ MAD_NORMALIZE = 1.4826;
    float_ sig_threshold = 0.51;

    if (rank != 1 && rank != 2 && rank != 3) {
        throw sklearn::RuntimeError("Number of model parameters \"rank\" should be 1, 2, or 3");
    }

    auto gmtstat_f_q = [](float_ chisq1, float_ nu1, float_ chisq2, float_ nu2) {
        if (chisq1 == 0.0) return 1.0;
        if (chisq2 == 0.0) return 0.0;
        return scipy::special::betainc(0.5 * nu2, 0.5 * nu1, chisq2 / (chisq2 + chisq1));
    };

    Array<float_> x;
    if (rank == 2 || rank == 3) {
        auto x_ = data[":,0"];
        x = interp(x_, Array<float_>{x_.min(), x_.max()}, Array<float_>{-1, +1});
    }
    Array<float_> y;
    if (rank == 3) {
        auto y_ = data[":,1"];
        y = interp(y_, Array<float_>{y_.min(), y_.max()}, Array<float_>{-1, +1});
    }
    auto z = data[":, 2"].copy();
    Array<float_> w = ones(z.shape()).copy();

    Array<float_> xy;
    if (rank == 1) {
        xy = expand_dims(zeros(z.shape()), 1);
    } else if (rank == 2) {
        xy = expand_dims(x, 1);
    } else if (rank == 3) {
        xy = stack(x, y).transpose();
    }

    auto mlr = linear_model::LinearRegression{};

    std::vector<float_> chisqs;
    Array<float_> coeffs;

    while (true) {
        mlr.fit(xy, z, w);

        auto r = abs_sub(z, mlr.predict(xy));
        auto chisq = sum_sq_weighted(r, w) / static_cast<float_>(z.size() - 3);
        chisqs.push_back(chisq);

        auto k = 1.5 * MAD_NORMALIZE * median(r);
        w = where_tukey(r, k);
        auto sig = (chisqs.size() == 1 ? 1 : gmtstat_f_q(chisqs[chisqs.size() - 1], static_cast<float_>(z.size() - 3), chisqs[chisqs.size() - 2], static_cast<float_>(z.size() - 3)));
        if (chisqs.size() == 1 or chisqs[chisqs.size() - 2] > chisqs[chisqs.size() - 1]) {
            coeffs = mlr.coeffs_();
        }

        if (sig < sig_threshold) {
            break;
        }
    }
    auto result = Array<float_>{};
    for (int i = 0; i < rank; ++i) {
        result = append(result, Array<float_>{coeffs.get(i)});
    }
    return result;

auto calculate_mse(const Array<float_> &data, const Array<float_> &coeffs, int rank) {
    auto z_actual = data[":,2"].copy();
    Array<float_> z_predicted;

    if (rank == 1) {
        z_predicted = coeffs.get(0) * ones(z_actual.shape());
    } else if (rank == 2) {
        // Interpolate x the same way as in GMT_trend2d
        auto x_ = data[":,0"];
        auto x = interp(x_, Array<float_>{x_.min(), x_.max()}, Array<float_>{-1, +1});
        z_predicted = coeffs.get(0) + coeffs.get(1) * x;
    } else if (rank == 3) {
        // Interpolate x and y the same way as in GMT_trend2d
        auto x_ = data[":,0"];
        auto x = interp(x_, Array<float_>{x_.min(), x_.max()}, Array<float_>{-1, +1});
        auto y_ = data[":,1"];
        auto y = interp(y_, Array<float_>{y_.min(), y_.max()}, Array<float_>{-1, +1});
        z_predicted = coeffs.get(0) + coeffs.get(1) * x + coeffs.get(2) * y;
    }
    using sklearn::metrics::mean_squared_error;
    using sklearn::metrics::MeanSquaredErrorParameters;
    MeanSquaredErrorParameters<Array<float_>> params{.y_true = z_actual, .y_pred = z_predicted};
    return mean_squared_error(params);
}

void test_mse(int num_points = 100 * 1000, const std::vector<int> &ranks = {1, 2, 3}, const std::vector<int> &noise_levels = {0, 1, 10, 50}) {
    std::vector<std::tuple<int, int, float_>> results;
    for (auto rank: ranks) {
        for (auto noise_level: noise_levels) {
            auto data = generate_data(rank, num_points, noise_level);
            auto coeffs_gmt = GMT_trend2d(data, rank);
            // round coefficients to 8 decimal places
            auto coeffs_rounded = coeffs_gmt.copy();
            for (std::size_t i = 0; i < coeffs_rounded.size(); ++i) {
                coeffs_rounded.set(i, std::round(coeffs_rounded.get(i) * 1e8) / 1e8);
            }
            auto mse_gmt = calculate_mse(data, coeffs_rounded, rank);
            // round MSE to zero decimal places
            mse_gmt = std::round(mse_gmt);
            results.emplace_back(rank, noise_level, mse_gmt);
        }
    }
    // print table
    std::cout << "Rank\tNoise Level\tGMT_trend2d, MSE\n";
    for (const auto &[rank, noise_level, mse]: results) {
        std::cout << rank << "\t" << noise_level << "\t" << mse << "\n";
    }
}

Результаты бенчмарка на AVX-2

Rank

Noise Level

C++ Time [ms]

Python Time [ms]

Speedup (C++ vs Py)

Result

1

0

6.623

12.580

1.9x (+90%)

C++ FASTER

1

1

6.326

11.698

1.8x (+85%)

C++ FASTER

1

10

8.351

17.884

2.1x (+114%)

C++ FASTER

1

50

8.423

17.564

2.1x (+109%)

C++ FASTER

2

0

11.848

24.378

2.1x (+106%)

C++ FASTER

2

1

13.988

21.392

1.5x (+53%)

C++ FASTER

2

10

14.298

21.454

1.5x (+50%)

C++ FASTER

2

50

13.892

21.267

1.5x (+53%)

C++ FASTER

3

0

22.118

27.332

1.2x (+24%)

C++ FASTER

3

1

21.651

26.097

1.2x (+21%)

C++ FASTER

3

10

22.267

25.905

1.2x (+16%)

C++ FASTER

3

50

24.563

33.731

1.4x (+37%)

C++ FASTER

Результаты бенчмарка на AVX-512

Rank

Noise Level

C++ Time [ms]

Python Time [ms]

Speedup (C++ vs Py)

Result

1

0

10.465

14.564

1.4x (+39%)

C++ FASTER

1

1

8.728

13.618

1.6x (+56%)

C++ FASTER

1

10

11.995

20.686

1.7x (+72%)

C++ FASTER

1

50

12.432

19.809

1.6x (+59%)

C++ FASTER

2

0

13.804

16.047

1.2x (+16%)

C++ FASTER

2

1

16.994

26.332

1.5x (+55%)

C++ FASTER

2

10

16.816

25.047

1.5x (+49%)

C++ FASTER

2

50

15.862

25.106

1.6x (+58%)

C++ FASTER

3

0

22.385

29.881

1.3x (+33%)

C++ FASTER

3

1

21.063

29.933

1.4x (+42%)

C++ FASTER

3

10

21.285

30.517

1.4x (+43%)

C++ FASTER

3

50

25.981

36.520

1.4x (+41%)

C++ FASTER

Заключение

Таким образом, мы смогли ускорить работу библиотек более чем в два раза и сократить использование памяти примерно в 1.5 раза. Что нас ждет дальше? В планах продолжить развитие темы линейной регрессии, чтобы обеспечить быстрое вычисление на больших массивах данных. Также хотелось бы довести все библиотеки до полного соответствия с API аналогичных библиотек на Python. Кроме того, мы планируем создать новые библиотеки для машинного обучения и реализовать нейронные сети, скажем, аналог PyTorch или Tensorflow.

С помощью вайбкодинга возможности разработчиков становятся поистине безграничными.

Вопросы

  • Я с нетерпением жду обратной связи от сообщества. Интересно, действительно ли эта разработка окажется полезной для пользователей или она останется лишь увлечением для разработчиков, не находя применения в широких кругах?

  • Есть ли предложения по дальнейшему развитию этих библиотек? Возможно, кому-то нужно ускорить работу в Jupyter и подготовить его в виде компактного и быстрого исполняемого файла для продакшена? Размер бинарника из примера составляет всего около 2 Мб. Не стесняйтесь обращаться!

  • Какое направление, по вашему мнению, следует развивать более активно? Возможно, это будет numpy, pandas, scipy, sklearn, или стоит сосредоточиться на нейросетях?

  • Также приглашаю желающих помочь протестировать библиотеки на процессорах AMX. У меня нет доступа к таким процессорам, но подойдут машины с CPU 4-го поколения Intel Xeon Scalable (Sapphire Rapids), 5-го поколения Intel Xeon Scalable (Emerald Rapids) или процессорами Intel Xeon 6 (Granite Rapids / Sierra Forest).

  • Ищем помощь для настройки сборки и тестирования библиотек под Windows. У меня есть компьютер с этой ОС, но я давно не проверял его, и, скорее всего, сборка сейчас не работает.

  • Если кто-то хочет поучаствовать в разработке и внести свой вклад, буду рад вашему отклику! Пишите мне в личные сообщения. Спасибо!

Ссылки