超自己満足プログラミング このページをアンテナに追加 RSSフィード

2013-01-23

ActiveRecord::Base storeの速度検証

Rails 3.2.0から追加された機能

3.2.0リリースから1年近くたっていて、

今更な感じですが、最近使ってパフォーマンス的に痛い思いをしたので、

ちゃんと計測してみました。


機能/用途の説明は、こちらにおまかせ。

http://d.hatena.ne.jp/hichiriki/20120229


さっそく計測。(rails 3.2.11)


用意したテーブル

$ rails g model store3    # storeで3カラム (実際には textカラム1つ)
$ rails g model store10  # storeで10カラム (実際には textカラム1つ)
$ rails g model store30  # storeで30カラム (実際には textカラム1つ)

$ rails g model serialize3    # serializeで3カラム (実際には textカラム1つ)
$ rails g model serialize10  # serializeで10カラム (実際には textカラム1つ)
$ rails g model serialize30  # serializeで30カラム (実際には textカラム1つ)

$ rails g model normal3      # 実際に3カラム
$ rails g model normal10  # 実際に10カラム
$ rails g model normal30  # 実際に30カラム

カラム数での、カラム名

3カラム: num1, str1, time1
10カラム: num1, num2, num3, num4, str1, str2, str3, str4, time1, time2
30カラム: num1-15, str1-10, time1-5

insert

require 'benchmark'

now = Time.now

three_columns = {num1: 1, str1: 'a', time1: now}
ten_columns = {num1: 1, num2: 2, num3: 3, num4: 4, str1: 'a', str2: 'b', str3: 'c', str4: 'd', time1: now, time2: now}
thirty_columns = {num1: 1, num2: 2, num3: 3, num4: 4, num5: 5, num6: 6, num7: 7, num8: 8, num9: 9,
  num10: 10, num11: 11, num12: 12, num13: 13, num14: 14, num15: 15,
  str1: 'a', str2: 'b', str3: 'c', str4: 'd', str5: 'e', str6: 'f', str7: 'g', str8: 'h', str9: 'i', str10: 'j',
  time1: now, time2: now, time3: now, time4: now, time5: now}

n = 1000
Benchmark.bm do |x|
  x.report('store3') { n.times{Store3.create!(three_columns)} }
  x.report('store10') { n.times{Store10.create!(ten_columns)} }
  x.report('store30') { n.times{Store30.create!(thirty_columns)} }

  x.report('serialize3') { n.times{Serialize3.create!(text: three_columns)} }
  x.report('serialize10') { n.times{Serialize10.create!(text: ten_columns)} }
  x.report('serialize30') { n.times{Serialize30.create!(text: thirty_columns)} }

  x.report('normal3') { n.times{Normal3.create!(three_columns)} }
  x.report('normal10') { n.times{Normal10.create!(ten_columns)} }
  x.report('normal30') { n.times{Normal30.create!(thirty_columns)} }
end

$ rails runner script/benchmark.rb 
       user     system      total        real
store3  2.150000   0.160000   2.310000 (  3.218724)
store10  2.600000   0.130000   2.730000 (  3.445697)
store30  4.980000   0.170000   5.150000 (  6.126592)
serialize3  1.770000   0.120000   1.890000 (  2.484414)
serialize10  2.390000   0.130000   2.520000 (  3.181018)
serialize30  4.060000   0.150000   4.210000 (  5.224046)
normal3  1.330000   0.120000   1.450000 (  1.945207)
normal10  1.800000   0.120000   1.920000 (  2.531964)
normal30  3.240000   0.150000   3.390000 (  4.348845)

たった1000回でもそれなりに差が出る。

select

require 'benchmark'

now = Time.now

three_columns = {num1: 1, str1: 'a', time1: now}
ten_columns = {num1: 1, num2: 2, num3: 3, num4: 4, str1: 'a', str2: 'b', str3: 'c', str4: 'd', time1: now, time2: now}
thirty_columns = {num1: 1, num2: 2, num3: 3, num4: 4, num5: 5, num6: 6, num7: 7, num8: 8, num9: 9,
  num10: 10, num11: 11, num12: 12, num13: 13, num14: 14, num15: 15,
  str1: 'a', str2: 'b', str3: 'c', str4: 'd', str5: 'e', str6: 'f', str7: 'g', str8: 'h', str9: 'i', str10: 'j',
  time1: now, time2: now, time3: now, time4: now, time5: now}

