天泣記

2018-02-04 (Sun)

#1 mjit を試した

ruby に mjit がマージされたというので、あからさまに効くだろう例を作って試してみた。

% ./ruby -v --jit -e '
def m(a)
  i = 0
  b = 0
  while i < 10000000
    b += a
    i += 1
  end
  b
end
40.times {
  t1 = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
  m 1
  t2 = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
  t = t2 - t1
  p ["*"*(t*100), t]
}
'
ruby 2.6.0dev (2018-02-04 trunk 62205) [x86_64-linux]
["************************", 0.240898976]
["*********************", 0.217471835]
["*********************", 0.21591224500000006]
["*********************", 0.21689099499999998]
["*********************", 0.21545454900000005]
["*********************", 0.2165155729999999]
["**********************", 0.22314411900000009]
["**********************", 0.22222180000000002]
["**********************", 0.22140864100000024]
["**********************", 0.22204918100000004]
["**********************", 0.22756300800000018]
["**********************", 0.22621370000000018]
["**********************", 0.22455513000000016]
["*********************", 0.21907512499999982]
["**********************", 0.22392001500000003]
["**********************", 0.22442923799999992]
["**********************", 0.22244357599999987]
["**********************", 0.22384103199999972]
["**********************", 0.220822289]
["**********************", 0.22050174499999997]
["**********************", 0.22094057699999947]
["**********************", 0.22087908700000014]
["**********************", 0.2252509439999999]
["************************", 0.24046692499999978]
["************************", 0.2441191690000002]
["************************", 0.24029502700000016]
["*********", 0.09336170299999935]
["********", 0.08989909800000007]
["*********", 0.09510676600000068]
["********", 0.08622591299999982]
["********", 0.08650536399999975]
["*********", 0.09120596999999986]
["********", 0.08059538800000077]
["*******", 0.0799009040000005]
["*******", 0.07997619300000025]
["********", 0.08294788099999995]
["********", 0.082763366]
["********", 0.08611019199999959]
["********", 0.0862023350000003]
["********", 0.08604439600000013]

JIT なので最初は遅いが、適当なタイミングでコンパイルされたものに置き換わって速くなる。 このケースだとだいたい 2倍速くらいか。

なお、C に変換するので、/tmp を監視していると、それっぽいテンポラリファイルが生成されるのが見える。

2018-02-22 (Thu)

#1 Anomaly "cvtac's exception: "Anomaly: rewrite rule not an application""

coq issue #6804

#2 Coq の Vector

よくある依存型の例として、長さも型で区別するリスト型がある。 Coq にはこれが Vector という名前のライブラリで提供されている。

ここで、2つの vector を連結するのは append という関数が提供されているが、 3つの vector を連結する関数は提供されていないので、定義してみよう。

From mathcomp Require Import all_ssreflect.
Require Import Vector.
Import VectorNotations.

Infix "++" := append.

Fail Definition append3 {T : Type} {a b c : nat}
  (x : Vector.t T a) (y : Vector.t T b) (z : Vector.t T c) :
  Vector.t T (a + b + c) :=
  x ++ y ++ z.
(*
The term "x ++ y ++ z" has type "t T (a + (b + c))"
while it is expected to have type "t T (a + b + c)".
*)

ところが、上記のような定義はエラーになる。

理由はエラーメッセージのとおりで "x ++ y ++ z" の型が "t T (a + (b + c))" なのに、 返り値の型として "t T (a + b + c)" が期待されているので、 違うじゃないか、ということである。 (t というのは Vector.t のことである。)

違うところは (a + (b + c)) と (a + b + c) なので、 それは同じだろうと人間にはわかるし、 Coq のライブラリの中には自然数の加算の結合則の定理はあるのだが、 Coq は型の一致については convertible というごく限定された関係しか 調べてくれないので型エラーになってしまう。

この型エラーが起きる原因はプログラムの x ++ y ++ z で使った ++ が右結合で x ++ (y ++ z) の意味なのに、 a + b + c で使った + が左結合で (a + b) + c という意味だというミスマッチである。 だからどちらかにあわせるという手はある。 ただ、x ++ y ++ z を (x ++ y) ++ z にすると、リストの作り直しの手間が増える (x が 2回コピーされることになる) のでよろしくない。 また、a + b + c を a + (b + c) にすると、プログラムの内部的な都合で外部APIを変えるということになってこれもよろしくない。

ということで、なんとか x ++ y ++ z と a + b + c のままで定義できないか考えたところ、 Vector.t T (a + (b + c)) 型の値を受け取って Vector.t T (a + b + c) という型の値を返す関数があれば いいのではないかと考えついた。

ということで以下のように定義してみる。 まぁ、このくらいなら証明項を直接書けるような気もするけれど、 面倒なので proof editing mode で定義した。 rewrite するだけだけど。

Definition f {T : Type} {a b c : nat}
  (v : Vector.t T (a + (b + c))) : Vector.t T (a + b + c).
Proof.
  by rewrite -addnA.
Defined.

