l'essentiel est invisible pour les yeux

Thursday, November 30, 2006

[Cell] Function-Offloadプログラミングモデル 2

[Cell] Function-OffloadモデルでHello, Worldでは、SPE上でhelloプロシージャを実行した。

今回は入力引数として2つの配列を受け取り、出力として配列の各々の値を合計した値を返すリモートプロシージャーを定義する。この加算処理を行うプロシージャを次の3パターン定義する。

  • 同期(SPEでの処理が終了するまで待つ)して実行する。
  • 同期+SIMDで実行する。
  • 非同期で実行する。
前回同様に、次の順番で3つのファイルを作成する。
  1. インタフェース定義ファイル(IDL)を定義 (add.idl)
  2. プロシージャの実装を定義 (spu/add.c)
  3. PPEメインプログラム (ppu/add.c)
インタフェースの定義フォーマット

プロシージャーの定義は次の4つのコンポーネントから構成され、フォーマットは次の通り。
  • 同期タイプ
  • 返り値(常にidl_id_t)
  • プロシージャの名前
  • パラメータの詳細
[<op_attribute>] idl_id_t <identifier> <parameter_declarators>

  • op_attribute - プロシージャーの同期に関して指定する。
  • sync - 同期実行する。PPEアプリケーションはSPEでの実行が終了するまで待たされる。
  • async_b - 非同期で実行する。プログラムは関数が値を返すとすぐに入力用のバッファを再利用できる。プロシージャーの返り値idl_id_tは、以降のPPEプログラム中のjoin_func(idl_id_t id)関数中で、プロシージャーの実行完了を待つ際に使用する。
  • async_i - async_bと同じだが、プロージャーを呼び出した後の入力引数のバッファをjoin_funcが成功する以前に再利用することが出来ない。
  • async - async_iと同じ。
引数の定義(parameter_attributes)は次のようなフォーマットに従う。
[<parameter_attributes>] <type_specifier> <parameter_declarator>

parameter_attributes以下のどれかを指定する。
  • in - 入力パラメータであることを指定する。データはPPEプログラムからSPEプロシージャーへ渡される。
  • out - 出力パラメータであることを指定する。データはPPEプログラムからSPEプログラムへ渡される。
  • size_is(val) - valのサイズを指定する。整数か定数かを指定する。
  • タは整数か定数で指定されるvalのサイズを持つ。この値が配列のサイズに一致しなければダブルバッファリングクエリは無視される。

引数に配列を指定する際は、size_is(val)属性を使用してサイズを指定しなければならない。



add.idl: リモートプロシージャのインタフェースを定義する。

interface add {
import "../stub.h";

const int ARRAY_SIZE = 1024;
[sync] idl_id_t do_add([in] int array_size, [in, size_is(array_size)] int array_a[],
[in, size_is(array_size)] int array_b[],
[out, size_is(array_size)] int array_c[]);

[sync] idl_id_t do_vec_add([in] int array_size, [in, size_is(array_size)] int array_a[],
[in, size_is(array_size)] int array_b[],
[out, size_is(array_size)] int array_c[]);

[async] idl_id_t do_async_add([in] int array_size, [in, size_is(array_size)] int array_a[],
[in, size_is(array_size)] int array_b[],
[out, size_is(array_size)] int array_c[]);
}


spu/do_add.c: プロシージャの実装をする。

#include <stdio.h>
#include <spu_intrinsics.h>
#include <idl_util.h>
#include "../stub.h"

idl_id_t do_add(int array_size, int array_a[], int array_b[], int array_c[]) {
int i;
printf("SPU do_add: start executing with array_size=%d, array_a=0x%x, array_b=0x%x\n", array_size, (int)array_a, (int)array_b);
for(i=0;i<array_size;i++) {
array_c[i] = array_a[i] + array_b[i];
}

return 0;
}

idl_id_t do_vec_add(int array_size, int array_a[], int array_b[], int array_c[]) {
int i;
printf("SPU do_vec_add: start executing with array_size=%d, array_a=0x%x, array_b=0x%x\n", array_size, (int)array_a, (int)array_b);
vec_int4 *vi_a = (vec_int4 *) array_a;
vec_int4 *vi_b = (vec_int4 *) array_b;
vec_int4 *vi_c = (vec_int4 *) array_c;
for(i=0;i<array_size/4;i++) {
vi_c[i] = spu_add(vi_a[i], vi_b[i]);
}

return 0;
}

idl_id_t do_async_add(int array_size, int array_a[], int array_b[], int array_c[]) {
int i;
printf("SPU do_add: start executing with array_size=%d, array_a=0x%x, array_b=0x%x\n", array_size, (int)array_a, (int)array_b);
for(i=0;i<array_size;i++) {
array_c[i] = array_a[i] + array_b[i];
}
return 0;
}

プロシージャの出力は、インタフェース定義ファイルで[out]属性を指定したパラメータに設定することでPPE上から値を参照できる。


ppu/add.c: PPE上で実行するメインプログラムを定義する。

#include <stdio.h>
#include <libspe.h>
#include <libidl.h>
#include "../stub.h"
#include "../ppu_time.h"

int array_a[ARRAY_SIZE] __attribute__((aligned(128)));
int array_b[ARRAY_SIZE] __attribute__((aligned(128)));
int array_add[ARRAY_SIZE] __attribute__((aligned(128)));

#define PrintDebug(array, size) { \
unsigned int i;\
printf("array: {");\
for(i=0;i<size;i++) printf("%d ", array[i]); \
printf("}\n"); \
}

int main() {
int i, async_add_id;
uint64_t ts, time_do_add, time_do_vec_add, time_do_async_add, time_ppu_add;

puts("PPU starting.");
for(i=0;i<ARRAY_SIZE;i++) {
array_a[i] = i << 1;
array_b[i] = i;
array_add[i] = 0;
}

// リモートプロシージャを同期呼び出し—
printf("PPU: do_add calling...\n");
StartTimer(ts);
do_add(ARRAY_SIZE, (int*)(&array_a), (int*)(&array_b), (int*)(&array_add));
StopTimer(time_do_add, ts);
puts("done.");

// SIMDを用いて加算処理するプロシージャーの呼び出し†
printf("PPU: do_vec_add calling...\n");
StartTimer(ts);
do_vec_add(ARRAY_SIZE, (int*)(&array_a), (int*)(&array_b), (int*)(&array_add));
StopTimer(time_do_vec_add, ts);
puts("done.");

// リモートプロシージャを非同期呼び出し—
#ifdef IDL_ASYNC
printf("PPU: do_async_add calling...\n");
StartTimer(ts);
async_add_id = do_async_add(ARRAY_SIZE, (int*)(&array_a), (int*)(&array_b), (int*)(&array_add));
StopTimer(time_do_async_add, ts);

printf("PPU join_do_add \n");
idl_join_do_async_add(async_add_id);
#endif

// PPU 上で同じ処理を実行Œ
StartTimer(ts);
for(i=0;i<ARRAY_SIZE;i++) array_add[i] = array_a[i] + array_b[i];
StopTimer(time_ppu_add, ts);

// 実行時間を出力Š›
printf("PPU do_add: ");
PrintTimer(time_do_add);
printf("PPU do_vec_add: ");
PrintTimer(time_do_vec_add);
printf("PPU do_async_add: ");
PrintTimer(time_do_async_add);
printf("PPU add: ");
PrintTimer(time_ppu_add);

return 0;
}
Tips:
プロシージャで利用する、array_a, array_b, array_cとDMA転送されるデータを128byte境界でアラインする。実際PPEプログラム中でリモートプロシージャに渡す引数の値は128byteでアラインしなくても動作した。これは生成されたスタブ中で128byte境界でアラインするようにメモリの再割り当てを行っているためである。だが、ここでは128byteアラインを明示的に書くことにした。

IDLコンパイラにより生成されるソースコードの一部(ppu/stub_add.c)
/* make sure array_a is properly aligned */

/* allocate 128 bytes aligned chunk of mem for idl_wi->array_a */
idl_alloc_array_a = (int*)alloca(array_size*sizeof(int) + 127);
idl_wi->array_a = (int*)(((unsigned int)idl_alloc_array_a + 127) & 0xFFFFFF80);

memcpy (idl_wi->array_a, array_a, array_size*sizeof(int));
全ての引数に対してこの処理を適用している。

プロシージャの返り値であるidl_id_t型は、IDLコンパイラによってプロシージャーごとにユニークな値(1001から順にインクリメントされた値)が割り当てられる。この値を使用してプロシージャーを識別しidl_join_{プロシージャ名}で実行完了を待つことが出来る。

ppu_time.hの内容は、[Cell] PPE上での実行時間計測のプロファイリング用の関数とマクロをヘッダファイルにしたもの。

コンパイルと実行
ディレクトリ構造は、[Cell] Function-OffloadモデルでHello, Worldの記事と同じspu, ppuというサブディレクトが存在する。Makefileを次のように作成する。

./Makefile
DIRS      := ppu spu
IDL_SRC := add.idl
IDL_FLAGS := -i -p ppu/stub_add.c -s spu/stub_add.c -n 4 -b spu_add
INCLUDE := -I $(SDKINC) -I spu
include /opt/IBM/cell-sdk-1.1/make.footer

ppu/Makefile
PROGRAM_ppu     = ./ppu_add
CFLAGS := -DIDL_ASYNC -DDEBUG
CC_OPT_LEVEL := -O0
IMPORTS = $(SDKLIB)/libidl.a $(SDKLIB)/libmisc.a -lspe

CC_OPT_LEVELで最適化レベルを最適化無しにしないと、ppu_time.h中のmftb関数がインライン展開されて、loopラベルが衝突する。(これ、どうすればいいのだろう?)

spu/Makefile

PROGRAM_spu = $(SDKBIN_ppu)/samples/spu/spu_sync_add
IMPORTS = $(SDKLIB)/libc.a
include ../../../../../../make.footer
コンパイルと実行
% make
% cp spu/spu_add ppu/ppu_add /tmp

Cellシミュレータ上に転送

# callthru source /tmp/ppu_add > ppu_add
# callthru source /tmp/spu_add > spu_add
# chmod +x spu_add ppu_add
# ./ppu_add
省略
PPU do_add: time: 17.489200(msec)
PPU do_vec_add: time: 7.99760(msec)
PPU do_sync_add: time: 2.018320(msec)
PPU add: time: 0.008000(msec)
#

シミュレータ上での実行なので、上記のプロファイリング値はあくまで参考だが、実機でもスタブを介したDMA転送のコストが気になる。DMA転送を減らしながらタスクをSPEに並列化してスケジューリングするあたりのTipsが必要な気がする。PPU上のL1, L2キャッシュ機構やDMA転送のコスト、MFCといったCellのアーキテクチャをあとでやる。

