Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Python стал основным языком машинного обучения и анализа данных благодаря своей простоте, гибкости и огромному выбору вспомогательных библиотек. Процесс разработки на Python идет гораздо быстрее, чем на любом другом языке, и хотя Python довольно часто комбинируют с R и Julia, ни тот, ни другой язык не может полностью его заменить. Недавно у Python появился новый конкурент – Rust. Он гораздо сложнее Python, но у него есть два важных преимущества – высокая производительность, сопоставимая с C/C++, и максимально надежный механизм обеспечения безопасности. Теперь каждый ML-разработчик и аналитик данных должен решить для себя дилемму – какой из этих языков выбрать. Попробуем сравнить особенности Python, которые сделали его фактическим стандартом в AI/ML и анализе данных, с вескими преимуществами восходящей звезды Rust.
Наследие Python
Python имеет простой и интуитивно понятный синтаксис, который иногда в шутку называют исполняемым псевдокодом. Эта простота и доступность приглянулись многим талантливым разработчикам: они начали создавать всевозможные дополнительные модули, библиотеки и фреймворки. В результате Python очень быстро обзавелся необъятной экосистемой, в которой есть инструменты для решения любых задач. Не все эти задачи решаются максимально эффективно – из-за невысокой производительности Python не подходит для разработки серьезных 3D-игр, например – но для анализа данных, машинного обучения и многих других вещей скорости языка вполне хватает. К тому же многие критически важные модули и библиотеки Python реализованы на уровне С и работают с соответствующей скоростью. Вот так просто выглядит чтение данных из CSV-файла в Python с помощью Pandas:
import pandas as pd
data = pd.read_csv("mydataset.csv")
print(data.head())
Новый конкурент – Rust
Основные плюсы Rust – высокая производительность, безопасность и многопоточность. Он отлично подходит для системного программирования, стремительно набирает популярность в серверной разработке и геймдеве. Rust – не самый очевидный выбор для анализа данных и машинного обучения. Однако в последние несколько лет, благодаря своим веским преимуществам, он все чаще применяется и в этих областях – хотя использовать его заметно сложнее. Чтение того же самого CSV-файла в Rust выглядит так:
use std::error::Error;
use csv::ReaderBuilder;
fn main() -> Result<(), Box<dyn Error>> {
let file = std::fs::File::open("mydataset.csv")?;
let mut rdr = ReaderBuilder::new().has_headers(true).from_reader(file);
for result in rdr.records() {
let record = result?;
println!("{:?}", record);
}
Ok(())
}
Python или Rust: что проще
Python известен плавной кривой обучения: с минимальными знаниями языка уже можно писать полезные скрипты, а изучение более сложных концепций отложить на потом. По этой причине Pyhton идеально подходит:
- Специалистам, которым нужно с помощью программирования решать профессиональные задачи – научные и инженерные.
- Новичкам, не имеющим никакого опыта в программировании.
# Пример кода на Python
print("Hello, World!")
Rust, напротив, может показаться слишком сложным для начинающих: в нем есть непростые концепции, которые надо осмыслить сразу, например, системы владения и заимствования:
- Система владения в Rust означает, что каждый объект имеет только одного владельца, который отвечает за его уничтожение. Это предотвращает многие ошибки, связанные с утечкой памяти или двойным освобождением ресурсов.
- Система заимствования позволяет использовать один и тот же ресурс в нескольких местах кода без необходимости его копирования. Это может значительно повысить производительность, так как не тратятся ресурсы на создание лишних копий объектов.
Однако эти концепции, среди прочих уникальных особенностей Rust, и делают его таким быстрым, безопасным и надежным. Это как раз тот случай, когда усилия, затраченные на изучение языка, будут многократно компенсированы качеством и производительностью готовых приложений.
// Пример кода на Rust
fn main() {
println!("Hello, World!");
}
Rust или Python: что быстрее
Сравним производительность Rust и Python на решении одной и той же задачи. Напишем рекурсивный код, который вычисляет первые 35 чисел в последовательности Фибоначчи и выводит время, затраченное на это вычисление.
Вычисление последовательности Фибоначчи на Rust:
use std::time::Instant;
fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn main() {
let start_time = Instant::now();
for i in 0..35 {
println!("{}", fibonacci(i));
}
let duration = start_time.elapsed();
println!("Время выполнения: {:.2} секунд", duration.as_secs_f64());
}
Результат:
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269
2178309
3524578
5702887
Время выполнения: 0.05 секунд
Вариант на Python:
import time
def fibonacci(n):
if n <= 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
start_time = time.time()
for i in range(35):
print(fibonacci(i))
end_time = time.time()
print(f"Время выполнения : {end_time - start_time} секунд")
Результат:
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269
2178309
3524578
5702887
Время выполнения : 7.8505754470825195 секунд
Очевидно, что Rust справился с задачей быстрее:).
Библиотеки и фреймворки
С экосистемой Python сложно соперничать: библиотеки для математических вычислений, анализа данных и работы с нейронными сетями (Numpy, Pandas, TensorFlow, PyTorch, Scikit-Learn и т. д.) стали стандартом де-факто в индустрии анализа данных, Data Science и ML/AI.
Однако Rust и его экосистема быстро развиваются:
- В распоряжении разработчиков есть модуль ndarray с аналогичной NumPy функциональностью.
- Имеется аналог Pandas для анализа данных – Polars.
- Есть библиотека statrs для статистических расчетов и анализа.
- Библиотека Tangram используется для машинного обучения и прогнозирования.
- Linfa, Autograd и SmartCore предоставляют функциональность, сходную с PyTorch и TensorFlow.
Кроме того, PyTorch и TensorFlow тоже можно использовать в Rust, а список DS/ML/AI библиотек, разработанных специально для Rust, регулярно пополняется.
Безопасность и конкурентость
В отличие от Python, Rust обеспечивает безопасность памяти: программа на Rust не может случайно обратиться к неверному адресу в памяти и вызвать ошибку. Это делает Rust более надежным и безопасным для использования в критических приложениях.
Кроме того, Rust является языком с конкурентной моделью выполнения: несколько операций могут выполняться параллельно без необходимости блокировки или синхронизации. Это позволяет Rust достигать более высокой производительности, чем Python, особенно при работе с большими объемами данных.
Для создания пула потоков и выполнения задач параллельно в Python можно использовать модуль concurrent.futures из стандартной библиотеки:
import concurrent.futures
def process_data(data_chunk):
return data_chunk * 2
data_chunks = [1, 2, 3, 4, 5]
with concurrent.futures.ThreadPoolExecutor() as executor:
future_to_data = {executor.submit(process_data, data_chunk): data_chunk for data_chunk in data_chunks}
for future in concurrent.futures.as_completed(future_to_data):
data_chunk = future_to_data[future]
try:
data = future.result()
except Exception as exc:
print(f'{data_chunk} исключение: {exc}')
else:
print(f'{data_chunk} обработано: {data}')
Для параллельных операций в Rust используется библиотека Rayon:
use rayon::prelude::*;
fn process_data(data_chunk: &mut Vec<i32>) {
*data_chunk = data_chunk.iter().map(|&x| x * 2).collect();
}
fn main() {
let mut data_chunks: Vec<Vec<i32>> = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
data_chunks.par_iter_mut().for_each(|chunk| {
process_data(chunk);
});
println!("{:?}", data_chunks);
}
Управление памятью при обработке данных
В Python управление памятью абстрагировано, что делает его более простым для разработчиков и специалистов по анализу данных, поскольку они могут сосредоточиться на алгоритмах, а не на тонкостях использования памяти. Однако это может привести к проблемам при работе с большими объемами информации: Python использует автоматическую сборку мусора, которая может быть неэффективной для объемных наборов данных.
В Rust есть возможность управлять памятью на более низком уровне, обеспечивая эффективное использование ресурсов. Это позволяет точно контролировать использование памяти и может быть очень полезно при работе с большими наборами данных. В приведенном ниже примере происходит следующее:
- large_array создается внутри функции main и становится владельцем Vec. Это означает, что large_array отвечает за освобождение памяти, выделенной для Vec, когда он выходит из области видимости.
- sum создается как ссылка на результат вызова large_array.iter().sum(). Таким образом, sum не владеет данными, а просто ссылается на них.
- В конце функции main, когда large_array выходит из области видимости, память, выделенная для Vec, автоматически освобождается. Это гарантируется системой управления памятью Rust, которая автоматически освобождает память, когда владелец выходит из области видимости.
fn main() {
let mut large_array: Vec<f64> = vec![1.0; 1_000_000];
let sum: f64 = large_array.iter().sum();
println!("Сумма элементов равна {}", sum);
}
Параллелизм и многопоточность
В Python многопоточность и параллелизм можно реализовать помощью модулей threading и multiprocessing, но они имеют свои особенности и ограничения. В частности, из-за Global Interpreter Lock (GIL) в Python, многопоточность не всегда может привести к увеличению производительности при выполнении задач, которые интенсивно нагружают процессор. В то же время модуль multiprocessing позволяет обойти GIL, создавая отдельные процессы, каждый из которых имеет свой собственный интерпретатор Python и свою собственную копию памяти. Приведенный ниже пример демонстрирует многопроцессорность, которая является формой параллелизма:
import multiprocessing
import time
def heavy(data, i, proc):
for index in range(len(data)):
data[index] += 1
print(f"Обработка № {i} ядро {proc}")
def sequential(calc, proc, data):
print(f"Запускаем поток № {proc}")
for i in range(calc):
heavy(data, i, proc)
print(f"{calc} обработок данных завершено. Процессор № {proc}")
def processesed(procs, calc):
# procs - количество ядер
# calc - количество операций на ядро
processes = []
manager = multiprocessing.Manager()
data = manager.list([num for num in range(1, 10)])
# делим вычисления на количество ядер
for proc in range(procs):
p = multiprocessing.Process(target=sequential, args=(calc, proc, data))
processes.append(p)
p.start()
# ждем, пока все ядра завершат свою работу
for p in processes:
p.join()
return data
start = time.time()
# узнаем количество ядер у процессора
n_proc = multiprocessing.cpu_count()
# вычисляем, сколько циклов вычислений будет приходиться
# на 1 ядро, чтобы в сумме получилось 80+
calc = 80 // n_proc + 1
data = processesed(n_proc, calc)
end = time.time()
print(f"Количество ядер в процессоре: {n_proc}\n"
f"На каждом ядре произведено обработок данных: {calc}\n"
f"Итого {n_proc * calc} обработок за: {end - start}\n"
f"Обработанные данные: {data}")
В Rust есть библиотека Rayon, которая позволяет легко преобразовать последовательные вычисления в параллельные. Rayon гарантирует отсутствие условий гонки данных, что идеально обеспечивает параллельность вычислений:
use rayon::prelude::*;
use num_cpus;
fn main() {
let mut data: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
// Определяем количество ядер процессора
let num_cpus = num_cpus::get();
// Вычисляем количество циклов вычислений для каждого ядра
let num_cycles = 80 / num_cpus + 1;
// Выполняем циклы вычислений
for _ in 0..num_cycles {
data.par_iter_mut().for_each(|x| {
*x += 1;
});
}
println!("{:?}", data);
}
Визуализация данных
Python располагает несколькими библиотеками для визуализации данных: Мatplotlib, Seaborn, Bokeh, Plotly, Altair и т.д. Самые популярные из них – Мatplotlib и Seaborn. Эти библиотеки позволяют легко создавать графики и диаграммы, что делает визуализацию данных в Python максимально простой и удобной:
import matplotlib.pyplot as plt
# Данные
data = [1, 2, 3, 4, 5]
# Названия месяцев
months = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май']
# Создаем график
fig, ax = plt.subplots()
# Выводим данные
ax.plot(months, data)
# Устанавливаем названия осей
ax.set_xlabel('Месяц')
ax.set_ylabel('Данные')
plt.show()
Результат:

В Rust для визуализации можно использовать библиотеку plotters:
use plotters::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let data = vec![1, 2, 3, 4, 5];
let months = vec!["Январь", "Февраль", "Март", "Апрель", "Май"];
let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption("Месячная статистика", ("Arial", 50).into_font())
.margin(5)
.x_label_area_size(30)
.y_label_area_size(30)
.build_cartesian_2d(months.iter().cloned().map(|m| m.to_string()).collect::<Vec<String>>(), 0..10)?;
chart.configure_mesh().draw()?;
chart.draw_series(LineSeries::new(
data.iter().enumerate().map(|(i, &v)| (months[i].to_string(), v)),
&BLUE,
))?;
Ok(())
}
Интеграция с другими языками программирования
Python может бесшовно интегрироваться с библиотеками C и C++ с помощью Cython. Это дает возможность использовать функции и данные из этих библиотек в Python-коде.
Если у вас есть библиотека my_lib на C:
// my_lib.c
#include <stdlib.h>
int add(int a, int b) {
return a + b;
}
Ее можно скомпилировать:
gcc -shared -o my_lib.so my_lib.c
И вызвать в Python:
from ctypes import CDLL
# Загрузка библиотеки
my_lib = CDLL('./my_lib.so')
# Вызов функции add
result = my_lib.add(1, 2)
print(result) # Выводит: 3
Rust предлагает возможности для интеграции с библиотеками C через Foreign Function Interface (FFI). FFI позволяет вызывать функции, определенные в другом языке программирования, из кода на Rust. Это достигается путем определения внешней функции в Rust с сигнатурой функции, совместимой с C, и затем динамического связывания с общей библиотекой, содержащей реализацию функции:
// Пример использования FFI для вызова функции C из Rust
extern "C" {
fn my_c_function(arg1: i32, arg2: f64) -> f64;
}
fn main() {
let result = unsafe { my_c_function(42, 3.14) };
println!("Result: {}", result);
}
Стоит заметить, в Python-проектах можно использовать модули, написанные на Rust, и наоборот, в Rust можно вызывать Python. Проще всего это сделать с помощью фреймворка PyO3, который позволяет создавать нативные модули Python на Rust. PyO3 обеспечивает простоту и удобство в создании привязок и интеграции кода Rust и Python. Вот простейший пример создания Python-модуля на Rust:
// Rust код, использующий PyO3 для создания модуля Python
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
#[pyfunction]
fn process(data: Vec<i32>) -> Vec<i32> {
data.iter().map(|x| x * 2).collect()
}
#[pymodule]
fn rust_module(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(process, m)?)?;
Ok(())
}
Скомпилированный модуль в коде Python можно использовать так:
import rust_module
data = [1, 2, 3, 4, 5]
result = rust_module.process(data)
print(result) # Выводит: [2, 4, 6, 8, 10]
Подведем итоги
Выбор между Python и Rust для анализа данных и машинного обучения – сложная дилемма, поскольку оба языка предлагают уникальные преимущества:
- Python обладает максимально простым, понятным и гибким синтаксисом, располагает обширной экосистемой библиотек и фреймворков для машинного обучения и работы с данными. Это делает его отличным выбором для разработчиков, которые только начинают изучать анализ данных и машинное обучение.
- С другой стороны, Rust обеспечивает максимальную производительность, безопасность, эффективную поддержку многопоточности и параллелизма. Rust отлично подходит для продвинутых разработчиков, создающих ПО для работы с большими наборами данных и выполнения сложных вычислительных задач.
В конечном итоге выбор между Python и Rust зависит от ваших конкретных потребностей и уровня опыта. Если вы только начинаете изучать анализ данных и машинное обучение, то лучше выбрать Python. Если у вас уже есть опыт работы с другими языками программирования, а ваш проект нуждается в более высокой производительности и безопасности, то Rust будет самым подходящим вариантом.
Комментарии