All Posts

全ての記事をそのまま表示している。

とても重くなってしまっているかもしれない。

MinCaml をブラウザで動かした

デモはこのページの一番下にある。

ソースコードは yuki67/MinCamlOnline にある。

何をしたか

前の記事 で bucklescript を使って OCaml プログラムをブラウザ上で動かすというのをやった。 この記事で試したのはかなり小さいプログラムだったので、もう少し大きいプログラムでも上手く行くのかどうか気になり試した。

もう少し大きいプログラムとして MinCaml を使った。 MicCaml なら自分もそれなりに中身を理解していて、プレーンの OCaml で書かれていることを知っているのでちょうどいいと思った。

感想

MinCaml をブラウザ上で動かすために bucklescript を設定するのは思っていたよりも簡単だった。 ただ動かすだけなら ocamlyacc と ocamllex を設定して C で書かれていた一部のコードを OCaml で書きなおすだけで動くようになった。

動くようにした後にテキストエリアやボタンを設定するのが多少手間だった。 ボタンが押されたときにコンパイルする関数を呼べるようにモジュールの構成を変える必要があったり、 MinCaml にコンパイル結果を文字列として返す関数が無かったのでそれも書いたりした。 これらの手間が生じたのは MinCaml がそのような用途のために書かれていないからであり、OCaml や bucklescript の問題ではない。

変換された javascript プログラムは Parsing, Lexing, Format モジュールを含めて正しく動いた。 ただし、 Format.sprintf が常識的な長さの入力に対して Maximum call stack size exceeded を出して動かないことがあった (デモで mandelbrot をコンパイルするとエラーが出る)。 bucklescript の実装が原因だと思うが、対処する方法もわからないのでそのままにした。

デモ

Program



After Type Checking


After K-normalization


After Alpha Conversion


After Optimization


After Closure Conversion


Virtual Assembly Code


After Immediate Value Optimization


After Register Allocation


Final Output




無限ストリーム in C++

SICP 3.5節 にある無限ストリームを C++ で実装した。 それを使ってフィボナッチ数列や素数、Aitken 加速などを実装できた。 ラムダ式とか auto とかをいい感じに使えた、と思う。 あとこの記事を描くのに noweb を使った文芸的プログラミングを初めて使った。

実装

SICP では無限ストリームは car の値と cdr 部分のストリームを計算する関数の組として実装されている。 たとえばnから始まる整数を並べた無限ストリームは

;; n から始まる整数ストリーム
(define (integers-starting-from n)
  (cons-stream n (integers-starting-from (+ n 1))))

となる。

これと同じように C++ でストリームを実装しようとするとすごく面倒臭くなる。 C++ にはラムダ式と auto を使って関数を定義するときには再帰が使えないという制限があるので、上の例と似た関数を

// コンパイルできない
auto integers_starting_from = [](auto n) {return cons(n, integers_starting_from(n + 1)); };

と定義することができず、型(いまの例なら std::functional<Stream<int>(int)> )を毎回書かなければならない。

流石にこれは面倒臭すぎるので、ストリームを配列っぽく実装した。 T 型のストリームは第n項を計算する関数によって初期化され、 一度計算した値は std::unordered_map<size_t, T> に保存される。 さらに、第n項を計算するときに他の項の値を参照できるように、ストリームを初期化するときに与える 関数には自分自身 (*this) が引数として与えられる。

Code Snippet 1: Stream の定義

#include <functional>
#include <iostream>
#include <unordered_map>

template <typename T>
struct Stream {
 private:
  // genrator(self, n) はストリームの第n項を返す
  // 第一引数には自分自身 (*this) が与えられるので、第n項を計算するときに他の項を参照することができる
  std::function<T(Stream &, size_t)> generator;

  // 計算された値を保存するハッシュ
  std::unordered_map<size_t, T> memo;

 public:
  Stream() {}

  explicit Stream(std::function<T(Stream &, size_t)> generator_)
      : generator(generator_) {}

  // ストリームの第n項を返す
  T &operator[](size_t n) {
    if (memo.count(n) == 1) {
      // 計算済みなら、その値を返す
      return memo[n];
    } else {
      // 計算してないなら、計算を行い、結果を保存してから、結果を返す
      memo[n] = generator(*this, n);
      return memo[n];
    }
  }
};