実行結果



実行できた。

Cellプログラミングチュートリアル

オリジナルの文章に加えていくつか書いていきます。
Cell Broadband Engine Programming Tutorial Version 1.1

順次更新予定。

2.1 PPE レジスタ

GPRs(General-Purpose Registers)
32個の64ビット幅のGPR(汎用レジスタ)を持つ。

FPR(Floating-Point Registers)
32個の64ビット幅の浮動小数点レジスタを持つ。浮動小数点の形式はIEEE754の倍精度フォーマットに従う。単精度の結果も内部的には倍精度として扱われる。

LR(Link Register)
64bitのLRは分岐先の実行アドレスを扱う。分岐命令は、LK(link bit)に1が設定されている場合次の命令をLRにコピーする。Move To Special-Purpose Register命令は、GPRの内容をLRにコピーする。

CTR(Counter Register)
64bitのCTRは分岐先の実行アドレスかループカウンタのどちらかを保持することが出来る。いくつかの条件分岐命令フォームはCTRの値を減少させ0かどうか判定する。Move To Special-Purpose Register命令はGPRの値をCTRにコピーする。

XER(Fixed-Point Exception Register)
64bit XERレジスタは、整数オペレーションでのキャリーやオーバフローなどの条件、及びインデクス付きロードストア命令で転送されるバイト数を扱う。

CR(Condition Register)
32bitのコンディション・レジスタはテストと分岐のためのメカニズムを提供する。
比較命令か命令を読みこむ事により32ビットCR中の条件コードが初めに設定されて比較命令によって使われる。

FPSCR(Floating-Point Status and Control Register)
プロセッサは、結果と関連した例外に関する全ての浮動小数点演算の後で、32bitのFPSCRを更新する。IEEE 754によって必要とされているステータス情報が含まれる。加えて例外を扱うためのいくつかの情報がある。

VMRs(Vectoor Multimedia Registers)
全てのベクタ演算のためのソースとあて先を処理するための128bit幅のVMRが32個存在する。

VSCR(Vector Status and Control Register)
32bit VSCRはFPSCRと似た方法で読み書きが行われる。2つの定義されたビットを持ち、non-Javvaモードビットとsaturationビットであり、残りのビットは予約済みである。VSCRからVMRレジスタに移動させるための命令が提供されている。

VRSAVE(Vector Save Register)
32bit VRSAVEレジスタはコンテクストスイッチを横切った状態中のユーザ情報や特権のあるソフトウェアをサポートする。



2.2.1.3 Compatibility with Existing PowerPC Code
PowerPCアーキテクチャのPPE version2.0.2は以下のユーザモード命令が実装されている。

fsqrt(.) - 倍精度浮動小数の平方根
fsqrts(.) - 単精度浮動小数の平方根
fres(.) - 単精度浮動小数の逆数
frsqrte(.) - 倍精度浮動少数の逆数
fsel(.) - 浮動少数
mtofrf - CRのフィールドの一つをXFX-formに移動する
bccbr - CBRへの分岐条件

3.5.1 DMA変換

3.5.2 DMAリスト変換

3.5.3 ダブルバッファ

3.5.4 スカラー演算のベクタ演算化



3.5.5.3 分岐ヒントを用いて分岐予測ミスを減少させる
SPUは26段という深いパイプラインを動作周波数を向上させている。SPUのパイプライン処理は深いため、分岐予測が外れた場合には15サイクルのペナルティが発生する。この分岐ペナルティへの対抗策として、プログラマがソフトウェアレベルで分岐予測アドレスを設定する分岐ヒント命令を用意している。ハードウェアレベルレベルでの分岐予測回路はない。

一般的なプロセッサは通常BHT(分岐ヒストリテーブル), BTAC(分岐先アドレスキャッシュ), BTIC(分岐先命令キャッシュ)を用いたハードウェアレベルの分岐予測を持っている。

ブランチヒント命令は3種類提供される。
・ブランチ先のアドレス
・実際のブランチ命令のアドレス
・プリフェッチスケジュール


3.5.5 分岐処理のコストを減少させる。


3.5.6 関数のインライン化とループアンローリング


4. プログラミングモデル
Cellプログラミングでは7つのプログラミングモデルが提唱されている。

0. Function-Offloadモデル
パフォーマンスを要求するような計算処理をRPCを利用してPPE上からPPE上で定義された関数呼ぶようにして、SPE上で実行する事を可能にする。PPEとSPEの通信処理は、IDLコンパイラが生成するスタブクラスが隠蔽してくれる。DMA転送のコストが気になる。

Tutorials:
  1. [Cell] Function-OffloadモデルでHello, World
  2. [Cell] Function-Offloadプログラミングモデル 2

1. デバイス拡張モデル
デバイス拡張モデルは、SPEがI/Oデバイスのように動作するFunction-Offloadモデルの特別なケース。
全てのI/Oデバイスはメモリマップされているため、SPEはI/Oデバイスと対話することができ、SPEのDMA変換は単一のバイトサイズへの変換をサポートする。I/Oデバイスは、SPEのシグナル通知機構を利用しコマンドの完了をSPEに通知することが出来る。

SPEがデバイス拡張モデルを使用しているとき、それらはOSの一部として特権を持ったプログラムとして実行される。このコードは信頼されて、物理的なデバイスのレジスタへのアクセス権限を与えられるかもしれない。例えば、セキュアなファイルシステムはデバイスとして扱われる。OSのデバイスドライバは、ディスクコントローラが仮想デバイス上の全てのファイルの読み込みと書き込みに応じるためや暗号化と複合化のためにSPEを使用して書き込むことが出来る。

2. 演算加速モデル(Computation-Acceleration Model)
演算加速モデルは、小さい粒度でSPEを統合するSPE中心のプログラミングモデル。
このモデルは、アプリケーションの大きな書き換え無しにアプリケーションをスピードアップさせることが出来る。このモデルはSPEと通信するために共有メモリかメッセージ・パッシングモデルを使用する。

3. ストリーミングモデル
ストリーミングモデルでは、データストリームを介して連続または並列にパイプラインで演算がおこなわれる。PPEはストリーム制御として動作し、SPEはストリームデータの処理をする。SPEでは、チップへの読み込みと書き込みの帯域は、チップ外のDMA転送のバンド幅を一桁上回る。もしそれぞれのSPEは大量の仕事を持っているならば、Cell上ではこのモデルは効果的に動作する。なぜなら、データ長い間Cell Broadband Engine上にとどまらせることができるからである。PPEとSPEは、PPEと処理中のSPE間、その他のSPE間のメッセージパッシングをサポートしている。

だけれども、SDKでは一般的なストリーミング言語はサポートしていない。大抵のCellプログラマは、ストリーミングモデルを拡張して使うことを好む。例えば、P104のSection 3.6.3中のオイラー粒子シミュレーションはストリーミングモデルを実装している。粒子シミュレーションはデータパケットを同時にそれぞれのステップを同時にストリームするカーネルからなる。

参考
http://ja.wikipedia.org/wiki/Stream_processing

4. 共有メモリマルチプロセッサモデル

5. 非対称スレッド実行モデル(Asymmetric-Thread Runtime Model)


6. ユーザモードスレッドモデル
ユーザモデルスレッドは、一つのSPEスレッドが並列に動作するユーザレベル関数の集合を管理する。ユーザレベル関数は、マイクロスレッド(ユーザスレッドとユーザレベルのタスクも)と呼ばれる。SPEスレッドはOSによってサポートされる。マイクロスレッドはユーザソフトウェアによって作成されサポートされる。OSは含まれない。しかしながらマイクロスレッドの集合は複数のSPUをまたがって実行することが出来る。

SPUアプリケーションは共有メモリ中のタスクをスケジューリングし、タスクは利用可能なCPUで処理される。例えばゲームプログラミングでは、タスクは更新する必要があるシーンオブジェクトを参照することが出来る。マイクロスレッドはいつでも完了することが出来、新しいマイクロスレッドはいつでも生成される。

このプログラミングモデルの利点は、SPUの集合をSPEスレッド下で実行することで予測可能なオーバヘッドを持つことである。一SPEは、PPEの補助無しにMFCコマンドキューを削除したり格納したりすることは出来ない。


7. SPE プラグイン
SPEプラグインでは、SPEのLS(256KB)にプログラムが収まらないとき、オーバーレイを使用することで大きなプログラムを実行できる。オーバーレイは、SPUコードを動的にロードしSPUプログラムとして動作させる。

SPE Pluginsでは、モジュラ型内のSPUコードをプログラマが管理することができる。実行時に必要とされる特定のSPUコードは動的にロードされる。このモデルはコードをrequireする他のSPEプログラミングモデルとは異なり、読み込まれるコードはあらかじめ知ることはできない。
SPE PluginはSPUプログラムの実行スタックを使用し、グローバルから参照することは出来ない。SPE Pluginは、入力/出力用のパラメータではなく、実行中のSPUプログラムを用いて通信をする。


参考
次世代プロセッサ Cell Broadband Engine

[Cell] Function-OffloadモデルでHello, World

Cellプログラミングにはいくつかのプログラミングモデルが存在する。
Cell上でのプログラミングモデルを順を追って紹介してみる。

第一回は、PPEとSPEの間でRemote Procedure Callを行うFunction-Offloadモデルと呼ばれるプログラミングモデルで一番単純なHello worldを書いてみた。このプログラミングモデルは、PPEからSPE上のプロシージャをPPE上で定義されたプロシージャのように呼び出すことが出来るRPCを実現する。

前回の記事、SPE上で実行時間を計測するでは、SPEの実行プログラムをオープンしSPEスレッドを作成して処理を実行し、そして処理が終了するのを待ってから、SPEプログラムハンドラを閉じて終了した。


spe_open_image();
spe_create_thread();
spe_wait();
spe_close_image();


Function-Offloadモデルでは、プログラム単位でSPEに処理をさせるのではなく、プロシージャ単位でSPUを使用して処理を並列化することを可能にする。Cell上ではスタブを利用することでMDAやMFCといった低レベルの詳細を知ることなく、PPE上からSPE上のプロシージャを実行することが可能になる。スタブがプロキシの役割を果たしPPEとSPEの通信の詳細であるDMA転送やMFCといった細かくて面倒な処理をラップしてくれる。

IDLのパラメータ定義やPPEからSPE上のプロシージャを呼び出すRPCの仕組みの詳細についてはとりあえず保留。

始めにプロシージャのインタフェースをIDLファイルで定義する。そのファイルをCBE SDKが提供するIDLコンパイラでコンパイルすることで、スタブクラスのソースコード(C言語)を作成してくれる。

初めに、IDLコンパイラの実行ファイルが存在しなかったのでビルドする。