n = 1000
Benchmark.bm do |x|
  x.report('store3') { Store3.limit(n).all.map(&:num1) }
  x.report('store10') { Store10.limit(n).all.map(&:num1) }
  x.report('store30') { Store30.limit(n).all.map(&:num1) }

  x.report('serialize3') { Serialize3.limit(n).all.map{|s|s.text[:num1]} }
  x.report('serialize10') { Serialize10.limit(n).all.map{|s|s.text[:num1]} }
  x.report('serialize30') { Serialize30.limit(n).all.map{|s|s.text[:num1]} }

  x.report('normal3') { Normal3.limit(n).all.map(&:num1) }
  x.report('normal10') { Normal10.limit(n).all.map(&:num1) }
  x.report('normal30') { Normal30.limit(n).all.map(&:num1) }
end

.allでやめると、ロードされてなさそうだったので、カラムに触るという意味mapしてます。

$ rails runner script/benchmark.rb 
       user     system      total        real
store3  0.650000   0.040000   0.690000 (  0.725642)
store10  0.630000   0.010000   0.640000 (  0.652668)
store30  1.670000   0.010000   1.680000 (  1.684106)
serialize3  0.330000   0.020000   0.350000 (  0.385289)
serialize10  0.650000   0.010000   0.660000 (  0.668932)
serialize30  1.690000   0.010000   1.700000 (  1.716288)
normal3  0.090000   0.000000   0.090000 (  0.093420)
normal10  0.130000   0.000000   0.130000 (  0.132085)
normal30  0.200000   0.010000   0.210000 (  0.216454)

オブジェクトにするところがとても重い。

容量

show table statusのData_length (1000件)

store3s: 131,072
store10s: 245,760
store30s: 1,589,248

serialize3s: 131,072
serialize10s: 245,760
serialize30s: 1,589,248

normal3s: 65,536
normal10s: 98,304
normal30s: 196,608

storeとserializeがまったく同じ。どっちもyamlかな?

当然ながらstoreとserializeは、とても大きい。

結論

基本的に遅いし容量も大きくなるので、使う場所はよく考えた方がいいかも

2011-05-31

[] ハッシュの値を宣言と同時に参照する方法

久々に更新しますが、かなり自分用のメモですorz


ruby だと以下のように書くのをperlでどう書けばいいのか。。

# ruby
# 無名ハッシュ(という呼び方でいいかわからないけど)を作成と同時に値を引っ張る
{:k1 => 'v1', :k2 => 'v2'}[:k2]  # ==> "v2"

一応、こんな感じで書けたけど、リファレンスにせずに引っ張る方法はわからず。。

# perl
{k1 => 'v1', k2 => 'v2'}->{k2}  # ==> "v2"

(k1 => 'v1', k2 => 'v2'){k2}  # syntax error...

配列だとrubyと同じ感じでできるんだけどな〜。

# ruby
# 無名配列を作成と同時に値を引っ張る
%w[one two three][1]  # ==> "two"
# perl
qw(one two three)[1]  # ==> "two"

2011-02-25

[] image_tagで付加される画像タイムスタンプを、production環境でも画像ファイル更新されたタイミング更新されるようにする

image_tagで画像を表示した際に、srcの後ろに付くタイムスタンプ

基本的には File.mtime(image_path) なので、画像ファイルの最終更新時刻です。

image_tag 'rails.png'  #=> <img src="/images/rails.png?1298610241" alt="Rails" />

なので、developement環境では画像ファイルをtouchしてあげたりすると、タイムスタンプ更新されます。

しかし、production環境ではtouchしても更新されません。(passengerなり、サーバリスタートすれば更新されます)


なぜこうなるかは、ActionView::Helpers::AssetTagHelper の rails_asset_id メソッドを見るとわかります。

