天泣記

2014-10-24 (Fri)

#1 Hash.new の集計

Hash.new はどのように使われているのか

% time gmilk -a 'Hash.new' |sed 's/^.*Hash.new */Hash.new /'|sort|uniq -c|sort -n|tail -100
Because number of records is large, Milkode use external tool. (Same as 'gmilk -e grep')
     26 Hash.new { |h,k| h[k] = h.length }
     26 Hash.new {|h, k| h[k] = [] }
     26 Hash.new {|h, k| h[k] = {}}
     26 Hash.new {|hash, key| hash[key] = lambda {|entry| CoercibleString.coerce(entry)}}
     27 Hash.new (hash)
     27 Hash.new (self, env, @default_options)
     27 Hash.new { |hash, key|
     27 Hash.new { |hash,key| hash[key] = {} }
     28 Hash.new ) { |row| ... }
     28 Hash.new { |h,k| h[k] = '' }
     29 Hash.new ("ETag" => 'HELLO', "content-length" => '123')
     29 Hash.new (*args)
     29 Hash.new (0) }
     29 Hash.new (false)) do |methods, attr|
     29 Hash.new , pirate.changes
     30 Hash.new ()
     30 Hash.new , node.attributes
     30 Hash.new { |h,k| h[k] = Mutex.new }
     30 Hash.new }.should raise_error(ArgumentError)
     31 Hash.new (header)
     32 Hash.new (false).update(
     32 Hash.new ;
     33 Hash.new ) }
     34 Hash.new ("default")
     34 Hash.new (self, env)
     35 Hash.new (0)]
     35 Hash.new { |hash,key| hash[key] = [] }
     36 Hash.new ({
     36 Hash.new { |h, k|
     37 Hash.new (0)) { |h,part| h[part.first] += part.last; h }
     37 Hash.new ]
     37 Hash.new { |h, k| h[k] = Array.new }
     37 Hash.new {|h,base|
     38 Hash.new { |h, k| h[k] = Set.new }
     39 Hash.new (0.0)
     39 Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
     42 Hash.new { [] }
     43 Hash.new { 0 }
     43 Hash.new { |h,k| h[k] = 0 }
     43 Hash.new { |h,pid| h[pid] = {} }
     44 Hash.new (1)
     44 Hash.new (self)
     44 Hash.new do |hash, key|
     44 Hash.new {|h,k| h[k] = 0}
     46 Hash.new (nil)
     46 Hash.new , &block)
     46 Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
     47 Hash.new ("foo" => ["bar", "baz"])
     47 Hash.new (output)
     48 Hash.new ("")
     48 Hash.new {|hash, key| hash[key] = []}
     50 Hash.new (Codepoint.new)
     50 Hash.new .tap do |hash|
     50 Hash.new Hash(getRuntime());
     53 Hash.new ("Foo-Bar" => "baz")
     54 Hash.new { |h,k|
     56 Hash.new (default)
     56 Hash.new do |h, table_name|
     58 Hash.new ("Content-MD5" => "d5ff4e2a0 ...")
     58 Hash.new unless defined?(@_cycles)
     59 Hash.new (default).merge!(self)
     60 Hash.new ([]))
     60 Hash.new (default).merge(self)
     63 Hash.new { |hash, key| hash[key] = {} }
     65 Hash.new { |hash, key| hash[key] = Array.new }
     66 Hash.new (
     67 Hash.new (0)
     67 Hash.new { |h, k| h[k] = 0 }
     69 Hash.new { |h, k| h[k] = k.chr }
     75 Hash.new { |h,k| h[k] = Array.new }
     76 Hash.new {|h,k| h[k] = {}}
     84 Hash.new ('')
     95 Hash.new ({})
     99 Hash.new { |h, k| h[k] = {} }
    102 Hash.new {|h, k| h[k] = []}
    103 Hash.new do |h, k|
    108 Hash.new , params
    119 Hash.new Hash(runtime);
    121 Hash.new ,
    126 Hash.new do |h,k|
    133 Hash.new {|h,k| h[k] = [] }
    136 Hash.new (false)
    139 Hash.new 0
    144 Hash.new ([])
    160 Hash.new ("foo" => "bar")
    162 Hash.new do |hash, region|
    162 Hash.new do |region_hash, key|
    261 Hash.new (headers)
    262 Hash.new { |hash, key| hash[key] = [] }
    285 Hash.new { |h,k| h[k] = {} }
    308 Hash.new {|h,k| h[k] = []}
    429 Hash.new ()
    448 Hash.new { |h, k| h[k] = [] }
    789 Hash.new { |h,k| h[k] = [] }
    827 Hash.new
   1015 Hash.new }
   1038 Hash.new do |hash, key|
   1220 Hash.new )
   1672 Hash.new (0)
  17878 Hash.new