% sudo yum install byacc flex
% cd /opt/IBM/cell-sdk-1.1/src/tools/idl
% make
% ls /opt/IBM/cell-sdk-1.1/host/bin/idl

/opt/IBM/cell-sdk-1.1/host/bin/idlという実行バイナリができあがる。

では、Function-Offload モデルを利用して"Hello World!"を書いていく。
始めに次のように作業用ディレクトリを作成する。ファイルについては順に作っていく。

% ls -FR
.:
Makefile hello.idl ppu/ spu/

./ppu:
Makefile hello.c

./spu:
Makefile hello.c
%

Function Offloadモデルでプログラミングを行うには、3つのソースファイルを作成する必要がある。
  • PPE上で実行するメインファイル
  • SPE上で実行するプロシージャ
  • プロシージャのインタフェースを定義したIDLファイルである。
始めに作業ディレクトリのトップディレクトリにhello.idlというファイルを作成する。
stub.hはIDLコンパイラにより自動生成される。

IDLファイルでhelloという名前のプロシージャーを定義する。[in]と指定されているのは関数への入力のための引数で、[out]と指定される場合は出力のための引数。返り値はつねにidl_id_t型で無ければならない。(その他については別途説明)
helloは、第一引数に文字列のサイズ、第二引数に文字列へのポインタを引数として受け取る。


interface greeting
{
import "../stub.h";
[sync] idl_id_t hello ([in] int nbytes, [in, size_is(nbytes)] char message[]);
}


このIDLファイルからスタブクラスを作成するためのMakeファイルを定義する。

########################################################################
# Subdirectories
########################################################################
DIRS := ppu spu

########################################################################
# Local Defines
########################################################################
IDL_SRC := hello.idl
IDL_FLAGS := -i -p ppu/stub_hello.c -s spu/stub_hello.c -n 4 -b spu_hello

INCLUDE = -I $(SDKINC) -I spu

include /opt/IBM/cell-sdk-1.1/make.footer


-b引数はスタブをプロキシとして実行されるSPUプログラム名、-n引数は割り当てるSPUの最大数を設定する。

makeを実行すると、ppu/stub_hello.cとspu/stub_hello.cというスタブが作成される。合わせてstub.hというヘッダが作成されるのでPPEとSPEプログラム上からこのヘッダを読み込むことでプロシージャを利用することが出来る。

次にPPU上で実行するプログラムppu/hello.cを書く。

#include <stdio.h>
#include <string.h>
#include <libidl.h> // stub.hの前に読み込む‚‹
#include "../stub.h"

int main() {
char *str = "Hello, World!";
hello(strlen(str), str);
return 0;
}


SPEプログラムにhelloプロシージャーの実装の詳細を定義する。
返り値はidl_id_tで固定。引数はIDLファイルで定義したものと合わせる。

#include <stdio.h>
#include <stdlib.h>
#include <idl_util.h>
#include "../stub.h"

idl_id_t hello(int nbytes, char msg[]) {
printf("SPE: %s\n", msg);
return 0;
}
PPE/SPEプログラムをコンパイルするためのMakefileを次のように書く。

ppu/Makefile
########################################################################
# Target
########################################################################
PROGRAM_ppu = ppu_hello

########################################################################
# Objects
########################################################################
IMPORTS = $(SDKLIB)/libidl.a $(SDKLIB)/libmisc.a -lspe

########################################################################
# make.footer
########################################################################
include /opt/IBM/cell-sdk-1.1/make.footer

spu/Makefile

########################################################################
# Target
########################################################################
PROGRAM_spu = ./spu_hello

########################################################################
# make.footer
########################################################################
include /opt/IBM/cell-sdk-1.1/make.footer


作業ディレクトリのトップディレクトリでmakeしシミュレータ上に転送する。

% make
% cp ppu/ppu_hello spu/ppu_hello /tmp/


シミュレータ上でゲストOSから/tmp/ppu_helloと/tmp/spu_outを転送する。

# callthru source /tmp/ppu_hello > ppu_hello
# callthru source /tmp/spu_hello > spu_hello
# chmod +x ppu_hello spu_hello
# ./ppu_out
SPE: Hello, World!
#


見事にHello, WorldをFunction-Offloadモデルを使用し、SPE上で実行させることが出来た。

Wednesday, November 29, 2006

Enorme(エノルメ)の意味

Enorme (エノルメ)は何語かわからないが、グランデよりもさらに大きいサイズのことを意味するみたい。
スタバのVendiに相当し、タリーズではEnormeで注文できるらしい。

コーラの1.5リットル、ペットボトルを大学の構内で常に持ち歩き一日で全部飲む話とか、グランデよりも大きいVendiでホットを余裕で頼む話とかを、アメリカ生活の長い人から聞くといつも驚かされる。

やっぱり、向こうで生活しないとそういう感覚は身につかないだろうな。

エノルメと14年ぶりに再会

14年ぶりぐらいにこの電話と再会した。「発想する会社」中に大失敗したがとても楽しかったプロジェクトとして紹介されていたエノルメという電話である。

14年ほど前に父のオフィスでこの電話を見た。
本中の写真を見た瞬間に「あ、あれだ!」と10年以上前の記憶が鮮明によみがえったため、尋ねたところ押入れにあるという。

そんなわけで、エノルメが今朝押入れから出てきた。
写真を見るとコードが無くコードレスのようだが、実際は太くて長いらせん状のコードがついている。手に取ると重いし、留守番電話機能も無いらしい。値段も当時4万円ほどしたとか。しかし、そんなデメリットを抑えても、そのデザインは今見ても綺麗だと思える。(売れるかどうかは別問題)

調べたところ、エノルメはイタリア人デザイナーのエットーレ・ソットサスが作った電話のようだ。


優れたデザインがヨーロッパに集中するのも、ルネサンスの時代から続くデザインの歴史があるからだと聞く。ものつくりをする一人として、優れたデザインは時代を超えて愛されることに少し感動した。でもデザインとは歴史の積み重ねなのだろうな。ヨーロッパがデザインの中心地であるのも、ルネサンスの時代から続くデザインの歴史があるからなのだろう。
この電話は今見ても綺麗だ。

そして押入れから他にもいろいろと面白いプロダクトデザインを持った製品がでてきた。
ものつくりにソウルをこめている人たちの、製品を見ているとクリエイティビティを刺激された。

この時計は、倉俣史郎というデザイナの時計で昔から家に置かれていた。父が気に入っている時計のようだが、今壊れていて、新宿のActusまで持っていかないと修理できないとか。

黒色の2本の短針と長針。
赤色の1本の秒針。

文字盤はない。
正確な時間を知ることは出来ないが、この時計を部屋置く人は正確な時間を知ることを求めていないだろうな。






こちらも時計。
開封して動作させるのは面倒だったため、パッケージのみ。目を離せない展開の連続とパッケージに書かれている。

動作イメージはこんな感じ。

写真









こちらはダイヤル式の電話。飛行機のプロペラの形をしている。
昔家に置かれていた記憶はあるが、実際にこの電話を使った記憶はない。当時(80年代前半?)はNTTが家に置く電話を規制していたというから驚く。

こんな変な電話を置くことは許されていなかったらしく、NTTの電話を置くように強制していたようだ。だから、こそこそと使っていたとか。







こちらは壁掛けカレンダー。
上のパターンはいくつものパターンを重ねあわせて表現されており、日によってパターンを代えることで無限代のパターンを作ることが出来る。結構面白い。

「ニューヨーク近代美術館に展示されています」と英語で書かれている。










こちらも電話機。
Panasonic製であるがデザインが独特。昔父のオフィスにエノルメの電話機とこの電話機が置かれていた。

こちらも、値段が高くほとんど売れなかったらしい。











綺麗なデザインを持ったものが必ずしもヒットすることは無いし、むしろそのこだわりが悪く作用することもある。でも、これらは使っているだけで楽しくなるような製品だと思う。一ものつくりの人間としては、ソウルのあるものつくりをすることに魂を注ぎ込みたいし、それを感じることのできる感性を磨く必要性を凄く感じる。

PS
12月12日のデジタル・テレビの新たなる挑戦にいけることになったので、そのときにSteelecase Life/Work Center in Tokyoを見に行こう。楽しそう。

次の二冊の本が読みたかったので、大学の図書館を調べたがどちらも置いていなかった。
オライリーの本は大量にそろっているのに。上の本とかAPUにあってしかも館内利用限定。

経験デザインとタトゥー

AM5時30分 。どこかの誰かみたいに「寝るのはあの世に行ってからにします。」なんて気にはならない。

今関心を持っているテーマのひとつが「経験デザイン」。
経験デザインとは何だろう?それはスターバックスのヒットにも見られるし、一連のレクサスブランドにも見られるかもしれない。

多くのユーザは、よりいい経験デザインに惹きつけられるが、それの何がいいかをうまく語ることができない。また逆に現在使っているものが洗練されていなくても、それの不満をうまく表現することはできない。

今の経験をリデザインすることや全く新しい経験デザインを創造することが、イノベーションのヒントにつながるかもしれない。

より経験デザインを提供するには、良い経験デザインを体験し感じることだと思う。それには、リッツカールトンのスイートに泊まってサービスを受けるのがよいかもしれないし、レクサスに乗るのがいいかもしれないし、スターバックスが売り出す商品がなぜうれるのか考えてみるのもいいと思う。

そして、よい経験デザインに共通することがあるとすれば、そこに考え抜かれた物語があることだとおもう。
AppleのiPodにしてもスターバックスのヒットにしてもレクサスブランドにしても。こんなの作ったら面白いからというレベルの話でなく、真にユーザを満足させる物語があると思う。その物語は人やインターネットを通じてどんどんと広まっていく。

経験をデザインすることの出来る場所は、普段のライフスタイルの中に多く潜んでいる。
敏感にならないといけないな。


キーワードを入れてそのタトゥーに関する画像が何件ヒットするかやってみた。
体に刺繍を入れるにはそのデザインがカッコよかったり、それ自身を愛している証だろうと思ったから。

Image: harley-davidson tatoo 62件
Image: apple tatoo 74件
Image: microsoft tatoo 7件
Image: Debian tatoo 17件
Image: Redhat tatoo 0件

ノイズが多いので実際の結果は上の数値より少なくなる。Microsoftは実質0件。
ハーレーのタトゥーがカッコイイと思われていたり、Debianはあるけど、FedoraやRedhat関係は無いといったことやAppleはあるがMSはないといった結果はなかなか面白い。

Tuesday, November 28, 2006

[Cell] SPE上での実行時間計測

SPE上で実行時間を計測するには、SPU Decrementerを利用する。
SPU Decrementerは参考資料のPDFに次のように説明されている。


