サンデーダウの信頼性検証
昨今の株価暴落で、twitterを検索するとサンデーダウが若干流行っていたので、信頼性を検証してみる。(ざっくり検証のため、参考程度でお考えください)
①価格のグラフを書いてみる
- 青色の期間:サンデーダウのみ取引可能時間
- 橙色の期間:CFDの取引可能時間でプロットした。
概ね陸続きになっているように見える。(拡大しないと分かりにくいが)
Web Browserを作った
Sushi BrowserというWebブラウザを作りました。
Electronというデスクトップアプリ用のフレームワークがあり、
さらにそのForkのmuonというライブラリを使っている。
特徴はマルチパネルでいろいろ操作できること。
検索しながら別の窓で開くみたいなことができます。
とにかく自分が使いたい機能を詰め込んだ感じになります。
少しでも気になった方は、ぜひご利用ください。
↓デモ画像(GIF)
Rubyの配列、ハッシュ、文字列のメモリ使用量を見る。そして、固定長テキスト最強説..
※一行のサイズが40byte以下だとあまり良い検証にならないことに気づいたので、再検証予定。
最近よくRubyで大規模テキストの処理をしています。
大規模と言っても、数GBレベルで今どきのマシンであれば全部メモリに乗せれるんじゃ..というサイズになります。
ということで、Ruby(ver.2.3)のArray,Hash,Stringのメモリ使用量を比較してみました。※Windowsで実施。(気が向いたらLinuxでも)
まず入力用テキストを生成。(その後、改行込みで10MBになるように加工)
1行は「000000074,bx,75,M,182,62」な感じになります。
※読込み時は、String,String,Fixnum,String,Fixnum,Fixnumとしています。
open("input.txt","wb"){|w| w.puts %w[No Name Age Sex Height Weight].join(",") str = "a" (0..4000000).each{|i| w.puts [sprintf("%09d",i),str.next!,rand(100),rand(2)==0 ? "M" : "F", rand(60)+130, rand(60)+40 ].join(",") } }
次にそのINPUTに対し、メモリ割り当てを実施した結果が以下。
・メモリ割り当て(MB)
Hash Array | Array Array | Array Hash | Array Class | Hash String | Array String | Big String (<<) | Big String (read once) | Big String (array join) | |
---|---|---|---|---|---|---|---|---|---|
Hash | 18.2 | 0 | 86.2 | 0 | 18.2 | 0 | 0 | 0 | 0 |
String | 28.7 | 28.7 | 28.7 | 28.7 | 28.7 | 14.4 | 11.5 | 10.0 | 9.3 |
Array | 14.4 | 17.7 | 3.3 | 3.3 | 0 | 3.3 | 0 | 0 | 0 |
Human | 0 | 0 | 0 | 31.6 | 0 | 0 | 0 | 0 | 0 |
Fixnum | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Symbol | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Total | 61.3 | 46.4 | 118.2 | 63.6 | 47.0 | 17.7 | 11.5 | 10.0 | 9.3 |
・オブジェクト数
Hash Array | Array Array | Array Hash | Array Class | Hash String | Array String | Big String (<<) | Big String (read once) | Big String (array join) | |
---|---|---|---|---|---|---|---|---|---|
Hash | 1 | 0 | 376503 | 0 | 1 | 0 | 0 | 0 | 0 |
String | 753008 | 753008 | 753008 | 753008 | 753006 | 376503 | 1 | 1 | 1 |
Array | 376503 | 376504 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
Human | 0 | 0 | 0 | 376503 | 0 | 0 | 0 | 0 | 0 |
Fixnum | 160 | 160 | 160 | 160 | 0 | 0 | 0 | 0 | 0 |
Symbol | 0 | 0 | 6 | 0 | 0 | 0 | 0 | 0 | 0 |
Total | 1129672 | 1129672 | 1129678 | 1129671 | 753007 | 376504 | 1 | 1 | 1 |
結果を見るに、Hashはメモリ使用量が多く、Arrayはかなり少なくなっています。
Hash+Arrayで1配列で40byte、Array+Hashで1Hashで240byteと6倍の差があります。
大きな配列/Hashだと、Array+Hashで3.3Mbyte、Hash+Arrayで1Hashで18.2Mbyteと同じく6倍の差があります。
よって、メモリが足りない場合はHash+Arrayではなくて、Array+Stringでbsearchを使ってメモリを削減しましょう!
例:arr.bsearch{|e| "00000001" <=> e[0..7]}.split(",") #O(logN)で若干遅いが、メモリ使用率は3分の1以下!
Classは意外と小さく、88byte/Humanとなりました。StringはArrayと同じ40byte(いわゆるRVALUEというやつのようです)でした。
また、Symbol/Fixnumはほとんどパターンがないので、メモリはほとんど使われていません。(Array/Hashの参照のみ)
よって、一定パターンの整数や文字列が中心のテキストの場合は、Arrayで持つと効率的になると考えられますが、様々な文字列が存在するとStringとArrayのオーバーヘッドでメモリ使用率が上がります。
一方で、Big Stringはテキストサイズが、ほぼメモリ使用量になっています。また、<<で文字列を結合していくと余計なメモリが割り当てられることがわかります。
ということで、今は懐かしの固定長テキストを1つのStringで保持して配列操作ライクなことができれば、メモリ使用率最強(Hash+Arrayの6分の1!)とみなして以下のライブラリを作ってみました。(半分ネタですが、暇になったらテスト作ってgemにしたい)
ちなみに、検索/値代入は配列の2~4倍遅いですが、メモリに入ったら勝ち理論なので問題ないと考えています。
bsearchはRuby/Bsearchhttp://0xcc.net/ruby-bsearch/の丸コピです..
class FixedArray include Enumerable def initialize str,index,len @str,@index,@len = str,[index.first,index.size],len end def [] i @str[i*@len+@index[0],@index[1]] end def []= i,val @str[i*@len++@index[0],@index[1]] = val end def each (0...size).each{|i| yield @str[i*@len++@index[0],@index[1]] } end def size @str.size/@len end alias :length :size #Below here is a copy of Ruby/Bsearch[http://0xcc.net/ruby-bsearch/] # # The binary search algorithm is extracted from Jon Bentley's # Programming Pearls 2nd ed. p.93 # # # Return the lower boundary. (inside) # def bsearch_lower_boundary (range = 0 ... self.length, &block) lower = range.first() -1 upper = if range.exclude_end? then range.last else range.last + 1 end while lower + 1 != upper mid = ((lower + upper) / 2).to_i # for working with mathn.rb (Rational) if yield(self[mid]) < 0 lower = mid else upper = mid end end return upper end # # This method searches the FIRST occurrence which satisfies a # condition given by a block in binary fashion and return the # index of the first occurrence. Return nil if not found. # def bsearch_first (range = 0 ... self.length, &block) boundary = bsearch_lower_boundary(range, &block) if boundary >= self.length || yield(self[boundary]) != 0 return nil else return boundary end end alias bsearch bsearch_first # # Return the upper boundary. (outside) # def bsearch_upper_boundary (range = 0 ... self.length, &block) lower = range.first() -1 upper = if range.exclude_end? then range.last else range.last + 1 end while lower + 1 != upper mid = ((lower + upper) / 2).to_i # for working with mathn.rb (Rational) if yield(self[mid]) <= 0 lower = mid else upper = mid end end return lower + 1 # outside of the matching range. end # # This method searches the LAST occurrence which satisfies a # condition given by a block in binary fashion and return the # index of the last occurrence. Return nil if not found. # def bsearch_last (range = 0 ... self.length, &block) # `- 1' for canceling `lower + 1' in bsearch_upper_boundary. boundary = bsearch_upper_boundary(range, &block) - 1 if (boundary <= -1 || yield(self[boundary]) != 0) return nil else return boundary end end # # Return the search result as a Range object. # def bsearch_range (range = 0 ... self.length, &block) lower = bsearch_lower_boundary(range, &block) upper = bsearch_upper_boundary(range, &block) return lower ... upper end end class FixedMatrix include Enumerable def initialize str,indexes,len @str,@indexes,@len = str,indexes,len @indexes = @indexes.map{|range| [range.first,range.size]} end def [] i,j ind = @indexes[j] @str[i*@len+ind[0],ind[1]] end def get_row i @str[i*@len,@len] end def []= i,j=0,val ind = @indexes[j] @str[i*@len+ind[0],ind[1]] = val end def each (0...row_size).each{|i| @indexes.each{|ind| yield @str[i*@len+ind[0],ind[1]] } } end def row_size @str.size/@len end end #Usage big_str = open("input_fixed.txt","rb").read fa = FixedArray.new(big_str,0..25,28) #1行28文字で、改行はCRLF。 fm = FixedMatrix.new(big_str,[0..8,10..13,15..16,18..18,20..22,24..25],28) #1行28文字で、1カラム目が0-8文字目、2カラム目が10-13文字目...、改行はCRLF。 fa[fa.bsearch_first{|x| x[0..8] <=> "000202764" }].split(",") #=> ["0002027642,"kmxr","69","F","149","68"] fm.each{|x| x} # x => "000000074"," bx","75","M","182","62"
・メモリ割り当ての検証コード
require 'objspace' class Human attr_accessor :no,:name,:age,:sex,:height,:weight def initialize no,name,age,sex,height,weight @no,@name,@age,@sex,@height,@weight = no,name,age,sex,height,weight end end #計測開始 start = Time.now hash_arr = {} arr_arr = [] arr_hash = [] arr_class = [] hash_str = {} arr_str= [] b_str = "" open("input.txt","rb").each_with_index{|x,i| next if i == 0 puts "#{i}:#{Time.now - start}" if i % 100000 == 0 x = x.chomp y = x.split(",",-1) z = y.map.with_index{|e,j| [0,1,3].include?(j) ? e.freeze : e.to_i } hash_arr[z[0]] = z[1..-1] arr_arr << z arr_hash << {}.tap{|h| [:no,:name,:age,:sex,:height,:weight].each_with_index{|x,i| h[x] = z[i]}} arr_class << Human.new(*z) hash_str[y[0]] = y[1..-1].join(",") arr_str << x b_str << x } b_str2 = open("input.txt","rb").read b_str3 = arr_str.join("") puts Time.now - start GC.start puts "Hash+Array" puts Hash.new{|h,k| h[k] = []}.tap{|mem| hash_arr.tap{|x| mem[x.class] << x}.each{|k,v| mem[k.class] << k; mem[v.class] << v; v.each{|x| mem[x.class] << x}}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n") puts puts "Array+Array" puts Hash.new{|h,k| h[k] = []}.tap{|mem| arr_arr.tap{|x| mem[x.class] << x}.each{|x| mem[x.class] << x; x.each{|x| mem[x.class] << x}}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n") puts puts "Array+Hash" puts Hash.new{|h,k| h[k] = []}.tap{|mem| arr_hash.tap{|x| mem[x.class] << x}.each{|h| mem[h.class] << h; h.each{|k,v| mem[k.class] << k; mem[v.class] << v}}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n") puts puts "Array+Class" puts Hash.new{|h,k| h[k] = []}.tap{|mem| arr_class.tap{|x| mem[x.class] << x}.each{|c| mem[c.class] << c;c.instance_variables.each{|v| val = c.instance_variable_get(v);mem[val.class] << val}}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n") puts puts "Hash+String" puts Hash.new{|h,k| h[k] = []}.tap{|mem| hash_str.tap{|x| mem[x.class] << x}.each{|k,v| mem[k.class] << k; mem[v.class] << v}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n") puts puts "Array+String" puts Hash.new{|h,k| h[k] = []}.tap{|mem| arr_str.tap{|x| mem[x.class] << x}.each{|x| mem[x.class] << x}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n") puts puts "Big String(<<)" puts Hash.new{|h,k| h[k] = []}.tap{|mem| b_str.tap{|x| mem[x.class] << x}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n") puts puts "Big String(read once)" puts Hash.new{|h,k| h[k] = []}.tap{|mem| b_str2.tap{|x| mem[x.class] << x}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n") puts puts "Big String(array join)" puts Hash.new{|h,k| h[k] = []}.tap{|mem| b_str3.tap{|x| mem[x.class] << x}}.map{|k,v| [k,v.uniq.size,v.uniq.reduce(0){|sum,e| sum + ObjectSpace.memsize_of(e)} / (1024.0*1024)].join("\t")}.join("\n")
Maybeモナド的な何かに惹かれて、RailsのtryのgetOrElse的なものを作った。
皆さん、tryっていいですよね。でも、MayBeモナド的なgetOrElseにつながるチェーンもかっこいいですよね。
※ScalaでいうところのList(0,-1,1).find(_ > 1).getOrElse(999) // 999
ということで、以下を考えました。
class Object def try_else(*a,else_val, &b) if a.empty? #else値がない場合は、elseの場合はnilを返却 a = [else_val] else_val = nil end if a.empty? || respond_to?(a.first) ret = if a.empty? && block_given? if b.arity.zero? instance_eval(&b) else yield self end else public_send(*a, &b) end ret.nil? ? else_val : ret #実行結果がnilの場合はelse値を返却 end end end class NilClass def try_else(*args,else_val) args.empty? ? nil : else_val #selfがnilの場合は、引数の最後を返却。必ず、nilを返したほうが良い? end end
使い方は以下。チェーンが短くなってイイ!
#tryの場合 [0,-1,1].try(:find){|x| x > 1} #=> nil [0,-1,3].try(:find){|x| x > 1} #=> 3 nil.try(:find){|x| x > 1} #=> nil #tryのgetOrElse版の場合 (最後の引数をnilの場合に返却) [0,-1,1].try_else(:find,999){|x| x > 1} #=> 999 [0,-1,3].try_else(:find,999){|x| x > 1} #=> 3 nil.try_else(:find,999){|x| x > 1} #=> 999 #Else値を入れないと、nilを返却。但し、引数がないメソッドの場合のみ。 [0,-1,1].try_else(:find){|x| x > 1} #=> nil [0,-1,3].try_else(:find){|x| x > 1} #=> 3 nil.try_else(:find){|x| x > 1} #=> nil #tryのgetOrElse版と同じことをtryでしようとする、結構だるい。 #tapからのbreakで値返しが一番楽そう。 [0,-1,1].try(:find){|x| x > 1}.tap{|x|break 999 if x.nil? } #=> 999
rsyncでサーバ移行
さくらのVPSで上位プランに移行するための手順 - さくらインターネット創業日記で書いてある通りにやったが「Bringing up interface eth0: Device eth0 has different MAC address than expected, ignoring, FAILED」と出てネットワークが認識されなかったため、備忘録。
↓のコマンドで上手く行った。
rsync -rtlzvogpHAX --delete --exclude /boot/ --exclude /lib/modules/ --exclude /etc/udev/rules.d/70-* --exclude /dev/ --exclude /proc/ --exclude /sys/ --exclude /var/run/ --exclude /var/lock/ --exclude fstab --exclude /etc/sysconfig/network-scripts/ifcfg* --block-size=4096 -e ssh / XXX.XXX.XXX.XXX:/
70-persistent-cd.rules、70-persistent-net.rules がコピーされて、MACアドレスの認識がおかしくなっていた模様。
ActiveRecordのwhereが素晴らしい件
Railsのwhereは非常に使い勝手が良い。
慣れれば、普通にSQLを書くより読みやすいと思う。
一番簡単な例だと、次のものになるが、まあ便利ですねくらいのものである。
Article.where(id: [1,3]) #=> select * from articles where id in (1,3)
しかし、次のようにサブクエリを使うことができる点が素晴らしい。
Article.where(id: Blog.select(id).where(title: 'hoge')) #=> select * from articles where id in (select id from blogs where title = 'hoge')
当然、一度変数に落としてサブクエリ化もできる。
sub_query = Blog.select(id).where(title: 'hoge') Article.where(id: sub_query) #=> select * from articles where id in (select id from blogs where title = 'hoge')
他にも、NOT IN な SQLが書けたりする。すごい
Ruby on Rails で NOT IN な SQL をかく。 - そんなこと覚えてない
正規表現クックブック(O'Reilly Japan)読感
正規表現クックブックを読んだ。
詳説 正規表現とどちらを買うか迷ったのだが、
本屋でぱっと見、先読みとか後読みについて詳しく書いてたのが
クックブックの方だったので購入した。
さて、僕は基本的にはRubyを第一言語で使っており、そこそこ正規表現は使えると思っていたりする。
が、正直先読み後読みとかなんかはさっぱりだったので、もぐりの正規表現使いだなぁと感じていた。
前置きはさておき、この本はどうだったのか。
一言で言えば、流石のオライリークオリティでした。
内容を大きく割ると、
①1~2章:正規表現の基本について説明
②3~8章:正規表現応用(まさしくクックブック)
という感じになっている。
①は圧巻の内容。これを読まないと正規表現については語れませんねって感じ。
特に「アトミックグループ」と「先読み後読み」に関しては必ず読むべき。
アトミックはパフォーマンス向上に必須(僕はこれを使ってみて、10倍くらい早くなった)で、先読み後読みのアンカーという性質は非常に便利。(\bの拡張版という感じ)
②に関しては、様々な問題に対し、複数の言語(C#,Java,php,ruby,python,perl)で
どのように実装するかが書かれている。
ちょっと飛ばしながら読んでしまったところはあるが、rubyに関しては
大体知っている内容だったので、Rubyistの皆さんご存知ではないかと思う。
一方、他の言語で正規表現を使うときには良いリファレンスになると思う。
rubyでいうところの、gsub(/(re)/){|x| x}やscan(/(re)/){|x| x}を他の言語で
どう実現するかというのを知ることができる。
Perlは流石の短さでかけていた。(Perlすごい!)
JavaとかC#は長くなるので、コメントがかなり書いてあるサンプルコードが掲載されていた。
ということで、総論として、1,2章はしっかり読んで、3章からはリファレンスとして利用しよう!という感じです。(アメリカの郵便番号とか正直どうでもいい例もありますが)
あ、後クレジットカード番号について詳しくなれますよ!