0Pricing
R Academy · レッスン

高速化のためのベクトル化

明示的なループをベクトル化演算に置き換え、大幅な高速化を実現します。

「高速化のためのベクトル化」はCoddyKit上の無料R Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはR Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 R Academyコースには全4レッスンが含まれています。

ベクトル化が重要な理由

Rはインタープリタ型言語であるため、forループでは反復ごとにオーバーヘッドが発生します。これには関数呼び出しのディスパッチ、境界チェック、型変換などが含まれます。ベクトル化された処理では、これらの処理をコンパイル済みのCコードに移すため、何桁も高速に実行できます。

ベクトル化は、base Rで利用できる最も効果の大きい最適化です。

ループとcumsum()の例

forループと組み込みのcumsum()で累積合計を計算すると、その差がはっきりわかります。cumsum()はCレベルのコンパイル済みコードを呼び出し、ベクトル全体を1回の走査で処理します。

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レベルの1回の処理で置き換えます。

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ルーチンです。行ごとにR関数sumを1回ずつディスパッチするapply(m, 1, 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() — はすべてベクトル化されています。これらは1回の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コードが1回の走査で処理します。

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)で事前に領域を確保する
  • apply()よりもcolSums/rowSums/colMeans/rowMeansを使用する
  • 論理的なサブセット化はフィルタリングのループより効率的である

クイックチェック:事前割り当て

大きなnに対して、ループ内でresult <- c(result, new_val)のようにベクトルを拡張すると、なぜ非常に遅くなるのでしょうか。

ベクトル化のおさらい

ベクトル化は、Rでパフォーマンスを向上させる主要な手段です。

  • ベクトル化された関数(cumsum、ifelse、算術演算子)はコンパイル済みのCコードを呼び出すため、同等のRループより10~100倍高速です
  • 避けられないループの前に結果を格納するコンテナを事前に確保し、O(n^2)のコピーを避けます
  • 行列の集計では、colSums/rowSumsはapply()より高速です
  • 論理的なサブセット化を使うと、フィルタリングのループを簡潔かつ高速に置き換えられます

よくある質問

「高速化のためのベクトル化」レッスンは無料ですか?

はい。「高速化のためのベクトル化」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、R Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 R Academyコースには全4レッスンが含まれています。

「高速化のためのベクトル化」で何を学びますか?

明示的なループをベクトル化演算に置き換え、大幅な高速化を実現します。 ブラウザで直接実行するハンズオンコードでR Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

R Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのR Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「高速化のためのベクトル化」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このR Academyレッスンでコードを書いて実行できますか?

はい。すべてのR Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. system.time() と proc.time()
  2. Rprof と profvis でコードをプロファイリングする
  3. 高速化のためのベクトル化
  4. microbenchmark でベンチマークを行う
← R Academyに戻る