A register that counts down each time an event occurs. Each SPU
contains dedicated 32-bit decrementers for scheduling or performance
monitoring, by the program or by the SPU itself.


プロファイリングのために利用できる32bitレジスタで一定周期で値が減少していく。この差を取ることで実行時間の計測が出来る。

PPUのTime Base Registerと同じように周期を知るには、/proc/cpuinfoを見る。

% cat /proc/cpuinfo | grep -w timebase
timebase: 25000000
%

SPU Decrementerもこの値が周期の基準になるのか少し疑問だが、fixstartsのWikiでもこの値を使っているようなので。

SPUチャネルにSPU_WrDec, SPU_RdDecというニーモニックを指定し書き込む or 読み込むことでレジスタの値が取得できる。

uint32_t t;
spu_writech(SPU_WrDec, 0xffffffff); // 0xffffffffを書き込み
t = spu_readch(SPU_RdDec); // SPU Decrementerの値を読み込む。€‚


この低レベルAPIをラップした関数として、spu_write_decrementer(uint32_t), spu_read_decrementer(void)という関数がspu_mfcio.hで提供されている。

uint32_t t;
spu_write_decrementer(0xffffffff);
t = spu_read_decrementer();


この関数を利用したプロファイリング用のマクロを定義する。

static const int TIMEBASE = 2.5 * 1.0e7; // cat /proc/cpuinfo | grep -w timebase

// プロファイル用関数ƒ­
#define StartTimer(ts) {spu_write_decrementer(0xffffffff); ts=spu_read_decrementer();}
#define StopTimer(te) {te -= spu_read_decrementer();}
#define PrintTimer(te) {printf("timer: %f(sec)\n", te / (float)TIMEBASE * 1.0e3);}


初期値としてSPU Decrementerに大きな値を設定しておく。StopTimerでStartTimer時に取得した値との差を計算する。PrintTimerでレジスタの減少値から実行時間を算出する。
これを利用してスカラー演算とベクタ演算の実行速度を比較する。

ソースコード

#include <stdio.h>
#include <stdint.h>
#include <spu_intrinsics.h>
#include <unistd.h>
#include <spu_mfcio.h> // spu_read_decrementer and spu_write_decrementer

static const int N = 200000;
static const int TIMEBASE = 2.5 * 1.0e7; // cat /proc/cpuinfo | grep -w timebase

// プロファイリング用マクロ
#define StartTimer(ts) {spu_write_decrementer(0xffffffff); \
ts=spu_read_decrementer();}
#define StopTimer(te) {te -= spu_read_decrementer();}
#define PrintTimer(te) {printf("timer: %f(msec)\n", te / (float)TIMEBASE * 1.0e3);}

int main(unsigned long long spu_id, unsigned long long arg) {
uint32_t profile, profile_simd, i;
uint32_t
in[4] __attribute__((aligned(16))) = {1,2,3,4};
uint32_t
out[4] __attribute__((aligned(16))) = {0};
vec_int4 *v_in = (vec_int4 *) in;
vec_int4 *v_out = (vec_int4 *) out;

// スカラー値で計算
StartTimer(profile);
for(i=0;i<N;i++) {
out[0] += in[0];
out[1] += in[1];
out[2] += in[2];
out[3] += in[3];
}
StopTimer(profile);

// SIMDでベクタ演算
StartTimer(profile_simd)
for(i=0;i<N;i++) {
spu_add(*v_out, *v_in);
}
StopTimer(profile_simd);

// 出力
printf("Scalar: ");
PrintTimer(profile);

printf("Vector: ");
PrintTimer(profile_simd);

return 0;
}

コンパイルとシミュレータへの転送用に/tmpへコピー

% make
spu-gcc -Wall -I/opt/IBM/cell-sdk-1.1/src/include/spu/ -I/opt/IBM/cell-sdk-1.1/src/include/ -I/opt/IBM/cell-sdk-1.1/sysroot/usr/lib/gcc/spu/4.0.2/ 0.c -c
spu-gcc -L/opt/IBM/cell-sdk-1.1/sysroot/usr/lib/ 0.o -o spu.out
% make install
cp spu.out ../ppu-main/ppu.out /tmp/
%
SPUプログラムを動かすためのPPUプログラムを作る。
#include <stdio.h>
#include <libspe.h>

static const char SPE_FILE_NAME[] = "./spu.out";
int main() {
int status;
spe_program_handle_t *spe_handle;
speid_t spe_id;

printf("[PPE] Open SPE program.\n");
spe_handle = spe_open_image(SPE_FILE_NAME);
if(spe_handle == 0) {
printf("ERROR: Cannot open SPE program.\n");
return 1;
}

printf("[PPE] Create SPE thread.\n");
spe_id = spe_create_thread(0, spe_handle, NULL, NULL, -1, 0);
if(spe_id == 0) {
printf("ERROR: Cannot crate SPE thread.\n");
return 1;
}

printf("[PPE] Waiting SPE thread...");
spe_wait(spe_id, &status, 0);
printf("done.\n");

printf("[PPE] Release SPE program.\n");
spe_close_image(spe_handle);

return 0;
}


コンパイルと転送
% make                                                                                                   
ppu-gcc -m32 -Wall -I/opt/IBM/cell-sdk-1.1/sysroot/usr/include 1.c -c
ppu-gcc -L/opt/IBM/cell-sdk-1.1/sysroot/usr/lib 1.o -lspe -o ppu.out -m32
% make install
cp ../spu-main/spu.out ppu.out /tmp/
%


シミュレータ上に転送し実行
# callthrue source /tmp/ppu.out > ppu.out
# callthrue source /tmp/spu.out > spu.out
# chmod +x ppu.out spu.out
# ./ppu.out
[PPE] Open SPE program.
[PPE] Create SPE thread.
Scalar: timer: 2.812480(sec)
Vector: timer: 0.687480(sec)
[PPE] Waitin SPE thread...done.
[PPE] Release SPE program.
#
追記
SPUの実行形式オブジェクトファイル単体でも実行可能。

SIMDを利用したベクタ演算の方が4倍ほど早くなっています。
SIMDでは同時に4データを扱えるので妥当なところかな。
参考
Cell Broadband Engine Programming Tutorial Version 1.1 (PDF)
lesson12 - Pukiwiki

[Cell] PPE上での実行時間計測

CellはPowerPC G5互換の汎用プロセッサPPE一つと浮動小数点演算に優れたSIMDアーキテクチャのSPE八つを持つPS3にも搭載されている注目のCPU。こいつが家電に載って、ブロードバンド時代のライフスタイルのために何を作れるだろうと今から考えるだけでもテンションが上がる。

PPEもSPEもハードウェアレベルの分岐予測を持たないなど、シンプルな構造を採用し高クロックを実現している。逆に言うと、ソフトウェアレベルでプログラマ自身がアーキテクチャを理解した上でパフォーマンスを最大限に引き出すようにプログラムを書く必要がある。

さらに、スキルも要求されるとあってますます楽しい。

どれぐらい少ない時間でどれだけ多くの命令が実行できているかを知るプロファイリングのテクニックはCellプログラミングでも非常に重要。

Cellプロセッサ上では、PPEとSPU上で実行時間の計測方法が異なる。
ここでは、PPE上での実行時間の取得について説明します。

PPE上での実行時間の取得には、C言語標準のgettimeofday関数を使用する方法とPowerPC上のTime Base Register(TBR)を使用する方法がある。TBRを使用する方法の方がOS上の他のプロセスの影響を受けにくくより正確な値が取得できるというメリットがある。

Time Base Registerは、64bitのサイズを持ち一定の周期でその値が増加していくレジスタである。参考資料によるとカウンタがインクリメントされる周期はCPUの実装依存のようですが、1クロック毎に1増加すると考えてよさそうだ。

PPE(PowerPC上)で実行時間を測定するマクロとTime Base Registerの値を読み込む関数mftbを定義する。Cell実機が無いため、IBMが提供するCell Broadband Engineをx86の32bit CPU上で実行する。64bitレジスタの値を一度に読み取ることができないため、Time Base Upper(上位32bit)とTime Base Lower(下位32bit)を別々に読み込む。この時に、下位32bitの桁上がりを考慮し、TBUの値を2度読み込み以前に読み込んだ値と異なるかどうか比べる。異なる際は、もう一度上位32bitを読み込む。


// TBLの桁上がりを考慮したTBRの読み出し—
static inline unsigned long long mftb(void) {
register uint32_t tbu, tbl, tmp;

__asm__ __volatile__ (
"loop:\n\t"
"mftbu %0\n\t" /* Time Base Upper 32bitを取得*/
"mftb %1\n\t" /* Time Base Lower 32bitを取得*/
"mftbu %2\n\t" /* 再びTime Base Upper 32bitを取得*/
"cmpw %0, %2\n\t" /* TBLの桁上がりが無いかどうか確認する */
"bne loop" /* 桁上がりがあればもう一度TBUを取得しなおす */
: "=r"(tbu), "=r"(tbl), "=r"(tmp)
: /* nope */
: "cc");

return (((uint64_t) tbu) << 32) + tbl;
}


PowerPCのアセンブリ命令については参考資料の一つ目が参考になる。

実行時間を求めるにはTimbaseの周期を知る必要がある。
これは/proc/cpuinfoで確認できる。(周期は環境により異なる)

# cat /proc/cpuinfo | grep -w timebase
timebase : 25000000
#


周期とTime Base Regiterの増加量を利用すれば実行時間を求めることが出来る。

実行時間(msec) = TBR増加量 / 周期 × 1.0e3


プロファイル用マクロの全ソース
TIMEBASEの値は環境に応じて上記で調べた値に変更する必要あり。

#include <stdio.h>
#include <stdint.h>
#include <libspe.h>
#include <unistd.h>

static const int N = 3;
static const int TIMEBASE = 2.5 * 1.0e7; // cat /proc/cpuinfo | grep timebase

// TBLの桁上がりを考慮したTBRの読み出し—
static inline unsigned long long mftb(void) {
register uint32_t tbu, tbl, tmp;

__asm__ __volatile__ (
"loop:\n\t"
"mftbu %0\n\t" /* Time Base Upper 32bitを取得*/
"mftb %1\n\t" /* Time Base Lower 32bitを取得*/
"mftbu %2\n\t" /* 再びTime Base Upper 32bitを取得*/
"cmpw %0, %2\n\t" /* TBLの桁上がりが無いかどうか確認する */
"bne loop" /* 桁上がりがあればもう一度TBUを取得しなおす */
: "=r"(tbu), "=r"(tbl), "=r"(tmp)
: /* nope */
: "cc");

return (((uint64_t) tbu) << 32) + tbl;
}

