Erlang

wxErlang

前回の続きでwxErlangの資料を読んで、試してみた。こういう資料が家に居ながらにして読めるって、このコロナの時代有り難い事です。

OpenBSDでもwxErlangは、いわゆるErlangとは別パッケージになってた。 Elixir入門 21: デバッグ でも取り上げられているけど、GUI系ですからねぇ。人によってはGUI拒否症の人もいるから、無理強いしませんって事だな。

頭に付くwxは色々有る。有名な所では、wxPythonとかwxMaximaとかね。x11/wxWidgetsには

wxWidgets gives you a single, easy-to-use API for writing GUI
applications on multiple platforms. Link with the appropriate library
for your platform (Windows/Unix/Mac, others coming shortly) and compiler
(almost any popular C++ compiler), and your application will adopt the
look and feel appropriate to that platform. On top of great GUI
functionality, wxWidgets gives you: online help, network programming,
streams, clipboard and drag and drop, multithreading, image loading and
saving in a variety of popular formats, HTML viewing and printing, and
much much more.

This package contains the GTK3 version of the library.

こんな説明がされていた。現代風なTkと思っていればいいんだな。Cフラフラ語での提供らしい。これだけでは、みんな大好きAVには対応出来ないので、コンパニオンさんが居る。

wxMediaCtrl is a wxWidgets class for displaying types of media,
such as videos, audio files, natively through native codecs.
wxWidgets-gtk3-3.0.5.1.tgz                8572658
wxWidgets-media-3.0.5.1.tgz                 46479
wxchordpro-0.974.1.tgz                      14698
wxglade-1.0.0.tgz                         1100557
wxsvg-1.5.22v0.tgz                        1300750

パッケージがこれだけある。でも、このパッケージを支える底辺にgtk3群が必要になるので、わんさかと、子分を連れてくるぞ。GUIは大飯喰らいである。

-module(hello).
-include_lib("wx/include/wx.hrl").
-export([start/0]).

start() ->
    Wx = wx:new(),
    Frame = wx:batch(fun() -> create_window(Wx) end),
    wxWindow:show(Frame),
    loop(Frame),
    wx:destroy(),
    ok.

create_window(Wx) ->
    Frame = wxFrame:new(Wx,
                        -1, % window id
                        "Hello World", % window title
                        [{size, {600,400}}]),
    wxFrame:createStatusBar(Frame,[]),
    wxFrame:connect(Frame, close_window),
    ok = wxFrame:setStatusText(Frame, "Hello World!",[]),
    Frame.

loop(Frame) ->
    receive
        #wx{event=#wxClose{}} ->
            io:format("~p Closing window ~n",[self()]),
            ok = wxFrame:setStatusText(Frame, "Closing...",[]),
            wxWindow:destroy(Frame),
            ok;
        Msg ->
            io:format("Got ~p ~n", [Msg]),
            loop(Frame)
    end.

これがGUI版のハロワである。メニューとかが(File,Edit,…About)とかが必要になると、途端にソースの分量が増える。何時の時代もGUIは使って天国、作って地獄であるな。

compile from src

vbox$ ls -l /usr/ports/distfiles/
total 166848
-rw-r--r--  1 root  wheel  85351485 Dec 11  2018 otp_src_21.2.tar.gz

OpenBSDでは、結構昔の物が使われている。これとて、展開してインストールしようとすると、wxの所で、out of memoryエラーを喰らってしまった。clangの最適化でメモリーを大量消費してるんだろうね。ulimit を駆使して使うメモリーを増やすって手も考えてはみたんだけど、なんだかなあの気分。

どうせならerlang処女地のOpenBSD(64Bit)で、wxには目もくれずにコンパイルしてみるか。どうせなら最新式な奴が良い。んだけど、最新式なやつは、仮想マシンのネイティブをサポートしていないらしい(FreeBSDのportsからの情報)。そんなに真剣に使う訳ではないので、目をつぶろう。

普通にconfigureしてmakeしたら、ものの5分で完了。おまけで、elixirも最新式を入れてみた。

ob$ iex
Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]

