Unity 勉強メモ

ゲームエンジンのUnityを勉強するブログです。

Elmを使ってみた時のメモ (その2)

ウインドウ内をボールが飛び跳ねるプログラム (改良版)

import Graphics.Element(..)
import Graphics.Collage(..)
import Color(..)
import Signal(..)
import Time(..)
import Window(..)

type alias State = {
  x : Float, y : Float,
  vx : Float, vy : Float,
  w : Int, h : Int
}

main : Signal Element
main = draw <~ foldp update init tick

tick : Signal ((Int, Int), Time)
tick = (,) <~ dimensions ~ fps 30

init : State
init = { x = 0, y = 0, vx = 5, vy = 5, w = 0, h = 0 }

draw : State -> Element
draw st =
  collage st.w st.h [
    move (st.x, st.y) (filled blue (circle 10))
  ]

update : ((Int, Int), Time) -> State -> State
update ((w, h), _) st =
  let new_x = st.x + st.vx
      new_y = st.y + st.vy in
  { x = new_x, y = new_y,
    vx = new_v st.x st.vx w,
    vy = new_v st.y st.vy h,
    w = w, h = h }

new_v : Float -> Float -> Int -> Float
new_v x vx w =
  if | x > toFloat w / 2   -> -(abs vx)
     | x < -(toFloat w / 2) -> abs vx
     | otherwise            -> vx

前回の記事ではdelayを使って二つのSignalをマージしていたが、 よく考えるとこれは不要であることに気付いた。 今回のバージョンは、よりすっきりとしたElmらしいプログラムに仕上がっていると思う。

Elmを使ってみた時のメモ (その1)

Elmとは

とりあえず使ってみた

Hello, World.

Elmはサンプルプログラムが非常に充実している。とりあえずHello, World.プログラムを作ってみた。

import Text(plainText)
main = plainText "Hello, World."

オンライン環境でコンパイルしたら、Hello, World.と表示された。

plainTextは以下の型の関数でTextモジュールに定義されている。

plainText : String -> Element

型を明記する

以下のようにプログラムの型を明示することもできる。

import Graphics.Element(Element)
import Text(plainText)

main : Element
main = plainText "Hello, World."

式の計算をする

import Graphics.Element(Element)
import Text(..)

main : Element
main = plainText <| toString <| 2 + 3 * 4

toStringBasicsモジュールの関数で任意の値を文字列に変換する。

toString : a -> String

(<|)は関数適用の演算子で、Basicsモジュールで定義されている。

(<|) : (a -> b) -> a -> b

ここら辺はF#と同じ。|><<>>も定義されている。

Basicsモジュールはデフォルトでインポートされるので明示的なインポートは不要。 デフォルトでは以下がインポートされるらしい。

import Basics (..)
import List ( List )
import Maybe ( Maybe( Just, Nothing ) )
import Result ( Result( Ok, Err ) )
import Signal ( Signal )

関数を定義する

階乗計算

まずは定番の階乗計算から。

import Graphics.Element(Element)
import Text(..)

main : Element
main = plainText <| "fact 5 = " ++ toString (fact 5)

fact : Int -> Int
fact n =
  if | n == 0    -> 1
     | otherwise -> n * fact (n - 1)

ifthenの部分に->を使う所にとても親近感がわく。良くできたシンタックスだと思う。

実行すると、

fact 5 = 120

と表示される。

階乗の表(1)

次は階乗の表を表示してみたいと思う。

import Graphics.Element(..)
import Text(..)
import List(..)

main : Element
main = flow down <| map factElement [1..10]

factElement : Int -> Element
factElement n =
  plainText <| "fact " ++ toString n ++
               " = " ++ toString (fact n)

fact : Int -> Int
fact n =
  if | n == 0    -> 1
     | otherwise -> n * fact (n - 1)

Graphics.Elementモジュールのflow関数でElementを配置できるらしいのでそれを使った。

flow : Direction -> List Element -> Element

階乗の表(2)

せっかくなので、=のところを揃えて表示させたいと思う。ついでに等幅フォントにしてみた。

import Graphics.Element(..)
import Text(..)
import List(..)

main : Element
main =
  let range = [1..10] in
  flow right <| map (flow down) <|
    map (\f -> map f range) [expr, equal, value]

expr : Int -> Element
expr n = container 60 18 midRight << leftAligned
  << monospace << fromString <| "fact " ++ toString n

equal : Int -> Element
equal n = container 30 18 middle << leftAligned
  << monospace <| fromString "="

value : Int -> Element
value n = container 60 18 midLeft << asText <| fact n

