今更だけどrailsのmapとpluckを比較してみた

# 今更だけどrailsのmapとpluckを比較してみた
あけましておめでとうございます。
キーボードと戯れていたら2020年を迎えることができました。mendです。
就職してサーバーサイドエンジニアとして働いていて、現在のお仕事ではRailsを使用しております。
学生時代はRailsでの開発経験がなく、かなり浅い知識で仕事に取り組む中で`model`の特定カラムの情報を取得する部分のレビューに

__??? 「`map`は重いからやめたほうがいいよ」__

__??? 「ここ(`map`)、`pluck`に変えたほうがいいですよ」__

とのご指摘をもらうことがよくありました。
~~帰省した実家で退屈をしているのもあって~~今回の記事では普段癖で書いてしまう`map`よりも、本当に`pluck`のほうが軽いのかを検証してみたいと思います。

# 検証環境
デフォルト設定です

- Ruby on Rails： 5.2.3
- データベース： SQLite


# 忙しい人のためにまずは結論

### 処理時間

|件数|100|1000|10000|100000|
|:--:|:--:|:--:|:--:|:--:|
|`map`|0.002795s|0.019125s|0.125839s|1.529347s|
|`pluck`|0.000799s|0.001634s|0.015934s|0.164019s|

**結果：`map`が重い**
すごく当たり前ですが、やはりレコードが多くなるにつれて処理にかかる時間が長くなりました。

## rakeタスクを作成する
いちいちコンソール叩いて検証するのも面倒なのでタスクを作成して検証しました。

```shell
$bundle exec rails generate task method_performance_test
```
このコマンドで`lib/tasks/method_perforance_test.rake`が作成されるので、こちらにタスクとして行う処理を書いていきます。

railsでモデルを扱うタスクの場合、`:environment`が必要みたいなので書き足しておきました。

```ruby:lib/tasks/method_performance_test.rake
task method_performance_test: :environment do |_task|
  # ここに処理を書いていく
end
```

## 検証用モデルの作成
`map`と`pluck`の検証に使用するモデルは結果的にカラムの情報が取得できればいいので特に何も気にせず作成しました。
とりあえず今回の記事では文字列を格納する`name`のカラムを持つ`User`を作成しました。

```shell
$ bundle exec rails g model User name:string
```
これでモデルとマイグレーションファイルが作成されるので

```shell
$ bundle exec rails db:migrate
```
マイグレーションを忘れずに行いましょう。個人的には`ridgepole`のほうが好きです。

## rakeタスクの実装
### ユーザー全件の削除
今回は検証として行うので、きちんとレコードを空にしてから処理を行うようにします。
全部消すのは簡単なのでササッと処理を書いちゃいます。

```ruby
# ユーザー全件を削除する
User.all.map { |user| user.destroy! }
```
うわこっわ....

### task内で環境変数を呼び出す
レコード数の件数ごとにパフォーマンスの測定を行いたいので、コマンド実行時に環境変数を参照できるようにしたいと思います。

```shell
$ bundle exec rake method_performance_test SIZE=10
```
このように最後に付け足すことで、タスク内で環境変数を呼び出せるようにします

```ruby:lib/tasks/method_performance_test.rake
puts "#{ENV['SIZE'].to_i}" # 10
```

### 検証用のレコードを作成する
先程の環境変数から受け取った値の件数だけレコードを作成できるようにします

```ruby:lib/tasks/method_performance_test.rake
num = ENV['SIZE'].to_i
num.times do |n|
  User.create(name: "test#{num.to_s.rjust(7, "0")}")
end
```

### `map`と`pluck`の処理時間を計測する
実行時間の計測自体は結構シンプルです。
現在時刻から計測開始時刻を引くだけです。

```ruby:lib/tasks/method_performance_test.rake
# mapの処理時間を計測する
start_time = Time.current
User.all.map(&:id)
map_time = Time.current - start_time

# pluckの処理時間を計測する
start_time = Time.current
User.all.pluck(:id)
pluck_time = Time.current - start_time
```

### 作成したタスク
最終的にこんな感じになりました

```ruby:lib/tasks/method_performance_test.rake
task method_performance_test: :environment do |_task|
  puts "user_size:#{ENV['SIZE'].to_i}"
  
  num = ENV['SIZE'].to_i

  puts "=====initializing====="
  User.all.map { |user| user.destroy! }

  puts "=====create users: size = #{num}====="
  insert_time = Time.current
  num.times do |n|
    User.create!(name: "test#{num.to_s.rjust(7, "0")}")
  end
  puts "=====create users: done====="
  puts " "
  puts "INSERT time: #{Time.current - insert_time}s"
  puts " "

  puts "=====user.all.map(&:id): start====="
  puts "=====class:#{User.all.map(&:id).class}====="
  start_time = Time.current
  User.all.map(&:id)
  map_time = Time.current - start_time
  puts "result map: #{map_time}s"
  puts "=====user.all.map(&:id): end====="

  puts "=====user.all.pluck(:id): start====="
  puts "=====class:#{User.all.pluck(:id).class}====="
  start_time = Time.current
  User.all.pluck(:id)
  pluck_time = Time.current - start_time
  puts "result pluck: #{pluck_time}s"
  puts "=====user.all.pluck(:id): end====="

  puts "=====clean up test data====="
  delete_time = Time.current
  User.all.map { |user| user.destroy! }
  puts " "
  puts "DELETE time: #{Time.current - delete_time}s"
  puts " "

  puts "=====result====="
  puts "map   :#{map_time}s"
  puts "pluck :#{pluck_time}s"
    
end
```