Help us understand the problem. What is going on with this article?

GitHub Actions で R の環境ごとのベンチマークをとった

要旨

  • GitHub Actions を使って R をサポートする各 OS(Windows、 Linux、 macOS)で複数の R のバージョンが動く環境で CSV の読み取り速度を計測した

はじめに

CSV を読み込むときに utils::read.csv は遅いとか tidyverse 時代には readr::read_csv とか巨大ファイルなら data.table::fread だとか最近なら vroom::vroom が速いだとか、よくわからないので比較計測(ベンチマーク)してみたいと思います。

ベンチマークをとる際に注意したいのが環境の差をどう扱うかという点です。ハードウェア環境やソフトウェア環境をできるだけそろえた状態で計測しなければ、関数のパフォーマンスを正しく比較することはできません。

ところで今年 GitHub Actions という機能が公開され、先月正式版として取り入れられました。 GitHub Actions はいわゆる CI/CD を実現する機能なのですが、その実行環境の OS は Windows、 macOS、 Ubuntu と各種取り揃えられています。しかもいずれの実行環境も同一ハードウェア環境の仮想マシンとして提供されているため、クロスプラットフォームでベンチマークをとるのに適しています1

というわけで、本記事では GitHub Actions を用いてベンチマークをとってみようという話です。ソースコードは GitHub で公開しています

注意

本記事は GitHub Actions を様々な環境で利用するということを主眼に置いているため、ベンチマークとしては不完全です。ベンチマークをとる際は、自身の環境に合わせた適切な方法で行ってください。

やること

比較対象

関数

以下の 3 関数で CSV を読み込む場合の速度を比較します2

  • utils::read.csv
  • readr::read_csv
  • data.table::fread

データ

行数、列数が様々なサイズのヘッダー付き CSV を読み込みます。中身はいずれも数値とし、上記関数の読み込み時に (1) 自動型判別、 (2) 型指定、 (3) 文字列型指定の 3 通りで読み込みます。

環境

R

R 3.4.4 および R 3.6.1 を利用します。 ALTREP が導入された R 3.5 より前の最新バージョンと、現在の最新バージョンです。

他にも R 実行環境は Microsoft R OpenFastR といった高速を謳ったものもありますが、本記事では扱いません。

OS

GitHub Actions のサポートする下記 OS を利用します。

  • Windows Server 2019(windows-latest)
  • Ubuntu 18.04(ubuntu-latest)
  • macOS X Catalina 10.15(macos-latest)

制限

環境、ファイルサイズによっては、メモリー不足などの理由により、ベンチマークが回りきらない場合がありました。これについては、全部の関数が回りきるように調整したため、多少ベンチマークとしては物足りない部分があるかもしれません。

GitHub Actions の準備

GitHub Actions は大まかに以下の流れで処理します。

  1. 計測用データ生成
  2. ベンチマーク(OS、R バージョンごと)
    1. 計測用データ取得
    2. R インストール
    3. パッケージインストール
    4. ベンチマーク実行
    5. ベンチマーク結果保存
  3. 可視化

全部説明すると長くなるので、ベンチマークのための環境準備(R が実行できるようになるまで)についてのみ説明します。

環境準備

r-lib/actions/setup-r を利用するのと簡単に R がインストールできます。

steps:
  - uses: r-lib/actions/setup-r@v1
    with:
      r-version: '3.6.1'

r-lib/actions/setup-r はマトリックスビルドをサポートしているため、今回のような複数バージョンでの実行が簡単に実現できます。

strategy:
  matrix:
    R: ['3.4.4', '3.6.1']
  steps:
    - uses: r-lib/actions/setup-r@v1
      with:
        r-version: ${{ matrix.R }}

パッケージインストール

R のパッケージをインストールする際に、管理者権限が要求されたりすると面倒なので、カレントディレクトリー内にパッケージをインストールするのが簡単だと思います。 .libPaths 関数でカレントディレクトリー内の lib ディレクトリーをパッケージサーチパスに追加することができます(install.packages の lib パラメーターでも OK)。

特に Linux はパッケージのビルドに時間がかかるため3、インストールしたパッケージはキャッシュしておけば次回以降のビルド時間の節約になります。今回は setup.R で lib ディレクトリーにパッケージインストールを行うようにしたため、 OS と R のバージョンごとにパッケージをキャッシュするためのキャッシュ手順は以下のようになります。

steps:
  - id: cache-packages
    uses: actions/cache@v1
      with:
        path: lib
        key: lib-${{ runner.os }}-${{ matrix.R }}-${{ hashFiles('setup.R') }}

  - if: steps.cache-packages.outputs.cache-hit != 'true'
    run: Rscript --vanilla setup.R

ジョブ間のデータ共有

ジョブごとに実行環境が異なるため、前のジョブで作成したファイルを別のジョブに利用させるためには upload-artifact アクションおよび download-artifact アクションを利用します。前述の GitHub Actions の流れでは、生成したデータと、ベンチマーク結果をそれぞれ後続のジョブに共有しています。

以下は data ディレクトリーとその中身をアーティファクト(成果物)としてジョブ間で共有する例です。