# File actionpack/lib/action_view/helpers/asset_tag_helper.rb
        def rails_asset_id(source)
          if asset_id = ENV["RAILS_ASSET_ID"]
            asset_id
          else
            if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
              asset_id
            else
              path = File.join(ASSETS_DIR, source)
              asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''

              if @@cache_asset_timestamps
                @@asset_timestamps_cache_guard.synchronize do
                  @@asset_timestamps_cache[source] = asset_id
                end
              end

              asset_id
            end
          end
        end

最初1回は、File.mtime(path) をちゃんと取ってくれるのですが、

その際に、@@asset_timestamps_cache[source] = asset_id で、キャッシュして、

以降は、そのキャッシュが使われます。


で、@@cache_asset_timestamps が何かを見ると

# File actionpack/lib/action_view/helpers/asset_tag_helper.rb
      def self.cache_asset_timestamps
        @@cache_asset_timestamps
      end

      # You can enable or disable the asset tag timestamps cache.
      # With the cache enabled, the asset tag helper methods will make fewer
      # expense file system calls. However this prevents you from modifying
      # any asset files while the server is running.
      #
      #   ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
      def self.cache_asset_timestamps=(value)
        @@cache_asset_timestamps = value
      end

      @@cache_asset_timestamps = true

booleanの値です。

さらにセッターの上に今回の答えがコメントで書かれてますが、

ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false すれば、

毎回 File.mtime(path) してくれるようになります。


なので、initializersとかに、適当ファイルを作って、

# config/intializers/cache_asset_timestamp.rb とか
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false

だけ書いておけば、production環境でもtouchしたらタイムスタンプ更新されるようになります。


ちなみに、development環境は、config/environments/development.rb で

config.cache_classes = false

になってると思いますが、cache_classesがfalseだと、

ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false

が実行されるので、development環境では何もしなくてもタイムスタンプ更新されるっぽいです。

2011-02-17

[] jpmobileを使って、au/softbankでも、controller内で、ドコモ絵文字マッピング済みのutf8値を得る方法

非常にわかりにくいタイトルですが、

ユーザテキストフォームに絵文字を入れて送ってきたときの話しです。

以下のようなコードがあった際に、

class HogeController < ApplicationController
  mobile_filter

  def hoge
    # 例えば、ドコモの「晴れ」に対応する絵文字は、jpmobileの
    # before_filterを通った、actionの時点で、
    #   docomo   : \xEE\x98\xBE
    #   au       : \xEE\x92\x88
    #   softbank : \xEF\x81\x8A
    # となっている
  end
end

そのままhogeアクションviewで表示するなら、jpmobileのafter_filterがかかって、

docomo   : \xF8\x9F (sjis)
au       : \xF8\x9F (sjis)
softbank : \xEE\x98\xBE (utf8)

になるのですが、hogeアクションの中で、au/softbankでもdocomoの値を手に入れるにはどうしたらよいでしょうか。


一応、下記の方法で手に入れることが出来たので、メモっておきます。

class HogeController < ApplicationController
  mobile_filter

  def hoge
    # strが変換したい文字列とすると
    str = Jpmobile::Emoticon::utf8_to_unicodecr(str)
    str = Jpmobile::Emoticon::unicodecr_to_external(str, Jpmobile::Emoticon::CONVERSION_TABLE_TO_DOCOMO, false)
    # これで strは
    #   docomo   : \xEE\x98\xBE
    #   au       : \xEE\x98\xBE
    #   softbank : \xEE\x98\xBE
    # となります
  end
end

単純に、jpmobileのafter_filterをかけているだけですが、

unicodecr_to_externalの3つ目の引数をfalseにしてutf8-->sjis変換を行なわないようにしています

2010-12-13

[] 主キー(primary key)の id を任意に指定して、createする

# ruby script/console

# 普通に指定しても無視される
>> User.create(:id => 100)
=> #<User id: 1, created_at: "2010-12-13 02:47:03", updated_at: "2010-12-13 02:47:03">

# ブロックで指定すると
>> User.create do |u|
?>   u.id = 100
>> end
=> #<User id: 100, created_at: "2010-12-13 02:48:22", updated_at: "2010-12-13 02:48:22">

使う機会は・・・orz