#define StartTimer(ts) {ts=mftb();}
#define StopTimer(te, ts) {uint64_t t=mftb();te=t-ts;}
#define PrintTimer(te) {printf("time: %f(msec)\n", te/TIMEBASE * 1.0e3);}

int main() {
int i;
uint64_t ts, te;

StartTimer(ts);
for(i=0;i<N;i++) {
sleep(1);
puts("looping..");
}
StopTimer(te, ts);

// 実行時間の出力Š›
PrintfTimer(te);
return 0;
}


コンパイルとシュミレータにコピーするために/tmpへコピー

% make
ppu-gcc -m32 -Wall -I/opt/IBM/cell-sdk-1.1/sysroot/usr/include 0.c -c
ppu-gcc -L/opt/IBM/cell-sdk-1.1/sysroot/usr/lib 0.o -lspe -o ppu.out -m32
% make install
cp ../spu-main/spu.out ppu.out /tmp/
%


シュミレータ上に転送し実行

# cellthru source /tmp/ppu.out > ppu.out
# chmod +x ppu.out
# ./ppu.out
looping..
looping..
looping..
time: 3000.000000(msec)
#


とりあえずPS3かわないとな。


参考文献
32ビット PowerPCアーキテクチャ プログラミング環境 (PDF)
The Programming Environments for 32-Bit Microprocessors(原文)
[Mac] PowerPC でもクロックカウント
tips_timebase - PukiWiki

全てのサービスの背景には人間がいる

ただいまAM4時前。 前作のイノベーションの達人が面白かったため同じ作者の前作である「発想する会社」を大学の図書館で借りてきて読んだ。

遊び心が溢れる楽しい会社だということが本からも伝わる。一度IDEOのオフィスに行ってみたい。

どちらの本も、何事も自由を尊重する会社がブレインストーミングに関してはきっちりとした取り決めをしていることや、ホットグループといわれるチームワーク・クリエイティビティの発揮できる仕事環境・ユーザエクスペリエンスの徹底した観察・アジャイルなプロトタイプ作成といった盛りだくさんの内容である。



失敗から得る経験

今年はたくさんのプロトタイプ作成を通じて色々な経験をしたが、その中で以前に作った社内用のお弁当注文システムから得たユーザエクスペリエンスは、本の内容とも重ね合わせることが出来る。

少し前、社内では、毎日のランチが悩みの種だった。
近くにおいしい定食屋が少なかったりすぐ近くの弁当屋にみんな飽き飽きしていた。「そんな時に代々木の大勝軒うまいよ!」って話が社内で出て、弁当を注文することになった。そのお弁当は単品注文と4品から好きなメニューを番号で選んで手書きの内容を毎回FAXするという面倒なフローになっていた。

そんなフローを毎日AM11時ごろになると全員に声をかけてメニューを聞いて回る、弁当大臣がうけもっていたのだが、「社内用弁当注文システム欲しいなぁ」という声が出たので、その日のうちに作ることにした。

システムはお弁当のメニューの画像が表示され日替わりなら、4項目選んでクリックするだけで注文ボタンを押すだけで注文が完了する。と、言ってもその内容を弁当大臣(のみ印刷ボタンが表示される)がプリント用ページから印刷して大勝軒にFAXすることで注文が完了する。

このシステムは1週間ほど使われたが、その後結局使われなくなった。原因は二つあると思っていて、
一つ目は、大勝軒のお弁当のボリュームが多くて毎日食べると太るしキツイ。
二つ目の理由が興味深い。
その理由とは、ワンクリックで注文できるシステムがあるのに、お弁当大臣がみんなに聞いて回ったことだ。そして、結局元通りのお昼の出前メニューがあるところにみんなが集まり、しゃべりながら注文を書き込むというスタイルに戻った。

この経験は、「お昼何たべようか?」といった話をすることの楽しみが重要であることを教えてくれた。もし次にお弁当注文システムを設計&実装するならば、注文ではなく(お昼までに)投票のような仕組みにして、エスプレッソマシーンでも置いたみんなが集まるくつろぎ場所に、数人で操作可能なタッチパネル式のモニタを用意する。そこから今日のランチの投票数や地図情報などが見れるといったものにするだろう。

「お昼何食べる?」といったコミュニケーションが楽しいからであって、ユーザはワンクリックで注文できることをたいして求めていなかったのだ。


お菓子箱

京都ドリコムにはお菓子箱があった。
会社の中央の場所に配置された、お菓子箱にはみんなが自分の好きなお菓子を次々と補給していく。「誰々はこのお菓子が好き」といった情報をみんなが知っていたりする。このお菓子箱は、そこにコミュニケーションを生みただのお菓子以上の価値をもっていたと思う。

もしここにIDEO的な発想が加われば、そのお菓子箱で人気のお菓子のランキングや、または社員が書き込む「こんなお菓子あったらなぁ・・・」という内容をブログで公開
(ブログ企業なので)しようとなるかもしれない。会社のカルチャーを表現し風通しをよくするとともに、うまくいけばそこからヒット商品のアイデアが生まれるかもしれない。

UIEJのお菓子箱は、まだまだ改善の余地がある。
会社の隅っこ(しかもトイレの隣)に置かれてしまっていてお菓子があまり減らない気がする。自分がキットカットばっかり食べているのはバレていたが、他の人が何のお菓子が好きかは知らない。


オープン・オフィス
自分の周りの環境を楽しくする。これって大事だよな。
自分のデスクやイスといった部分には改善の余地がいくらでもある。思いついたアイデアを紙に書き留めるが、その紙がバラバラになってしまいなくなるといったことがよくある。じゃあ、壁にホワイトボードフィルムを貼ろう。本は山積みになっている。カッコよく並べれないだろうか?といったことや、スワイプボードを壁に貼り付けてもよさそうだ。遊び心をくすぐるような環境を作って試すのに一番良い場所がやっぱり(長時間の時間を過ごすことになる)仕事場だろうな。


UIEJはオフィスの舞台装置設計はまだまだ改善の余地がたくさんある。
コミュニケーションが活発に行える場所はないし、席はホットグループごとに集まっているわけでもない。定期購読雑誌が一目でわかるマガジンラックのようなものもない。これからだ。

観察力
IDEOの本で一番感動したのが観察力であり、なぜ?なんで?といった経験を必ずより良いユーザエクスペリエンスとして提供できないか?と考えるところである。それが医療でありMacのマウスであり、セレブ向けの自家用ジェットの機内リモコンであり。

写真は、机の上につまれた本。LRUって感じで本棚と机の上を行き来する。
本の中の「あとでつかうてきなコメント」にはポストイットをはってるのだが、そういったデータもバーコードで管理できて(本に貼り付けるとか)、PCから本棚の検索もできるとかもっと楽しくできるだろうな。

積まれている本は、上から

  1. 発想する会社
  2. BinaryHacks
  3. UNIQLO PAPER
  4. Cell Broadband Engineのアーキテクチャに関する技術仕様書
  5. Linkers&Loaders
  6. イノベーションの達人
と続く。
最近、雑誌は立ち読みが多い。

Tuesday, November 21, 2006

UNIQLO PAPER N゜1


UNIQLOが発行するフリーペーパーUNIQLO PAPER N゜1を手に入れた。 さすがに力が入ってます。とてもフリーペーパーとは思えないし、デザインカタログとして販売していそうです。

左端は刺繍でとめられている。
中央には小学校のときに立体工作?で使った方眼紙のようなデザイン上にUNIQLO&ユニクロのロゴが配置されている。"紙"という媒体が持つ面白さを最大限利用している

紙の質だろうが写真などはそれほど鮮明でないが、それがかえって紙ってすばらしいなと思わしてくれる。

当分、全てが電子ペーパとその上で動くペーパ風リーダーに置き換わることはなさそうだ。


なんと、TOPページだけでも、UNIQLOという文字9つもある。
UNIQLO・ユニクロブランドをNYで受け入れてもらいたい。そんな日本のトップクリエイターの挑戦心あふれる気持ちがびしびしと伝わる。音楽もファッションもデザインもアートもWEBも世界に通じる。それを証明しようとしている。


中身は、

UNIQL・O

という五文字でコンテンツが区切られている。

Lのページではさまざまな動物のかわいらしいぬいぐるみがマフラーをまいているかと思えば、
OのページではMEN'S NON-NOの表紙を飾るような写真が続く。

日本発で大きなイノベーションを仕掛けたい。
そんな気持ちをところどころに強く感じる。Uのページ中には、「I Love New York」・「ODE TO TOKYO」という見開き2ページが広がるが、白地に赤丸と赤色のハートマーク。
「白に赤色の日の丸」。日本国旗をデザイン化しました。そんな印象。

現地のお店で流しているBGMも日本のクラブミュージックを流しているみたい。
日本代表のコンピュレーションアルバムを手がけたのは、FPMの田中氏。i-depやRIPSLYME×FPM, HALFBYなど普段良く聞いているアーティストの曲も入っています。
FPMといいHALFBYといい京都アツいなぁ。

これは知らなかったのだが、WEBサイトはYugopこと中村勇吾氏が手がけている。
僕は元々Yugop氏のことは知らなかったのだが、今年PMに教えてもらって色々なFlashの作品を見た。
前の記事でも少し書いたけど、UNIQLOのサイトは楽しみながら服を見ることが出来るユーザインタフェース。サイト内で遊んでいるとゆうに30分はたってしまう。

UNIQLOが出す安価な服にそれほど興味はなかったが、
Tシャツが天高く積み上げられるUNIQLO Soho NY店は一度言ってみたいし、Tシャツは買いたいって思った。

この"ユニクロ"ロゴだけは、どうもミスマッチな感じしてならないけど、
"ユニクロ"というブランドを世界で成功させる挑戦なのだろう。

UNIQLOのNY展開カッコイイと思った。

ページの最後にはショッピングリストのメモ用紙とユニクロ紙幣がついている。
ちなみに$15クーポン。NY店しか使えません。



Sunday, November 19, 2006

普通のやつらの上を行けと普通のやつらの下を行け

BinaryHacksの第二章・第四章を勉強しおえた。
マシンの目線でプログラムを少しだけ見れるようになった。

普通のやつらの上を行けは、ポールグラハムの言葉。
普通のやつらの下を行けは、Write Grate Codeのあとがきで鵜飼さんが書かれていた言葉。
(ネタ元はわかりません)

「普通のやつらの下を行ったうえで、普通のやつらの上を行く」

UIEでは中島さんのようなマシン語でプログラムを書かれていたバイナリアンな方から、勉強のために社内ブログに書いている内容にたくさんのツッコミをもらえる。

Wednesday, November 15, 2006

[binary] Hack #5 ELFフォーマット

via Binary Hack #5 ELF入門



