備忘録的な何か

技術ブログ的な何かです

サンデーダウの信頼性検証

昨今の株価暴落で、twitterを検索するとサンデーダウが若干流行っていたので、信頼性を検証してみる。(ざっくり検証のため、参考程度でお考えください)

www.ig.com

①価格のグラフを書いてみる
  • 青色の期間:サンデーダウのみ取引可能時間
  • 橙色の期間:CFDの取引可能時間でプロットした。

概ね陸続きになっているように見える。(拡大しないと分かりにくいが)

f:id:geecooker:20200315204428p:plain
2019年3月16日~2019年9月13日

f:id:geecooker:20200315204502p:plain
2019年9月14日~2020年3月12日

②値の変動幅をグラフ化してみる
  • 青色の期間:サンデーダウのCFD開始直前の終値 - CFDの週末終値
  • 橙色の期間:CFDの週初始値 - CFDの週末終値

変動幅にブレはあるが、大きな変動の場合、上げか下げかはほぼ100%一致している。

f:id:geecooker:20200315205407p:plain
変動幅比較

よって、過去1年間をみると、概ね信頼できるのではと判断できる。

しかし、①のグラフを見ると、サンデーダウの大きな変動後に取引開始した場合、
結局その後は、上か下に大きく変動している。

そのため、始値としては概ね信頼できるが、
サンデーダウが大きく動く→
ボラティリティが高い状態→
市場ではサンデーダウの変動価格より大きく上下する可能性が高い

という結論に至りました。コロナ恐ろしい

Web Browserを作った

Sushi BrowserというWebブラウザを作りました。

Electronというデスクトップアプリ用のフレームワークがあり、
さらにそのForkのmuonというライブラリを使っている。

特徴はマルチパネルでいろいろ操作できること。
検索しながら別の窓で開くみたいなことができます。

とにかく自分が使いたい機能を詰め込んだ感じになります。
少しでも気になった方は、ぜひご利用ください。

公式サイト(日本語)
公式サイト(英語)

↓デモ画像(GIF)
f:id:geecooker:20170729235334g:plain

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章からはリファレンスとして利用しよう!という感じです。(アメリカの郵便番号とか正直どうでもいい例もありますが)

あ、後クレジットカード番号について詳しくなれますよ!