gmilk -a 'Hash.new'  1.42s user 0.20s system 98% cpu 1.645 total
sed 's/^.*Hash.new */Hash.new /'  0.10s user 0.00s system 6% cpu 1.644 total
sort  0.14s user 0.00s system 8% cpu 1.782 total
uniq -c  0.00s user 0.01s system 0% cpu 1.782 total
sort -n  0.02s user 0.00s system 1% cpu 1.799 total
tail -100  0.00s user 0.00s system 0% cpu 1.798 total

Hash.new { |h,k| h[k] = [] } がとても多いな。

2014-10-22 (Wed)

#1 メソッドチェーンの集計

どのようなメソッドのチェーンが多いだろうか。つまり、obj.m1.m2 というような呼び出しで、m1 と m2 の組み合わせは何が多いだろうか。

というわけで調べてみた。Ripper.sexp で構文木を取り出して、そのような式をてきとうに (完璧を目指さずに) 取り出すのは以下のように簡単にできる。

% cat extract-call-seq.rb
#!/usr/bin/ruby

# usage:
#   extract-call-seq.rb file.rb ...

require 'ripper'

def method_call_name(tree)
  if tree.is_a?(Array) &&
     tree[0] == :call &&
     tree[3].is_a?(Array) &&
     tree[3][0] == :@ident &&
     tree[3][1].is_a?(String)
    n = tree[3][1]
    n.sub!(/\A\s*\./, '') # [ruby-dev:48684] [Bug #10411]
    if /\A[a-z_A-Z][a-z_A-Z0-9]*[!?]?\z/ !~ n
      warn "unexpected method name: #{n.inspect}"
    end
    n
  else
    nil
  end
end

def check(tree)
  meth2 = method_call_name(tree)
  if meth2
    meth1 = method_call_name(tree[1])
    if meth1
      puts "#{meth1} #{meth2}"
    end
  end
end

def extract(tree)
  return unless tree.is_a? Array
  check(tree)
  tree.each {|elt|
    extract(elt)
  }
end

def main
  STDERR.puts ARGV.inspect
  ARGV.each {|fn|
    STDERR.puts fn.inspect
    extract(Ripper.sexp(File.read(fn)))
  }
end

main

各 gem の最新版を展開してあるので、そこにある *.rb なファイル全部に適用して集計すると上位100個は以下のようになる。いくつかコメントもつけてみた。

367550 u normalize            なんか機械生成されたコードみたい
 27444 size should            rspec の類ですかね
 25358 class name             クラスの名前を使うのはよくわかる
 20018 name should            rspec
 16083 should be              rspec
 15603 to_s should            rspec
 13568 length should          rspec
 13178 body should            rspec
 11638 count should           rspec 多すぎ
 11395 to_s gsub
 10983 now to_i               Time.now.to_i だとすると、これは使ってほしくないんだけどなぁ
  9990 class to_s
  9241 class should           rspec
  8884 should not             rspec
  8600 to_s split
  8479 keys each              Hash なら each_key もあるけど、破壊的変更が必要なときに hash.keys.each を使うこともあるのはわかる
  8295 to_s downcase
  7918 compact join
  7744 interval end
  7657 class new
  7579 status should
  7495 endpoint const_get
  6863 to_s upcase
  6667 value should
  6590 once with
  6584 last is_a?             引数の最後が Hash かどうかの検査とか?
  6538 now utc
  6429 keys sort
  6222 backtrace join
  6164 routes draw
  6116 sort each
  5900 should raise
  5884 any_instance expects
  5791 name to_s
  5611 id should
  5597 first should
  5560 to_s strip
  5481 first name
  5340 errors on
  5313 message should
  4917 to_i to_s              整数の文字列ってこんなに使うのだな
  4831 flatten compact
  4704 first is_a?
  4699 new tap
  4642 sort should
  4349 any_instance stub
  4339 should equal
  4338 downcase to_sym
  4315 keys first
  4270 now to_f
  4251 keys include?
  4229 now strftime
  4005 type should
  4001 text should
  3968 elements each
  3952 title should
  3865 any_instance stubs
  3811 flatten each
  3766 to_s sub
  3756 root join
  3743 id to_s
  3716 application config
  3665 be ok
  3646 sort join
  3642 values first
  3466 first id
  3454 application routes
  3424 to_s empty?
  3414 children first
  3326 once and_return
  3313 to_s camelize
  3304 path should
  3269 sql should
  3268 first to_s
  3266 keys map
  3062 to_s underscore
  3049 to_a should
  3030 be nil
  2987 errors full_messages
  2986 name underscore
  2970 strip empty?
  2963 children each
  2914 errors add
  2904 name split
  2838 sqls should
  2830 name to_sym
  2824 url should
  2800 flatten map
  2794 to_s capitalize
  2793 values each
  2793 class send
  2784 flatten uniq
  2780 logger debug
  2779 should match
  2762 any_instance should_receive
  2740 string should
  2738 all should
  2722 keys join
  2694 all each
  2671 not be