バイナリの勉強。一日目は、ELF入門からやった。
はじめてのバイナリ入門なため間違いがあるかもしれません。

ELF とは実行可能バイナリやオブジェクトファイルなどのフォーマットを規定したフォーマットである。

ELFは先頭4バイトが


0x7F 0x45 0x4C 0x46

のようなマジックナンバーを持つ。

また、fileコマンドで確認できる。

[4296]% file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.0, dynamically linked (uses shared libs), for GNU/Linux 2.2.0, stripped
[4297]% file /bin/cat
/bin/cat: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.0, dynamically linked (uses shared libs), for GNU/Linux 2.2.0, stripped
[4298]%


ELFは4つの部分からなる。
ELFヘッダ
プログラムヘッダテーブル
複数のセクション
セクションヘッダテーブル

プログラムヘッダは、次のコマンドで調べる。

[4298]% readelf -h /bin/ls
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8049a50
Start of program headers: 52 (bytes into file)
Start of section headers: 74948 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 25
Section header string table index: 24
[4299]%


ELFヘッダからプログラムヘッダの開始位置やプログラムヘッダ数・セッションヘッダ数といった情報を知ることが出来る。

odコマンドでプログラムヘッダをダンプする。

[4301]% od -N 34 -t x1z /bin/ls
0000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 >.ELF............<
0000020 02 00 03 00 01 00 00 00 50 9a 04 08 34 00 00 00 >........P...4...<
0000040 c4 24 >.$<
0000042
[4302]%


それぞれの値は、

typedef struct {
unsigned char e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
ElfN_Addr e_entry;
ElfN_Off e_phoff;
ElfN_Off e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
}

に対応している。

プログラムヘッダには、実行開始時にメモリにロードされるべきデータが含まれます。プログラムコードや初期化済みグローバル変数の領域などが含まれる。(参考より)
プログラムヘッダを出力するには、readelfコマンドが使用できる。

[4300]% readelf -l /bin/ls


シンボルテーブルを表示

[4307]% readelf -s /bin/ls | head -10

Symbol table '.dynsym' contains 107 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 08049484 60 FUNC GLOBAL DEFAULT UND readlink@GLIBC_2.0 (2)
2: 08049494 283 FUNC GLOBAL DEFAULT UND getgrnam@GLIBC_2.0 (2)
3: 080494a4 42 FUNC GLOBAL DEFAULT UND __fpending@GLIBC_2.2 (3)
4: 080494b4 58 FUNC GLOBAL DEFAULT UND acl_entries@ACL_1.0 (4)
5: 080494c4 83 FUNC GLOBAL DEFAULT UND sigaction@GLIBC_2.0 (2)
6: 080494d4 239 FUNC GLOBAL DEFAULT UND readdir64@GLIBC_2.2 (3)


シンボルテーブルを実際にダンプさせてみて上の表示と一致するか確かめる。

[4310]% readelf -S /bin/ls | grep DYNSYM
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 4] .dynsym DYNSYM 080484a0 0004a0 0006b0 10 A 5 1 4


先頭から0x4a0の位置から0x6b0バイト表示する。

[4313]% od -j 0x4a0 -N 0x6b0 -t x1z /bin/ls | head -10 [/home/furutani]
0002240 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
0002260 6c 01 00 00 84 94 04 08 3c 00 00 00 12 00 00 00 >l.......<.......<
0002300 7d 03 00 00 94 94 04 08 1b 01 00 00 12 00 00 00 >}...............<
0002320 3b 01 00 00 a4 94 04 08 2a 00 00 00 12 00 00 00 >;.......*.......<
0002340 49 00 00 00 b4 94 04 08 3a 00 00 00 12 00 00 00 >I.......:.......<
0002360 9a 02 00 00 c4 94 04 08 53 00 00 00 12 00 00 00 >........S.......<
0002400 fd 00 00 00 d4 94 04 08 ef 00 00 00 12 00 00 00 >................<
0002420 dc 03 00 00 e4 94 04 08 67 01 00 00 12 00 00 00 >........g.......<
0002440 75 01 00 00 f4 94 04 08 67 00 00 00 12 00 00 00 >u.......g.......<
0002460 62 00 00 00 84 a1 05 08 00 00 00 00 11 00 f1 ff >b...............<
[4314]%

一つ目のインデクス(0002260)は本書に取り上げられている通り。
x86はリトルエンディアンであるので、最初の32ビットは0x000037dとなりこの値がシンボル名へのオフセット値となっている。
0002260のインデクスについても同じで、0x16cがオフセットである。

ストリングテーブルを調べてストリングテーブルの開始地点と上記で求めたオフセットを合計して、ダンプする領域を特定する。

[4319]% readelf -S /bin/ls | grep STRTAB
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 5] .dynstr STRTAB 08048b50 000b50 00047b 00 A 0 0 1
[24] .shstrtab STRTAB 00000000 012400 0000c3 00 0 0 1


.dynsymのOffsetの値0xb50+0x37D=0xECD

[4320]% od --skip-bytes 0xecd --read-bytes 16 -t x1z /bin/ls
0007315 67 65 74 67 72 6e 61 6d 00 5f 73 65 74 6a 6d 70 >getgrnam._setjmp<
0007335
[4321]%


getgrnamという文字列は、シンボルテーブルの二つ目のインデクスの値と一致する。

[4326]% readelf -s /bin/ls | head -10

Symbol table '.dynsym' contains 107 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 08049484 60 FUNC GLOBAL DEFAULT UND readlink@GLIBC_2.0 (2)
2: 08049494 283 FUNC GLOBAL DEFAULT UND getgrnam@GLIBC_2.0 (2)
3: 080494a4 42 FUNC GLOBAL DEFAULT UND __fpending@GLIBC_2.2 (3)
4: 080494b4 58 FUNC GLOBAL DEFAULT UND acl_entries@ACL_1.0 (4)
5: 080494c4 83 FUNC GLOBAL DEFAULT UND sigaction@GLIBC_2.0 (2)
6: 080494d4 239 FUNC GLOBAL DEFAULT UND readdir64@GLIBC_2.2 (3)
[4327]%


参考
オブジェクトファイルについて

Tuesday, November 14, 2006

UNIQLOのNY進出

最高の人達を用意して望むといった、UNIQLOのNY進出。
総合ディレクターにN702iDのデザイナーでもある佐藤可士和氏、インテリアデザイナーに片山正道氏を向かえNY進出を果たしたUNIQLO.

UNIQLOはヨーロッパ・アジアといった海外進出ではことごとく失敗している。(香港は黒字?)
失敗している理由は何なんだろう?既に低コストで良質のウェアを提供している企業が存在するからだろうか。ニューヨーカーは新しいものに飛びつくが根付かせるには難しいとも聞く。

ロゴが日本とは異なる。
佐藤氏デザインの新デザイン。

以前柳井会長のゴルフ場付きの邸宅を会社の人に教えてもらったが、車で一周するだけで5分以上かかった。

UNIQLO USのWebサイトは完全にFlashで作られている。
画面全体がタイル上になりそれぞれのタイルが一つのアイテムの画像になっている。画像をクリックするとズームアップしそれがクローズUpされる。そして、クローズUpされた状態からもう一度クリックするとそのクローズUpされた画像が、またタイル上になって次の商品を探せる。

マウスホバーするたびに、文字が変化し動くエフェクトはどうかなという気もするが、少し遊べそうなサイトです。

参考
http://www.uniqlo.com/us/

ホテル ボラボラ

4年後には、ここの水上バンガローに泊まって、トローリングでブルーフィッシュ釣りをして遊べるぐらいになっていたい。25歳・・・4年は厳しいか。5年、26歳。

HOTEL BORA BORA
http://www.amanresorts.com/bora/res.htm
http://www.amanresorts.com/bora/gallery.htm
http://www.tahiti-weddingbell.com/hotel/htb.html

参考
ttp://ameblo.jp/yukino-sakura-hana/theme-10002626343.html#4081

Binary Hacksが届いた

先日発売されたBinary Hacksが 届いた。

抽象化された世界で美しいコードを書いて感動できるのも、全て低レイヤの世界がしっかりとした土台を作ってくれているからである。土台が崩れたときには、その上のレイヤに気づかれた理論全てが崩壊してしまう。

先日読んだ、フェルマーの定理にも似たような話があった。
数学の世界というのは、証明というのが圧倒的な力を持つ。証明にいたらないものは"予測"と呼ばれる。証明は完全無欠であり、よく知られているピタゴラスの定理なども紀元前に証明されたわけだが、現在でもそれは定理であり覆ることは無い。

数学では予想の上に理論を展開することは非常にリスキーである。もし、何十年後にその予想が成り立たないと証明されてしまえばその上に築かれた数学会の何十年にも及ぶ知識の結晶が一瞬にしてくずれてしまうからである。

低レイヤを理解した上で、その上のレイヤ上で美しいソースコードを書きたいと常々思うのだが、なかなか実践できずサボっていた。OS30日本は1日目で挫折した。本重すぎ。

こう書くと、あまりにも技術LoveなT型人間に聞こえるが、T型が全く別の畑を持つT型人間を歓迎し、コラボレートしていくことで世の中を楽しくするようなモノを作っていけるんだろう。
横幅を興味の範囲・縦軸を知識の深さとして、TをⅡ、そしてⅢにしていくような感じ。

今日からBinary Hacksを一つずつ実践していき、より高レイヤで美しく効率的なプログラムを展開できるように努力を続けたい。

こういうすばらしい本を出版してくださった方には本当に感謝です。

Monday, November 13, 2006

[javascript] JavascriptでMap by Method

[ruby] RubyでMap by MethodをJavascriptに移植してみた。

SpiderMonkey限定になるのがとてもイタイのですが、IEでも同じような方法が試せないかあとで考える。(定義されていないメソッドを呼び出した際に呼び出すフックメソッドを定義するこの方法は何て名前がついてるんだろう?誰か教えてください。)


String.prototype.singularize = function() { // 手抜き実装
return this.replace(/s$/, '');
}
Array.prototype.map = function(lambda) {
var ret = [];
for(var i=0,l=this.length;i<l;i++) ret[i] = lambda(this[i]);
return ret;
}
Array.prototype.collect = Array.prototype.map;

Array.prototype.__noSuchMethod__ = function(name, args) {
if(match = name.match(/(map|collect)_([\w_]+)/)) {
iterator = match[1], methods = match[2].split('_and_');
return this[iterator](function(item) {
return methods.map(function(method){
return item[method];
});
});
} else {
return this.map(function(item){
return item[name.singularize()];
});
}
}

var data = [{name: 'Matz', lang: 'ja', loves: 'ruby'},
{name: 'DHH', lang: 'en', loves: 'ruby'},
{name: 'Takahashi', lang: 'ja', loves: 'ruby'},
{name: 'Moriq', lang: 'ja', loves: 'ruby'}];