Interactive Elixir (1.13.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

otp_src_23.3 を入れたので、バナーでOTP 23 と言って来るのは理解出来る。erts-11.2ってのは、coreになるバージョンかな? erlang run time system とかの略だろうね。smpは4CPUで動いてますの報告だろう。次のdsは何だろう? 得意のソース嫁かな。

Eshell V11.2  (abort with ^G)

erlを単独で動かすと、eshellのバージョンも表示される。これってreplの事だよね。そこら辺も見たいと言う欲望が沸々と湧いてますよ。 ざっとMakefileを眺めた限りでは、gdb用の情報が添付されてるみたいだ。

観光

gdbにかかるか、アタッチして確認したい。だってerlはshellスクリプトだからね。

ob$ ps a
  PID TT  STAT        TIME COMMAND
77389 p2  I+       0:00.55 /usr/local/lib/erlang/erts-11.2/bin/beam.smp -- -roo

動いているやつを追う。

ob$ gdb -q /usr/local/lib/erlang/erts-11.2/bin/beam.smp -p 77389
Reading symbols from /usr/lib/libutil.so.15.0...done.
Reading symbols from /usr/lib/libm.so.10.1...done.
Reading symbols from /usr/lib/libcurses.so.14.0...done.
Reading symbols from /usr/lib/libm.so.10.1...done. 
Reading symbols from /usr/lib/libcurses.so.14.0...done.
Reading symbols from /usr/lib/libc.so.96.0...done.
Reading symbols from /usr/libexec/ld.so...done.
[New thread 125326]
:                    23 threads runs
[New thread 205805]
[Switching to thread 202773]
_thread_sys_select () at /tmp/-:3
3       /tmp/-: No such file or directory.

随分スレッドが動いている。

(gdb) bt
#0  _thread_sys_select () at /tmp/-:3
#1  0x0000030f403777ee in _libc_select_cancel (nfds=0, readfds=0x0,
    writefds=0x0, exceptfds=0x30f4038ab1a <_thread_sys_select+10>, timeout=0x0)
    at /usr/src/lib/libc/sys/w_select.c:28
#2  0x0000030c7ef1d6f0 in erts_sys_main_thread () at sys/unix/sys.c:1161
#3  0x0000030c7edc098d in erl_start (argc=<optimized out>,
    argv=<optimized out>) at beam/erl_init.c:2376
#4  0x0000030c7ed45699 in main (argc=0, argv=0x0) at sys/unix/erl_main.c:30

スレッドスイッチの嵐に見舞われそうなんで、尻尾を巻いて逃げ出します。後で、ピンポイントで調べるかも知れないけど。

79330 p2  S        0:00.16 epmd

psした時、気になる奴を見つけた。ひょっとしてウィルスに侵されたかと冷や冷や。erlang一族だったよ。

Erlang Port Mapper Daemon

This daemon acts as a name server on all hosts involved in distributed Erlang 
computations. When an Erlang node starts, the node has a name and it obtains 
an address from the host OS kernel.

The daemon is started automatically by command erl(1)

debian(64Bit)に入れてgdbしたら、少しは親切にスレッドの名前が出て来た。

(gdb) info threads
  Id   Target Id                                           Frame
 * 1    Thread 0x7f37ad746b80 (LWP 62980) "beam.smp"        0x00007f37ad839037 in __GI___select (nfds=nfds@entry=0, readfds=readfds@entry=0x0,
    writefds=writefds@entry=0x0, exceptfds=exceptfds@entry=0x0,
    timeout=timeout@entry=0x0) at ../sysdeps/unix/sysv/linux/select.c:41
  2    Thread 0x7f376c6bf700 (LWP 62984) "sys_sig_dispatc" __libc_read (
    nbytes=4, buf=0x7f376c6bee60, fd=12)
    at ../sysdeps/unix/sysv/linux/read.c:26
  3    Thread 0x7f376b77f700 (LWP 62985) "sys_msg_dispatc" futex_wait_cancelable (private=0, expected=0, futex_word=0x56435ab5e32c <smq_cnd+44>)
    at ../sysdeps/unix/sysv/linux/futex-internal.h:88
  4    Thread 0x7f376c728700 (LWP 62986) "async_1"         syscall ()
    at ../sysdeps/unix/sysv/linux/x86_64/syscall.S:38
  5    Thread 0x7f376adff700 (LWP 62988) "1_scheduler"     syscall ()
    at ../sysdeps/unix/sysv/linux/x86_64/syscall.S:38
  :
  24   Thread 0x7f376a516700 (LWP 63007) "0_poller"        0x00007f37ad8417ef in epoll_wait (epfd=4, events=events@entry=0x7f376d209f38,
    maxevents=maxevents@entry=512, timeout=-1)
    at ../sysdeps/unix/sysv/linux/epoll_wait.c:30

