0Pricing
R Academy · Урок

Векторизация для ускорения

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

«Векторизация для ускорения» — бесплатный урок R Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения R Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс R Academy содержит 4 уроков всего.

Почему важна векторизация

R — интерпретируемый язык, поэтому циклы for создают накладные расходы на каждой итерации: диспетчеризацию вызовов функций, проверку границ и приведение типов. Векторизованные операции передают эту работу скомпилированному коду C, который выполняется на порядки быстрее.

Векторизация — самая эффективная оптимизация, доступная в базовом R.

Пример: цикл и cumsum()

Сравнение вычисления накопительной суммы с помощью цикла for и встроенной функции cumsum() наглядно показывает разницу. cumsum() вызывает скомпилированный код уровня C и обрабатывает весь вектор за один проход.

n <- 500000
x <- rnorm(n)

t_loop <- system.time({
  result <- numeric(n)
  result[1] <- x[1]
  for (i in 2:n) result[i] <- result[i-1] + x[i]
})['elapsed']

t_vec <- system.time({
  result2 <- cumsum(x)
})['elapsed']

cat('Loop  :', t_loop, 's
')
cat('cumsum:', t_vec, 's
')

ifelse() и for + if

ifelse(condition, yes, no) — это векторизованное условие, которое сразу проверяет условие для всего вектора. Оно заменяет циклы for + if с обработкой элементов по одному одним проходом на уровне C.

n <- 300000
x <- rnorm(n)

t_loop <- system.time({
  result <- numeric(n)
  for (i in seq_len(n)) result[i] <- if (x[i] > 0) x[i] else -x[i]
})['elapsed']

t_vec <- system.time({
  result2 <- ifelse(x > 0, x, -x)
})['elapsed']

cat('for+if :', t_loop, 's
')
cat('ifelse :', t_vec, 's
')

Предварительное выделение векторов результата

Если без цикла не обойтись, заранее выделите память под вектор результата до начала цикла. Увеличение вектора с помощью c(result, new_val) внутри цикла копирует весь вектор на каждой итерации — всего выполняется O(n^2) операций с памятью.

n <- 20000

t_grow <- system.time({
  result <- c()
  for (i in seq_len(n)) result <- c(result, i^2)
})['elapsed']

t_prealloc <- system.time({
  result2 <- numeric(n)
  for (i in seq_len(n)) result2[i] <- i^2
})['elapsed']

cat('Growing vector:', t_grow, 's
')
cat('Pre-allocated :', t_prealloc, 's
')

Правильные типы при предварительном выделении

Используйте конструктор нужного типа, соответствующего вашим данным, чтобы избежать неявного приведения типов при предварительном выделении:

  • numeric(n) — числа с плавающей точкой двойной точности
  • integer(n) — целые числа
  • character(n) — пустые строки
  • logical(n) — значения FALSE
  • vector('list', n) — список значений NULL
n <- 5
cat('numeric  :', numeric(n), '
')
cat('integer  :', integer(n), '
')
cat('logical  :', logical(n), '
')
cat('character:', character(n), '
')
cat('list len :', length(vector('list', n)), '
')

colSums() и rowSums() по сравнению с apply()

Для операций с матрицами colSums(m), rowSums(m), colMeans(m) и rowMeans(m) используются тщательно оптимизированные процедуры C. Они стабильно работают быстрее, чем apply(m, 1, sum), где функция R sum вызывается отдельно для каждой строки.

m <- matrix(rnorm(1000 * 2000), nrow = 1000)

t_apply <- system.time(apply(m, 2, sum))['elapsed']
t_colsums <- system.time(colSums(m))['elapsed']

cat('apply(m,2,sum):', t_apply, 's
')
cat('colSums(m)    :', t_colsums, 's
')

Векторизованная арифметика всегда быстрая

Базовые арифметические операции над векторами — +, -, *, /, ^, sqrt(), log(), exp() — все векторизованы. Они выполняются поэлементно над всем вектором за один вызов C. Всегда отдавайте им предпочтение перед циклами.

x <- 1:1000000

t1 <- system.time(y <- x^2 + 2*x + 1)['elapsed']

t2 <- system.time({
  y2 <- numeric(length(x))
  for (i in seq_along(x)) y2[i] <- x[i]^2 + 2*x[i] + 1
})['elapsed']

cat('Vectorized:', t1, 's
')
cat('Loop      :', t2, 's
')

Логическая индексация вместо циклов

Фильтрация вектора с помощью логического условия выполняется в векторизованном виде. Вместо цикла с добавлением элементов по условию создайте логический индекс и выполните индексацию один раз — базовый код на C за один проход обработает все элементы.

x <- rnorm(500000)

t_loop <- system.time({
  pos <- c()
  for (v in x) if (v > 0) pos <- c(pos, v)
})['elapsed']

t_vec <- system.time({
  pos2 <- x[x > 0]
})['elapsed']

cat('Loop filter:', t_loop, 's
')
cat('Logical idx:', t_vec, 's
')

which() и tabulate() для работы с индексами

Если Вам нужны позиции элементов со значением TRUE, функция which(condition) выполняется в векторизованном виде и работает быстро. tabulate(bin_vector) считает количество целочисленных значений быстрее, чем table(), если диапазон целых чисел плотный.

x <- sample(1:10, 100000, replace = TRUE)

t_table    <- system.time(table(x))['elapsed']
t_tabulate <- system.time(tabulate(x, nbins = 10))['elapsed']

cat('table()   :', t_table, 's
')
cat('tabulate():', t_tabulate, 's
')

idx <- which(x == 5)
cat('Positions of 5: first 5 =', head(idx, 5), '
')

Когда циклы всё ещё допустимы

Не каждый цикл плох. Циклы допустимы, когда:

  • Каждая итерация зависит от результата предыдущей (последовательная зависимость)
  • Число итераций невелико (< 1000)
  • Тело цикла вызывает сложную функцию, для которой нет векторизованного аналога

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

# Sequential dependency -- loop is correct here
fib <- function(n) {
  result <- integer(n)
  result[1] <- 1L
  if (n >= 2) result[2] <- 1L
  for (i in seq_len(n)[-c(1,2)]) result[i] <- result[i-1] + result[i-2]
  result
}
cat('Fibonacci:', fib(10), '
')

Итоги векторизации

Основные правила векторизации для быстрого кода на R:

  • Используйте cumsum/cumprod/diff для последовательного накопления
  • Используйте ifelse() для поэлементных условий
  • Заранее выделяйте память с помощью numeric(n) / vector('list',n)
  • Используйте colSums/rowSums/colMeans/rowMeans вместо apply()
  • Логическая индексация эффективнее циклов фильтрации

Быстрая проверка: предварительное выделение памяти

Почему увеличение вектора с помощью result <- c(result, new_val) внутри цикла настолько медленно при большом значении n?

Повторение: векторизация

Векторизация — основной способ повысить производительность в R:

  • Векторизованные функции (cumsum, ifelse, арифметические операторы) вызывают скомпилированный код на C и работают в 10–100 раз быстрее эквивалентных циклов на R
  • Заранее выделяйте контейнеры для результатов перед неизбежным циклом, чтобы избежать копирования за O(n^2)
  • colSums/rowSums эффективнее apply() при агрегировании матриц
  • Логическая индексация аккуратно и быстро заменяет циклы фильтрации

Часто задаваемые вопросы

Урок «Векторизация для ускорения» бесплатный?

Да — полный текст урока «Векторизация для ускорения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс R Academy, подпишись на CoddyKit PRO. Курс R Academy содержит 4 уроков всего.

Чему я научусь в уроке «Векторизация для ускорения»?

Заменяйте явные циклы векторизованными операциями для значительного ускорения Ты практикуешь R Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать R Academy?

Предыдущий опыт не требуется. R Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Векторизация для ускорения»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке R Academy?

Да. Каждый урок R Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. system.time() и proc.time()
  2. Профилирование кода с помощью Rprof и profvis
  3. Векторизация для ускорения
  4. Бенчмаркинг с помощью microbenchmark
← Назад к R Academy