jobs:
  first:
    steps:
      - uses: actions/upload-artifact@v1
        with:
          name: data
          path: data
  second:
    needs: first
    steps:
      - uses: actions/download-artifact@v1
        with:
          name: data

複数のジョブから同一名のアーティファクト(ディレクトリー)に対してアップロードを行っても、ファイル名が異なればそれぞれのアーティファクトが保存されます。

ベンチマーク

microbenchmark::microbenchimark を利用して各 CSV 読み込み関数の比較を行います。 read_type の値によって、読み込み時の型指定を制御しています。

switch(
   read_type,
   "auto" = {
      c1 <- NA
      c2 <- NULL
   },
   "specify" = {
      c1 <- rep("numeric", ncol)
      c2 <- paste(rep("d", ncol), collapse = "")
   },
   "character" = {
      c1 <- rep("character", ncol)
      c2 <- paste(rep("c", ncol), collapse = "")
   }
)

microbenchmark(
   "utils::read.csv" = utils::read.csv(file, colClasses = c1),
   "readr::read_csv" = readr::read_csv(file, col_types = c2),
   "data.table::fread" = data.table::fread(file, colClasses = c1),
   times = times
) %>% 
   summary(unit = "s") %>% 
   select(expr, lq, median, uq) %>%
   mutate(filesize = filesize, nrow = nrow, ncol = ncol, read_type = read_type, os = os, version = glue("{major}.{minor}"))

結果

同一環境内での比較

OS および R バージョンごとの比較結果を示します。横軸ファイルサイズ、縦軸処理時間の両対数グラフで、点は中央値、線は第 1 四分位点と第 3 四分位点の幅を示しています。各セルは読み込み時のデータ型指定の方法(auto: 指定なし、 character: 文字列指定、 specify: 数値指定)を表します。

  • R 3.4
    • Windows mingw32-3.4.4.png
    • Linux linux-gnu-3.4.4.png
    • macOS darwin15.6.0-3.4.4.png
  • R 3.6
    • Windows mingw32-3.6.1.png
    • Linux linux-gnu-3.6.1.png
    • macOS darwin15.6.0-3.6.1.png

一定以上のファイルサイズでは安定して data.table::fread が速いですね。ただし文字列型の場合は readr::read_csv とそれほど変わらず、数値型に比べてパフォーマンスは低下するようです。

Linux の R 3.6 ではデータ型指定時に utils::read.csv が readr::read_csv より速くなっています。 R 3.5 でパフォーマンス改善が行われた影響かと思います。

環境間での比較

各環境のベンチマーク結果をマージした結果を示します。横軸は関数、縦軸は計測中央値で先ほどと異なり対数ではなく通常の目盛です。行ごと、列ごとにベンチマークに利用した CSV の行数と列数を表します。行数は 10 倍ずつ、列数は 2 倍ずつ変わります。行数 1,000,000 行、列数 40 列と 80 列のセルが空欄になっているのはリソース制限のためにベンチマークをスキップしたためです。

  • 自動型推定 auto.png
  • 数値型指定 specify.png
  • 文字列型指定 character.png

utils::read.csv はバージョンが 3.4 から 3.6 に上がるとかなりパフォーマンスが改善していることが見て取れます。また、 utils::read.csv や data.table::fread は文字列型として読み込むときと比べて自動型推定もしくは数値型指定の場合にパフォーマンス改善が認められます。

結論

data.table::fread は小さいサイズの CSV はともかく、分析者が普段扱うような大きさの CSV については安定して速いですね。また、 utils::read.csv はパフォーマンスが悪いと言われていますが、環境や指定方法によっては readr::read_csv より速いケースもあるようです。

昔は data.table はクセがあって使いづらかったのですが、最近は dtplyr のおかげで dplyr 操作が問題なく行えるため、データ分析ステップにおけるデータの読み込み以降の操作に関してはほとんど意識することなくできるようになっています。

GitHub Actions はジョブごとにランタイムを変更したり、ジョブ間のデータ共有も簡単です。環境ごとの特性を理解しつつ GitHub Actions を利用することで、快適な CI/CD を送りましょう。


  1. 厳密に言えば、同一ホスト内の別の仮想マシンの影響は受けるため完全に同一の環境は保証されません。 

  2. vroom::vroom も最初比較対象に入れていたのですが、 Windows の R-3.4 でインストールできなかったり、 Linux で Too many open files エラーが出たり面倒だったので対象から外しています。 

  3. CI/CD 時代ではいかにビルド時間を削り、変更から出力までの時間を短縮できるかが重要です。 tidyverse を利用するのであれば tidyverse がセットアップ済みの Docker イメージを利用したり、パッケージがビルド済みバイナリーで配布される Windows を使うような工夫が大事です。ちなみに本記事では microbenchmark、 tidyverse、 data.table、(当初使う予定だったけど使わなくなった)vroom をインストールしていますが、バイナリーでインストールする Windows と macOS が 30 秒足らずでインストールが完了するのに対し、 Linux は 20 分弱の時間を要しました。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away