ここに出て来るソースは思わせぶりな奴だなあ。

eshell

erlってreplだ、じゃなくてシェッルだ。helpぐらいは有るだろう。

1> help().
 ** shell internal commands **
b()        -- display all variable bindings
e(N)       -- repeat the expression in query <N>
 :
 ** commands in module c **
bt(Pid)    -- stack backtrace for a process
c(Mod)     -- compile and load module or file <Mod>
 :
 ** commands in module i (interpreter interface) **
ih()       -- print help for the i module
true

やはり有った。

5> i().
Pid                   Initial Call                          Heap     Reds Msgs
Registered            Current Function                     Stack
<0.0.0>               otp_ring0:start/2                      233     1531    0
init                  init:loop/1                              2
<0.1.0>               erts_code_purger:start/0               233       11    0
 :

これって、ps相当だな。色々なプロセスが動いている。それで、スレッドが沢山って事なんだな。OS任せにしないで、自前で疑似OS風に振る舞うとな。

6> m().
Module                File
application           /usr/local/lib/erlang21/lib/kernel-6.2/ebin/application.beam
erl_tracer            preloaded
erlang                preloaded
 :

全部のモジュールを列挙出来る。疑似OS上でのls相当か。ls().は、本物のOSのlsだから、混乱しないでね。

7> m(timer).
Module: timer
MD5: 9b3d4bac79ba3daac2e45692552fbd13
Compiled: No compile time info available
Object file: /usr/local/lib/erlang21/lib/stdlib-3.7/ebin/timer.beam
 :
kill_after/2                  tc/2
kill_after/1                  tc/3
                              terminate/2
ok

そして、個別にモジュールを指定すると、モジュール情報が出て来る。モジュール内で定義されてる関数名が出て来る事か、unixにこじつけると簡易なmanだな(余りに貧弱だけど)。細かい事は、実験しつつソース嫁がOSSと付き合う為の基本的な態度です。

Eshellのソースは、stdlib/の中に、 shell_default.erl shell_docs.erl shell.erl こんな3個のファイルとして置いてあるぞ。

Erlang/OTP 23.3 see Basic/stdlib

Erlang/OTP 23.3(with search)

gg出来るマニュアルとかも揃っているな。

ふと思い立って、起動状態を確認してみた。長いコマンド引数なので、wideなフォーマットで、コマンドだけを表示するように指定。それでも冒頭部分は長いので省略してる。

vbox$ ps -awocommand
beam.smp -- -root /usr/local/lib/erlang21 -progname erl -- -home /home/sakae --
beam.smp -- -root /usr/local/lib/erlang21 -progname erl21 -- -home /home/sakae -- -pa /EXTd/l

上段はerl(eshell)の起動。下段はelixir(iex)の起動。/EXTdは、2nd-DISKのマウント・ポイントと思われる。

vbox$ sh -x /usr/local/bin/erl
+ ROOTDIR=/usr/local/lib/erlang21
+ BINDIR=/usr/local/lib/erlang21/erts-10.2/bin
+ EMU=beam
+ echo /usr/local/bin/erl
+ sed s/.*\///
+ PROGNAME=erl
+ export EMU
+ export ROOTDIR
+ export BINDIR
+ export PROGNAME
+ exec /usr/local/lib/erlang21/erts-10.2/bin/erlexec
Erlang/OTP 21 [erts-10.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1]

Eshell V10.2  (abort with ^G)

