この返信や他の返信に取り組んだ後、ここでの最適化戦略 (およびおおよそのスピードアップ) は次のようになります。
- (30x) 適切なデータ表現 (data.frame ではなくマトリックス) を選択します。
- (1.5x) 不要なデータ コピーを削減 -- 行平均ではなく列の違い
- 関数としてのループの構造
*apply
(コード構造を強調し、メモリ管理を簡素化し、型の一貫性を提供するため)
- (2x) ループ外でベクトル演算をホイスト -- 列の abs と sum は、行列の abs と colSum になります。
全体で約 100 倍の高速化。このサイズとコードの複雑さでは、コンパイラまたは並列パッケージの使用は効果的ではありません。
あなたのコードを関数に入れます
f0 <- function(x) {
y <- rowMeans(x)
totaldiff <- numeric()
for (i in 1:ncol(x)){
x.after <- x
x.after[,i] <- sample(x[,i])
diff <- abs(y-rowMeans(x.after))
totaldiff[i] <- sum(diff)
}
which.max(totaldiff)
}
ここにある
x <- data.frame(matrix(runif(50*100),nrow=50,ncol=100)) #larger example
set.seed(123)
system.time(res0 <- f0(x))
## user system elapsed
## 1.065 0.000 1.066
データは行列として表すことができ、R 行列での操作は data.frames での操作よりも高速です。
m <- matrix(runif(50*100),nrow=50,ncol=100)
set.seed(123)
system.time(res0.m <- f0(m))
## user system elapsed
## 0.036 0.000 0.037
identical(res0, res0.m)
##[1] TRUE
これがおそらく最大の高速化です。ただし、ここでの特定の操作では、更新された行列の行平均を計算する必要はありません。1 つの列をシャッフルすることによる平均の変化だけです。
f1 <- function(x) {
y <- rowMeans(x)
totaldiff <- numeric()
for (i in 1:ncol(x)){
diff <- abs(sample(x[,i]) - x[,i]) / ncol(x)
totaldiff[i] <- sum(diff)
}
which.max(totaldiff)
}
for
ループは、結果ベクトルを埋めるための正しいパターンに従いません( totaldiff
「事前に割り当てて埋める」必要があるためtotaldiff <- numeric(ncol(x))
) が、 を使用しsapply
て R にそれを心配させることができます (このメモリ管理は、R の利点の 1 つです)。関数の apply ファミリを使用)
f2 <- function(x) {
totaldiff <- sapply(seq_len(ncol(x)), function(i, x) {
sum(abs(sample(x[,i]) - x[,i]) / ncol(x))
}, x)
which.max(totaldiff)
}
set.seed(123); identical(res0, f1(m))
set.seed(123); identical(res0, f2(m))
タイミングは
> library(microbenchmark)
> microbenchmark(f0(m), f1(m), f2(m))
Unit: milliseconds
expr min lq median uq max neval
f0(m) 32.45073 33.07804 33.16851 33.26364 33.81924 100
f1(m) 22.20913 23.87784 23.96915 24.06216 24.66042 100
f2(m) 21.02474 22.60745 22.70042 22.80080 23.19030 100
@flodelは、より高速になる可能性があることを指摘していvapply
ます(およびタイプセーフを提供します)
f3 <- function(x) {
totaldiff <- vapply(seq_len(ncol(x)), function(i, x) {
sum(abs(sample(x[,i]) - x[,i]) / ncol(x))
}, numeric(1), x)
which.max(totaldiff)
}
そしてそれ
f4 <- function(x)
which.max(colSums(abs((apply(x, 2, sample) - x))))
はまだ高速です (ncol(x)
は一定の要素であるため、削除されます)。abs
とsum
は の外側に持ち上げられsapply
ます。メモリの使用量が増える可能性があります。関数をコンパイルするためのコメントのアドバイスは、一般的には良いものです。ここにいくつかのさらなるタイミングがあります
> microbenchmark(f0(m), f1(m), f1.c(m), f2(m), f2.c(m), f3(m), f4(m))
Unit: milliseconds
expr min lq median uq max neval
f0(m) 32.35600 32.88326 33.12274 33.25946 34.49003 100
f1(m) 22.21964 23.41500 23.96087 24.06587 24.49663 100
f1.c(m) 20.69856 21.20862 22.20771 22.32653 213.26667 100
f2(m) 20.76128 21.52786 22.66352 22.79101 69.49891 100
f2.c(m) 21.16423 21.57205 22.94157 23.06497 23.35764 100
f3(m) 20.17755 21.41369 21.99292 22.10814 22.36987 100
f4(m) 10.10816 10.47535 10.56790 10.61938 10.83338 100
「.c」はコンパイルされたバージョンで、
コンパイルは、for ループで記述されたコードでは特に役立ちますが、ベクトル化されたコードではあまり効果がありません。これはここに示されています。f1 の for ループをコンパイルすると、小さいながらも一貫して改善されますが、f2 の sapply では改善されません。