以下のプログラムではフィボナッチ数列を計算している。

<<Streamの定義>>

// フィボナッチ数列
static Stream<size_t> fib([](auto &self, auto n) -> size_t {
  if (n < 2) {
    return 1;
  } else {
    // 他の項を参照する。メモ化は適切に行われる。
    return self[n - 1] + self[n - 2];
  }
});

int main() {
  for (size_t i = 0; i < 10; i++) {
    std::cout << "fib[" << i << "] = " << fib[i] << "\n";
  }
  std::cout << "fib[" << 100 << "] = " << fib[100] << "\n";
  return 0;
}
fib[0] = 1
fib[1] = 1
fib[2] = 2
fib[3] = 3
fib[4] = 5
fib[5] = 8
fib[6] = 13
fib[7] = 21
fib[8] = 34
fib[9] = 55
fib[100] = 1298777728820984005

その他の例:

#include <cassert>

<<Streamの定義>>

// 整数
static Stream<size_t> integers([](auto &, auto n) { return n; });

// 素数
static Stream<size_t> primes([](auto &self, auto n) {
  static int i = 2;
  for (;; i++) {
    auto flag = true;
    // n番目の素数は、n-1番目までの素数全てで割り切れない最小の自然数
    for (size_t j = 0; j < n; j++) {
      flag &= (i % self[j]) != 0;
    }
    if (flag) {
      return i;
    }
  }
});

// 双子素数
static Stream<std::pair<size_t, size_t>> twin_primes(
    [](auto &, auto n) -> std::pair<size_t, size_t> {
      // primes[i] と primes[i+1] が双子素数であるようなiを返す
      static Stream<size_t> twin_primes_idx([](auto &self, auto n) -> size_t {
        if (n == 0) {
          return 1;
        }
        auto i = self[n - 1] + 1;
        for (;; i++) {
          if (primes[i] + 2 == primes[i + 1]) {
            return i;
          }
        }
      });
      auto younger = primes[twin_primes_idx[n]];
      return {younger, younger + 2};
    });

// コラッツ予想の操作をしたときに何回で1になるかを返す
static Stream<size_t> Collatz([](auto &self, auto n) -> size_t {
  if (n == 1) {
    return 0;
  } else if ((n % 2) == 0) {
    return self[n / 2] + 1;
  } else {
    return self[3 * n + 1] + 1;
  }
});

int main() {
  assert(integers[0] == 0);
  assert(integers[1] == 1);
  assert(integers[2] == 2);
  assert(integers[3] == 3);
  assert(integers[100] == 100);

  assert(primes[0] == 2);
  assert(primes[1] == 3);
  assert(primes[2] == 5);
  assert(primes[3] == 7);
  assert(primes[4] == 11);
  assert(primes[99] == 541);

  assert(twin_primes[0].first == 3);
  assert(twin_primes[1].first == 5);
  assert(twin_primes[2].first == 11);
  assert(twin_primes[3].first == 17);
  assert(twin_primes[99].first == 3821);

  assert(Collatz[1] == 0);
  assert(Collatz[11] == 14);
  assert(Collatz[27] == 111);
  assert(Collatz[871] == 178);

  std::cout << "OK\n";
  return 0;
}
OK

数列の加速

SICP にある Aitken 加速の例も実装できたので解説する。

まず \(\frac{\pi}{4}\) に収束する数列

\begin{align*} a_{n} = \sum_{i=0}^{n} \frac{(-1)^{n}}{2n+1} \end{align*}

を計算するストリームを作った。

この数列は

\begin{align*} a_{n} = \begin{cases} 1 & (n = 0) \\
a_{n-1} + \frac{(-1)^{n}}{2n+1} & (\text{otherwise}) \end{cases} \end{align*}

と書き直せるので、この数列の第 n 項を計算するストリームを以下のように定義した。

Code Snippet 2: 数列Aの定義

#include <cmath>

// pi / 4 に収束する
static Stream<double> A([](auto &self, auto n) -> double {
  if (n == 0) {
    return 1;
  } else {
    return self[n - 1] + std::pow(-1, n) / (2 * n + 1);
  }
});
#include <iostream>

