Hatena::ブログ(Diary)

word-iteration

2012-02-20

Developers Summit 2012に行ってきた

行ってきました。お仕事の都合で二日目だけかなり無理して参加したけど、いつもの感想ながら参加して良かった。

Developers Summit 2012

いつもは、二日間めいっぱい講演を聴講してるんだけども、今年はお仕事でくたびれてたのでラウンジで休み休み聴講した。

【17-C-1】Continuous DeliveryとJenkinsアブストラクト

Jenkinsの川口さん直々のご講演。CI, CDの基本的な効用などは承前のおはなし。

計算機の能力を潤沢に使える現代の文脈下で、Jenkinsのような自動化ツールをどのような考え方で使いこなせば強力な開発力を生み出せるかという話。Jenkinsの持つ力強さのようなものを感じた。

きょうび、変化への追随とか対応力とか言われるが、そういう対応力のお話はともすればプロジェクトマネジメントの話で終始しがちだが、こういう道具をうまく使いこなす技術力および基盤力を持っていないとタダのかけ声倒れに終わるだろう。

↓スライド

http://www.slideshare.net/kohsuke/developer-summit-continuous-deliveryjenkins

【17-B-3】言語の世界

Matzプログラミング言語のお話。スライド見つけられず><。

↓へーへーへー。

【17-B-5】アジャイルマニフェスト ディケイド

角谷さんのお話。ディケイドとは10年紀の意味だそうだ。

Twitterに書いた感想とほぼ同じだけど。さまざまな場面・高い文脈下で使われるソフトウェアを、いかに使えるようにどうやって作るか作られているかというとても地に足のついた話で、「工学と文芸の間やなー」と思って聞いてたら、後半あたりから飛び出してた。でも、飛び出していると言っても話の筋は通っていたし腑に落ちるし面白かったしで、とても良い講演で聴いて良かった。

後半のソーシャルコーディングや!のくだりは話としては理解できるし同意できる内容であったが、自分には未体験ゾーンなところが情けなや。

ご本人曰く背景画像↓。

http://speakerdeck.com/u/kakutani/p/agile-manifesto-decade

【17-B-7】ソーシャルコーディングの世界

松田さんのお話。さらに未体験ゾーンの話。

コード書いてキメて、みんなでトリップして、ルネッサンスして人間讃歌だそうで。

コードでコミュニケーションすることで、すてきなソースコード(ソフトウェア)を生み出し続けることで、世界を変える革命に参加し続けることができる。松田さんのように活動している人だからこそ説明できる内容で、とてもすばらしい発表内容だと思った。

スライド↓

http://speakerdeck.com/u/a_matsuda/p/social-coding

まとめ

コードを書かない人に、どうやってすばらしいソフトウェアが生み出されるのか、どういう環境で作るのが良いのかを説明するのはとても困難。コードの目的や要求や利用される場面などでも全然違う。

しかも悪いことに、コードを書かない人は、良いソフトウェアが生まれる状況や場についてとても無頓着なことが多いのである。最悪の場合(しかも多くの場合)は、とにかくプログラマと名乗っている人を最安値ででも集めれば必要なソフトウェアが完成すると考えている。おおっぴらにはそう言わないけど、行動や言葉で示す人もたくさんいる。で、良くないことにソフトウェアが生み出される環境に無頓着の人が、無頓着な環境で作られたソフトウェアを使ったりお金を支払ったり販売することがままあるのであり、これはホントに誰も幸せにならないのである。非常に良くない。全国的に雨模様なのである。

じゃぁお前さんはコードを書いている人間だが、すばらしいソフトウェアを書けるのか、書いているのか、少なくとも同業者に説明できるのかって言われると、できている訳では無いのがもどかしい。結局の所、機会を見つけて自分なりにその場でできる最大限を尽くすだけなのである。そういう意味で各講演には元気と勇気を与えられたが、厳しい言葉を投げかけられたと思う。


