LoginSignup
1
1

More than 5 years have passed since last update.

[翻訳]R6 vignette: R6と参照クラスの性能テスト

Last updated at Posted at 2017-01-15

この文書は,Winston Chang によるRパッケージ R6 (version 2.2.0) のビネット "R6 and Reference class performance tests" の日本語訳です.

License: MIT

関連文書


この文書では,Rの参照クラスのメモリ負荷と速度を,R6クラスと単純な環境に対して比較します.大抵の用途においてR6と参照クラスは同等の機能を持っていますが,これから見るようにR6クラスの方が高速で軽量です.

この文書では,参照クラスをR6クラスに対して(様々なバリエーションで)テストします.単純な参照オブジェクト(関数呼び出しで作成した環境)に対してもテストします.


まずは以下で使用するパッケージをロードしましょう.

library(microbenchmark)
options(microbenchmark.unit = "us")
library(pryr)  # object_size 関数用
library(R6)

クラス定義

参照クラスやR6,それから単純な環境を用いて,多数のクラスやクラスに類似したものを定義することから始めます.これらは関数によって直接作成できます.R6にはオブジェクトのサイズに影響する多くのオプションがあるので,様々なバリエーションを使うことにします.これらのクラスは以下で速度とメモリのテストに使用されます.退屈なコードばかりなのでテスト結果まで飛ばしてもかまいません.

これらのクラスはすべて共通の特性を備えています.

  • 数値を含むxという名前のフィールド
  • xの値の初期化方法
  • xの値を取得するためのgetxという名前のメソッド
  • xの値をインクリメントするためのincという名前のメソッド

これらのフィールドやメソッドには$演算子でアクセスします.仮にobjという名前のオブジェクトがあれば,obj$xobj$getx()が使えるでしょう.

Rの参照クラス

RC <- setRefClass("RC",
  fields = list(x = "numeric"),
  methods = list(
    initialize = function(x = 1) .self$x <- x,
    getx = function() x,
    inc = function(n = 1) x <<- x + n
  )
)

参照クラスでは,オブジェクト自身を指し返すように束縛されている名前を.selfといいます.メソッド内での代入は.selfを用いて.self$x <- 10のように行えます.あるいは<<-を用いてx <<- 10のようにもできます.

オブジェクトを作成するには,単にクラスの$new()を呼び出します.

RC$new()
#> Reference class object of class "RC"
#> Field "x":
#> [1] 1

R6クラス

R6クラスの作成方法は参照クラスと同様です.ただしフィールドとメソッドを分ける必要がなく,またフィールドの型は指定できません

R6 <- R6Class("R6",
  public = list(
    x = NULL,
    initialize = function(x = 1) self$x <- x,
    getx = function() self$x,
    inc = function(n = 1) self$x <- x + n
  )
)

参照クラスでは.selfを使いますが,R6クラスでは(先頭のピリオドのない)selfを使います.参照クラスと同様,オブジェクトは$new()を呼んでインスタンス化します.

R6$new()
#> <R6>
#>   Public:
#>     clone: function (deep = FALSE) 
#>     getx: function () 
#>     inc: function (n = 1) 
#>     initialize: function (x = 1) 
#>     x: 1

本質的には,R6オブジェクトは環境の集まりをあるやり方で構造化したものにすぎません.R6オブジェクトのフィールドとメソッドはパブリック環境に束縛されています(すなわち,パブリック環境に名前を持ちます).これとは別にメソッドのエンクロージング環境があります.(メソッドはselfという名前が束縛された環境で実行されます.selfはパブリック環境への単なる参照です.)

R6クラス(class属性なし)

デフォルトでは,R6オブジェクトにはclass属性が付加されます.この属性の付加によって,わずかに性能が損なわれます.なぜかというと,オブジェクトに対して$が使用されると,RはS3のディスパッチを試みるからです,

class=FALSEとすればclass属性なしのオブジェクトを生成することができます.

R6NoClass <- R6Class("R6NoClass",
  class = FALSE,
  public = list(
    x = NULL,
    initialize = function(x = 1) self$x <- x,
    getx = function() self$x,
    inc = function(n = 1) self$x <- self$x + n
  )
)

class属性がないと,オブジェクトに対してS3メソッドのディスパッチができないことに注意してください.

R6クラス(移植不可)