ともあれ、この f を使うと、以下のように append3 が定義できる。

Definition append3 {T : Type} {a b c : nat}
  (x : Vector.t T a) (y : Vector.t T b) (z : Vector.t T c) :
  Vector.t T (a + b + c) :=
  f (x ++ y ++ z).

とはいえ、定義できることはできるが、型では保証されない性質 (要素の順序とか) が 期待通りに実現されているかどうかは明らかでない。 これを確かめるには、rewrite がけっきょく eq_rect の呼び出しを生成して、 eq_rect は (型は変えるけど) 値をそのまま返すから変えない、 ということを確認すれば納得できるか。

それはそうと、動かしてみるか、とやってみると例えば以下のようになる。

Compute (append3 [1; 2; 3] [4; 5; 6] [7; 8; 9]).
= match addnA 3 3 3 in (_ = x) return (t nat x) with
  | erefl _ _ => [1; 2; 3; 4; 5; 6; 7; 8; 9]
  end
: t nat (3 + 3 + 3)

あー、addnA は展開できないので (Qed で定義されていて Opaque なので)、そこは計算が進まないなぁ。

あと、f を別に定義せずに済ますこともできる。

Definition append3' {T : Type} {a b c : nat}
  (x : Vector.t T a) (y : Vector.t T b) (z : Vector.t T c) :
  Vector.t T (a + b + c).
Proof.
  refine (_ (x ++ y ++ z)).
  by rewrite -addnA.
Defined.

ここでは、proof editing mode の中で refine を使って、 f があったところ以外は具体的なプログラムを与え、 f のところは tactic で埋めている。

#3 SSReflect の tuple

Vector みたいなものは SSReflect にあったっけか、と思って探してみると、tuple というのがあるようだ。

同じことを試してみよう。

まず、x ++ y ++ z が (a + b + c).-tuple T 型にならずにエラーになるのは同じである。

From mathcomp Require Import all_ssreflect.

Fail Definition cat3 {T : Type} {a b c : nat}
  (x : a.-tuple T) (y : b.-tuple T) (z : c.-tuple T) :
  (a + b + c).-tuple T :=
  [tuple of x ++ y ++ z].
(*
The term "[tuple of x ++ y ++ z]" has type "(a + (b + c)).-tuple T"
while it is expected to have type "(a + b + c).-tuple T".
*)

で、型を変換する必要があるのだが、これも同じようにできる。

Definition cat3 {T : Type} {a b c : nat}
  (x : a.-tuple T) (y : b.-tuple T) (z : c.-tuple T) :
  (a + b + c).-tuple T.
Proof.
  refine (_ [tuple of x ++ y ++ z]).
  by rewrite addnA.
Defined.

ここでは refine を使ってみたが、もちろん型を変換する関数を別に定義して使っても良い。

動かしてみると以下のようになる。

Compute (cat3 [tuple 1; 2; 3] [tuple 4; 5; 6] [tuple 7; 8; 9]).
= match
    match addnA 3 3 3 in (_ = x) return (x = 9) with
    | erefl _ _ => erefl (3 + (3 + 3))
    end in (_ = x) return (x.-tuple nat -> 9.-tuple nat)
  with
  | erefl _ _ => id
  end
    (Tuple (n:=9) (tval:=[:: 1; 2; 3; 4; 5; 6; 7; 8; 9])
       (cat_tupleP
          (Tuple (n:=3) (tval:=[:: 1; 2; 3])
             (valP
               ... 以下省略 ...

addnA は展開できないので計算が進んでいないのはそれとして、かなり大きな証明項がついている。 これは、tuple は (Vector と違って) リストと、リストの長さが特定の値であるという証明をペアにしたものとして定義されているからで、 その証明のところがかなり大きくなっている。

あと、SSReflect には tcast という、長さが等しいという証明を与えると型を変えてくれる関数があるようだ。 それを使うと 3つの tuple の連結は以下のように定義できる。

Definition cat3' {T : Type} {a b c : nat}
  (x : a.-tuple T) (y : b.-tuple T) (z : c.-tuple T) :
  (a + b + c).-tuple T.
Proof.
  refine (tcast _ [tuple of x ++ y ++ z]).
  (* subgoal: a + (b + c) = a + b + c *)
  by apply addnA.
Defined.

これも動かしてみよう、と思って試すと、以下のようになる。

Compute (cat3' [tuple 1; 2; 3] [tuple 4; 5; 6] [tuple 7; 8; 9]).
= ecast x (x.-tuple nat) (addnA 3 3 3)
    (Tuple (n:=3 + (3 + 3)) (tval:=[:: 1; 2; 3; 4; 5; 6; 7; 8; 9])
       (cat_tupleP
          (Tuple (n:=3) (tval:=[:: 1; 2; 3])
  ... 以下省略 ...

ecast というのが一番外側に残っているが、 そのちょっと内側に [:: 1; 2; 3; 4; 5; 6; 7; 8; 9] というのがあって、 そのあたりが計算結果かな。


[latest]


田中哲