<<Streamの定義>>
<<数列Aの定義>>

int main() {
  for (size_t i = 0; i < 10; i++) {
    std::cout << "4*A[" << i << "] = " << 4*A[i] << "\n";
  }
  std::cout << "4*A[" << 100 << "] = " << 4*A[100] << "\n";
  std::cout << "4*A[" << 1000 << "] = " << 4*A[1000] << "\n";
  std::cout << "4*A[" << 10000 << "] = " << 4*A[10000] << "\n";
  return 0;
}
4*A[0] = 4
4*A[1] = 2.66667
4*A[2] = 3.46667
4*A[3] = 2.89524
4*A[4] = 3.33968
4*A[5] = 2.97605
4*A[6] = 3.28374
4*A[7] = 3.01707
4*A[8] = 3.25237
4*A[9] = 3.04184
4*A[100] = 3.15149
4*A[1000] = 3.14259
4*A[10000] = 3.14169

かなりゆっくりではあるが A が \(\frac{\pi}{4}\) に収束しているのが分かる。

この数列 A に対して Aitken 加速を適用することができる。 数列 \(x_{n}\) に対して Aitken 加速を適用した数列 \(x_{n}^{\prime}\) は

\begin{align*} x^{\prime}_{n} &= x_{n} - \frac{(\Delta x_{n})^{2}}{\Delta^{2}x_{n}} \\
\Delta x_{n} &= x_{n+1} - x_{n} \\
\Delta^{2} x_{n} &= \Delta x_{n+1} - \Delta x_{n} \\
\end{align*}

と定義され、\(x_{n}^{\prime}\) は \(x_{n}\) に比べて収束がとても速くなる(ことがある)。

Aitken 加速をストリームを受け取ってストリームを返す関数として実装した。

Code Snippet 3: Aitken加速の定義

auto Aitken = [](auto &x) {
  return Stream<double>([&x](auto &, auto n) {
    auto ddx = (x[n + 2] - x[n + 1]) - (x[n + 1] - x[n]);
    auto dx = x[n + 1] - x[n];
    if (ddx == 0) {
      // 分母が0になるときの例外処理
      return x[n];
    }
    return x[n] - dx * dx / ddx;
  });
};

Aitken 加速によって収束が速くなることを確認した:

#include <iostream>

<<Streamの定義>>
<<数列Aの定義>>
<<Aitken加速の定義>>

int main() {
  auto AA = Aitken(A);
  for (size_t i = 0; i < 10; i++) {
    std::cout << "4*AA[" << i << "] = " << 4*AA[i] << "\n";
  }
  return 0;
}
4*AA[0] = 3.16667
4*AA[1] = 3.13333
4*AA[2] = 3.14524
4*AA[3] = 3.13968
4*AA[4] = 3.14271
4*AA[5] = 3.14088
4*AA[6] = 3.14207
4*AA[7] = 3.14125
4*AA[8] = 3.14184
4*AA[9] = 3.14141

第10項で小数以下5桁まで正しく計算できているので、収束が加速されたことが分かる。

Aitken 加速を適用した数列に更に Aitken 加速を適用することができ、さらにその数列を加速することもできる。 この再帰的な Aitken 加速をストリームのストリームを使って実装した。

Code Snippet 4: 再帰的Aitken加速の定義

// 再帰的に Aitken 加速されたストリームのストリームを返す
auto AitkenTableau = [](auto &x) {
  return Stream<Stream<double>>([&x](auto &self, auto n) {
    if (n == 0) {
      return Aitken(x);
    } else {
      return Aitken(self[n - 1]);
    }
  });
};

auto AitkenRecursive = [](auto &x) {
  auto tableau = AitkenTableau(x);
  // 計算した tableau をクロージャの中に入れる
  // tableau は計算の中で更新されるので、mutable が必須
  return Stream<double>(
      [tableau](auto &, auto n) mutable { return tableau[n][0]; });
};
#include <iostream>
#include <iomanip>

<<Streamの定義>>
<<数列Aの定義>>
<<Aitken加速の定義>>
<<再帰的Aitken加速の定義>>