EMUって、エミュレータの意味(だよね)。

vbox$ sh -x /usr/local/bin/iex
+ set -e
+ [  = --help ]
+ [  = -h ]
+ readlink_f /usr/local/bin/iex
+ SELF=/EXTd/local/lib/elixir/bin/iex
+ dirname /EXTd/local/lib/elixir/bin/iex
+ SCRIPT_PATH=/EXTd/local/lib/elixir/bin
+ exec /EXTd/local/lib/elixir/bin/elixir --no-halt --erl -noshell -user Elixir.IEx.CLI +iex
Erlang/OTP 21 [erts-10.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1]

Interactive Elixir (1.11.4) - press Ctrl+C to exit (type h() ENTER for help)

まて、これが正しい観測方法。

timer:tc

実行時間を計測してくれる奴。schemeだとtimeってマクロだったな。

3> timer:tc(io, format, ["~nHello World!~n", []]).

Hello World!
{172,ok}

定義場所を探してみたよ。 stdlib/timer.erl

%%
%% Measure the execution time (in microseconds) for an MFA.
%%
-spec tc(Module, Function, Arguments) -> {Time, Value} when
      Module :: module(),
      Function :: atom(),
      Arguments :: [term()],
      Time :: integer(),
      Value :: term().
tc(M, F, A) ->
    T1 = erlang:monotonic_time(),
    Val = apply(M, F, A),
    T2 = erlang:monotonic_time(),
    Time = erlang:convert_time_unit(T2 - T1, native, microsecond),
    {Time, Val}.

頭がマイナスで始まっている -spec は、アトリビュートって言うらしい。 erlang:monotonic_time() はOSが提供する各種の分解能を持った時計から、インストール時に選択されて使われるようだ。余り深入りすると苦くなりそうなので、この辺にしておく。

banner

起動時に出て来るあれ、どういう意味なの?

vbox$ erl
Erlang/OTP 21 [erts-10.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1]

Eshell V10.2  (abort with ^G)

OpenBSD(32Bit)のports版

sakae@pen:/tmp$ erl
Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Debian(64Bit)の自前コンパイル版

定義はconfig時に決まるとな。 erts/emulator/beam/erl_bif_info.c

static char erts_system_version[] = ("Erlang/OTP " ERLANG_OTP_RELEASE
                                     "%s"
                                     " [erts-" ERLANG_VERSION "]"
#ifndef OTP_RELEASE
#ifdef ERLANG_GIT_VERSION
                                     " [source-" ERLANG_GIT_VERSION "]"
#else
                                     " [source]"
#endif
#endif
#ifdef ARCH_64
                                     " [64-bit]"
#endif
                                     " [smp:%beu:%beu]"
                                     " [ds:%beu:%beu:%beu]"
#if defined(ERTS_DIRTY_SCHEDULERS_TEST)
                                     " [dirty-schedulers-TEST]"
#endif
                                     " [async-threads:%d]"
#ifdef HIPE
                                     " [hipe]"
   :
                                     "\n");

それはいいんだけど、肝心の smp:%beu の意味が分からん。

eusakae@pen:~$ man printf
       :
       %b     ARGUMENT  as  a string with '\' escapes interpreted, except that
              octal escapes are of the form \0 or \0NNN

これとは違うだろうしね。

hipe

こういうのが有るそうなので、Debian(64Bit)で試してみる。 HiPE(High Performance Erlang)について

例題は何時ものやつ。

-module(fib).
-export([fib/1]).

-spec fib(non_neg_integer()) -> non_neg_integer().
fib(N) when N < 2 ->
    1;
fib(N) ->
    fib(N - 2) + fib(N - 1).

まずは普通に。

3> c(fib).
{ok,fib}
4> timer:tc(fib, fib, [40]).
{3681145,165580141}

3.6秒かかった。

6> c(fib, [native]).
{ok,fib}
7> timer:tc(fib, fib, [40]).
{998711,165580141}

hipeを有効にするおまじないnativeを付けると、1秒を切った。

説明によると功罪が有るので、数値計算ぐらいに留めておいた方が良さそう。

etc