ホーム DoRuby ActiveRecordの速さを追い求める. 1
ActiveRecordの速さを追い求める. 1
 

ActiveRecordの速さを追い求める. 1

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

2があるかどうかは微妙。

 こんにちは、HelloWorld+です。
 今回もまた、ActiveRecord関連の話をしようと思います。

状況

 どんなゲームにおいても、キャラクターは無くてはない存在です。
 そして、以下のような人間マスタと、人間パラメータマスタがあるとします。

HumanMaster

id:                          integer
code:                        integer
name:                        string
hp_parameter_code:           integer
power_parameter_code:        integer
intelligence_parameter_code: integer
speed_parameter_code:        integer


HumanParameterMaster

id:        integer
code:      integer
age:       integer
parameter: integer

 才能とか、画像コードとか、職業とか年収とか余命とか、そんなものも実際のHumanMasterには含まれますが、今回は簡略化してこんな感じで。
 そして、上記のマスタからだと、人間そのもののデータの形は以下のようになります。

Human
id:             integer
human_code:     integer
hp:             integer
power:          integer
intelligence:   integer
speed:          integer
age:            integer

 Humanそのもので保存するデータは、HumanMasterと紐付ける為の最小限のデータ(今回だと、HumanMasterのcodeとHumanのhuman_codeが紐付いている形になります)と、Human一体一体で違った値になるパラメータ周りになります。
 そして、Humanが加齢した際には、HumanMasterのパラメータコード、そしてHumanの年齢を参照し、HumanParameterMasterそれぞれのパラメータの値を取得、更新する形になります。
 今回は、パラメータはHuman単位ではなく、Parameter単位で管理されている、とします。どちらにも利点と欠点があるのですが、そこあたりは割愛します。

加齢の工程

Human加齢!

Humanのcodeから該当するHumanMasterを取得。

Humanのage、HumanMasterのそれぞれのparameter_codeからHumanParameterMasterのパラメータを取得。

Humanのパラメータを上書きして加齢による能力値変動が完了。

この工程を、サーバー側で行う為の、Railsのコードに起こしてみましょう。

def birthday!(human)
  #人間マスタ取得
  human_master = HumanMaster.find_by(code: human.code)
  #パラメータ上書き
  ["hp", "power", "intelligence", "speed"].each do |param_str|
    parameter_code = human_master[param_str + "_parameter_code"]
    parameter_master = HumanParameterMaster.find_by(code: parameter_code, age: human.age)
    human[param_str] = parameter_master.parameter
  end
  #保存
  human.save
end

 基本的には、まあ、こんな感じでしょう。でも、これだとやや冗長なところがあります。
 以下の一文になります。

parameter_master = HumanParameterMaster.find_by(code: parameter_code, age: human.age)

 え? 単純に取得してきてるだけじゃん、と思うかもしれませんが、これだとまだ冗長なのです。
 パラメータマスタを取得してきて、使っているのはparameterだけです。それ以外のデータは必要ありません。
 そして、レコードそのものを取ってくるより、カラムの一つのデータを取ってくる方が速いのです。
 今回はパラメータを参照する回数はhp, power, intelligence, speedの4回だけですが、もっと数が増えてきた場合、同じように一々レコードそのものを参照していると如実に処理速度の差が出てきます。
 ただ、ここで問題が生じます。
 find_byからでは、カラム単体のデータを取ってくる術はありません。
 では、どうしたら一つのデータを取って来る事が可能か?
 方法は二つあります。
 一つ目は、whereからのselectとかpluckとか。

parameter = HumanParameterMaster.where(code: parameter_code, age: human.age).select(:parameter).take.parameter

 もう一つは、SQL直打ち。

parameter = ActiveRecord::Base.connection.select_value("SELECT parameter FROM human_parameter_masters WHERE code = #{parameter_code} AND age = #{human.age}")

 SQL直打ちの方がちょっとだけ速いのですが、まあ、コードの中にSQL直打ちするのは美しくないと色々言われたりするので、本当に速さを求めない限りは、上で大丈夫でしょう。上に置き換えるだけでも、処理速度はかなり速くなります。

 さて、最後にRailsで実際に作った人間パラメータマスタで以下のようなコードを打ってみて、実行時間の差を感じて貰って終わりにしたいと思います。

def get_time
  time = Time.now
  yield
  p Time.now - time
end

#レコードそのまんま
get_time do
  1000.times do |n|
    HumanParameterMaster.find_by(id: n + 1)
  end
end
#3.851174sec

#絞り込み
get_time do
  1000.times do |n|
    HumanParameterMaster.where(id: n + 1).select(:parameter).take.parameter
  end
end
#0.714646sec

#SQL直打ち
get_time do
  1000.times do |n|
    ActiveRecord::Base.connection.select_value("SELECT parameter FROM human_parameter_masters WHERE id = #{n}")
  end
end
#0.369945sec

 では、より速さを求める(時にはコードの綺麗さを犠牲にして)Railsライフを送りましょう。

記事を共有

最近人気な記事