{"id":14275,"url":"\/distributions\/14275\/click?bit=1&hash=bccbaeb320d3784aa2d1badbee38ca8d11406e8938daaca7e74be177682eb28b","title":"\u041d\u0430 \u0447\u0451\u043c \u0437\u0430\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442 \u043f\u0440\u043e\u0444\u0435\u0441\u0441\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u043e\u0434\u0430\u0432\u0446\u044b \u0430\u0432\u0442\u043e?","buttonText":"\u0423\u0437\u043d\u0430\u0442\u044c","imageUuid":"f72066c6-8459-501b-aea6-770cd3ac60a6"}

Mojo… Будущий убийца Python

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

На связи команда DS Арсений Рыбаков, Александра Османкина и Никита Тюгаев.

Mojo — это новый язык программирования, который, по сути, является надмножеством Python, как TypeScript и JavaScript.

Он устраняет имеющиеся у него проблемы производительности и развертывания, а, по заявлениям разработчиков, в некоторых задачах обгоняет Python в 35 000 раз.

Вдобавок, за Modular, разработчиками Mojo, стоит Крис Латтнер, создатель языка программирования Swift и компилятора LLVM, который облегчил разработку новых мощных ЯП (Rust, Julia), что заставляет быть еще более уверенным в успехе Mojo.

Проблемы Python

Python - прекрасный язык, с большим количеством областей применения. Он доминирует в области Data Science, обладает огромным количеством фреймворков, что надежно закрепляет его в инструментарии современного разработчика.

Так почему бы не использовать его при решении всех задач? Просто он в несколько тысяч раз медленнее, чем C-подобные языки :) Это делает непрактичным его использование для чувствительных к производительности частей кода, но у Python есть одно неоспоримое преимущество: он может обращаться к коду, написанному на быстрых языках, таких, как C, позволяя программистам Python чувствовать себя как в родной среде, даже если они используют высокооптимизированные числовые библиотеки. Но и у такого подхода существуют неизбежные проблемы с производительностью: приходится иметь дело с невозможностью эффективной параллельной обработки в Python, а также с отсутствием слияния, то есть множества скомпилированных функций подряд приводят к накладным расходам.

Как Mojo может решить эти проблемы?

Создать быстрый, а также простой и гибкий в использования язык программирования, который будет лишен проблем Python, очень сложно, однако Modular, кажется, имеют все шансы на успех.

В первую очередь, Mojo – язык, разработанный для программирования на графических процессорах, что крайне важно для разработок в области искусственного интеллекта. В качестве “фундамента” Mojo использует гибкую инфраструктуру для современных оптимизирующих компиляторов MLIR, которая позволяет разработчикам Mojo извлечь все преимущества векторов, потоков и аппаратных устройств искусственного интеллекта.

Память управляется аналогично Rust через систему владения с набором правил, которые проверяются компилятором, при нарушении которых программа не будет скомпилирована. Это никак не замедляет скорость работы программы, но повышает безопасность. Также поддерживается ручное управление, как в C++.

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

Но это еще не все, есть и новый функционал, например: статическая типизация (вы все еще можете использовать динамические типы, но статические необходимы для оптимизации) и наличие возможности у разработчика в любой момент перейти в более быстрый «режим», используя «fn» вместо «def» для создания своей функции. В этом режиме нужно явно объявить тип каждой переменной, и в результате Mojo сможет создать оптимизированный машинный код для реализации функции. Более того, если используется «struct» вместо «class», атрибуты будут плотно упакованы в память, поэтому их можно будет использовать даже в структурах данных, не гоняясь за указателями. Именно этот функционал позволяет языкам C быть настолько быстрыми, а теперь он доступен и для разработчиков на Python, нужно просто изучить немного нового синтаксиса.

Тесты и сравнения

Начнем с простого, вычислим сумму арифметической прогрессии, используя цикл for.

Вот так будет выглядеть наш код на Python:

%%python from time import time start_time = time() sum = 0 for i in range(10_000_000_001): sum += i end_time = time() python_execution_time = (end_time - start_time) * 1_000_000_000

Получим следующее:

sum = 500000000500000000 python_execution_time = 73 357 328 653.33557 nanoseconds

А так на Mojo:

from Time import now start_time = now() var sum: Int = 0 for i in range(10_000_000_001): sum += i end_time = now() mojo_execution_time = end_time - start_time

sum = 500000000500000000

mojo_execution_time = 82 nanoseconds

По итогам тестирования можем установить, что Mojo считает сумму арифметической прогрессии от 0 до 1 000 000 000 быстрее Python почти в 1 000 000 000 раз! При этом, чем больше элементов, тем больше разница между соревнующимися. Естественно, эта задача мало похожа на реальную, но она показывает, насколько быстро Mojo может работать с циклами.

Теперь попробуем что-нибудь сложнее, например, вычисление числа π.

Python:

%%python from time import time def pi(n) -> float: total = 0.0 for i in range(1 - 2*n, 2*n + 1, 4): total += 1.0 / i return 4 * total start_time = time() print(pi(10_000_000_000)) end_time = time() python_execution_time = (end_time - start_time) * 1_000_000_000

pi = 3.1415926534900813

python_execution_time = 73357328653.33557 nanoseconds

Mojo:

