Lua (2)

Gnu Radio

とある方からgnuradioってどのような作りになってるのって質問を受けた。その一次回答はリナで簡単に確認出来る。

sakae@pen:~$ apt info gnuradio
Installed-Size: 93.1 MB
Download-Size: 9,656 kB
 :
 GNU Radio applications are primarily written using the Python
 programming language, while the supplied performance-critical signal
 processing path is implemented in C++ using processor floating-point
 extensions, where available. Thus, the developer is able to implement
 real-time, high-throughput radio systems in a simple-to-use,
 rapid-application-development environment.

作り易いpythonで書いたけど(python2の時代にね)、スピードを要求される所はCフラフラ語を、フラフラ学習して置き換えていったよとな。お定まりのコースを辿っている訳だ。例えばpython2は棄ててpython3に置き換えるとか。

実験的に使ってたvolkってライブラリーを標準にして楽しようとか。こやつ、アセンブラのSIMD命令を使って並列演算するベクラーライブラリーだ。ぷち、スパコンっぽいな。

OpenBSDでもパッケージになっているので、Makefileを調べて、構成部品を洗い出してみる。

WANTLIB += ${COMPILER_LIBCXX} ${MODPY_WANTLIB}
WANTLIB += QtCore QtGui SDL boost_atomic-mt boost_chrono-mt boost_date_time-mt
WANTLIB += boost_filesystem-mt boost_program_options-mt boost_regex-mt
WANTLIB += boost_system-mt boost_thread-mt c fftw3f fftw3f_threads
WANTLIB += gsl gslcblas gsm iconv jack log4cpp m orc-0.4 portaudio qwt
WANTLIB += usb-1.0 zmq

boostって奴はC++が必要とするライブラリーだな。fftwってのはFFT用のライブラリィーか。 パッケージのパッキングリストも参照出来るので、ざっと見。

include/gnuradio/
include/gnuradio/analog/
include/gnuradio/analog/agc.h
include/gnuradio/analog/agc2.h
 :
lib/cmake/gnuradio/FindALSA.cmake
lib/cmake/gnuradio/FindCppUnit.cmake
lib/cmake/gnuradio/FindFFTW3f.cmake
lib/cmake/gnuradio/FindGSL.cmake
 :
@lib lib/libgnuradio-audio.so.${LIBgnuradio-audio_VERSION}
@lib lib/libgnuradio-blocks.so.${LIBgnuradio-blocks_VERSION}
@lib lib/libgnuradio-channels.so.${LIBgnuradio-channels_VERSION}
@lib lib/libgnuradio-digital.so.${LIBgnuradio-digital_VERSION}
 :
lib/python${MODPY_VERSION}/site-packages/gnuradio/__init__.py
lib/python${MODPY_VERSION}/site-packages/gnuradio/__init__.pyc
 :
share/examples/gnuradio/analog/
share/examples/gnuradio/analog/fmtest.py
share/examples/gnuradio/analog/noise_power.grc
 :
share/gnuradio/grc/blocks/analog_feedforward_agc_cc.xml
share/gnuradio/grc/blocks/analog_fm_deemph.xml
share/gnuradio/grc/blocks/analog_fm_demod_cf.xml

山のようにincludeファイルが有って、それを使う為のcmakeファイル(テンプレートなんだろうね)が有る。実体のsoファイル群が有る。勿論Python糊も必要。それから例題だな。最後はpythonが使うであろう定義ファイルとな。

これだけあれば良いかと思ったら、 py-{matplotlib,scipy}とかの標準になってるPythonライブラリーも必要。但しこれは余りに標準なので、パッケージには同梱されていない。

ユーザーはpythonの波打ち際で、ぴちゃぴちゃと戯れろとな。難しい所は隠してあるからね。 それから、実際のUSBドングルを接続しようとすると、ドライバー(UHD)とかが必要になる。それ系はOpenBSDでは充実していない。

これらも含めてインストールしようとしたら、pybombs ってのを使うが通らしい。ソースを残しておけば、深い所まで潜って行けるぞ。

オイラーには無理、baseがPythonって所で、尻をまくって逃げ出しますよ。だって、わんさかとモジュールを使ってて、さっぱり要領を得ないんだもの。疲れるだけです。

でも、折角なんで実習ぐらいはやっておけ。GNURadio introduction to Satellites

もっとすっきりしたので楽しましょ。

lua-mode

emacsにlua-modeなんてのを入れておいたんだけど、どんな使い道が有るの? C-c C-f でマニュアルを引ける。説明して欲しい関数にカーソルを合わせて C-c C-f で、のっさりとfirefoxが動いてきて、5.1のマニュアルを引いてくれる。