makeContainer : Int -> Position -> Element -> Element
makeContainer w pos elem =
  container w 12 pos elem

fact : Int -> Int
fact n =
  if | n == 0    -> 1
     | otherwise -> n * fact (n - 1)

以下のようにそれらしい表示が出た。

f:id:lambdataro:20150314162951p:plain

マウス座標の表示

マウスのX座標の値を表示するプログラムを作ってみた。

import Graphics.Element(..)
import Text(..)
import Signal
import Mouse

main : Signal Element
main = Signal.map asText Mouse.x

mainの型がこれまでのElementからSignal Elementに変わった。 Signalというのは要素の無限列というイメージだろうか。 例えばMouseモジュールの値xは、

x : Signal Int

という型を持っている。 これをSignalモジュールのmap関数で、Signal Elementにすると、リアクティブなプログラムができるらしい。

map : (a -> result) -> Signal a -> Signal result

なんだ、意外と簡単ではないかと思った。 要はこれを組み合わせてプログラミングしていけば良いということなのだろう。

時間を扱ってみた

とりあえず1秒に1ずつカウントするプログラムを作ってみた。

import Graphics.Element(..)
import Text(..)
import Signal
import Time(..)

main : Signal Element
main = Signal.map asText << Signal.foldp update initial
  <| every second

initial : Time
initial = 0

update : Time -> Time -> Time
update v st = st + 1

状態を扱うためには、Signalモジュールのfoldp関数を使うらしい。

ボールが移動するプログラム

左右に移動する

import Graphics.Element(..)
import Graphics.Collage(..)
import Color(..)
import Signal(..)
import Time(..)

main = draw <~ foldp update init (fps 30)

draw st =
  collage 300 200 [
    moveX st.x (filled blue (circle 10)),
    outlined (solid black) (rect 300 200)
  ]

init = { x = 0, vx = 5 }

update v st =
  let new_x = st.x + st.vx in
  { st | x <- new_x,
         vx <- if new_x > 150 || new_x < -150
                 then negate st.vx
                 else st.vx }

座標系を理解するのに少し時間がかかった。レコードを初めて使ってみた。

ウインドウ内をボールが飛び跳ねる

import Graphics.Element(..)
import Graphics.Collage(..)
import Color(..)
import Signal(..)
import Time(..)
import Window(..)

type Event = Tick | Resize Int Int

type alias State = {
  x : Float, y : Float,
  vx : Float, vy : Float,
  w : Int, h : Int
}

main : Signal Element
main = map draw <| foldp update init event

event : Signal Event
event = delay 1 (sampleOn tick resize) `merge` tick

tick : Signal Event
tick = always Tick <~ fps 30

resize : Signal Event
resize = uncurry Resize <~ dimensions

init : State
init = { x = 0, y = 0, vx = 5, vy = 5, w = 500, h = 500 }

draw : State -> Element
draw st =
  collage st.w st.h [
    move (st.x, st.y) (filled blue (circle 10)),
    outlined (solid black) (rect (toFloat st.w) (toFloat st.h))
  ]

update : Event -> State -> State
update ev st =
  case ev of
    Tick ->
      let new_x = st.x + st.vx
          new_y = st.y + st.vy in
      { st | x <- new_x, y <- new_y,
             vx <- new_v st.x st.vx st.w,
             vy <- new_v st.y st.vy st.h }
    Resize w h ->
      { st | w <- w, h <- h }

new_v : Float -> Float -> Int -> Float
new_v x vx w =
  if | x > toFloat w / 2   -> -(abs vx)
     | x < -(toFloat w / 2) -> abs vx
     | otherwise            -> vx

ウインドウの初期サイズを取得するところで非常に苦労した。 時間0のウインドウサイズの通知と同時にfpsのイベントも発生するため、 片方を遅延させる必要があった。 もっとエレガントな解法があるのかもしれないが、現時点では分からない。

今日はここまでにしよう。

C言語でオレオレ行列積ルーチンを作るプロジェクト (その11)

オレオレ行列積。まだ諦めていませんよ。

プログラム

プログラム全体は以下のGitHubリポジトリにあります。 今回利用するプログラムは、 main.cmy_dgemm11.cです。

コンパイル

$ gcc -static -fopenmp -mavx -O3 main.c my_dgemm11.c 
  -I/opt/OpenBLAS/include -L/opt/OpenBLAS/lib -lopenblas -lpthread -lrt

実行結果

プログラムを5回実行した結果を以下に示します。

回数
1回目 2.100111 (中央値)
2回目 2.048678
3回目 2.105635
4回目 2.110453
5回目 2.078582

速度の比較