うんまぁ rspec は多いですね。

そういえば、all_symbols がどう使われているのかという話が前に (開発者会議で) あったな。

24 all_symbols count
10 all_symbols map
 8 all_symbols collect
 4 all_symbols size
 3 all_symbols select
 2 all_symbols sort_by
 2 all_symbols length
 1 all_symbols should
 1 all_symbols is_a?
 1 all_symbols clone
 1 all_symbols all?

数を数えることが多いというのはわかる。

2014-10-19 (Sun)

#1

GitHub: milkode: Use Open3.pipeline to avoid shell.

2014-10-10 (Fri)

#3

GitHub: milkode: grenfiletest.rb:12:in `match': invalid byte sequence in UTF-8 (ArgumentError)

#2

GitHub: rubygems: Don't define MirrorCommand if already defined.

#1 gem のサイズの集計

gem mirror が終わったので、手元にはたくさん gem がある。具体的には 477367個、137GB

とりあえずどんなサイズの gem があるだろうかということでグラフにしてみた。

gem-size.png

10KB くらいが多い。

2014-10-09 (Thu)

#1 gem のバージョンのグラフ

ふと gem mirror をしたところ、spec.4.8 というファイルが目についた。

一瞬 gemspec が入っているのかな、と思ったが、中身を覗いてみると名前、バージョン、プラットフォームだけだった。

% ruby -rpp -e 'pp Marshal.load(File.binread("specs.4.8"))'
[["_", Gem::Version.new("1.0"), "ruby"],
 ["_", Gem::Version.new("1.1"), "ruby"],
 ["_", Gem::Version.new("1.2"), "ruby"],
 ["-", Gem::Version.new("1"), "ruby"],
 ["0mq", Gem::Version.new("0.1.0"), "ruby"],
 ["0mq", Gem::Version.new("0.1.1"), "ruby"],
 ["0mq", Gem::Version.new("0.1.2"), "ruby"],
 ["0mq", Gem::Version.new("0.2.0"), "ruby"],
 ["0mq", Gem::Version.new("0.2.1"), "ruby"],
 ["0mq", Gem::Version.new("0.3.0"), "ruby"],
...

これで何かできるだろうかと思って、とりあえず最初の数の分布を調べてみた。(同じ名前のパッケージはいちばん大きなバージョン以外は無視)

% ruby -rpp -e '
Marshal.load(File.binread("specs.4.8")).
group_by {|ary| ary[0] }.
each {|k, vs|
  v = vs.map {|ary| ary[1] }.max
  puts v.to_s[/\d+/].to_i
}
'|sort -n |uniq -c
  68581 0
  15280 1
   2943 2
    945 3
    332 4
     82 5
     46 6
     27 7
     13 8
     26 9
     17 10
     12 11
      3 12
      7 13
      7 14
      2 15
      2 19
      2 20
      2 21
      1 22
      1 26
      1 28
      2 30
      1 35
      5 42
      1 99
      1 100
      1 111
      1 969
      1 971
      1 1987
      2 2004
      1 2006
      2 2008
     22 2009
      6 2010
     19 2011
     15 2012
     26 2013
     35 2014
      1 3000
      1 7159
      2 9000
      1 9001
      1 50000
      1 20120125
      1 20120701
      1 20121026
      1 2009022403
      1 2010072900

バージョン0 が多いというのはたしかにそうだな。

グラフを描いてみよう。

ふつうに描くと Y軸、X軸にはりついてしまうのだが、さりとて 0 があるから対数グラフには向かないしどうしたものか、と思いつつとりあえず ggplot2 で対数グラフにしたら 0 は印がはみ出る感じに描かれるのだな。

top-version.png

両対数グラフで、バージョン10あたりまでは直線的に落ちていく感じ。

2014-09-29 (Mon)

#1

shellshock で有名になった、シェル関数を子孫のプロセスに export するという bash の機能は、どうも ksh88 に由来するような気がする。

ksh88 のマニュアル には typeset で、-f (関数) と -x (export) を組み合わせられると記述されている。

ksh88 はあまり生き残っていないのだが、AIX 7.1 にまだあったので試してみた。試行錯誤の結果、以下のようにすると動くことが確認できた。

-bash-4.2$ ksh
$ echo bar > bin/foo       # foo という名前のファイルで shell procedure を作る。中身は bar を呼び出す
$ chmod 755 bin/foo        # shell procedure は実行可能でないといけないので実行可能にする
$ which foo                # which で foo が見つかる
/home/akr/bin/foo
$ foo                      # ここで foo を呼び出すと bar が見つからないというエラー
foo: bar:  not found                                                                                  .
$ bar () { echo baz; }     # bar を関数として定義する
$ foo                      # ここで foo を呼び出しても bar は見つからない
foo: bar:  not found                                                                                  .
$ typeset -fx bar          # bar を export するように設定する
$ foo                      # foo を呼び出すと bar が呼び出される
baz
$

試してわかったのだが、これは bash のように環境変数を介する機能ではないようだ。例えば、ksh -c bar としたり、foo の先頭に #!/bin/ksh をつけると bar の定義は伝わらない。

truss すると以下のようになる。

-bash-4.2$ truss -f ksh -c 'bar () { echo baz; }; typeset -fx bar; foo'
...
12124234: 51511337: execve("/home/akr/bin/foo", 0x200119B8, 0x20011EB8) Err#8  ENOEXEC
12124234: 51511337: kopen("/home/akr/bin/foo", O_RDONLY)        = 3
...
baz
12124234: 51511337: kwrite(1, " b a z\n", 4)            = 4
...

つまり、ksh は foo を execve しようとして失敗し、しょうがないので foo を自分で読んで処理して (bar を呼んで) baz を出力する、という流れである。

execve してないので呼び出し元の情報が利用可能で、typeset により export とマークされている bar を見つけて使うのであろう。

というわけで、もし ksh88 が使われていても、(環境変数は関係ないので) shellshock のような話にはならないだろう。

ちなみに、ksh93 では動かない。(ksh93 のマニュアルでも、typeset で -f と -x を組み合わせた時の動作の記述は消えている。)

-bash-4.2$ ksh93 -c 'bar () { echo baz; }; typeset -fx bar; foo'
foo: line 1: bar: not found

なお、pdksh のマニュアル には typeset -fx は効果がないことが書いてある。

2014-09-19 (Fri)

#1

本日のまつもとさんの発表 (基調講演) をネタになるせさんと議論した結果、非常に簡単化した静的解析なら簡単に実装できることがわかったので実装してみた。

% cat static-check.rb
class C
  def m
    xxxx
  end
end

def_methods = {}
call_methods = {}
ObjectSpace.each_object {|o|
  methods = []
  methods.concat o.instance_methods.map {|msym| o.instance_method(msym) } if Module === o
  methods.concat o.methods.map {|msym| o.method(msym) }

  methods.each {|m|
    def_methods[m.name] = true
    asm = RubyVM::InstructionSequence.disasm(m)
    next if !asm
    asm.scan(/mid:([a-zA-Z0-9]+)/) {
      call_methods[$1] = true
    }
  }
}

a = call_methods.keys - def_methods.keys
if !a.empty?
  p a.sort
end
% ruby --disable-gems static-check.rb
["synchronize", "xxxx"]

これにより、定義されていないけれど呼び出しが存在する、xxxx というメソッドを発見できている。いままでは実行が xxxx というメソッド呼び出しに到達しないかぎりこのようなものを発見することはできなかったのだが、ここではそこを実行せずに発見している。

話は簡単で、すべてのメソッド定義のメソッド名をリストアップし、すべてのメソッド呼び出しのメソッド名をリストアップし、後者だけに存在するメソッド名を表示しているだけである。(実装が不完全であるのはわかっているので気にしていない)

飲み会の中で実装したのだが、ちょうどそこにいたまつもとさんやささださんなどに見せていろいろな話が出た。

単純な typo の発見ならこれでもかなり効くだろうとか、定期的に実行して名前の増減を監視すればどうかとか。いつやるかが問題だけど、Rails みたいなのはリクエストを受付始める前でやればいいとか、require の終わりでやればいいとか。あと、InstructionSequence#to_a が使えるだろうとか。



田中哲