この記事はアピリッツの技術ブログ「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ライフを送りましょう。