『[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識』読了

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

結構前に読了してたけど書いてなかったので書きます。

ざっというと、OS初心者向けの解説本ですね。タイトルから受ける印象とレベル感が合っていたと思います。
僕自身も適当に ubuntu 触ったりふつリナやったくらいでそんなに詳しいわけじゃないのですが、特に詰まることなく読み進められました。
本書の特徴として書名にもあるように「試して理解」という点が挙げられています。具体的なソースコードと計測結果をもとにどう動いているかを解説するコンセプトは非常に面白かったです。

github.com

(が、上記レポを見ればわかるとおりそんなに自分ではやってません……。特に興味があったところくらい)

なお、Vagrant 上で試す場合には /vagrant のような共有フォルダ上ではファイル操作がうまく動かないことがあるのでご注意ください。確か、mmap とかやってるときにうまくいかなかった。

普段はあんまり意識していないOOMの話やファイルシステムの話に触れられたのがよかったです。この次のステップとして挙げられていた本がだいたいイカツいのでどうしたもんかと困っていますが、まあ気長にやっていこうと思います。はい。

1点だけ難点をあげると、誤記がかなり多かったように思いました。正誤表もあるので、版が進むにつれてよくなっていくとは思います。なのでそんなに気にすることではないかもしれません。

RubyVM::AST を用いた Ruby 製 Ruby インタプリタを作っている話

Ruby では v2.6 から RubyVM::AST というのが入ることになりまして、Ruby ランタイム上から AST を取得できるようになります。この話は以前に書いたので省略しますが、ASTがとれるということはASTを解釈して実行することもできるわけです。やるしかないですね。

というわけでやったのがこれです。まだ全然途中ですが。

github.com

デモ

if 式を解釈できるようす

# spec/snippets/if.rb
a = true
if a
  puts "truthy"
else
  puts "falsy"
end

b = false
if b
  puts "truthy"
else
  puts "falsy"
end
$ bin/stray spec/snippets/if.rb
truthy
falsy

メソッドを定義したりできてるようす

# spec/snippets/method.rb
def foo
  puts :foo
end

foo

def bar(arg)
  puts arg
end

bar :bar

def blk
  yield 1
end

blk do |arg|
  puts arg
end
$ bin/stray spec/snippets/method.rb
foo
bar
1

進捗

プロジェクトとしての進捗はこんな感じです。今回は紹介程度に留めて、実装時に留意したことなどは別エントリに書きます。インタプリタの実装をするのは初めてだったのでいろんな壁にぶつかりながらやってます。

できること

  • Integer, String, Symbol, Array あたりのリテラル
    • Float とかそういうのはちょっと面倒なんでやりません
  • ローカル変数代入
  • メソッド定義
    • オプショナル引数、キーワード引数はまだ
  • メソッド呼び出し
  • yield
  • if 式
  • ぼっち演算子

できないこと

  • クラス定義
    • サボってます
    • rb_cClass とかの定義を見てそれに合わせないとダメなのはわかってるんだけどオレオレ実装してしまったので修正が面倒……。
    • 当然継承とか include とか extend とか using ( refinements )とか
  • Kernel モジュール、Objectクラスに定義されてるだいたいのもの
    • 実装するのがめんどくさくて puts くらいしかやってない
  • case 式
    • === をちゃんとするのが単純にめんどくさそうでやってない
  • lambda とか

ちょっと外れた話

cruby のソースを読んでたらフォーマットが他と違うところを発見したのでPRしました。

Use nd_X shorthand for annotation by hkdnet · Pull Request #1901 · ruby/ruby · GitHub

また、実装していると NODE_DEFN から nd_mid を取れないことに気づきました。 NODE_DEFNdef foo; end のようなメソッド定義を示すノード種別です。nd_mid はメソッド名に対応するシンボルです。先程の例だと :foo という情報です。これがないとなんのメソッドを定義したのかわかりません。 twitter で y.kaneko さん( @spikeolaf )に質問したら快く修正してくれました。

新しい機能を実際に使ってみた結果をフィードバックをする人はたぶん少ないので意味がありそうですね。

オフラインリアルタイムどう書く E25 の解答

qiita.com

オンライン参加で解けました。17:02なので45分くらいでしょうか。

方針としては、たかだか200x200マス = 40000通りなので、スタート地点以外の1点を選んでベクトル作ってベクトル回して伸ばして座標が盤面内にあるかどうかを確認するので間に合いそうです。おわり。

というのを問題をみて3分くらいで決めたんだけど、回転下手なのと盤面の境界条件を間違えるといういつもので苦戦してました。 というわけでコード。割と汚めです。

class Solver
  def initialize
  end

  def solve(input)
    w, h, str = input.split(',', 3)
    @w = w.to_i
    @h = h.to_i
    x, y = str.scan(/\d+/).map(&:to_i)
    answer = {}
    s = [x, y]

    @w.times do |sx|
      @h.times do |sy|
        v = [sx - x, sy - y]
        ps = points(s, v)
        if ps.all? { |p| valid?(p) }
          l = len_of(v)
          answer[l] ||= []
          answer[l] << ps
        end
      end
    end

    m = answer.keys.max
    if answer[m].size == 1 && m != 0
      answer[m].first.map { |p| "(#{p.first},#{p.last})" }.join(",")
    else
      '-'
    end
  end

  def len_of(v)
    Math.sqrt(v.first**2 + v.last**2)
  end

  def valid?(p)
    (0...@w).include?(p.first) && (0...@h).include?(p.last)
  end

  def points(s, v)
    sx, sy = s
    a, b = v
    p1 = [sx + a, sy + b]
    p2 = [p1.first - b, p1.last + a]
    p3 = [p2.first - 2*a, p2.last - 2*b]
    [p1, p2, p3]
  end
end

リファクタリングしたのでこっちのがみやすいかも。あとサイズ小さいやつとか見る意味ないじゃんって気づいたので途中で next いれるようにしました(max_l あたり)

class Solver
  Pair = Struct.new(:x, :y) do
    def add(other)
      Pair.new(x + other.x, y + other.y)
    end

    def sub(other)
      Pair.new(x - other.x, y - other.y)
    end

    def multi(s)
      Pair.new(x * s, y * s)
    end

    def rotate270
      Pair.new(-y, x)
    end

    def to_s
      "(#{x},#{y})"
    end

    def size
      Math.sqrt(x**2 + y**2)
    end
  end

  def solve(input)
    w, h, str = input.split(',', 3)
    @w = w.to_i
    @h = h.to_i
    x, y = str.scan(/\d+/).map(&:to_i)
    answer = {}
    s = Pair.new(x, y)
    max_l = 0

    @w.times do |sx|
      @h.times do |sy|
        v = Pair.new(sx, sy).sub(s)
        next if v.size < max_l
        ps = points(s, v)
        if ps.all? { |p| valid?(p) }
          l = v.size
          answer[l] ||= []
          answer[l] << ps
          max_l = l if l > max_l
        end
      end
    end

    m = answer.keys.max
    if answer[m].size == 1 && m != 0
      answer[m].first.map(&:to_s).join(",")
    else
      '-'
    end
  end

  def valid?(p)
    (0...@w).include?(p.x) && (0...@h).include?(p.y)
  end

  def points(s, v)
    p1 = s.add(v)
    p2 = p1.add(v.rotate270)
    p3 = p2.sub(v.multi(2))
    [p1, p2, p3]
  end
end

TaPL読書録 #5

前回: hkdnet.hatenablog.com

今回は6章まるごと。

アルファ変換を許可する、というのは第5章で学んだが、しかし変数名が重複してるんだっけ?という疑念は常につきまとう。特に理論的な部分ではなく実装するという観点においては「標準の」の表現を定めておいたほうがいいとうことで デブルーイン ド・ブラウンの de Bruijn index という概念を導入してがんばるよ、という章である。

p.57
手法(4) の「明示的代入」とはなにか?がよくわからなかった。"explicit assignment" か?と思いググってみたがそれっぽいのが見つからず……( "explicit assignment lambda calculus" でぐぐった)。その場では放置したが後日論文から引けばいいのか、と思って著者から検索したら以下がヒット。 explicit substitution だったらしい。

http://www.hpl.hp.com/techreports/Compaq-DEC/SRC-RR-54.pdf

p.59 \w. y w -> \. 4 0 なのが初見殺しっぽい。ラムダ内なのでシフトが起きていて、名前付け文脈Τにおいて y = 3 なのに 4 になっている。

p.59 演習 6.1.5
ここの問題により「同じ名前がでてきても大丈夫であること」が確認できたとの声あり。

p.59 最終段落
[1 -> s](\. 2) の元になる de Bruijn index を用いない表現を考えられるか?という問題で僕が「これでしょ〜」って提示したのが間違ってて死亡。
提示したのは T: z => 2 という名前付け文脈における \x. -> x z だったのだけどこれはラムダ内に入るときにシフトが起きるので T: z => 1 における、 \x. -> x z が正しい(直後に書いてある)。

演習問題としてはやるだけが多かったのでさくさく進み、やるだけじゃないのが出てきたときはみんな「わからん」と言っていたのでアレだった。
さくっと答え合わせしたものを以下に書いておく

ex 6.2.3 -> スルーした
ex 6.2.6 -> 代入しても中でラムダができた場合との増減で相殺するからそうなるんじゃないの、とのこと
ex 6.2.8 -> スルー ex 6.3.1 -> 「0があってもラムダがあるのでセーフ」という走り書きが残っている
ex 6.3.2 -> ラムダの数をLとした場合に de Bruijn index i と de Bruijn level l について l = L - i として変換すればヨサソウ

次回7章全部。7章の末尾には「実装したからといって、理解したとは限らない」という挑戦的な文言が載っていることに注意。

RubyKaigi2018 基調講演2日目レポートで書かなかったことの補足

gihyo.jp

本記事は上記リンクの記事に関する補足です。プログラミング初心者です、Rails しかやってません、みたいな人でもわかることが望ましいとは思っていたのですが紙面の都合及びやたら技術解説がある記事になってもなあと思い書きませんでした。
明らかに説明足りてねーよな、というところについて補足します。

バインディング

Ruby/GTK3はGTK+プロジェクトのRubyバインディングです。言い換えると,C言語で書かれたGTK+にあるAPIRubyから実行できるようにするライブラリです。

バインディング」というのがそもそも「他言語で書かれたライブラリにあるAPIを、ある言語で実行できるようにするもの」みたいな感じですかね。別の言葉では ffi ( foreign function interface ) と呼ばれています。wikipedia によると ffiCommon Lisp 由来の言葉で、バインディングというのが Ada などに由来すると書かれています。
C や C++ で書かれているミドルウェアより下のレイヤについて Ruby などのからアクセスするのはだいたいバインディングの形式をとっている気がします。たまーにそういったバインディングではなくピュア実装です、と銘打ってるライブラリがあったりします。例えば nodejs の mysql ライブラリは "A pure node.js JavaScript Client implementing the MySql protocol." と自称しています。おそらくですがバインディングを使うとC, C++ のレイヤでブロックしてしまいノンブロッキングにならないからだと思います。

rb_gc_adjust_memory_usage

バインディングを利用していると、あるオブジェクトを new するときに実際には他にメモリ領域を確保している、ということが起きがちです。

  1. Ruby レベルで foo = MyLib::Foo.new する
  2. バインディングしてるCレベルで malloc がおきる <- コレ
  3. foo がスコープを抜けたとかでGCされる
  4. foo のデストラクタで free される

RubyGC があるので上記のままでも特に問題がないように思えますが、このままではGCを実行すべきタイミングをうまく予想できないという問題が隠れています。 Ruby から見るとあくまでも fooMyLib::Foo インスタンス1つ分のメモリしか使用していません。そのため Ruby は「まだ全然メモリ使ってないからGCしなくていいや」という判断をしてしまいます。しかし実際には malloc で確保されたメモリも使っています。これが記事中の「Ruby本体以外から確保されたメモリ量を認識できない」という問題です。
追加された rb_gc_adjust_memory_usage 関数は、malloc, free したメモリ量を記録することで Ruby に使用しているメモリ量を認識させることができます。

datadog の ruby ライブラリ dogapi で batch_metrics をネストするとダメそう

NOTE: v1.3.0 で見てます。

先日 datadog にメトリクスなげてーなと思ったのでライブラリの使い方を見てたのですがあんまり出来がよくなさそうです。気になったのはここ。

dog.batch_metrics do
  dog.emit_point('test.api.test_metric',10)
  dog.emit_point('test.api.this_other_metric', 1, :type => 'counter')
end

docs.datadoghq.com

batch_metrics というメソッド自体は、1回1回リクエストを投げずにためて投げるというやつですね。ふつーのバッチ処理

ですが、サンプルコードを見ただけでなんとなーくあやしい感じがします。内部実装を素直に予想するとこんな感じになりそうですね。

def batch_metrics
  @batch_mode = true # 逐次で投げないようにするフラグ
  yield
  @batch_mode = false
  flush! # ここでためたのを投げる
end

でもこれだとこういうときに困りそうです。手元で spec 書いたら落ちるしたぶんダメ。

dog.batch_metrics do
  # @batch_mode = true になる
  dog.batch_metrics do
    dog.emit_point('test.api.test_metric',10)
  end
  # @batch_mode = false になってしまう
  dog.emit_point('test.api.this_other_metric', 1, :type => 'counter') # 逐次で送られるんじゃね?
end

実際には @buffernil かどうかで同じようなことをやっています。このへんを参照。

https://github.com/DataDog/dogapi-rb/blob/d66698a363ae851845525d30a7d6a8afa1927dc5/lib/dogapi/facade.rb#L108-L116

https://github.com/DataDog/dogapi-rb/blob/d66698a363ae851845525d30a7d6a8afa1927dc5/lib/dogapi/v1/metric.rb#L52-L58

これより良さそうな API もあって、こんな感じ。

dog.batch_metrics do |batch_dog| # バッチリクエスト用のクライアントを返す
  batch_dog.emit_point('test.api.test_metric',10)
  batch_dog.batch_metrics do |batch_dog2| # batch_dog と batch_dog2 は同じものを返せばよい
    batch_dog.emit_point('test.api.test_metric',10)
  end
  # 最初の block から抜けるときに作られたものを flush すればよい
end

というのでなんかよくできるんじゃないかなーとプロジェクトを見てたら rspec なのに let 使ってなかったりでちょっと心が折れた日曜日なのでした。

まあ実際こういうの送るときにネストしたりしないからこまんないような気もしますね

Ruby でふつーの引数でもキーワード引数でも渡せるようにしたい

Swift の named parameters みたいな使い方のやつですかね。

こんなんでどうでしょうか、というのを tw では雑に書いたもののちゃんと清書していなかったので置いときます。

class Foo
  FOO__ARG1 = Object.new
  private_constant :FOO__ARG1
  FOO__NAME = Object.new
  private_constant :FOO__NAME


  def foo(_arg1 = FOO__ARG1, name: FOO__NAME)
    unless FOO__ARG1.equal?(_arg1)
      name = _arg1
    end
    if FOO__NAME.equal?(name)
      raise ArgumentError
    end

    puts "name: #{name}"
  end
end

Foo.new.foo(name: 'taro')
Foo.new.foo('hanako')
Foo.new.foo
$ ruby foo.rb
name: taro
name: hanako
Traceback (most recent call last):
        1: from foo.rb:24:in `<main>'
foo.rb:15:in `foo': ArgumentError (ArgumentError)

nil を使うと nil を渡せないということがあるので適当に Object.new して object_id 比較をしています。
引数が足りない場合は自前で raise してます。

これ書くのめんどいなーと思って、self.class.instance_method(__method__).params あたりでなんとかできないかなーとは思いつつ、実のところ自分ではそんなに使いそうにないからここでやめときます、


class Foo
  FOO__ARG1 = Object.new
  private_constant :FOO__ARG1

  def foo(_arg1 = FOO__ARG1, name: _arg1)
    if FOO__ARG1.equal?(name)
      raise ArgumentError
    end

    puts "name: #{name}"
  end
end

仮引数使うというアイデアのおかげでちょっと短くなりました。