今時、5.1のマニュアルってどうよ? それにもっさりのfirefoxも頂けない。こういう場合はソース嫁。

(defcustom lua-documentation-url
  (or (and (file-readable-p "/usr/share/doc/lua/manual.html")
           "file:///usr/share/doc/lua/manual.html")
      "http://www.lua.org/manual/5.1/manual.html")
  "URL pointing to the Lua reference manual."

ふむ、ローカルにマニュアルがセットされてれば、それを、そうでなかったら旧式マニュアルを参照するとな。OpenBSDで野良で入れたんで、マニュアルは無視されていたんだな。早速追加したよ。

(require 'w3m)
(setq browse-url-browser-function 'w3m-browse-url)

そして、これもinit.elに追加。

luac

前回は逆アセンブラを見つけて楽しんだ。でも純正のやつが有ったぞ。man luac してたら、

-l     produce a listing of the compiled bytecode for Lua's virtual
       machine.  Listing bytecodes is useful to learn about Lua's
       virtual machine.  If no files are given, then luac loads
       luac.out and lists its contents.  Use -l -l for a full listing.

仮想マシンを勉強するに最適ですって。今までluacって重視してなかったけど、これは楽しそうだ。早速、オプションテストを兼ねて実験してみる。

vbox$ echo xxx=123 yyy=456 zzz=xxx*yyy | luac -l -l -p -- -

main <stdin:0,0> (9 instructions at 0x42f63d00)
0+ params, 2 slots, 1 upvalue, 0 locals, 5 constants, 0 functions
        1       [1]     VARARGPREP      0
        2       [1]     SETTABUP        0 0 1k  ; _ENV "xxx" 123
        3       [1]     SETTABUP        0 2 3k  ; _ENV "yyy" 456
        4       [1]     GETTABUP        0 0 0   ; _ENV "xxx"
        5       [1]     GETTABUP        1 0 2   ; _ENV "yyy"
        6       [1]     MUL             0 0 1
        7       [1]     MMBIN           0 1 8   ; __mul
        8       [1]     SETTABUP        0 4 0   ; _ENV "zzz"
        9       [1]     RETURN          0 1 1   ; 0 out
constants (5) for 0x42f63d00:
        0       S       "xxx"
        1       I       123
        2       S       "yyy"
        3       I       456
        4       S       "zzz"
locals (0) for 0x42f63d00:
upvalues (1) for 0x42f63d00:
        0       _ENV    1       0

luaの5.4系とそれ以前の物で若干出力が違う。バージョンや機種を跨いでの互換性は無い。あくまで、スクリプトのロード時間短縮が目的。

fib.lua

もう少し複雑なやつって事で、5.1.5に有ったやつ。素直に実行したfibと、キャッシュ版のfib。evalsが実際のfibを呼び出した回数。素直な版は愚直に呼び出しているんで時間がかかる 。

vbox$ lua fib.lua 30
        n       value   time    evals
plain   30      832040  0.84    2692537
cached  30      832040  0.0     31

こういうの皆大好きなんでネットに多数挙がっている。 メモ化, memo, memoize, define-memo とか なんか違うフィボナッチ関数とかメモ化とかそのベンチとか 等だ。

これを見ると、実行時間の比較とトレースが大事だよって主張がされている。時間の方は上で既にやっているんで、トレースの方だな。

大雑把な関数定義位置は下記のようになってる。

 4  function fib(n)

14  function cache(f)

27  function test(s,f)   -- 時間計測と結果表示

39  fib=cache(fib)
40  test("cached",fib)

多少分かり易いように、return時は、矢印の向きを逆に加工してある。

ob$ lua -l trace-calls fib.lua 3
 :
1 >>>  fib.lua:39 call cache <14:fib.lua>
2 <--   fib.lua:39 return cache <14:fib.lua>
1 >>>  fib.lua:40 call test <27:fib.lua>
2 >>>   fib.lua:29 call clock [C]
3 <--    fib.lua:29 return clock [C]
2 >>>   fib.lua:30 call f <16:fib.lua>
3 >>>    fib.lua:19 call f <4:fib.lua>
4 >>>     fib.lua:9 call fib <16:fib.lua>
5 >>>      fib.lua:19 call f <4:fib.lua>
6 >>>       fib.lua:9 call fib <16:fib.lua>
7 >>>        fib.lua:19 call f <4:fib.lua>
8 <--         fib.lua:19 return f <4:fib.lua>
7 <--        fib.lua:9 return fib <16:fib.lua>
6 >>>       fib.lua:9 call fib <16:fib.lua>
7 >>>        fib.lua:19 call f <4:fib.lua>
8 <--         fib.lua:19 return f <4:fib.lua>
7 <--        fib.lua:9 return fib <16:fib.lua>
6 <--       fib.lua:19 return f <4:fib.lua>
5 <--      fib.lua:9 return fib <16:fib.lua>
4 >>>     fib.lua:9 call fib <16:fib.lua>
5 <--      fib.lua:9 return fib <16:fib.lua>
4 <--     fib.lua:19 return f <4:fib.lua>
3 <--    fib.lua:30 return f <16:fib.lua>
2 >>>   fib.lua:31 call clock [C]
3 <--    fib.lua:31 return clock [C]
:

dis-assm

問題のメモ化はどうよ? こんな関数定義になってた。

14  function cache(f)
15          local c={}
16          return function (x)
17                  local y=c[x]
18                  if not y then
19                          y=f(x)
20                          c[x]=y
21                  end
22                  return y
23          end
24  end

これが内部的には下記のようにコンパイルされる。

function <fib.lua:14,24> (5 instructions at 0x5f9c9500)
1 param, 3 slots, 0 upvalues, 2 locals, 0 constants, 1 function
        1       [15]    NEWTABLE        1 0 0   ; 0
        2       [15]    EXTRAARG        0
        3       [23]    CLOSURE         2 0     ; 0x6e5e6d80
        4       [23]    RETURN          2 2 0   ; 1 out
        5       [24]    RETURN          2 1 0   ; 0 out

cacheって関数は、関数を組み立てて返す事をやってる。返す関数は、cと言うテーブルに答えが有るか検索して無ければ、関数を作った時に渡された関数を呼ぶ。そして結果をメモ。答えが有れば、素直に返す。cは、cacheの中に閉じ込められているので、外からはアクセス出来ないしかけになってる。

function <fib.lua:16,23> (12 instructions at 0x6e5e6d80)
1 param, 4 slots, 2 upvalues, 2 locals, 0 constants, 0 functions
        1       [17]    GETUPVAL        1 0     ; c
        2       [17]    GETTABLE        1 1 0
        3       [18]    TEST            1 1
        4       [18]    JMP             6       ; to 11
        5       [19]    GETUPVAL        2 1     ; f
        6       [19]    MOVE            3 0
        7       [19]    CALL            2 2 2   ; 1 in 1 out
        8       [19]    MOVE            1 2
        9       [20]    GETUPVAL        2 0     ; c
        10      [20]    SETTABLE        2 0 1
        11      [22]    RETURN1         1
        12      [23]    RETURN0

こちらは、無名関数の中身だ。

lopcodes.h

仮想マシンの命令セットはlopcodes.hで定義されてる。元ハード屋としては、知りたい所だ。それが分かれば回路図と首っ引きで解読出来る。

/*===========================================================================
  We assume that instructions are unsigned 32-bit integers.
  All instructions have an opcode in the first 7 bits.
  Instructions can have the following formats:

        3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
        1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
iABC          C(8)     |      B(8)     |k|     A(8)      |   Op(7)     |
iABx                Bx(17)               |     A(8)      |   Op(7)     |
iAsBx              sBx (signed)(17)      |     A(8)      |   Op(7)     |
iAx                           Ax(25)                     |   Op(7)     |
isJ                           sJ(25)                     |   Op(7)     |

  A signed argument is represented in excess K: the represented value is
  the written unsigned value minus K, where K is half the maximum for the
  corresponding unsigned argument.
===========================================================================*/

32Bit命令長のRISC風な体系。どこかのCPUみたいに命令長が伸びたり縮んだりしないスッキリとした奴。

typedef enum {
/*----------------------------------------------------------------------
  name          args    description
------------------------------------------------------------------------*/
OP_MOVE,/*      A B     R[A] := R[B]                                    */
OP_LOADI,/*     A sBx   R[A] := sBx                                     */
OP_LOADF,/*     A sBx   R[A] := (lua_Number)sBx                         */
OP_LOADK,/*     A Bx    R[A] := K[Bx]                                   */
 :
OP_SETTABUP,/*  A B C   UpValue[A][K[B]:string] := RK(C)                */
OP_SETTABLE,/*  A B C   R[A][R[B]] := RK(C)                             */
OP_SETI,/*      A B C   R[A][B] := RK(C)                                */

こんな具合に定義されてるけど、説明だけでは、にわかに動作を把握できないぞ。どうしたものかねぇ。


This year's Index

Home