BLAS 比率
OpenBLAS 1.475622 100%
Intel MKL 1.574675 93.7%
my_dgemm11 2.100111 70.3%
my_dgemm10 2.138090 69.0%
ATLAS 5.104218 28.9%

まとめ

祝!70%到達。

C言語でオレオレ行列積ルーチンを作るプロジェクト (その10)

オレオレ行列積ルーチンが微妙に速くなった。

プログラム

プログラム全体は以下のGitHubリポジトリにあります。 今回利用するプログラムは、 main.cmy_dgemm10.cです。

コンパイル

$ gcc -static -fopenmp -mavx -O3 main.c my_dgemm10.c 
  -I/opt/OpenBLAS/include   -L/opt/OpenBLAS/lib -lopenblas -lpthread -lrt

実行結果

プログラムを5回実行した結果を以下に示します。

回数
1回目 2.169028
2回目 2.131479
3回目 2.141985
4回目 2.138090 (中央値)
5回目 2.135505

速度の比較

BLAS 比率
OpenBLAS 1.475622 100%
Intel MKL 1.574675 93.7%
my_dgemm10 2.138090 69.0%
my_dgemm09 2.305811 64.0%
ATLAS 5.104218 28.9%

まとめ

あと少しで70%に届いていない。

C言語でオレオレ行列積ルーチンを作るプロジェクト (その9)

オレオレ行列積ルーチンがまた少しだけ速くなった。

プログラム

プログラム全体は以下のGitHubリポジトリにあります。 今回利用するプログラムは、 main.cmy_dgemm09.cです。

コンパイル

$ gcc -static -fopenmp -mavx -O3 main.c my_dgemm09.c 
  -I/opt/OpenBLAS/include   -L/opt/OpenBLAS/lib -lopenblas -lpthread -lrt

実行結果

プログラムを5回実行した結果を以下に示します。

回数
1回目 2.295725
2回目 2.305811 (中央値)
3回目 2.298528
4回目 2.363331
5回目 2.333508

速度の比較

BLAS 比率
OpenBLAS 1.475622 100%
Intel MKL 1.574675 93.7%
my_dgemm09 2.305811 64.0%
my_dgemm08 2.374885 62.1%
ATLAS 5.104218 28.9%

まとめ

今回の改善は微妙だけど、確実に70%に近づいている。

C言語でオレオレ行列積ルーチンを作るプロジェクト (その8)

オレオレ行列積ルーチンがまた少しだけ速くなった。

プログラム

プログラム全体は以下のGitHubリポジトリにあります。 今回利用するプログラムは、 main.cmy_dgemm08.cです。

コンパイル

$ gcc -static -fopenmp -mavx -O3 main.c my_dgemm08.c 
  -I/opt/OpenBLAS/include   -L/opt/OpenBLAS/lib -lopenblas -lpthread -lrt

実行結果

プログラムを5回実行した結果を以下に示します。

回数
1回目 2.362352
2回目 2.398940
3回目 2.374885 (中央値)
4回目 2.364634
5回目 2.396504

速度の比較

BLAS 比率
OpenBLAS 1.475622 100%
Intel MKL 1.574675 93.7%
my_dgemm08 2.374885 62.1%
my_dgemm07 2.743161 53.8%
ATLAS 5.104218 28.9%

まとめ

ついにOpenBLASのスピードの60%超を達成した。

C言語でオレオレ行列積ルーチンを作るプロジェクト (その7)

色々試行錯誤した結果、大分速くなった。 OpenMPの使い方も大分わかってきた。 前のプログラムが動かなかったのはprivate節を指定していなかったからだった。

プログラム

プログラム全体は以下のGitHubリポジトリにあります。 今回利用するプログラムは、 main.cmy_dgemm07.cです。

コンパイル

iccでもgccでも通るようになったが、なぜかgccコンパイルしたほうが速くなるので、 gccコンパイルした。

$ gcc -static -fopenmp -mavx -O3 main.c my_dgemm07.c 
  -I/opt/OpenBLAS/include   -L/opt/OpenBLAS/lib -lopenblas -lpthread -lrt

実行結果

プログラムを5回実行した結果を以下に示します。

回数
1回目 2.739869
2回目 2.676472
3回目 2.769849
4回目 2.743161 (中央値)
5回目 2.747036

速度の比較

BLAS 比率
OpenBLAS 1.475622 100%
Intel MKL 1.574675 93.7%
my_dgemm07 2.743161 53.8%
ATLAS 5.104218 28.9%

まとめ

行列積ルーチンをいじくり回した結果大分速くなってきた。 OpenBLASの50%超えたのは相当嬉しい。