console.log(data.names()); // => ["Matz","DHH","Takahashi","Moriq"]
console.log(data.map_name_and_lang()); // => [["Matz","ja"],["DHH","en"],["Takahashi","ja"],["Moriq","ja"]]
console.log(data.map_name_and_lang_and_loves()); // => [["Matz","ja","ruby"],["DHH","en","ruby"],["Takahashi","ja","ruby"],["Moriq","ja","ruby"]]


どう見てもString#singularizeが手抜き実装です。

[ruby] RubyでMap by Method

2年前PHPでガリガリと作って工夫をこらし考えていたときには、試行錯誤を繰り返していたが、Railsを使い出してからついついRails内の機能で満足してしまう。

よほど気をつけていないと、そこには改善すべき面白い機能がたくさんあるのにそれを見逃してしまう。多少面倒でもRuby&Railsの十分な恩恵にあずかっていて、それで十分簡単だと思い込んでしまう。

例えばSymbol#to_procを使用した次のようなコードをよく書く。


Person.find(:all).map(&:username)
Person.find(:all).map {|obj| [obj.username, obj.age]}

Symbol#to_procは、とても美しいく便利でよく利用する分だけ、このコーディングが冗長に思えてくる。

method_missingを利用してメソッドをmapメソッドと関連付けDynamic Map?(ARのDynamic Finderにちなんで勝手に命名)として作用させるというアイデアに出会った。

require 'rubygems'
require 'active_support'

class Rubyist
attr_accessor :name
attr_accessor :lang

def initialize(name, lang)
self.name = name
self.lang = lang
end
end

module MapByMethod
def self.included(base)
super

base.module_eval <<-EOS
def method_missing(method, *args, &block)
begin
super
rescue NoMethodError => e
if match = method.to_s.match(/(map|select|collect|each|reject)_([\\w\\_]+)/)
iterator, methods = match[1], match[2].split('_and_')
return self.send(iterator) {|item| methods.map {|method| item.send method}}
else
return self.map {|item| item.send method.to_s.singularize}
end
raise e
end
end
EOS
end
end
Array.send :include, MapByMethod

rubyists = [Rubyist.new("Matz", "ja"), Rubyist.new("DHH", "en"), Rubyist.new("takahashi", "ja"), Rubyist.new("moriq", "ja")]
p rubyists.names
p rubyists.map_name
p rubyists.map_name_and_lang


やっていることは単純で、method_missingを利用して高階関数をDynamicに適用するメソッドを利用可能にしている。

  1. 定義されていないメソッド(ある英単語の複数系)が与えられた際には、メソッド名を単数形にしたメソッドがmapへの高階関数として渡される。
  2. _and_でメソッド名をつなぐことにより、それぞれのメソッドを呼び出した結果を配列にして返す。

実行結果は次の通り。

p rubyists.names # => ["Matz", "DHH", "takahashi", "moriq"]
p rubyists.map_name # => [["Matz"], ["DHH"], ["takahashi"], ["moriq"]]
p rubyists.map_name_and_lang # => [["Matz", "ja"], ["DHH", "en"], ["takahashi", "ja"], ["moriq", "ja"]]
はい、美しいです。

参考
New magical version of Symbol.to_proc

Saturday, November 11, 2006

イノベーションを生むということ - 人類学者と実験者

Apple.comはイノベーションを生んでいる会社だ。
Googleもイノベーションを生んでいる。
Microsoftもイノベーションを生んでいる?た?



イノベーションを生むというのは何?
Amazonでぶらぶらしているときに、おすすめされた「イノベーションの達人」を先日購入した。

この本はデザイン・ファームIDEOを支える人材を10のカテゴリに分けて、イノベーションを起こす会社を作るには、どういったキャラクタ(を演じる。またはもつ。)の人たちが必要か?という疑問に答えてくれている本である。

この本に10個登場する人間のタイプは、向き不向きは誰でもあるにせよ意識することでそのキャラクタになれるということがすばらしい。そして、一人の人間は一つのキャラクタだけではなく、複数のキャラクタを兼ね持つになることになる。つまりイノベーションを起こすチームに求められるキャラクタや人物ではなく、誰でもイノベーションを起こすチームを作ることができるということだ。

人間がすごいところは、誰でも情熱を持ち努力することで世の中に大きな影響を与えるエグゼクティブにもなれるし、次々とイノベーションを起こすこともできることだとあらためて思う。

第一章は人類学者という人達について書かれているが、彼らは人をよく観察しうまくコミュニケーションをとり問題を明らかにする。技術の世界にいると、「無知は悪とみなされたり」・「あるツールを使いこなせないのはその人がバカだから」と見下すような場面によく遭遇する。

でも、本当はそういうところに大きなチャンスが隠れているのに探そうともせず独りよがりになりがちである。この項では、フィールドワークの重要性を再認識した。

第二章の実験者について書かれている。実験者は、多くのプロトタイプを生み出し多くの失敗を経験する。失敗した分だけ実験者としての能力の幅を広げる。好奇心旺盛で情熱を持ち努力を欠かさないのが実験者に求められる能力である。

プロトタイプを数多く作ることの重要性はよく聞く話だが、「プロトタイプの基準を下げる」という言葉にはっとさせられた。プロトタイプを作る際に、みんなが素敵!すげぇ!というのを作りたいがためについつい本来のコンセプトから脱線してこってしまうことがよくある。脱線して本来の線路に戻ってこれればいいのだが、作りこんでいる途中にコンセプトを見失ってしまうことのほうが多い。

プロトタイプに求められるものは、見た目の美しさや使いやすさなどではなく、荒削りでもよいがキラリと光るアイデアである。見る目がある人たちは、そこを見る。ということを常に頭の中に入れておこう。

また失敗を恐れないカルチャーつくりの重要性。あるアイデアに対して、一つではなく複数のプロトタイプがあることがプロトタイプの長所と短所について有意義な議論の話につながる。

たとえ話としてこんな話が書かれていた。
彼女の服を選ぶ際に、もう買ってきた服をきて「この服どう思う?」と聞かれたときには彼の答えは決まっていて、いい or 悪いの二択しか存在しない。ショッピング中に一緒に選んでいて、7つの中から選ぶときに初めて、この服のここがかわいいといったよりよい選択への議論へとつながる。

ビデオによるプロトタイプの有効性ではBMWの話が出てくる。BMW者では世界屈指の映像監督を集めて8分間のショートドラマを作成し、自社のHP上でのみ公開したがあっという間に口コミで広まり頼んでもいないのに新聞にまでのって、リンクURLが書かれたメールが飛び交う自体となったという話である。

Ruby on RailsがJavaの10倍の生産性というデモムービーで世界中のプログラマが飛びついたり、Scrybeのデモビデオが世界中の人達の間で話題になったり、AppleのWinを皮肉るCMについて多くのブロガが言及したりと映像によるプロモーションは大きな力を持つ。Youtubeのようなサービスが出るにつれますますこの流れは加速していく。

映像は、短い時間でサービスのコンセプトを万国共通に伝えられるすばらしいプロモーションである。ケチって出来の悪い物を作ってはいけない。世界中でとりあげられることを考えると安い先行投資である。

と、中田英と一泊23万のバカンスセレブデートが報じられた白雪って子がカワイイとうらやましく思いながらも、第二章まで読んで思った事を忘れないように書き留めた。

[RSpec] Mock API

Mock Object

Mock Objectの作成


my_mock = mock(<name>)
my_mock = mock(<name>, <options>)
person = mock('person', :null_object => true)


Mockは名前を引数に取る。仕様の検証が終わった際に全てのMockが検証される。
option引数をハッシュで与えることでMockの振る舞いを調整できる。現在、:null_objectのみがサポートされている。:null_object => trueを引数に渡すとMockに対する全てのメソッドがMock自身を返すようになる。

Mockに対してスタブメソッドを定義する

person.should_receive(:name) # person.name => nil
person.should_not_receive(:name) # person.name => raise Spec::Mocks::MockExpectationError


Mockに対して引数を固定してスタブメソッドを定義する

person.should_receive(:say).with(:hello)
person.say(:hello) # => nil
person.say # => raise Spec::Mocks::MockExpectationError


初回の呼び出しのみ引数を固定

person.should_receive(:say).once.with(:hello)
person.say(:hello) # => nil
person.say(:hello) # => raise Spec::Mocks::MockExpectationError


引数を渡さないことを明示的に宣言

person.should_receive(:say).with(:no_args)
person.say(:hellow) # => raise Spec::Mocks::MockExpectationError


引数を取りうる(なしもOK)ことを明示的に宣言
これはwith()のデフォルトの定義である。

person.should_receive(:say).with(:any_args)


スタブメソッドの引数の型のみ指定する。

person.should_receive(:say).with(:string)
person.say('hello') # => nil
person.say(false) # => raise Spec::Mocks::MockExpectationError

:string, :numeric, :boolean, :anythingが指定可能。

should_receiveで定義したメソッドが呼び出される回数を定義する。
デフォルトでは、should_receiveで定義するメッセージは一度だけ呼び出し可能である。

person.should_receive(:say)
person.say(:hello) # => nil
person.say(:hello) # => raise Spec::Mocks::MockExpectationError



person.should_receive(:say).twice
person.say(:hello) # => nil
person.say(:hello) # => nil


メソッドが呼び出される任意の回数を指定する

person.should_receive(:say).exactly(1)
person.say(:hello) # => nil
person.say(:hello) # => raise Spec::Mocks::MockExpectationError


メソッドが呼び出される最小(下限) or 最大(上限)回数を指定する

person.sayを最低2回呼び出すように定義。
person.should_receive(:say).at_least(2)
person.say(:world)
# => raise Spec::Mocks::MockExpectationError


最低n回呼び出すように指定

person.should_receive(:say).at_least(n).times


呼び出される最大回数(上限)を定義

person.should_receive(:say).at_most(:once)
person.should_receive(:say).at_most(:twice)
person.should_receive(:say).at_most(n).times


何度でも呼び出し可能にする

person.should_receive(:say).any_number_of_times


返り値を明示的に定義する

person.should_receive(:say).and_return('Hi!')
person.say # => "Hi!"



person.should_receive(:say).and_return(['Hello', 'world'])
person.say # => ["Hello", "world"]


and_returnにはブロックを渡すことが出来る。ブロックの実行結果が返される。

calc.should_receive(:add).with(:numeric, :numeric).and_return {|a,b| a+b}
calc.add(1,2) # => 3


メソッド呼び出し時に例外を発生させる

person.should_receive(:say).and_raise('error')
person.should_receive(:say).and_throw('error')
person.say # => "error":String