int main() {
  auto AAA = AitkenRecursive(A);
  std::cout << std::setprecision(15);
  for (size_t i = 0; i < 10; i++) {
    std::cout << "4*AAA[" << i << "] = " << 4*AAA[i] << "\n";
  }
  return 0;
}
4*AAA[0] = 3.16666666666667
4*AAA[1] = 3.14210526315789
4*AAA[2] = 3.141599357319
4*AAA[3] = 3.14159271403378
4*AAA[4] = 3.14159265397529
4*AAA[5] = 3.14159265359118
4*AAA[6] = 3.14159265358978
4*AAA[7] = 3.1415926535898
4*AAA[8] = 3.14159265358979
4*AAA[9] = 3.14159265358979

第9項の値は小数以下14桁まで正しい。

参考

Structure and Interpretation of Computer Programs



ob-ipython で sympy を使うときに出力を綺麗にする

jupyter notebook で sympy を使うとセルの実行結果を Latex で表示してくれるので便利だが、 ob-ipython で sympy を使うとセルの実行結果の表示が jupyter notebook ほど便利でない。

#+BEGIN_SRC ipython :session :exports both :results output raw drawer
from sympy import *
init_printing()

x = symbols("x")
eqn = Eq(x**2+3*x-4)
print(latex(eqn))
#+END_SRC

#+RESULTS:
:RESULTS:
x^{2} + 3 x - 4 = 0
:END:
from sympy import *
init_printing()

x = symbols("x")
eqn = Eq(x**2+3*x-4)
print(latex(eqn))

x2 + 3 x - 4 = 0

幸い org-babel には「src block の実行結果を別の src block に通して、その結果を元の src block の結果にする」 という機能があるので、その機能を使えばこの問題を回避した。

引数付きの src block は emacs lisp だと書きやすいので、それを使って書いた。 sympy の出力にエスケープされたカッコをつけて MathJax のソースコードとして認識されるようにした。

#+name: wrapper-latex
#+begin_src emacs-lisp :var text="" :exports none
(concat "\\(" (replace-regexp-in-string "\n$" "" text) "\\)\n")
#+end_src

#+HEADER: :session :exports both :results output drawer
#+HEADER: :eval no-export :post wrapper-latex(text=*this*)
#+BEGIN_SRC ipython
x = symbols("x")
y = symbols("y", cls=Function)

eqn = Eq(y(x).diff(x), sin(x)*tan(y(x)))
print(latex(eqn))
#+END_SRC

#+RESULTS:
\(\frac{d}{d x} y{\left (x \right )} = \sin{\left (x \right )} \tan{\left (y{\left (x \right )} \right )}\)
x = symbols("x")
y = symbols("y", cls=Function)

eqn = Eq(y(x).diff(x), sin(x)*tan(y(x)))
print(latex(eqn))

\(\frac{d}{d x} y{\left (x \right )} = \sin{\left (x \right )} \tan{\left (y{\left (x \right )} \right )}\)

だいぶ良くなった。

次に数式の最初に \displaystyle をいれ、さらに数式全体を special block で囲んで見やすくした。

#+name: wrapper-latex2
#+begin_src emacs-lisp :var text="" :exports none
(concat "#+BEGIN_exa\n\\(\\displaystyle"
        (replace-regexp-in-string
         "# Out\\[.*\\]:\n: '\\(.*\\)'"
         "\\1"
         text)
        "\\)\n#+END_exa")
#+end_src

#+HEADER: :session :exports both :results value raw
#+HEADER: :eval no-export :post wrapper-latex2(text=*this*)
#+BEGIN_SRC ipython
x = symbols("x")
y = symbols("y", cls=Function)

eqn = Eq(y(x).diff(x), (x-2*y(x)+3)/(2*x+y(x)-4))
latex(eqn)
#+END_SRC

#+RESULTS:
#+BEGIN_exa
\(\displaystyle\\frac{d}{d x} y{\\left (x \\right )} = \\frac{x - 2 y{\\left (x \\right )} + 3}{2 x + y{\\left (x \\right )} - 4}\)
#+END_exa
x = symbols("x")
y = symbols("y", cls=Function)

eqn = Eq(y(x).diff(x), (x-2*y(x)+3)/(2*x+y(x)-4))
print(latex(eqn))