from Time import now fn pi(n: Int) -> FloatLiteral: var total: FloatLiteral = 0.0 for i in range(1 - 2*n, 2*n + 1, 4): total += 1.0 / i return 4 * total start_time = now() print(pi(10_000_000_000)) end_time = now() mojo_execution_time = end_time - start_time

pi = 3.1415926534900813

mojo_execution_time = 11596816608 nanoseconds

В этот раз разница не такая существенная, но все также ощутима, Python медленнее в 500 раз.

Ну и наконец, умножение матриц, одна из операций, на основе которой Modular демонстрируют преимущества своей разработки.

Сначала реализуем алгоритм умножения матриц на Python исходя из определения.

%%python def matmul_python(C, A, B): for m in range(C.rows): for k in range(A.cols): for n in range(C.cols): C[m, n] += A[m, k] * B[k, n]

Давайте протестируем нашу реализацию используя квадратные матрицы 128x128 и вычислим скорость в гигафлопсах.

%%python import numpy as np from timeit import timeit class Matrix: def __init__(self, value, rows, cols): self.value = value self.rows = rows self.cols = cols def __getitem__(self, idxs): return self.value[idxs[0]][idxs[1]] def __setitem__(self, idxs, value): self.value[idxs[0]][idxs[1]] = value def benchmark_matmul_python(M, N, K): A = Matrix(list(np.random.rand(M, K)), M, K) B = Matrix(list(np.random.rand(K, N)), K, N) C = Matrix(list(np.zeros((M, N))), M, N) secs = timeit(lambda: matmul_python(C, A, B), number=2) / 2 gflops = ((2 * M * N * K) / secs) / 1e9 print(gflops, "GFLOP/s") return gflops
python_gflops = benchmark_matmul_python(128, 128, 128).to_float64()

python_gflops = 0.0016717199881536883 GFLOP/s

Теперь реализуем на Mojo:

from Benchmark import Benchmark from DType import DType from Intrinsics import strided_load from List import VariadicList from Math import div_ceil, min from Memory import memset_zero from Object import object, Attr from Pointer import DTypePointer from Random import rand, random_float64 struct Matrix: var data: DTypePointer[DType.float32] var rows: Int var cols: Int fn __init__(inout self, rows: Int, cols: Int): self.data = DTypePointer[DType.float32].alloc(rows * cols) rand(self.data, rows*cols) self.rows = rows self.cols = cols fn __del__(owned self): self.data.free() fn zero(inout self): memset_zero(self.data, self.rows * self.cols) @always_inline fn __getitem__(self, y: Int, x: Int) -> Float32: return self.load[1](y, x) @always_inline fn load[nelts:Int](self, y: Int, x: Int) -> SIMD[DType.float32, nelts]: return self.data.simd_load[nelts](y * self.cols + x) @always_inline fn __setitem__(self, y: Int, x: Int, val: Float32): return self.store[1](y, x, val) @always_inline fn store[nelts:Int](self, y: Int, x: Int, val: SIMD[DType.float32, nelts]): self.data.simd_store[nelts](y * self.cols + x, val)

Функция умножения матриц:

fn matmul_naive(C: Matrix, A: Matrix, B: Matrix): for m in range(C.rows): for k in range(A.cols): for n in range(C.cols): C[m, n] += A[m, k] * B[k, n]

Алгоритм бенчмарка:

@always_inline def benchmark[func : fn(Matrix, Matrix, Matrix) -> None] (M : Int, N : Int, K : Int, python_gflops: Float64): var C = Matrix(M, N) C.zero() var A = Matrix(M, K) var B = Matrix(K, N) @always_inline @parameter fn test_fn(): _ = func(C, A, B) let secs = Float64(Benchmark().run[test_fn]()) / 1_000_000_000 _ = A.data _ = B.data _ = C.data let gflops = ((2*M*N*K)/secs) / 1e9 let speedup : Float64 = gflops / python_gflops print(gflops, "GFLOP/s, в", speedup.value, "раз быстрее Python") benchmark[matmul_naive](128, 128, 128, python_gflops)

13.978696 GFLOP/s, в 8361.864443 раз быстрее Python

Наблюдаем ускорение более чем в 8 000 раз в таком непростом алгоритме.

Однако, если использовать torch:

import numpy as np from timeit import timeit import torch def benchmark_torch_matmul_python(M, N, K): A = torch.randn(M, N) B = torch.randn(N, M) secs = timeit(lambda: torch.matmul(A, B), number=2) / 2 gflops = ((2 * M * N * K) / secs) / 1e9 print(gflops, "GFLOP/s") return gflops
python_gflops = benchmark_torch_matmul_python(128, 128, 128).to_float64()

python_gflops = 19.74407991241765 GFLOP/s

Видим, что Mojo оказался медленнее в 1.41244 раз. К сожалению, использовать torch с Mojo для дополнительных тестов пока нет возможности.

Все тесты проводились на ЦП.

Заключение

Протестировав Mojo, можно сделать как минимум один вывод, Python без использования сторонних библиотек медленнее, причем заметно. Конечно, новые языки и фреймворки появляются постоянно и не все из них становятся востребованными и популярными, да и Mojo еще находится в очень раннем развитии, но, кажется у него есть все шансы стать востребованным у современных разработчиков.

0
Комментарии
-3 комментариев
Раскрывать всегда