ブロック引数を定義する。メソッド呼び出し時にはブロックを渡す。

person.should_receive(:say).and_yield(1,2)
person.say {|a,b| a+b} # => 3


メソッドが呼び出される順番を明示的に定義する。
should_receiveをordered付きで定義した順番に呼び出しているので検証は成功する。

person.should_receive(:one).ordered
person.should_receive(:two).ordered
person.should_receive(:three).ordered
person.one
person.two
person.three


次の例は、定義した順番で呼び出していないので失敗する。

person.should_receive(:one).ordered
person.should_receive(:three).ordered
person.should_receive(:two).ordered
person.one
person.two
person.three


should_recieveにはブロックを渡すことも出来る。呼び出された際にブロックが実行され検証される。

person.should_receive(:say) do |a,b|
a.should_eql 'hello'
b.should_eql 'world'
end
person.say('hello', 'world')


次の例は第一引数が'hellow'出ないため検証に失敗する。

person.should_receive(:say) do |a,b|
a.should_eql 'hello'
b.should_eql 'world'
end
person.say('hello', 'world')


参考
Mock API

[rails] 実践RSpec on Rails - コントローラとモデルのBehaviourを書く

先日RSpec on Rails0.7(0.7.1も)が出ました。
まだまだ枯れていないので実際のプロジェクトで採用するのは難しい面もありますが、isoration from Databaseを支えるmock/stub frameworkや、isoration from viewsは強力なので、それほど影響の無い作成済みの社内アプリにRSpec on Railsを適用してみました。

maihaさんがやっておられるように、自作で作るのもすごく楽しそうなのですがとりあえずは使ってみます。mock/stub frameworkの実装の詳細をあとでみる。

BBDでは、Behaviourを書いてからコードの実装を行うのですが、今回は以前にうみがめで作った社内用情報共有ツールBasecamp(某signalsのパクリ)にBehaviourを書いていくことになります。

BasecampのSpec

  • 社内でブログをベースに情報共有を行うグループウェア
  • 認証は、Ajaxによるワンタイムパスワード認証
  • 3日以内で作るために、BBDもTDDをしていなかった。
  • はてな記法が使える。はてな日記のインポートが可能
rake stats

[4141]% rake stats [/var/www/sankhon]
(in /var/www/sankhon)
+----------------------+-------+-------+---------+---------+-----+-------+
| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Helpers | 37 | 33 | 0 | 5 | 0 | 4 |
| Controllers | 620 | 480 | 9 | 54 | 6 | 6 |
| Components | 0 | 0 | 0 | 0 | 0 | 0 |
| Models | 82 | 75 | 12 | 3 | 0 | 23 |
| Libraries | 574 | 472 | 6 | 15 | 2 | 29 |
| Model specs | 16 | 13 | 0 | 0 | 0 | 0 |
| View specs | 0 | 0 | 0 | 0 | 0 | 0 |
| Controller specs | 54 | 42 | 0 | 0 | 0 | 0 |
| Helper specs | 0 | 0 | 0 | 0 | 0 | 0 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total | 1383 | 1115 | 27 | 77 | 2 | 12 |
+----------------------+-------+-------+---------+---------+-----+-------+
Code LOC: 1060 Test LOC: 55 Code to Test Ratio: 1:0.1

You have new mail.
[4146]% [/var/www/sankhon]


セットアップ

% sudo gem install rspec
% sudo gem install zentest -v 3.4.1
% sudo gem install diff-lcs
% ./script/plugin install svn://rubyforge.org/var/svn/rspec/tags/REL_0_7_0/vendor/rspec_on_rails/vendor/plugins/rspec


AccountController#loginがログインに関する実装を提供するコントローラである。
GETの場合は、ワンタイムパスワードのためのトークンを埋め込んだログイン画面を表示し、POSTの際は認証を行い成功すると200を返し失敗すると400を返す。
ログインはXMLHttpRequestで行う。

BBDではBehaviourを書いてから実装するのがセオリーだが、ここでは既にあるコードにBehaviourを書いて必要に応じてリファクタリングを施していく。

旧AccountsController#login

def login
case request.method
when :get
@challenge_code = [rand(64), rand(64)].pack("C*").tr("\x00-\x3f", "A-Za-z0-9./").crypt([rand(64), rand(64)].pack("C*").tr("\x00-\x3f", "A-Za-z0-9./"))
session[:challenge_code] = @challenge_code
when :post
if user = User.find_by_username(params[:username])
hashed_password = Digest::MD5.new(user.password+session[:challenge_code]).to_s
if params[:hashed_passwd] == hashed_password
session[:challenge_code] = nil
session[:user] = user
render :text => "true"
else
render :text => "パスワードが間違っています", :status => 401 # Unauthroized
end
else
render :text => "入力されたユーザは存在しません", :status => 401 # Unauthroized
end
end
end

当初実装していたコードでは、認証の成功/失敗に関するBehaviourを書きづらいので、認証部分のロジックをモデルに移して再実装する。始めに、モデルに実装する認証メソッドのBehaviourを定義する。

[4138]% ./script/generate spec_model user

UserモデルのBehaviourを定義する。
spec/model/user_spec.rb
require File.dirname(__FILE__) + '/../spec_helper'
require 'digest/md5'

context "User class with fixtures loaded" do
fixtures :users

specify "should count one Users" do
User.should_have(1).records
end

specify "生のパスワード+トークンをMD5ハッシュ化した文字列をワンタイムパスワードとする" do
user = users(:junkonno)
challenge_code = [rand(64), rand(64)].pack("C*").tr("\x00-\x3f", "A-Za-z0-9./").crypt([rand(64), rand(64)].pack("C*").tr("\x00-\x3f", "A-Za-z0-9./"))
user.certify(challenge_code, Digest::MD5.new(user.password+challenge_code).to_s).should_eql true
end
end

Behaviourに基づきUser#certifyを実装する。(関連部分のみ)
app/model/user.rb

require 'digest/md5'

class User < ActiveRecord::Base
def certify(code, hashed_passwd)
hashed_passwd == Digest::MD5.new(self.password+code).to_s
end
end

fixturesを定義し検証を行う。
spec/controller/users.yml

# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
junkonno:
id: 1
username: 'junkonno'
password: 'junkonno'

User#certifyを検証する。

[4138]% ./script/rails_spec spec/models/user_spec.rb [/var/www/sankhon]

..

Finished in 0.102308 seconds

2 specifications, 0 failures
[4141]% [/var/www/sankhon]


検証クリア。
User#certifyを使用して認証を行うようにAccountsController#loginを書き直す。
app/controller/accounts_controller.rb

when :post
user = nil
if (user = User.find_by_username(params[:username])) && user.certify(session[:challenge_code], params[:hashed_password])
session[:user] = user.dup
session[:challenge_code] = nil
render :text => "true"
else
render :text => "ユーザが存在しないかパスワードが間違っています", :status => 401 # Unauthroized
end
end

書き直したAccountsControllerに関するBeahviourを定義する。

spec/controller/accounts_controller_spec.rb

require File.dirname(__FILE__) + '/../spec_helper'

context "The AccountsController" do
# fixtures :accounts
controller_name :accounts

setup do
@user = mock('user')
@user.stub!(:new_record?).and_return(false)
User.stub!(:new).and_return(@user)
end

specify "should be a AccountsController" do
controller.should_be_an_instance_of AccountsController
end

specify "/accounts/loginへGETした際は、チャレンジコードを設定する" do
get 'login'
assigns[:challenge_code].should_match /[\w\d]+/
end

specify "/accounts/logoutへPOSTした際は、認証を行い成功したらtrueを出力し200を返す" do
User.should_receive(:find_by_username).with('junkonno').and_return(@user)
@user.should_receive(:certify).and_return(true)
controller.should_render :text => "true"
post 'login', :username => 'junkonno'
end

specify "/accounts/logoutへPOSTした際は、認証を行い失敗したら401を返す" do
User.should_receive(:find_by_username).with('junkonno').and_return(@user)
@user.should_receive(:certify).and_return(false)
controller.should_render :status => 401, :text => "ユーザが存在しないかパスワードが間違っています"
post 'login', :username => 'junkonno'
end
end
RSpec on Rails0.7の強力な機能である、mock/stub frameworkを使用する。
Mock/stubを使用することで、モデルとDBを切り離しBehaviourを書くことが出来る。

次のコードは、User#newでインスタンスが作成される際に、実際のインスタンスではなくSpec::Mocks::Mockクラスのインスタンスを返すようにスタブを定義する。コントローラ内でUser.newが実行された場合は、Mockオブジェクトのインスタンスである@userが返される。

setup do
@user = mock('user')
@user.stub!(:new_record?).and_return(false)
User.stub!(:new).and_return(@user)
end
setupのブロック内でスタブを定義し、specifyのブロック内でshould_receiveによってスタブメソッドを再定義するのが推奨されているやり方です。二つのメソッドは期待される結果は同じだが、stub!が検証(例外とか)されないのに対して、should_receiveは検証が行われる。
Behaviourは、プログラマでなくとも普通の英文として読むことができます。美しい。

assingsオブジェクトでは、コントローラ内で設定されたクラス変数を確認することが出来る。

specify "/accounts/loginへGETした際は、チャレンジコードを設定する" do
get 'login'
assigns[:challenge_code].should_match /[\w\d]+/
end
'junkonno'が引数に与えられて、User.find_by_usernameが呼び出されたときには、@userを返すようにする。@user.certifyが呼びださされた際には、trueを返し認証が成功した際のBehaviourを検証する。

User.should_receive(:find_by_username).with('junkonno').and_return(@user)
@user.should_receive(:certify).and_return(true)

検証を行う。
[ERROR:1]% ./script/rails_spec spec/controllers/accounts_controller_spec.rb                                                           [/var/www/sankhon]

....

Finished in 0.233141 seconds

4 specifications, 0 failures
[4148]% [/var/www/sankhon]


まとめ
今回は、ログインの実装に関するBehaviour(コントローラとモデル)を定義してリファクタリングを行った。
BBDを進める際は、ロジックを可能な限りモデルに定義しBehaviourを書いていくほうがよい。既存のソースに適用することでソースコードが綺麗になる。
次回は、controllerとviewsのBehaviourの定義。

Rspecのサイトにも書かれているように、RSpec0.7以上ではControllerとViewsのBehaviourを完全に分離して定義できます。これによりViewによりControllerのBahaviourが失敗するといったことがなくなります。そして、Views単体のBBDはselenium等のほかのフレームワークを利用して進める方法が推奨されています。

では、ふるたにでした。

参考
Mock Objects
Mock API
RSpec on Rails – Specifying Controllers
RSpec on Rails – Specifying Models