\(\displaystyle\frac{d}{d x} y{\left (x \right )} = \frac{x - 2 y{\left (x \right )} + 3}{2 x + y{\left (x \right )} - 4}\)

さらに src block を実行するたびに latex 要素を画像として表示するようにした。 使い勝手が jupyter notebook にかなり近くなった。

(add-hook 'org-babel-after-execute-hook 'org-toggle-latex-fragment)

参考

Header arguments - Org Babel reference card



PAPI を使った

PAPI を使うと CPU のハードウェアカウンタを使って様々な値を測定できる。 例えば

  • 実行した命令の数
  • 実行した浮動小数点演算の数
  • パイプラインがストールした回数
  • キャッシュヒット/ミス の回数
  • 分岐予測の成功/失敗数

を測定できる。測定した値は FLOPS の測定なりパフォーマンスのチューニングなりに使える。

適切に設定すれば CPU 以外のハードウェアのカウンタも読めるらしいが、使ってないのでよくわからない。

インストール

昔はインストールするのにカーネルにパッチを当てる必要があったりして一苦労だったらしい (検索すると出てくる) が、 最新版の Linux カーネルであればインストールは ./configure; make; make install で済んだ。

利用可能なイベントの確認

papi_avail で利用可能なイベントの一覧を確認できる。コンシューマ向けの CPU だと性能測定用のハードウェアカウンタがしょぼいようなので、インストールしたら必ず確認したほうがいい。

自分の環境では以下のようになった。

$ papi_avail
Available PAPI preset and user defined events plus hardware information.
--------------------------------------------------------------------------------
PAPI version             : 5.6.1.0
Operating system         : Linux 4.4.0-128-generic
Vendor string and code   : GenuineIntel (1, 0x1)
Model string and code    : Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz (158, 0x9e)
...
--------------------------------------------------------------------------------

================================================================================
  PAPI Preset Events
================================================================================
    Name        Code    Avail Deriv Description (Note)
PAPI_L1_DCM  0x80000000  Yes   No   Level 1 data cache misses
PAPI_L1_ICM  0x80000001  Yes   No   Level 1 instruction cache misses
PAPI_L2_DCM  0x80000002  Yes   Yes  Level 2 data cache misses
PAPI_L2_ICM  0x80000003  Yes   No   Level 2 instruction cache misses
PAPI_L3_DCM  0x80000004  No    No   Level 3 data cache misses
PAPI_L3_ICM  0x80000005  No    No   Level 3 instruction cache misses
...
PAPI_FML_INS 0x80000061  No    No   Floating point multiply instructions
PAPI_FAD_INS 0x80000062  No    No   Floating point add instructions
PAPI_FDV_INS 0x80000063  No    No   Floating point divide instructions
PAPI_FSQ_INS 0x80000064  No    No   Floating point square root instructions
PAPI_FNV_INS 0x80000065  No    No   Floating point inverse instructions
PAPI_FP_OPS  0x80000066  No    No   Floating point operations
PAPI_SP_OPS  0x80000067  Yes   Yes  Floating point operations; optimized to count scaled single precision vector operations
PAPI_DP_OPS  0x80000068  Yes   Yes  Floating point operations; optimized to count scaled double precision vector operations
PAPI_VEC_SP  0x80000069  Yes   Yes  Single precision vector/SIMD instructions
PAPI_VEC_DP  0x8000006a  Yes   Yes  Double precision vector/SIMD instructions
PAPI_REF_CYC 0x8000006b  Yes   No   Reference clock cycles
--------------------------------------------------------------------------------
Of 108 possible events, 59 are available, of which 18 are derived.

出力の最終行から、全体の約半分 (59 / 108) のイベントしか測定できないことが分かる。

なお PAPI_FP_OPS という浮動小数点演算の数を数えるための一番基本的なイベントが使えないことから、 PAPI のサンプルコードやテストが上手く実行できなかった。 浮動小数点演算の回数自体は PAPI_DP_OPS というイベントを使って測定できたのでよかったが、 一番最初に実行するサンプルコードが segmantation fault で落ちるので原因の調査にかなり時間を食った。

使う

以下のプログラムは浮動小数点演算の回数、CPU時間を計測し、そこから計算される GFLOPS と合わせて表示させた。 それから実行された命令の数も計測して表示させている。