デフォルトでは,R6オブジェクトは移植可能です.つまり異なるパッケージにあるクラス間で継承ができます.しかし,このためにメンバにアクセスするのにself$private$の使用も必要となり,性能では少々損失を被ることになります.

portable=FLASEとすれば,メンバにはself$なしでアクセスでき,代入は<<-で行えます.

R6NonPortable <- R6Class("R6NonPortable",
  portable = FALSE,
  public = list(
    x = NULL,
    initialize = function(value = 1) x <<- value,
    getx = function() x,
    inc = function(n = 1) x <<- x + n
  )
)

R6クラス(cloneable=FALSE

デフォルトではR6オブジェクトはclone()メソッドを持ちますが,これはかなり大きな関数です.この機能が不要な場合にはcloneable=FALSEとすればメモリを節約できます.

R6NonCloneable <- R6Class("R6NonCloneable",
  cloneable = FALSE,
  public = list(
    x = NULL,
    initialize = function(x = 1) self$x <- x,
    getx = function() self$x,
    inc = function(n = 1) self$x <- self$x + n
  )
)

R6クラス(class属性なし,移植不可,クローン不可)

比較のために,class属性を持たず,移植不可で,クローン不可能なR6クラスを使います.これは余計なものを可能な限り取り除いたR6オブジェクトです.

R6Bare <- R6Class("R6Bare",
  portable = FALSE,
  class = FALSE,
  cloneable = FALSE,
  public = list(
    x = NULL,
    initialize = function(value = 1) x <<- value,
    getx = function() x,
    inc = function(n = 1) x <<- x + n
  )
)

R6クラス(パブリックメンバとプライベートメンバあり)

これはパブリックメンバとプライベートメンバを持つバージョンです.

R6Private <- R6Class("R6Private",
  private = list(x = NULL),
  public = list(
    initialize = function(x = 1) private$x <- x,
    getx = function() private$x,
    inc = function(n = 1) private$x <- private$x + n
  )
)

このR6オブジェクトは,selfだけでオブジェクト内の全ての項目にアクセスすることはできず,(パブリックな項目を指す)selfprivateを持ちます.

R6Private$new()
#> <R6Private>
#>   Public:
#>     clone: function (deep = FALSE) 
#>     getx: function () 
#>     inc: function (n = 1) 
#>     initialize: function (x = 1) 
#>   Private:
#>     x: 1

R6クラス(パブリックメンバとプライベートメンバあり,class属性なし,移植不可,クローン不可)

比較のため,class属性を持たず,移植不可で,クローン不可能なバージョンを加えます.

R6PrivateBare <- R6Class("R6PrivateBare",
  portable = FALSE,
  class = FALSE,
  cloneable = FALSE,
  private = list(x = NULL),
  public = list(
    initialize = function(x = 1) private$x <- x,
    getx = function() x,
    inc = function(n = 1) x <<- x + n
  )
)

環境(関数呼び出しで作成,class属性あり)

Rでは,環境は参照渡しされます.参照渡しのオブジェクトを作るには,関数実行によって作成される環境を使うのが簡単です.

FunctionEnvClass <- function(x = 1) {
  inc <- function(n = 1) x <<- x + n
  getx <- function() x
  self <- environment()
  class(self) <- "FunctionEnvClass"
  self
}

xは関数の本体では宣言されていませんが,関数の引数になっているので,環境に捕捉されます.

ls(FunctionEnvClass())
#> [1] "getx" "inc"  "self" "x"

こうして作成したオブジェクトは,上で作成したR6のジェネレータによく似ています.

環境(関数呼び出しで作成,class属性なし)

class属性をなくして,selfオブジェクトもなくすことで,前述のものよりさらに単純な種類の参照オブジェクトを作ることができます.

FunctionEnvNoClass <- function(x = 1) {
  inc <- function(n = 1) x <<- x + n
  getx <- function() x
  environment()
}

これはいくつかのオブジェクトを含む単なる環境です.

ls(FunctionEnvNoClass())
#> [1] "getx" "inc"  "x"

テスト

microbenchmark()による時間の計測結果はすべてマイクロ秒で報告されます.結果の中で最も役立つのはたぶん中央値でしょう.

メモリ負荷

各オブジェクトのひとつのインスタンスはどれだけメモリを使用するのでしょうか.また,オブジェクトを追加するとどれだけメモリを使用するのでしょうか.オブジェクトのサイズを計算するにはobj_size()obj_sizes()という関数を使います(関数定義はこの文書の最下部にあります).

それぞれの種類のオブジェクトのサイズ(バイト単位)は以下の通りです1

sizes <- obj_sizes(
  RC$new(),
  R6$new(),
  R6NoClass$new(),
  R6NonPortable$new(),
  R6NonCloneable$new(),
  R6Bare$new(),
  R6Private$new(),
  R6PrivateBare$new(),
  FunctionEnvClass(),
  FunctionEnvNoClass()
)
sizes
#>                         one incremental
#> RC$new()             461168        1368
#> R6$new()              56608        1008
#> R6NoClass$new()       57312         896
#> R6NonPortable$new()   56200         952
#> R6NonCloneable$new()  13608         896
#> R6Bare$new()          12800         728
#> R6Private$new()       57512        1120
#> R6PrivateBare$new()   13808         840
#> FunctionEnvClass()    10696         624
#> FunctionEnvNoClass()   9272         512

結果を以下にプロットしました.プロットのx軸のスケールが大きく異なっていることに注意してください.

unnamed-chunk-21-1.png unnamed-chunk-21-2.png

色々なクラスのひとつめのインスタンスについて,まず以下のことが分かります.参照クラスは大量のメモリを消費すること.R6オブジェクトに対して最も影響のあるオプションはcloneableであること.clone()メソッドなしだとメモリが約40kB節約されています.

各クラスの追加されたインスタンスについては,異なる種類のクラスの間で,ひとつめのインスタンスほどの違いはありません.

参照クラスは大量のメモリを占めるように見えましたが,その大部分は複数の参照クラスで共有されるものです.別の参照クラスのオブジェクトを追加しても,それほど多くのメモリは必要としません(約38kB).

RC2 <- setRefClass("RC2",
  fields = list(x = "numeric"),
  methods = list(
    initialize = function(x = 2) .self$x <<- x,
    inc = function(n = 2) x <<- x * n
  )
)

# RCオブジェクトに加えて,新しく作ったRC2オブジェクトのサイズを計算
as.numeric(object_size(RC$new(), RC2$new()) - object_size(RC$new()))
#> [1] 37344

オブジェクトのインスタンス化の速度

各オブジェクトを作成するのには,どれくらい時間がかかるのでしょうか.以下に,かかった時間の中央値をマイクロ秒で示します.

# microbenchmarkの結果から中央値を取り出す関数
mb_summary <- function(x) {
  res <- summary(x, unit="us")
  data.frame(name = res$expr, median = res$median)
}

speed <- microbenchmark(
  RC$new(),
  R6$new(),
  R6NoClass$new(),
  R6NonPortable$new(),
  R6NonCloneable$new(),
  R6Bare$new(),
  R6Private$new(),
  R6PrivateBare$new(),
  FunctionEnvClass(),
  FunctionEnvNoClass()
)
speed <- mb_summary(speed)
speed
#>                    name   median
#> 1              RC$new() 279.6430
#> 2              R6$new()  49.8450
#> 3       R6NoClass$new()  44.8755
#> 4   R6NonPortable$new()  48.0375
#> 5  R6NonCloneable$new()  48.0385
#> 6          R6Bare$new()  39.1540
#> 7       R6Private$new()  67.6145
#> 8   R6PrivateBare$new()  60.5370
#> 9    FunctionEnvClass()   3.0120
#> 10 FunctionEnvNoClass()   1.5060

以下のプロットはインスタンス化にかかった時間の中央値を示しています.

unnamed-chunk-24-1.png

参照クラスは,他の種類のクラスのインスタンス化よりずっと低速です.R6オブジェクトのインスタンス化は,おおよそ5倍高速です.単純な関数呼び出しで環境を作成するのは,さらに20~30倍高速です.

フィールドへのアクセス速度

オブジェクトのフィールドにアクセスするのには,どれだけ時間がかかるのでしょうか.まずオブジェクトを作ります.

rc           <- RC$new()
r6           <- R6$new()
r6noclass    <- R6NoClass$new()
r6noport     <- R6NonPortable$new()
r6noclone    <- R6NonCloneable$new()
r6bare       <- R6Bare$new()
r6priv       <- R6Private$new()
r6priv_bare  <- R6PrivateBare$new()
fun_env      <- FunctionEnvClass()
fun_env_nc   <- FunctionEnvNoClass()

そして,これらのオブジェクトから値を取得します.

speed <- microbenchmark(
  rc$x,
  r6$x,
  r6noclass$x,
  r6noport$x,
  r6noclone$x,
  r6bare$x,
  r6priv$x,
  r6priv_bare$x,
  fun_env$x,
  fun_env_nc$x
)
#> Warning in microbenchmark(rc$x, r6$x, r6noclass$x, r6noport$x, r6noclone
#> $x, : Could not measure a positive execution time for 30 evaluations.
speed <- mb_summary(speed)
speed
#>             name median
#> 1           rc$x  7.529
#> 2           r6$x  0.904
#> 3    r6noclass$x  0.000
#> 4     r6noport$x  0.904
#> 5    r6noclone$x  0.904
#> 6       r6bare$x  0.000
#> 7       r6priv$x  0.904
#> 8  r6priv_bare$x  0.000
#> 9      fun_env$x  0.904
#> 10  fun_env_nc$x  0.000

unnamed-chunk-27-1.png

参照クラスのフィールドへのアクセスは,他の方法よりもずっと低速です.

また,環境(R6か関数呼び出しで作成したもの)のフィールドへのアクセスには,class属性があると遅くなるという明確なパターンがあります.これは,class属性を持つオブジェクトに対してRが$のS3メソッドを探そうとするのが性能上の損失となるからです.これについては以下でさらに詳しく見ます.

フィールドの設定速度

オブジェクトのフィールドに値を設定するのにかかる時間はどれくらいでしょうか.

speed <- microbenchmark(
  rc$x <- 4,
  r6$x <- 4,
  r6noclass$x <- 4,
  r6noport$x <- 4,
  r6noclone$x <- 4,
  r6bare$x <- 4,
  # r6priv$x <- 4,         # プライベートフィールドは直接設定できないので,
  # r6priv_nc_np$x <- 4,   # この2つは省略する
  fun_env$x <- 4,
  fun_env_nc$x <- 4
)
speed <- mb_summary(speed)
speed
#>                name  median
#> 1         rc$x <- 4 38.5510
#> 2         r6$x <- 4  1.8070
#> 3  r6noclass$x <- 4  0.6030
#> 4   r6noport$x <- 4  1.8075
#> 5  r6noclone$x <- 4  1.9580
#> 6     r6bare$x <- 4  0.6030
#> 7    fun_env$x <- 4  1.8070
#> 8 fun_env_nc$x <- 4  0.6020

unnamed-chunk-29-1.png

やはり参照クラスは他のものよりはるかに低速です.この場合には,値の型チェックによる追加のオーバーヘッドがあります.

ここでもclass属性なしのオブジェクトはそうでないものよりはるかに高速です.これも`$<-`関数のS3ディスパッチが試みられるのが原因でしょう.

フィールドにアクセスするメソッド呼び出しの速度

各オブジェクトのメソッドを呼び出すときのオーバーヘッドはどのくらいでしょうか.どのオブジェクトのgetx()メソッドも単にxの値を返すだけのものです.このメソッドは必要な場合(portable=TRUEのR6オブジェクトの場合)にはself$xを使います.他の場合(portable=FALSEのR6と参照クラスの場合)には単にxを使います.

speed <- microbenchmark(
  rc$getx(),
  r6$getx(),
  r6noclass$getx(),
  r6noport$getx(),
  r6noclone$getx(),
  r6bare$getx(),
  r6priv$getx(),
  r6priv_bare$getx(),
  fun_env$getx(),
  fun_env_nc$getx()
)
speed <- mb_summary(speed)
speed
#>                  name median
#> 1           rc$getx()  7.529
#> 2           r6$getx()  2.409
#> 3    r6noclass$getx()  0.302
#> 4     r6noport$getx()  1.205
#> 5    r6noclone$getx()  2.410
#> 6       r6bare$getx()  0.301
#> 7       r6priv$getx()  1.506
#> 8  r6priv_bare$getx()  0.301
#> 9      fun_env$getx()  1.204
#> 10  fun_env_nc$getx()  0.301

unnamed-chunk-31-1.png

参照クラスが最も低速です.

r6も他のものよりはいくらか低速です.

r6privr6と同じ速さだろうと思うかもしれませんが,r6privの方が高速です.r6privにはclass属性があるのでr6priv$getxは遅いのですが,privateにはclass属性がないので,private$xself$xより高速なのです.

xに直接(selfprivateなしで)アクセスできて,class属性もないオブジェクトが最速です.

self$x <-x <<-による代入

参照クラスでは,<<-演算子か.selfオブジェクトを用いてフィールドを変更できます.例として,以下の2つのクラスのsetx()メソッドを比べてみてください.

RCself <- setRefClass("RCself",
  fields = list(x = "numeric"),
  methods = list(
    initialize = function() .self$x <- 1,
    setx = function(n = 2) .self$x <- n
  )
)

RCnoself <- setRefClass("RCnoself",
  fields = list(x = "numeric"),
  methods = list(
    initialize = function() x <<- 1,
    setx = function(n = 2) x <<- n
  )
)

移植不可R6クラスもこれと同様です.ただし.selfではなくselfを使います.

R6self <- R6Class("R6self",
  portable = FALSE,
  public = list(
    x = 1,
    setx = function(n = 2) self$x <- n
  )
)

R6noself <- R6Class("R6noself",
  portable = FALSE,
  public = list(
    x = 1,
    setx = function(n = 2) x <<- n
  )
)
rc_self   <- RCself$new()
rc_noself <- RCnoself$new()
r6_self   <- R6self$new()
r6_noself <- R6noself$new()

speed <- microbenchmark(
  rc_self$setx(),
  rc_noself$setx(),
  r6_self$setx(),
  r6_noself$setx()
)
speed <- mb_summary(speed)
speed
#>               name  median
#> 1   rc_self$setx() 45.7790
#> 2 rc_noself$setx() 28.3105
#> 3   r6_self$setx()  4.8190
#> 4 r6_noself$setx()  2.1080

unnamed-chunk-35-1.png

参照クラスと移植不可R6クラスの両方とも,.self$x <-による代入はx <<-よりやや低速です.

R6クラスはデフォルトでは移植可能なので,x <<-での代入はできないということは覚えておいてください.

class属性を持つオブジェクトにおける$使用のオーバーヘッド

class属性を持つオブジェクトで$を使うときにはオーバーヘッドが生じます.以下のテストでは3つの異なる種類のオブジェクトを作成します.

  1. class属性を持たない環境
  2. class属性"e2"を持つが,S3メソッド$.e2は持たない環境
  3. class属性"e3"を持ち,単にNULLを返すS3メソッド$.e3を持つ環境

各環境はオブジェクトxを含むものとします.

e1 <- new.env(hash = FALSE, parent = emptyenv())
e2 <- new.env(hash = FALSE, parent = emptyenv())
e3 <- new.env(hash = FALSE, parent = emptyenv())

e1$x <- 1
e2$x <- 1
e3$x <- 1

class(e2) <- "e2"
class(e3) <- "e3"

# クラスe3のS3メソッドを定義
`$.e3` <- function(x, name) {
  NULL
}

これでそれぞれの種類のオブジェクトに対して,$呼び出しの時間計測テストが実行できます.e3オブジェクトの$関数は何もせず,単にNULL返すだけであることに注意してください.

speed <- microbenchmark(
  e1$x,
  e2$x,
  e3$x
)
#> Warning in microbenchmark(e1$x, e2$x, e3$x): Could not measure a positive
#> execution time for 23 evaluations.
speed <- mb_summary(speed)
speed
#>   name median
#> 1 e1$x  0.000
#> 2 e2$x  0.603
#> 3 e3$x  0.603

e2e3$を使うと,e1よりかなり低速です.これはe2e3にはclass属性があるからです.e2には$メソッドは定義されていないにもかかわらず,Rが適切なS3メソッドを探すせいで,e2$xe1$xの約6倍も低速です.

e3$xe2$xよりわずかに高速です.これはおそらく,$.e3関数が実際にはNULLを返す以外は何もしないからでしょう2

オブジェクトがclass属性を持つ場合,Rは$が呼び出されるたびにメソッドの探索を試みます.$が頻繁に使われる場合には,これによって大きく速度が低下することがあり得ます.

リストと環境,$[[

クラスを作成するのにはリストも使えます(参照セマンティクスではありませんが).リストと環境において,$を用いて項目にアクセスするのにかかる時間はとれくらいでしょうか.また,obj$xを使う場合とobj[['x']]を使う場合も比較してみます.

lst <- list(x = 10)
env <- new.env()
env$x <- 10

mb_summary(microbenchmark(
  lst = lst$x,
  env = env$x,
  lst[['x']],
  env[['x']]
))
#> Warning in microbenchmark(lst = lst$x, env = env$x, lst[["x"]],
#> env[["x"]]): Could not measure a positive execution time for 101
#> evaluations.
#>         name median
#> 1        lst      0
#> 2        env      0
#> 3 lst[["x"]]      0
#> 4 env[["x"]]      0

環境とリストの性能は同等です.

[[演算子は$よりわずかに高速です.おそらく[[は未評価のシンボルを文字列に変換する必要がないからでしょう3


まとめ

R6オブジェクトはRの参照クラスオブジェクトよりメモリ消費が少なく,はるかに高速です.また,R6にはさらに速度を向上させるオプションもあります.

以上のテストにおいて,R6クラスの速度を最も大きく向上させるのは,class属性を使わないことです.これは$を使うときの速度を向上させます.移植不可R6クラスも,フィールドに$をまったく使わずにアクセスできるので,やや速度を向上させることができます.ほとんどの場合において,これらの高速化は無視できる程度(マイクロ秒のオーダー)であり,クラスメンバへのアクセスが何万回あるいは何十万回も行われるときにのみ重要になるでしょう.


付録

オブジェクトサイズを計算する関数

# サイズ計算用の便利関数
obj_size <- function(expr, .env = parent.frame()) {
  size_n <- function(n = 1) {
    objs <- lapply(1:n, function(x) eval(expr, .env))
    as.numeric(do.call(object_size, objs))
  }

  data.frame(one = size_n(1), incremental = size_n(2) - size_n(1))
}

obj_sizes <- function(..., .env = parent.frame()) {
  exprs <- as.list(match.call(expand.dots = FALSE)$...)
  names(exprs) <- lapply(1:length(exprs),
    FUN = function(n) {
      name <- names(exprs)[n]
      if (is.null(name) || name == "") paste(deparse(exprs[[n]]), collapse = " ")
      else name
    })

  sizes <- mapply(obj_size, exprs, MoreArgs = list(.env = .env), SIMPLIFY = FALSE)
  do.call(rbind, sizes)
}

システム情報 4

sessionInfo()
#> R version 3.3.2 (2016-10-31)
#> Platform: x86_64-w64-mingw32/x64 (64-bit)
#> Running under: Windows 10 x64 (build 14393)
#> 
#> locale:
#> [1] LC_COLLATE=Japanese_Japan.932  LC_CTYPE=Japanese_Japan.932   
#> [3] LC_MONETARY=Japanese_Japan.932 LC_NUMERIC=C                  
#> [5] LC_TIME=Japanese_Japan.932    
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] scales_0.4.1           ggplot2_2.2.0.9000     R6_2.2.0              
#> [4] pryr_0.1.2             microbenchmark_1.4-2.1
#> 
#> loaded via a namespace (and not attached):
#>  [1] Rcpp_0.12.8      knitr_1.15.1     magrittr_1.5     MASS_7.3-45     
#>  [5] splines_3.3.2    munsell_0.4.3    lattice_0.20-34  colorspace_1.3-1
#>  [9] multcomp_1.4-6   stringr_1.1.0    plyr_1.8.4       tools_3.3.2     
#> [13] grid_3.3.2       gtable_0.2.0     TH.data_1.0-7    htmltools_0.3.5 
#> [17] survival_2.40-1  yaml_2.1.14      lazyeval_0.2.0   rprojroot_1.1   
#> [21] digest_0.6.10    assertthat_0.1   tibble_1.2       Matrix_1.2-7.1  
#> [25] codetools_0.2-15 evaluate_0.10    rmarkdown_1.2    labeling_0.3    
#> [29] sandwich_2.3-4   stringi_1.1.2    backports_1.0.4  mvtnorm_1.0-5   
#> [33] zoo_1.7-13

  1. 訳注:以下,テスト結果は訳者の環境におけるものである. 

  2. 訳注:訳者の環境ではe3$xe2$xの速度にほとんど差がなかった.また,翻訳時点の原文でも,コードの実行結果上ではe2$xの方が高速となっている. 

  3. 訳注:訳者の環境では実行時間が短すぎて中央値を計測するのが困難であった.原文を参照されたい. 

  4. 訳注:システム情報は訳者のものである. 

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1