番外編・伝説の大場派静内Rubyist飲み会

大場さん(@koichiroo)からお誘いいただいて噂の静内で飲みました。北海道の幸のお店。多分日本酒もおいしいはずだ!が、ビールと熱燗というオッサンの組み合わせで酔っ払い。

6,7名だったのに、あれよあれよと有名Rubyistが大勢集まって豪華なメンバー。SIとSIer北海道のRubyKaigi、Rails前時代、GREEの経営層のスピード力やソーシャルゲーム業界、などのお話しをたくさんできたし聞けました。普段あまりお話しできないRubyistの人たちにお話ししてもらえてとても楽しかった。。。いい時間がすごせました。おさそいいただいてありがとうございました。

2012-02-11

Nodeで動かして完結編

終わる終わる詐欺ではありません。アクエリアスと体当たり爆沈して終了です。

Haskell vs F#

↑のエントリを見て、↓の2つのエントリを書いた。今日はその完結編。

HaskellとF#の解読の練習とちょっとした疑問でRubyに移植してみた

Cに書き直して満足して終了

元エントリの中の人とゆかいな仲間達の間では道具選びの結論が出て、既にアルゴリズム改良にいそしんでいます。が、その裏で、未だにワタクシおなじみの言語で書き換えたらどうなるかを一人でやってます。器用貧乏のサガですね。

今日はコードをCoffeeScript(JavaScript)に直して、Node.jsで動かしてみました。おお。速い。

実行結果
$ node --version
v0.6.10
$ coffee --version
CoffeeScript version 1.2.0
$ coffee -c hoge.coffee
$ time node hoge.js
999.9999999999998
node hoge.js  8.26s user 0.08s system 100% cpu 8.289 total

