2023年4月9日日曜日

Rの常用速度を新旧macで比較:Apple Silicon M1 Max vs. Intel Core i9

今更ながら、macでのRの計算速度をIntel版とApple Siliconとで比較してみました。先行するテスト結果は検索するといくつか見つかるのですが、自分にとって普段よく使いそうな実用的用途で試してみたいと思いました。(2023.04.25追記:Intelも第12世代でCPU速度は飛躍的に向上して、以下の比較機種とは状況は異なっている模様)

比較をした計算環境は次の通りです。Rは登稿時の最新 ver. 4.2.3に揃えました。ハードウェアはintelとapple siliconとで実質1年半の差なので、ほぼCPUの差で比較できているかと思います。

(1)macbook pro 14-2021(以下、MBP_M1mと略します)
Apple Silicon M1 Max 10コアCPU、32コアGPU 64GBメモリ


(2)macbook pro 16-inch 2019(以下、MBP_Intelと略します)
Intel Core i9 2.4GHz 8コアCPU 64GBメモリ

(3)新旧macだけでもよかったのですが、Ubuntuを載せたカスタムPCも比較に加えました。
カスタムPC 2019年製(OS: Ubuntu 22.04.1 LTS)(以下、Intel_Ubuntと略します)
Intel Core i9-9980XE Extreme Edition 3.0-4.4GHz 18コアCPU 128GBメモリ

計算は次の4つのケースで実行し比較しました。概ねシングルコアの速度を試す形になっていますが、マルチコアを活用できるケースならば、単にコア数の多いマシンを使えばいいんじゃないかと思うので、制約の掛かりやすそうな事例をわざと選んでいます。


### Case 1: Markov chain
いわゆるMCMC等の計算過程で動いているマルコフ連鎖、値の更新が必要なので一括処理できない計算の例の1つかと。

x <- matrix(0, 10^6, 10)
# 10^6回分のマルコフ連鎖、10本の鎖を同時に計算
system.time( for(r in 2:(nrow(x))) x[r,] <- 0.9*x[r-1,] + rnorm(10) )



### Case 2 raster_distance: raster画像の値あり全地点からの距離計算
都市や島からの距離の計算と思ったら良いです。
require(raster)
require(maptools)
data(wrld_simpl)
rgrid <- raster(ex=extent(c(-180,180,-90,90)), res=0.5)
rwrld <- raster::rasterize(wrld_simpl, rgrid)
system.time(rdst <- raster::distance(rwrld))
0.5度解像度の全世界の陸地からの距離を計算、かなり時間のかかるケース。
(ちなみにwrld_simplは気軽に呼び出しやすい粗い世界地図なのですが、{maptools}が2023年いっぱいで提供終了とのこと。高機能な{rnaturalearth}もよいですが...)



### Case 3 raster_linear_model: raster stackへの回帰モデル適用
重ねたrasterの全値について回帰モデルを適用、calcのhelpにあるコードを高解像度にしただけのもの。単純な回帰モデル(lm)に限定(逆行列を利用した方法も紹介されていますが、応用しづらいので省きます)。
help(calc)
r <- raster(nrow=100, ncol=100) # ここを10倍にした
s1 <- lapply(1:12, function(i) setValues(r, rnorm(ncell(r), i, 3)))
s2 <- lapply(1:12, function(i) setValues(r, rnorm(ncell(r), i, 3)))
s1 <- stack(s1)
s2 <- stack(s2)

# regression of values in one brick (or stack) with another
s <- stack(s1, s2)
# s1 and s2 have 12 layers; coefficients[2] is the slope
fun <- function(x) { lm(x[1:12] ~ x[13:24])$coefficients[2] }
system.time(x1 <- calc(s, fun))



### Case 4: Stan (cmdstan)
コマンドライン版のStanで単純な状態空間モデルの計算、chain毎に異なるスレッドを割り当てているので、他の例と異なり、少しだけマルチコアの恩恵があるはず。コードは長いので、後に載せておきます。



こちらが結果になります。左に行くほど高速です。



概ねMBP_M1mはMBP_intelの2倍弱ほどの速度があることが分かりました。計算時間の掛かるraster_distanceで2.33倍と最も差が大きいのは嬉しい結果です。マルチコアの恩恵があるStanでは1.5倍とさほどの差になっていないのも予想通りではあります。またOSの違いはあまり速度に関係ないようでした。

なお、マルコフ連鎖については、2012年のマシンで5.457秒という手元の記録が残っているので(Late 2012 iMac Intel Core i7 3.4GHz クアッドコア 32GB)、倍速いというのがどれほどの進化かよく分かるかと思います。

ちなみに、関連してCase 2, 3ではrasterパッケージの後継?terraパッケージでも比較をしてみたのですが、ほぼ変わりはなかったです。もしかすると連繋が進んでいたりして内部でterraを利用しているかもしれませんが、あくまで想像です。


もうM2が登場していますが、今回のようなRのシングルコア計算での比較ならば、おそらく速度は10%向上程度かなと見ています。

その他、Apple SiliconはGPUコアや機械学習用のニューラルエンジンを積んでいるので、用途次第では次元の違う速度になる期待もあります。高速なGPUを使用可能なRパッケージってまだあまり聞かないようには思いますが。

あとはSSDがかなり高速になっているそうで、物理メモリが不足した時にSSD領域を仮想メモリとして使用しても、かつてほどの速度低下はしないみたいで、もうさほどメモリに拘らなくてもよいのかもしれません。



# Case 4のcmdstanの計算コード:

require(cmdstanr)
stan_program <- "
data {
    int T;
    vector[T] Y;
}
parameters {
    real mu_Y0;
    vector[T] mu_Y;
    real<lower=0> sigma;
    real<lower=0> SIGMA;
}
model {
    // state model
    mu_Y[1] ~ normal(mu_Y0, sigma);
    for(t in 2:T) {
        mu_Y[t] ~ normal(mu_Y[t-1], sigma);
    }

    // observation model
    for(t in 1:T) {
        Y[t] ~ normal(mu_Y[t], SIGMA);
    }
}"

modfile <- write_stan_file(stan_program)

mod <- cmdstan_model(modfile) # 一時ファイルへ書き出し

data(Nile)
data <- list(Y=Nile, T=length(Nile))

system.time(fit <- mod$sample(
  data = data,
  chains = 4,
  parallel_chains = 4, # 並列化するchainの数
  iter_warmup = 5000, #burn-in数
  iter_sampling = 20000 #butn-in後のsampling数
  )
)