#include <iostream>
#include <papi.h>
#include <vector>

void print_papi_error(int error_num) {
  std::cout << "PAPI error " << error_num << ": " << PAPI_strerror(error_num)
            << "\n";
}

int main() {
  int retval;
  auto a = 0.0;

  // 測定するイベント
  std::vector<int> events = {PAPI_TOT_INS, PAPI_DP_OPS};
  // 測定結果を収める場所
  std::vector<long_long> values(events.size());

  // カウンタをスタートさせる
  // events が多すぎる場合はここでエラーが出る
  retval = PAPI_start_counters(events.data(), events.size());
  if (retval != PAPI_OK) {
    print_papi_error(retval);
  }

  auto cpu_time_begin_usec = PAPI_get_virt_usec();
  // 浮動小数点演算を 10^8 回行う
  // 前後の命令が依存関係を持つので性能は出ない
  for (auto i = 0; i < 1.e8; i++) {
    a += 1;
  }
  auto cpu_time_end_usec = PAPI_get_virt_usec();

  // カウンタの値を読む
  // 値を読んでもカウンタは止まらない
  retval = PAPI_read_counters(values.data(), events.size());
  if (retval != PAPI_OK) {
    print_papi_error(retval);
  }

  // 測定結果の表示
  auto &number_of_executed_instructions = values[0];
  auto &number_of_floating_point_operation = values[1];
  auto elapsed_cpu_time_usec = cpu_time_end_usec - cpu_time_begin_usec;
  auto GFLOPS = 1.e6 * number_of_floating_point_operation / elapsed_cpu_time_usec;

  std::cout << "number_of_floating_point_operation = " << number_of_floating_point_operation << "\n";
  std::cout << "elapsed_cpu_time_usec = " << elapsed_cpu_time_usec << "\n";
  std::cout << "GFLOPS = " << GFLOPS * 1.e-9 << "\n";
  std::cout << "number_of_executed_instructions = " << number_of_executed_instructions << "\n";

  // カウンタをクリア
  PAPI_stop_counters(values.data(), events.size());
}
number_of_floating_point_operation = 100000000
elapsed_cpu_time_usec = 264001
GFLOPS = 0.378786
number_of_executed_instructions = 1200002473

プログラムの出力のうち、 number_of_floating_point_operation だけは何回測っても変わらないという素晴らしい性質を持つ。他の値は測るたびに変わってしまう。

参考

PAPI



std::random_device を使うと valgrind が落ちる

環境

valgrind 3.11, clang++ 5.0

問題

#include <iostream>
#include <random>

int main() {
  std::random_device rd;
  std::cout << rd() << std::endl;
  return 0;
}

上記のコードはコンパイルすれば普通に実行できるが、 valgrind から実行すると valgrind が落ちる。

$ clang++ test.cpp -std=c++17
$ ./a.out
1240201163
$ /usr/bin/valgrind ./a.out
==13508== Memcheck, a memory error detector
==13508== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==13508== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==13508== Command: ./a.out
==13508==
vex amd64->IR: unhandled instruction bytes: 0xF 0xC7 0xF0 0x89 0x6 0xF 0x42 0xC1
vex amd64->IR:   REX=0 REX.W=0 REX.R=0 REX.X=0 REX.B=0
vex amd64->IR:   VEX=0 VEX.L=0 VEX.nVVVV=0x0 ESC=0F
vex amd64->IR:   PFX.66=0 PFX.F2=0 PFX.F3=0
==13508== valgrind: Unrecognised instruction at address 0x4ef67a5.
==13508==    at 0x4EF67A5: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==13508==    by 0x4EF6941: std::random_device::_M_getval() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==13508==    by 0x401AD4: std::random_device::operator()() (in /tmp/a.out)
==13508==    by 0x4018A2: main (in /tmp/a.out)
...
illegal instruction (core dumped)

原因

valgrindのバグ

解決法

valgrindを3.13に更新する。

参考

353370 – RDRAND amd64->IR: unhandled instruction bytes: 0x48 0xF 0xC7 0xF0 0x72 0x4 0xFF 0xC9

c++ - Valgrind Unrecognised instruction - Stack Overflow

The design and implementation of Valgrind