おお!Haskellの15倍の処理時間(15倍遅い)!これは速いほう!!(過去エントリ参照(Ruby編, C編)

v8エンジンって結構優れてるんですね。

感想めいたもの

作りたいアプリケーションに合わせて道具を選びましょうという超当たり前の感想と、F#見直したわーくらいしかなくて、Cに書き直して満足して終了で書いたのと一緒w。

比較表
処理系と実装Haskellを1とした場合実処理時間(s)
C(llvm-gcc-4.2)0.10.070
Haskell (7.4.1)1.00.534
Ruby very slow(YARV 1.9.3p0)54.228.935
Ruby slow(YARV 1.9.3p0)26.814.296
Ruby very slow(JRuby 1.6.5.1)58.331.139
Ruby slow(JRuby 1.6.5.1)14.57.724
Ruby very slow(MacRuby 0.10)331.0176.73
Ruby slow(MacRuby 0.10)24.112.855
Ruby (MacRuby 0.10) 並列化18.29.729
JavaScript(Node.js 0.6.10)15.58.289
CoffeeSctipt
induceBackwardVerySlow = (nodes, values) ->
  n = Math.floor(values.length / 2)
  nodes.map((node) ->
    node.branches.map((branch) ->
      branch.p * values[n + branch.k] * node.df
    ).reduce((a, b) -> a + b)
  )

ITERATION = 1000

last_values = (i) -> (i for n in [0..200])

test_tree = ->
  [0..99].map((i) ->
    [-i..i].map((j) ->
      {
       df: 1.0,
       branches: [{k: j - 1, p: 1.0 / 6.0},
                  {k: j    , p: 2.0 / 3.0},
                  {k: j + 1, p: 1.0 / 6.0}]
      }
    )
  )

value = (i) ->
  test_tree().
    reverse().
    reduce((values, nodes) ->
      induceBackwardVerySlow(nodes, values)
    , last_values(i))[0]

console.log(Math.max((value(i) for i in [1..ITERATION])...))

しかし、はてなダイアリーってCoffeeScriptシンタックスハイライト無いんだね…。なんかがっかりだなぁ。。。


コンパイル後のJavaScript
(function() {
  var ITERATION, i, induceBackwardVerySlow, last_values, test_tree, value;

  induceBackwardVerySlow = function(nodes, values) {
    var n;
    n = Math.floor(values.length / 2);
    return nodes.map(function(node) {
      return node.branches.map(function(branch) {
        return branch.p * values[n + branch.k] * node.df;
      }).reduce(function(a, b) {
        return a + b;
      });
    });
  };

  ITERATION = 1000;

  last_values = function(i) {
    var n, _results;
    _results = [];
    for (n = 0; n <= 200; n++) {
      _results.push(i);
    }
    return _results;
  };

  test_tree = function() {
    var _i, _results;
    return (function() {
      _results = [];
      for (_i = 0; _i <= 99; _i++){ _results.push(_i); }
      return _results;
    }).apply(this).map(function(i) {
      var _i, _results;
      return (function() {
        _results = [];
        for (var _i = -i; -i <= i ? _i <= i : _i >= i; -i <= i ? _i++ : _i--){ _results.push(_i); }
        return _results;
      }).apply(this).map(function(j) {
        return {
          df: 1.0,
          branches: [
            { 
              k: j - 1,
              p: 1.0 / 6.0
            }, {
              k: j,
              p: 2.0 / 3.0
            }, {
              k: j + 1,
              p: 1.0 / 6.0
            }
          ]
        };
      });
    });
  };

  value = function(i) {
    return test_tree().reverse().reduce(function(values, nodes) {
      return induceBackwardVerySlow(nodes, values);
    }, last_values(i))[0];
  };

  console.log(Math.max.apply(Math, (function() {
    var _results;
    _results = [];
    for (i = 1; 1 <= ITERATION ? i <= ITERATION : i >= ITERATION; 1 <= ITERATION ? i++ : i--) {
      _results.push(value(i));
    }
    return _results;
  })()));

}).call(this);

2012-02-08

Cに書き直して満足して終了

Haskell vs F#

↑のエントリを見て、↓のエントリを書いた。今日はその続き。

HaskellとF#の解読の練習とちょっとした疑問でRubyに移植してみた

.NETerやHaskllerが高速化のために高い技術力を投下してる裏で、ワタクシおなじみの言語で書き換えたらどうなるかを一人でやってます。今日はコードをCに直してみました。やっぱ速い。

結果
$ gcc --version
i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc -O2 hoge.c
$ time ./a.out 
1000.000000
./a.out  0.06s user 0.00s system 82% cpu 0.070 total

はっ、速い!!同じマシン上でHaskellの0.13倍!!(昨日のエントリ参照)

移植遊びの感想

前回エントリと今回エントリを通したベンチマークでは、プログラミング言語の表現力の豊かさと、内部的に計算器へ値を渡すショートカットがいかに両方優れているかを競う感じになってて面白かった。そういう意味でF#もHaskellも凄い優れてると思った。

自分の中での収穫はF#が意外に良いって事が分かったこと。性能について聞いたことなかったけど、意外にもかなり良いみたいだし、記述も簡潔なんだなぁとかなり見直した。あまり、注目してなかったけど機会があれば是非使ってみたい言語だと思った。

それにしてもC速いといってもHaskellのコード量の3〜4倍くらいになってしまった・・・。こんだけ書いてやっとこさなので、徒労感が激しい。「人月神話」によると、プログラミング言語に限らず、一人のエンジニアが一日に記述できるコード量は一定らしい。やっぱり計算機に優しいとか前例があるとかだけの理由で古くて表現力が乏しい言語を使い続けるのは問題がある。今回だとinduceBackward()内だけCで書くとかがいい。道具は選ぼう。やっぱCって工数的にはとても贅沢だよねぇと、そうとう月並みな感想。

使ったコード

無駄にmalloc()してるところあるので、減らせばもう少し速くなるかな?

#include <stdio.h>
#include <stdlib.h>
#include <float.h>

#define BRANCH_SIZE 3

#define ITERATION   1000

typedef struct Branch_t
{
    int    k;
    double p;
} Branch;

typedef struct Node_t
{
    double df;
    Branch branches[BRANCH_SIZE];
} Node;


double* createValues(int size)
{
    double* result;
   
    if (!(result = malloc(size * sizeof (double)))) 
    {
        fprintf(stderr, "alloc error...\n");
        exit(1);
    }
    return result;
}

void releaseValues(double** values)
{
    if (*values)
        free(*values);
    *values = NULL;
}

void induceBackwardVerySlow(double* result,
                            Node*   nodes,  int nodes_length, 
                            double* values, int values_length)
{
    int i, j, n;
    
    n = values_length / 2;

    for (i = 0; i < nodes_length; i++)
    {
        Node*  node;
        double sum;

        node = nodes + i;
        sum  = 0.0;

        for (j = 0; j < BRANCH_SIZE; j++) {
            Branch* branch;

            branch = node->branches + j;
            sum += branch->p * values[n + branch->k] * node->df;
        }
        result[i] = sum;
    }
}

#define TEST_TREE_LENGTH 100
double* lastValues(int init) 
{ 
    double* values;
    int i;

    values = createValues(TEST_TREE_LENGTH * 2 + 1);

    for (i = 0; i < TEST_TREE_LENGTH * 2 + 1; i++)
        values[i] = (double) init;

    return values;
}

Node** testTree() 
{
    Node** tree;
    int i, j, m;
    
    if (!(tree = malloc(TEST_TREE_LENGTH * sizeof (Node*))))
    {
        fprintf(stderr, "alloc error...\n");
        exit(1);
    }

    for (i = TEST_TREE_LENGTH - 1, m = 0; i >= 0; i--, m++) 
    {
        int l;

        if (!(tree[m] = malloc((i * 2 + 1) * sizeof (Node))))
        {
            fprintf(stderr, "alloc error...\n");
            exit(1);
        }

        for (j = -i, l = 0; j <= i; j++, l++) 
        {
            tree[m][l].df = 1.0;
            tree[m][l].branches[0].k = j - 1;
            tree[m][l].branches[0].p = 1.0 / 6.0;
            tree[m][l].branches[1].k = j;
            tree[m][l].branches[1].p = 2.0 / 3.0;
            tree[m][l].branches[2].k = j + 1;
            tree[m][l].branches[2].p = 1.0 / 6.0;
        }
    }
    return tree;
}

double value(Node** tree, int tree_length, int i) 
{
    double  result;
    double* last_values;
    int j;

    last_values = lastValues(i);

    for (j = 0; j < tree_length; j++) 
    {
        Node*   nodes;
        int     nodes_length;
        double* result_values;
        int     values_length;
        
        nodes = tree[j];

        nodes_length  = 2 * tree_length - j * 2 - 1;
        values_length = nodes_length + 2;

        result_values = createValues(nodes_length);

        induceBackwardVerySlow(result_values, 
                               nodes, nodes_length, 
                               last_values, values_length);

        releaseValues(&last_values);
        last_values = result_values;
    }
    result = last_values[0];
    releaseValues(&last_values);
    return result;
}

int main(int argc, char* argv[]) 
{
    Node** tree;
    double max;
    int i;

    max = DBL_MIN;
    tree = testTree();

    for (i = 1; i <= ITERATION; i++)
    {
        double ret;

        ret = value(tree, TEST_TREE_LENGTH, i);

        if (ret > max)
            max = ret;
    }
    printf("%f\n", max);

    return 0;
}

2012-02-07

HaskellとF#の解読の練習とちょっとした疑問でRubyに移植してみた

Haskell vs F#

↑の記事とそのトラックバック先の記事では関数型言語が1秒未満の世界で競ってる。なんだか凄い。

でも、これ、そのままRubyで書き直すとどの位になるんだろう。せめてHaskellの3,4倍くらい遅い感じで動くと嬉しいな。勉強にもなるし♪と思って書き直して動かしてみたら桁が違ってあわわとなったメモ。甘ちゃんでごめんなさい。

追記など

他のRuby実装について追記しました。

Cにも移植してみました。

http://d.hatena.ne.jp/takeshinoda/20120208/1328697927

JavaScriptにも移植してみました。

http://d.hatena.ne.jp/takeshinoda/20120211/1328943818

下記にコード置いておきます。

https://gist.github.com/1768665

コードと実行結果

上記エントリのF#版が書き直しやすかったので、まずはそのまま書き直した↓。

改修前版
Node = Struct.new(:df, :branches)

def B(int, double)
  [int, double]
end

def induceBackwardVerySlow(nodes, values)
  n = values.length / 2
  nodes.map do |node|
    node.branches.map do |k, p|
      p * values[n + k] * node.df
    end.inject(&:+)
  end
end

ITERATION = 1000

def last_values(i)
  Array.new(201, i.to_f)
end

def test_tree
  (0..99).map do |i|
    (-i..i).map do |j|
      Node.new(1.0, [B(j - 1, 1.0 / 6.0), B(j, 2.0 / 3.0), B(j + 1, 1.0 / 6.0)])
    end
  end
end
 
value = lambda do |i|
  test_tree.
    reverse.
    inject(last_values(i)) {|values, nodes| induceBackwardVerySlow(nodes, values) }.
    first
end
 
puts (1..ITERATION).map(&value).max

これの実行結果↓

$ ruby -v
ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-darwin11.2.0]
$ time ruby hoge.rb
999.9999999999998
ruby hoge.rb  28.88s user 0.04s system 99% cpu 28.935 total

