敏捷さとダイエット

女房が 蒟蒻畑 を買ってきた。いつも間に復活していたんだろう? あのつるっと した感触が堪らない。また買ってきてねとリクエストしてしまった。

同じ蒟蒻でも、ご飯に入れて、量は増えるけどご飯の分量は減っているというやつ は、勘弁願いたい。これって、ダイエットに効果があるんだろうか? 某、コマーシャルで 数日前から見せられている、サングラスの人がぱっと服を脱いで、自慢?のボディ を披露するのも、願い下げ。

胸よりも 前に出るなと 腹に言う

気持ち、分かるけどさ。

敏捷さ

ふと、昨日のコードに敏捷さがあるんだろうかと疑問が沸いてきた。 気になったのは、この部分

(cordec-write (fold + 0.0 (map * *xn* *hm*)))

map すると結果はリストで返ってくるけど、すぐに消費されてしまって、作られた リストは「ごみ」になってしまう。ごみが出れば、ごみ掃除が発動されて、かくっ、かくっ となるのではないか。強いては、敏捷さに欠けるものになってしまうと予測になった。 こういうのは、考えても分からないので、実験してみる。

(define hm (make-list 1000000 1))
(define xn (make-list 1000000 1))

(define (com)
    (fold (lambda (x y sum) (+ sum (* x y))) 0 hm xn))

(define (sep)
  (fold + 0 (map * hm xn)))

mapを無くしたものを書いてみた。gaucheのinfoを見たら、同様な例が載っていた 事からすると、有名な使い方なのだろう。ベクトルの内積と言うそうだ。

敏捷さを計るので、timeだな。

gosh> (time (com))
;(time (com))
; real   7.016
; user   6.781
; sys    0.023
1000000
gosh> (time (sep))
;(time (sep))
; real   5.479
; user   5.359
; sys    0.016
1000000

意に反して、mapを使った方が早かった。何度か実験してみたけど、優位な差(20%以上 速い)がある。これは、何故だろう? どんな風にコンパイルされてるか確認して みよう。

gosh> (disasm com)
main_code (name=com, code=0x8154f90, size=11, const=4, stack=7):
args: #f
     0 CLOSURE #<lambda 0>      ; (lambda (x y sum) (+ sum (* x y)))
     2 PUSH
     3 CONSTI-PUSH(0)
     4 GREF-PUSH #<gloc user##hm>; hm
     6 GREF-PUSH #<gloc user##xn>; xn
     8 GREF-TAIL-CALL(4) #<gloc gauche##fold>; (fold (lambda (x y sum) (+ sum (* x y))) ...
    10 RET
internal_closure_0 (name=#f, code=0x80e2df8, size=6, const=0 stack=2):
args: #f
     0 LREF0-PUSH               ; sum
     1 LREF2-PUSH               ; x
     2 LREF1                    ; y
     3 NUMMUL2                  ; (* x y)
     4 NUMADD2                  ; (+ sum (* x y))
     5 RET
gosh> (disasm sep)
main_code (name=sep, code=0x8105f80, size=16, const=6, stack=15):
args: #f
     0 GREF-PUSH #<gloc scheme##+>; +
     2 CONSTI-PUSH(0)
     3 PRE-CALL(3) 13
     5 GREF-PUSH #<gloc scheme##*>; *
     7 GREF-PUSH #<gloc user##hm>; hm
     9 GREF-PUSH #<gloc user##xn>; xn
    11 GREF-CALL(3) #<gloc scheme##map>; (map * hm xn)
    13 PUSH-GREF-TAIL-CALL(3) #<gloc gauche##fold>; (fold + 0 (map * hm xn))
    15 RET

コンパイルされたコードのサイズは同じであるけど、前者の方は、lambdaで定義した 無名関数が呼び出されるように見える。internal_clojure_0 が、リストのlength回数 呼び出されるので、そのオーバーヘッドが無視出来ないのだろう。

ダイエット

そんじゃ、fold . map の方が絶対的に有利かというと、そうでもない。 そもそもの発端となった危惧が、メモリー消費と言う面で顕著化してくるのだ。 (みみっちい話と言えばそれまでだが、)

  PID USERNAME    THR PRI NICE   SIZE    RES STATE    TIME   WCPU COMMAND
 2072 sakae         1   5    0  4568K  3444K ttyin    0:00  0.00% gosh
 2072 sakae         1   5    0 22656K 19468K ttyin    0:01  1.27% gosh
 2072 sakae         1   5    0 37440K 36200K ttyin    0:08 14.16% gosh
 2090 sakae         1   5    0 48572K 47256K ttyin    0:06 19.64% gosh
 2090 sakae         1   5    0 48572K 47300K ttyin    0:33 31.20% gosh

上のデータは、top の結果を抜き出して、貼り付けたものである。(emacsからgosh を起動してます)上から順番に

  1. goshを起動直後
  2. スクリプトをロード(C-c C-l)
  3. com を実行後
  4. 1. 2. を実行後に sep を実行
  5. 4.の後、数度 sep を実行

この結果をどう使うか(使わないって)は、ご自由に。

それにしても、100万回の積和演算が、5秒って、速いんですかね? そりゃ、DSP には負けるに決まってますが。

おまけ

Windowsマシンに乗っている petite上でも走らせてみた。(fold が無かったので fold-leftに 変更した上で)

> (time (com))
(time (com))
    4 collections
    350 ms elapsed cpu time, including 0 ms collecting
    357 ms elapsed real time, including 0 ms collecting
    16000584 bytes allocated, including 29058904 bytes reclaimed
1000000
> (time (sep))
(time (sep))
    2 collections
    291 ms elapsed cpu time, including 70 ms collecting
    296 ms elapsed real time, including 67 ms collecting
    14465896 bytes allocated, including 3780224 bytes reclaimed
1000000

やはり敏捷さと言う側面では、gaucheと同じだけど、メモリー負担の面では、分解 しておいた方が速いという結果になった。処理系に個性が出るのね。

さらに^2

VMWARE上のFreeBSDに乗っている ikarus では、

> (time (com))
running stats for (com):
    23 collections
    342 ms elapsed cpu time, including 10 ms collecting
    352 ms elapsed real time, including 19 ms collecting
    96000008 bytes allocated
1000000
> (time (sep))
running stats for (sep):
    6 collections
    3602 ms elapsed cpu time, including 2550 ms collecting
    3682 ms elapsed real time, including 2616 ms collecting
    21953840 bytes allocated
1000000

ふぅーー。