F#の実行環境は作るの面倒くさいので置いておいて、Haskellのコードを同じマシンで実行してみるとこんな感じ↓。

$ ghc --version                                                                                     
The Glorious Glasgow Haskell Compilation System, version 7.4.1   
$ ghc -O2 hoge.hs
[1 of 1] Compiling Main             ( hoge.hs, hoge.o )
Linking hoge ...
$ time ./hoge 
999.9999999999998
./hoge  0.53s user 0.00s system 99% cpu 0.534 total

f:id:takeshinoda:20120207193540p:image54倍差…。

F#のをそのまま移植してるので、そもそも書き方が良くないってのもあると思って工夫してみたけど、ワタクシの才覚ではどうにも計算時間の桁が上がらず。induceBackwardVerySlow()内のinjectやめてそのまま合算したり、Nodeを配列に展開してみたりしたけど、差はたいして埋まらず。アルゴリズム維持したままで差を詰めるのは難しい…(アルゴリズムどう変えれば良いのか分からないけどねw)。

(言い訳がてらに、F#移植してるのになんでHaskellと競ってるねん的なところもありますが…)

そういうモンだと言われてしまうとそうなんだろうけど、こういう書き方良くないよ的なのがあれば教えて欲しいです。もっとオラにRuby力があれば…。

悪あがき↓。27倍差まで縮めた。けど、だいぶ見たくないコードになりました><。

改修後版
def induceBackwardSlow(nodes, values)
  n = values.length / 2
  nodes.map do |node|
    (node[2] * values[n + node[1]] +
     node[4] * values[n + node[3]] +
     node[6] * values[n + node[5]]) * node[0]
  end
end

ITERATION = 1000

def last_values(i)
  Array.new(201, i.to_f)
end

def test_tree
  99.downto(0).map do |i|
    (-i..i).map do |j|
      [1.0, j - 1, 1.0 / 6.0, j, 2.0 / 3.0, j + 1, 1.0 / 6.0]
    end
  end
end

puts((1..ITERATION).map do |i|
  test_tree.
    inject(last_values(i)) {|values, nodes| induceBackwardSlow(nodes, values) }.
    first
end.max)

実行結果↓。

$ time ruby hoge3.rb 
999.9999999999998
ruby hoge3.rb  14.26s user 0.03s system 99% cpu 14.296 total

後者のコードの計算量を減らして(ITERATION=50)プロファイルにかけてみるとこんな感じでした。う〜ん。

$ ruby -r profile hoge3.rb
49.99999999999997
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 53.83    91.49     91.49     5000    18.30    27.59  Array#map
 14.35   115.88     24.39  5000000     0.00     0.00  Array#[]
 12.82   137.67     21.79     5001     4.36    40.25  Range#each
  5.70   147.35      9.68  2000000     0.00     0.00  Float#*
  5.43   156.57      9.22  2000000     0.00     0.00  Fixnum#+
  3.30   162.17      5.60  1500000     0.00     0.00  Float#/
  3.03   167.32      5.15  1000000     0.01     0.01  Float#+
  1.15   169.28      1.96   500000     0.00     0.00  Fixnum#-
  0.13   169.50      0.22     5000     0.04    27.65  Object#induceBackwardSlow
  0.08   169.63      0.13      100     1.30   315.90  Integer#downto
  0.08   169.76      0.13       51     2.55  2712.94  Array#each
  0.05   169.84      0.08     5051     0.02    46.12  Enumerable.map
  0.03   169.89      0.05     5000     0.01     0.01  Fixnum#-@
  0.02   169.93      0.04     5000     0.01     0.01  Fixnum#/
  0.01   169.95      0.02     5000     0.00     0.00  Array#length
  0.00   169.95      0.00        2     0.00     0.00  IO#set_encoding
  0.00   169.95      0.00       50     0.00     0.00  Object#last_values
  0.00   169.95      0.00       50     0.00     0.00  Class#new
  0.00   169.95      0.00       50     0.00     0.00  Array#initialize
  0.00   169.95      0.00       50     0.00     0.00  Fixnum#to_f
  0.00   169.95      0.00       50     0.00   631.80  Object#test_tree
  0.00   169.95      0.00       50     0.00   631.80  Enumerator#each
  0.00   169.95      0.00        3     0.00     0.00  Module#method_added
  0.00   169.95      0.00       50     0.00  2767.20  Enumerable.inject
  0.00   169.95      0.00       50     0.00     0.00  Array#first
  0.00   169.95      0.00       49     0.00     0.00  Float#<=>
  0.00   169.95      0.00        1     0.00     0.00  Enumerable.max
  0.00   169.95      0.00        1     0.00     0.00  Float#to_s
  0.00   169.95      0.00        2     0.00     0.00  IO#write
  0.00   169.95      0.00        1     0.00     0.00  IO#puts
  0.00   169.95      0.00        1     0.00     0.00  Kernel.puts
  0.00   169.95      0.00        1     0.00 169950.00  #toplevel
改修前と後の差分
$ diff hoge.rb hoge3.rb
1d0
< Node = Struct.new(:df, :branches)
3,7c2
< def B(int, double)
<   [int, double]
< end
<
< def induceBackwardVerySlow(nodes, values)
---
> def induceBackwardSlow(nodes, values)
10,12c5,7
<     node.branches.map do |k, p|
<       p * values[n + k] * node.df
<     end.inject(&:+)
---
>     (node[2] * values[n + node[1 +
>      node[4] * values[n + node[3 +
>      node[6] * values[n + node[5) * node[0]
23c18
<   (0..99).map do |i|
---
>   99.downto(0).map do |i|
25c20
<       Node.new(1.0, [B(j - 1, 1.0 / 6.0), B(j, 2.0 / 3.0), B(j + 1, 1.0 / 6.0)])
---
>       [1.0, j - 1, 1.0 / 6.0, j, 2.0 / 3.0, j + 1, 1.0 / 6.0]
32c27
<     reverse.inject(last_values(i)) {|values, nodes| induceBackwardVerySlow(nodes, values) }.
---
>     inject(last_values(i)) {|values, nodes| induceBackwardSlow(nodes, values) }.

【追記】他のRuby実装でやったらめっさ速かったり遅くなったりする

JRuby (改修後版で実行)
$ ruby -v
jruby 1.6.5.1 (ruby-1.8.7-p330) (2011-12-27 1bf37c2) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_29) [darwin-x86_64-java]
$ time ruby hoge3.rb
1000.0
ruby hoge3.rb  10.04s user 0.28s system 133% cpu 7.724 total

おおっ。14倍。浮動小数点の出力の仕様が少し違うらしく、丸まっている。

JRuby(改修前版で実行)
$ time ruby hoge.rb
1000.0
ruby hoge.rb  62.71s user 1.04s system 204% cpu 31.139 total

58倍。ありゃ。だいぶ遅い。

MacRuby(改修後版で実行)
$ ruby -v
MacRuby 0.10 (ruby 1.9.2) [universal-darwin10.0, x86_64]
$ time ruby hoge3.rb
999.99999999991
ruby hoge3.rb  15.73s user 0.45s system 125% cpu 12.855 total

おお、24倍。

MacRuby(改修前版で実行)
$ time ruby hoge.rb
999.99999999991
ruby hoge.rb  250.99s user 7.28s system 146% cpu 2:56.73 total

分越えちゃったよ。330倍。かかりすぎ><

@watson1978 さんにGCDという便利なライブラリを教えて貰った

今回はあえてマルチスレッド化はしていなかったんだけど、すばらしいライブラリですね。最近はいろんな言語にmap的な処理を自動で並列化してくれるライブラリの存在を聞くので、Rubyにもこういったのが出てきてるのは嬉しい。

軽いまとめ

改修版はベンチマーク向きのコードなんだろうね。攻守共に優れてるのがYARVなのかな。

2011-12-12

LEDキューブを作った

LEDキューブとは発光LED立方体に並べて作られた文字通りの箱。下の写真が完成品。

f:id:takeshinoda:20111213005905j:image

Make: Tokyo Meeting(MTM)に行くとよく見かけるんだけど、任意の点をあの少ない足の数でどうやって光らせてるのかさっぱり分からなった。ソフト屋が仕組み聞いても理解できないんだろうとスルーしてたんだけど、前回のMTM07で展示してる人に何気なく聞いてみると「LEDキューブでググるとわかるよ」とドラクエみたいな回答をもらったので検索してみると、確かに分かる。ので、コレなら俺でも作れそうと作ってみた次第。

とはいえ実際はググるだけじゃわからない事も多かったので、オライリーMake vol08に載ってた4 * 4 * 4の作り方を参考に、5 * 5 * 5にして作ってみた。クリスマス近いので電球色のLEDを選ぶ。

↓は雨粒が落ちるようなイメージでアニメーションさせたところ。

D


作ってみて分かったけど、LEDは小さめのものを選んだ方が綺麗かもしれない。LEDを綺麗に並べる為の治具も、真面目に木とドリルで作らないとウネウネになってしまったりして、作りが荒くなってしまった。参考にした本では1辺4つのキューブの作り方だったけど、小さくてなんだかつまんないので1辺を5つにしたら電流は設計以上に流れるわUSBの給電が不安になるわで、LEDだと省エネ節電とか言われてもこういう小さい電子工作の世界だと電気量もろ影響するんだなーとか妙に感心した。で、無計画にサイズ変更したので部品が足りなくなり秋葉原に何度も通うという失態。これだからソフトウェア発想の工作は駄目なんだ。

仕組みの話

Arduino Mega 2560というマイコンとIOポートとパソコンからのプログラムの焼き付けが一体となった超簡単基盤で点灯の制御をさせた。普及版のArduino DuemilanoveってやつはIOピンが20数本くらいだけど、Arduino Mega 2560はAnalogピン含めると70くらいあるので、今回みたいにピンが沢山要るのを作りたくなっても余裕なのだ。

f:id:takeshinoda:20111213014817j:image

少ない足で3Dの任意の場所に点灯させる仕組みは、上述のオライリー(Make vol08)を読むか「LEDのダイナミック点灯」で調べると大体分かる。

大まかに説明すると、1階部分だけ通電状態にして25個のLEDの点灯と消灯を設定。次は2階部分だけのLEDの点灯と消灯を設定...と5階分する。ゆっくりやると下から上に光るだけだけど、コレを目に見えないくらい高速に実施すると同時にキューブ全体が光ってるように見えるというカラクリ。

なので、1辺が5つのキューブの場合全部の球は125個だけど、制御に使うIOポートは30個で済む。1面25個で、各階の通電制御で5個。あわせて30個。

制御のためのソフトウェアArduino IDEと呼ばれる開発環境で開発できる。言語仕様は大体C。違うところはmain()から始まらずに、初回にsetup()が呼ばれて、loop()が呼ばれ続けてループし続けるというところが違う感じ。

アート方面などの異分野の人にでも簡単に開発できるように配慮されているらしくて、とても簡単(いややっぱ異分野にはムズイか)。コード書いてアップロードボタンを押すと、コンパイルからメモリへの焼き付けまで自動で実施してくれる優れもの。

そういえばRubyで書けるgemも発見したけどまだ試していない。今度試したいなー。

動画の制御のコードは以下の通り。

#define LED_SIZE   25
#define FLOOR_SIZE  5
#define SIDE_SIZE   5

byte dots[] = {
  22, 23, 25, 27, 29,
  31, 33, 35, 37, 39,
  41, 43, 45, 47, 49,
  51, 53, 69, 68, 67,
  57, 58, 59, 60, 61
};

byte floors[] = {
  2, 3, 4, 5, 6
};

byte memmap[FLOOR_SIZE *  LED_SIZE];


void pset(byte* pattern, int floor_num, int dot_num, byte value) {
  pattern[floor_num * LED_SIZE + dot_num] = value;
}

byte pget(byte* pattern, int floor_num, int dot_num) {
  return pattern[floor_num * LED_SIZE + dot_num];
}

void setup() {
  int i, j;
  
  randomSeed(analogRead(0));
  
  for (i = 0; i < LED_SIZE; i++)
    pinMode(dots[i], OUTPUT);
    
  for (i = 0; i < FLOOR_SIZE; i++)
    pinMode(floors[i], OUTPUT);
    
  setFloor(0);
  
  for (i = 0; i < FLOOR_SIZE; i++)
    for (j = 0; j < LED_SIZE; j++)
      pset(memmap, i, j, LOW);
}

// num: 0 origin
void writeDotValue(int num, int value) {
  digitalWrite(dots[num], value); 
}

// num: 0 origin
void setFloor(int num) {
  int i;

  for (i = 0; i < FLOOR_SIZE; i++)
    digitalWrite(floors[i], i == num ? HIGH: LOW);
}

// 一回分の描画
#define DELAY_US 600
int setDots(byte* pattern) {
  long current;
  long wait_us = 0;
  int f, i;

  for (f = 0; f < FLOOR_SIZE; f++) {
    current = micros();
    
    setFloor(f);
    for (i = 0; i < LED_SIZE; i++)
      writeDotValue(i, pget(pattern, f, i));

    delayMicroseconds(DELAY_US);
    wait_us += micros() - current;
  }
  return (int)(wait_us / 1000);
}

void draw(byte* pattern, int ms) {
  int total_ms = 0;
  
  while (true) {
     total_ms += setDots(pattern);
     if (total_ms >= ms)
       break;
  }
}

void loop() {
  int i, j;
  
  for (i = 0; i < FLOOR_SIZE - 1; i++)
    for (j = 0; j < LED_SIZE; j++) 
       pset(memmap, i, j,
            pget(memmap, i + 1, j));
  
  for (j = 0; j < LED_SIZE; j++) 
    pset(memmap, 4, j, random(10000) % 9 == 0 ? HIGH: LOW);
    
  draw(memmap, 150);
}