l'essentiel est invisible pour les yeux

Thursday, December 28, 2006

[日記] 2006年を振り返る

漫才という夢に向かって走っている一年目の友達がこんなことを書いていた。

話は少し変わるが最近、昔からの付き合いの友達と遊んでいても、合わないと感じる事が多くなったような気がする。
空気がぬるいというか何と言うか。
まあやっぱり普通に就職していく人と自分の本当にやりたい事を追求していく人とはどうしたって空気の違いが出てくるものだとは思う。


Life is beautifulでUIEJ立ち上げメンバ募集という記事を見つけ、気がついたら応募していたのがちょうど一年前だった。今年一年は本当に長かった。そしてきつかったが楽しい一年だった。

技術スキル的な成長もあるが、面白い人達と出会い考え方が大きく変わった。働き出して3年目の大学3年ぐらいまで、友達が書いている事と全く同じことを感じていた。

私の場合は、自分にバリアを貼ることで周りへの興味・関心をなるべく排除した。そうしないと、一人で走り続けるだけのモチベーションが維持できないような気がしていた。なぜか、ずっと一人で走っていると思いこんでいた。この業界は特殊だし、友達と夢を語り合うこともなかった。だから友達とはそんな話は全くしなかったし、「友達は友達。私は私」と勝手に分け隔てていた。

誰でも自分自身を周りから隔てるという事はあるのかもしれない。
オリックスの清原選手もプロに入ったときに周りのノイズを排除して自己に集中するために、番長というキャラクターを作り出し近寄りがたくしていったとテレビで語っていたし、サッカーの中田選手も感情を表に出さないように自分を作っていったと話していた。

今年は環境も大きく変わり、一年の半分近くを東京で過ごした。
物事の考え方も大きく変わった今だから振り返れる。私の場合は、バリアを貼って自分のモチベーションを維持してきたことにはメリットもデメリットもあったと思う。メリットは、モチベーションを維持し走ってきたことで強力なスキルを手にした。デメリットは、周りへの関心・興味が薄れ自己中心的になり、非常に狭い感性しか持てなくなったことである。今年は、自分に自信が持てるようになりバリアを外すよい時期だったのかもしれない。

そんな時に、エッジが効いている人が集まっているUIEJの人達に出会うことができた。
某世界的大企業出身のPMはタッチラインを割っていても割っていないというようなジャイアンぶりだし、理系の日本一の大学出身なのにものすごい柔軟な頭だしベンチャーの社長でもあり田園調布に住んでいたという面白い人もいるし、毎朝自転車で20kmぐらいの距離を通っていて映像系の出身なのにプロジェクトマネージメントを行いプログラムも書くマカーな人がいたり、自称問題児という今までであったことに無い新しいメイド系の楽しい人がいたり、笑顔がステキなトムクルーズがいたり、ホストだと思っていたらネットナンパ師だった人がいたり、CEOはマッドサイエンティストだし、社長は○○に凄い詳しいし、キャラクターに富んでいる。
(全てほめ言葉です。念のため。)

本当にすばらしい人達と一年間一緒にやれて、人を隔てることもなくなったしその人達のステキなところと自分のステキなところで何か起こせないか?常にそう思う。機会はそこら中にあって広く興味をもち感性を磨いておかないと見逃してしまう。

今年は出会いも別れもあったが、いい出会いや別れを経験することで物事を考えることでその人の感性や人間がつくられる。いい一年だった。
未来を予測する最善の方法は、それを発明することだ

というアラン・ケイ氏の言葉の通り、未来は創るものだと思う。シアトルかシリコンバレーに渡りガレージベンチャーを起こすまでに色々なステキな人と出会い影響をうけたい。また、そんなエッジの利いた人達に「この人と何かやると楽しそう」そう感じさせるオーラを出せるように自己を磨いていきたい。

人生はあっという間だ。どうせなら楽しいほうがいい。
最高のチームを作り、未来を創造することで時代をつくる。これが自分の人生の楽しみだと思っている。

Tuesday, December 26, 2006

[ruby] ウムラウトを実体参照に変換

卒論やらポルトガル語やらで、Cellができない。。
早く片付けてまたCell始めます。

というわけで今日は、Rubyでウムラウトを実体参照に変換するだけのプログラムです。実体参照を打つのが面倒だったため、同じことで誰かが苦しまないように書いておきます。


module Umlaut
UMLAUT_TRANSLATE_TABLE = %w(À Á Â Ã Ä Å Æ Ç È É Ê Ë
Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú
Û Ü Ý Þ ß à á â ã ä å æ ç è é
ê ë ì í î ï ð ñ ò ó ô õ ö ÷ ø
ù ú û ü ý þ ÿ)

def umlaut2entity
ret = ""
self.each_byte do |c|
(ret += c.chr and next) if c < 0xc0
ret += UMLAUT_TRANSLATE_TABLE[c - 0xc0]
end
ret
end
end

String.class_eval do
include Umlaut
end

if $0 == __FILE__
puts "Voc\xea \xe8 linda!".umlaut2entity
end


実行結果

% ruby test2.rb
Você è linda!
%


これでブラジル人とつきあうことになっても大丈夫です。


参考
http://www.pref.shizuoka.jp/kikaku/ki-28/bom_dia/lesson10.htm

Saturday, December 16, 2006

ライフスタイル2.0について考える - デジタル・テレビの新たなる挑戦に行ってきた

Connect

goo 辞書
Connect - つなぐ, 結合する
近頃よくConnectという言葉を耳にするが、この言葉が気に入っている。広い意味で解釈できる言葉で抽象的だが、「テクノロジーをユーザにつなげてより良いユーザエクスペリエンスを提供する」・「それぞれ単独で動いて自己完結していたモノ同士をつなげてより良いユーザエクスペリエンスを生み出す」。

デジタル・テレビの新たな挑戦 に行ってきた。SONY・シャープ・松下・NTT・Microsoftといった、そうそうたる大企業の方々ばかり。年齢層も30~50代ぐらいの方が多くスーツを着用されている方が目立った。

LLやWeb関連セミナーにしか出席したことが無く家電系のイベントは初めてだった。
当たり前だけど市場が変われば市場を創造している人も変わるなと感じた。

これまでメーカーが創造してきた市場にソフトウェアを作って成長してきたYahoo, Googleといったソフトウェア企業が参入できるような領域が生まれている。何だかとっても楽しそうなことが出来そうだが、とりあえずは自分の部屋にデジタルホームを作ってみないとどんなユーザエクスペリエンスがあると楽しいかわからない。うちのサーバ化しているPS3も何か家電としての活用方法を見出してiTV買ってなど。

とりあえず、ライフスタイル2.0って何って考えてみる。
  • ホームサーバ上にデータが蓄積され好きな場所から好きなときにデバイスに合わせて適切に変換され取り出すことが出来る。(ロケフリ)
  • Web2.0という言葉ともに広まった集合知やFolksonomyを家電上からも利用できる。
  • DLNAで家の中にある家電がつながり、テレビやPCからRemote UIで操作できる。
  • WinならMyネットワークに目覚ましやクーラーなどが表示される。
  • iTunes Storeで購入した映画のジャンルに合わせて、テレビからスピーカーのイコライザを自動で調整してくれる。
・・・と普通の発想や全く意味が無さそうな発想しかでないので、やはり実際にホームネットワークを構築しないと始まらないなぁ。NAS買うからYahoo Engineもバイナリでいいので公開してくれるとうれしいなぁ。

ライフスタイル2.0を創造できるなんて楽しすぎ。

TODO
  • DLNA 1.x 2.0
  • DRM
  • Remote UI
  • Cell

Friday, December 15, 2006

広尾の酉玉

京都に帰ってきた。

今回は、マニアな串焼き屋と書かれている酉玉に行くことができた。そろばん(聞くとせせりらしい)・きんちゃく・おたふく(胸)など聞いたことも無い串が色々と。焼酎・日本酒と豊富な品揃え。聞いたこと無い銘柄のお酒を注文せずに知っているお酒を頼んでしまいがちなのが負けな気がする・・・。

  • そろばん(せせりらしい)
  • おたふく
  • きんちゃく
  • 白レバのたたき

「注文したけど無い品」が結構あってそれが心残り。
おいしかったです。ごちそうさまでした。

Sunday, December 10, 2006

[Cell] ダブルバッファリングを用いたDMA転送

CellプログラミングではSPEプログラミングでの最適化テクニックが重要となる。CellにおけるDMA転送はLSとメインメモリの間をCPUを介さずに通信する仕組みである。今回は、DMA転送を最適化するダブルバッファリングというテクニックを試してみた。

DMA転送を最適化するテクニックとしてダブルバッファリングがある。 ダブルバッファリングでは、二つのバッファとタググループを使用し、DMA転送と計算を同時に行うことで処理時間を短縮することができる。

DMA転送に使用するバッファを2つ宣言する。

2^20という巨大なサイズを持つ配列に0~2^20-1間で初期値を代入する。PPE側で8つのSPEで処理を並列処理するようにデータを準備する。PPEの処理は次のようにする。

  1. DMA転送に使用するため128byte境界にアラインした領域をヒープにメモリを確保する。
  2. 配列の値を初期化する。
  3. 配列を16個のチャンクにわけ、1つのSPUで2つのチャンクの処理する。
  4. DMA転送するデータにはチャンクのサイズとチャンクの開始アドレスを設定する。


特定の境界にアラインされた領域をヒープ上に確保するには、CBE SDKで提供されているmalloc_align.hに含まれる_malloc_align()関数を利用する。確保したメモリは、free_align.hに含まれる_free_align()で開放する必要があることに注意する。


array_size = 2 << 20;
data = (int*)_malloc_align(array_size * sizeof(int), 7);
for(i=0;i<array_size;i++) data[i] = i;


配列をSPUで処理する際のアドレスやチャンクのサイズといった制御情報を持つ構造体を次のように定義する。padを入れて128byteのサイズで定義することでDMA転送を最適化する。

typedef struct _control_block {
unsigned int chunk_size;
unsigned int addrSB;
unsigned int addrDB;
unsigned char pad[116];
} control_block;


1つのDMA転送に渡すcontrol_block構造体は二つのチャンクへのアドレスを持つ。それぞれシングルバッファリングとダブルバッファリングを使用して処理するためのチャンクへの開始アドレスを設定する。1つのSPUで2つのチャンクを処理するので、元の配列を1/16したサイズをチャンクのサイズとする。

control_block cb[8] __attribute__ ((aligned(128)));
/** 省略 **/
chunk_size = array_size >> 4;
for(i=0;i<8;i++) {
cb[i].chunk_size = chunk_size * sizeof(int);
cb[i].addrSB = (unsigned int) &data[chunk_size * (2*i+0)];
cb[i].addrDB = (unsigned int) &data[chunk_size * (2*i+1)];
}


SPE側では、「データをインクリメントする関数」「シングルバッファリングでデータを読み込みデータをインクリメントする関数」「ダブルバッファリングを使用してデータを読み込みデータをインクリメントする関数」の3つを定義する。

DMA転送では1度の最大転送サイズが16KBとなっているため、バッファの大きさは16KB=4096byteの2倍のバッファを用意する。DMA転送のデータが格納されるため、128byteアラインする。


control_block cb __attribute__((aligned(128)));

/* DMA data buffer */
int dma_data_buffer[8192] __attribute__((aligned(128)));
int *data[2];


int *data[2]の二つのポインタは、dma_data_buffer変数で確保した領域をダブルバッファリングで使用するため二つにわけそれぞれの先頭アドレスへの参照を持つ。

data[0] = &dma_data_buffer[0];
data[1] = &dma_data_buffer[4096];


SIMD演算を使用してデータをインクリメントする関数の定義。

/* SIMD演算でデータをインクリメント */
void inc_data_SIMD(int *dest, unsigned int asize) {
int i;
vector unsigned int *vdest;
vector unsigned int vinc = (vector unsigned int) {1,1,1,1};
vdest = (vector unsigned int *) dest;
for(i=0;i<(int)asize/16;i++) vdest[i] = spu_add(vdest[i], vinc);
}


シングルバッファリングとダブルバッファリングを利用してデータを読み込む関数を比較する。
loopcountにはチャンクサイズを1度のDMA転送の最大サイズ16KBで割った値が格納されているものとする。

次の関数ではシングルバッファを利用し引数で渡された実効アドレスのデータを読み込み、バッファに格納する。タグ値として20を指定する。mfc_write_tag_maskで20bit目にマスクをあてDMA転送が完了するまで、mfc_read_tag_status_all()でプロセッサが停止する。その後データをインクリメントし、mfc_putマクロを使用してデータをLSからメインメモリに転送する。

このようにシングルバッファリングではデータ転送とデータの計算処理が逐次的に行われるため効率が悪い。

void single_buffer_example(unsigned int addr) {
int i;
for(i=0;i<loopcount;i++) {
mfc_get(data[0], addr+16384*i, 16384, 20, 0, 0); // 16KB
mfc_write_tag_mask(1 << 20);
mfc_read_tag_status_all();
inc_data_SIMD(data[0], 16384);
mfc_put(data[0], addr+16384*i, 16384, 20, 0, 0);
mfc_write_tag_mask(1 << 20);
mfc_read_tag_status_all();
}
}


ダブルバッファリングを利用したDMA転送では、転送とデータの計算処理が並行して行われる。
ダブルバッファリングを利用したDMA転送は、初めの読み込み・ループによる繰り返し読み込み・最後に転送されたデータの処理の3部から構成される。

void double_buffer_example(unsigned int addr) {
int i;
mfc_get(data[0], addr, 16384, 20, 0, 0); // 16KB
for(i=1;i<loopcount;i++) {
mfc_write_tag_mask(1<<(20+(i&1)));
mfc_read_tag_status_all();
mfc_get(data[i&1], addr+16384*i, 16384, 20+(i&1), 0, 0);
mfc_write_tag_mask(1<<21-(i&1));
mfc_read_tag_status_all();
inc_data_SIMD(data[(i-1)&1], 16384);
mfc_put(data[(i-1)&1], addr+16384 * (i-1), 16384, 21-(i&1), 0, 0);
}
mfc_write_tag_mask(1<<21);
mfc_read_tag_status_all();
inc_data_SIMD(data[1], 16384);
mfc_put(data[1], addr+16384*(loopcount-1), 16384, 21, 0, 0);
mfc_write_tag_mask(1<<20 || 1<<21);
mfc_read_tag_status_all();
}


次の関数では、ループ処理に入る前にDMA転送でメインメモリからLS上のdata[0]にデータをタグの値に20をつけて転送する。ループ一回目の動作は次のようになる。

mfc_write_tag_mask(21)が実行されるため、mfc_read_tag_status_all()でプロセッサがDMA転送が完了するまで待つことは無い。DMA転送ニ使用しているバッファとは違うもう1つのバッファdata[i&1]=>data[1]に並行してデータをシステムメモリから読み込む。このときタグの値として20とは違う値(21)を指定する。

mfc_get(data[0], addr, 16384, 20, 0, 0); // 16KB
for(i=1;i<loopcount;i++) {
mfc_write_tag_mask(1<<(20+(i&1)));
mfc_read_tag_status_all();
mfc_get(data[i&1], addr+16384*i, 16384, 20+(i&1), 0, 0);


mfc_write_tag_mask(1<<20);
で20ビット目にマスクを指定してmfc_read_tag_status_all();を実行することで、ループ処理の前に開始したDMA転送が完了するまで待つ。(その間もタグに21を指定したDMA転送が並行して動作している) 読み込まれたdata[0]から始まる値をインクリメントする。

mfc_get(data[i&1], addr+16384*i, 16384, 20+(i&1), 0, 0);
mfc_write_tag_mask(1<<21-(i&1));
mfc_read_tag_status_all();
inc_data_SIMD(data[(i-1)&1], 16384);
mfc_put(data[(i-1)&1], addr+16384 * (i-1), 16384, 21-(i&1), 0, 0);
}


loopcountの値は常に偶数になることに注意する。
ループ処理が完了した後に、mfc_get(data[i&1], addr+16384*i, 16384, 20+(i&amp;amp;amp;1), 0, 0);の転送処理が完了するまで待つ。ループの最後の繰り返しで実行されるmfc_put(data[(i-1)&1], addr+16384 * (i-1), 16384, 21-(i&amp;amp;amp;1), 0, 0);は常にdata[0]が指すバッファに対しての処理となるため、data[1]のバッファも同様にLSからメインメモリにDMA転送する。

そして、最後にmfc_write_tag_mask(1<<20)で全てのDMA転送が完了するまで待つためのマスクを指定する。

}
mfc_write_tag_mask(1<<21);
mfc_read_tag_status_all();
inc_data_SIMD(data[1], 16384);
mfc_put(data[1], addr+16384*(loopcount-1), 16384, 21, 0, 0);
mfc_write_tag_mask(1<<20 || 1<<21);
mfc_read_tag_status_all();
}


今回は、DMA転送関連のプログラミングTipsとしてダブルバッファリングを調べた。ダブルバッファリングを拡張したマルチバッファリング・共有I/Oバッファというテクニックもあるようだ。これについては次回。

参考
Cell Broadband Engine Programming Handbook Version 1.0 [PDF] (10MB近くあります)

Thursday, December 07, 2006

ジャムセッションの精神 - サービスを越える瞬間より

すばらしい。

今日届いたサービスを越える瞬間を読み終えた。本当に感動した。この一冊は「おもてなし=ホスピタリティとは何か」を心に刻む一冊として何度も繰り返し読むことになりそう。

ホテルのスタッフ一人一人には、常にその場でできる最高のアドリブの判断が求められる。それがリッツカールトンで「ストーリー・オブ・エクセレンス」として語り継がれる「心のためのチキンスープ」(心温まる話)となっている。スタッフが奏でるジャムセッションを支えるために、エンパワーメントやクルドがあり、それを支える人材採用・感性の共有といった内容にいたるまで盛りだくさんの内容となっている。

発想する会社やイノベーションの達人から垣間見えるIDEOの企業哲学にも感動したが、根にあるのはリッツカールトンの企業哲学でも同じであるように思えた。つまり「サービスを越える瞬間」に書かれている内容は、ホテル業界に限った話などではなく全ての企業や組織でもっと真剣に考えることで変わる部分な気がする。

IDEOではチャレンジを重要視する。リッツカールトンでも2000千ドル/日与えられるエンパワーメントをどのように使おうか常にアイデアをめぐらす。日常から考えて行動する場を提供し、フィードバックを得ることで次の成長につなげる機会をたくさん与えられるといったところはどちらにも共通している。

根にあるのは、「ゲストに感動を与えたい」・「使ってもらう人にすばらしい経験を届けたい」といったインスピレーションでありそれを全社員が共有しその感性を心から共有して仕事をすることでPRIDE&JOY(誇りと喜び)を感じている。みんなそれが楽しくてたまらない。全員が「感性を心から共有していること」には驚いた。自分が知らない世界から学べることは多い。

本中の一章にこんな言葉があった。


"目指す"年収の5%を自分に投資するぐらいでなくてはだめだ。

本業に時間もお金を投資するのは普通だが、センスやアイデアを磨くための自己投資としてもこの5%を(全く違ったことに)割くように心に留めておきたい。

Wiiリモコン+マイク

WiinRemoteというソフトが公開されている。

カラオケのマイクにWiiリモコンのようなセンサつけて、振り付けつきで採点してくれるとかって面白いような。振り付け付きのムービーはあるけど採点とかもあったりするのかな。
あと、あの退屈な映像は何がいいんだろう?採点ではなくて自分がステージ上でライブしているような映像でアバターが自分のマイクパフォーマンスの通り動くとかのほうが面白いよな。でもそうなると、マイクパフォーマンスに応じてお客の歓声も変化し、点数も変化したら面白いと思うけどなぁ。(カラオケか・・・。脱音痴。)

あと、ジムにもおいて欲しい。

Wednesday, December 06, 2006

社外の知識にアクセスする環境はどうやれば作れるか?

「イノベーションを行わない企業は死ぬ」
という本中の言葉を見たときに、中島さんがビルゲイツと大喧嘩したと書かれていた話をなぜか思い出した。イノベーションには大きなリスクを伴うがそれをしないと結果として死ぬことにつながる。という何とも厄介なもの。

イノベーションの実践方法についてのパラダイムシフトについて述べた本「OPEN INNOVATION - ハーバード流イノベーション戦略のすべて」では、ZeroxやIBM/インテルといった企業の取り組みが事例とともに紹介されている。研究開発から使用に関する権利まで全て自社内にとどめて独占するのがクローズイノベーションであり、このモデルは知識があらゆるところに普及した現在では成功しないという。「テクノロジーはそれ自体で価値を持つことは稀であり有効なビジネスモデルにつなげて初めて競争優位性となる」という表現が印象に残った。

2年ほど前に進路について考えていたときに、MOT(技術経営)に興味を惹かれたけどMOTではこの本に書かれているようなことを詳しく習うのかな。

大学の研究室での面白い研究成果や知識にアクセスしやすい環境を社内に用意する。研究室に投資をして教授や学生と自社の研究員の交流を増やすというのはインテルのアプローチだけど、大規模な投資を出来ない小さいところではどうするのがいいだろう。少し前に光学迷彩や透明マントが話題になって「これすごいな!」って盛り上がった(で、盛り上がっただけで終わった。)けど、面白い研究や新しい研究を自社の技術とつなげることを推奨するのがいいかも。

面白い研究(分野は様々)をチェックするようにする。
研究者を社内に招くようにしてブレスト。
研究室に突撃して話を聞いてブレスト。
お金を出して定期的に実際会う。

答えになってないな。
全く知らなかった技術へのアクセス手段を用意し、一人一人が"よく"考えることは重要だな思った。

[Cell] オーバーレイで使用するリンカスクリプトを詳しく

[Cell] SPE上でオーバーレイプログラムを書くでオーバーレイを利用したプログラムを書いた。オーバーレイではリンカスクリプトによる処理が肝となるので、もう少し詳しくみていく。

リンカスクリプトは、-c commandfile(または、-T commandfile)で指定する。これはldのデフォルトコマンドに追加されるわけではなく全て上書きすることになるので、オーバーレイのセクション以外にもターゲットフォーマットに関して必要な全ての記述がされなければならない。

以前使ったld.script中の出力ファイルに関する指定。


OUTPUT_FORMAT("elf32-spu", "elf32-spu", "elf32-spu")
OUTPUT_ARCH(spu)
NOCROSSREFS(.olay1 .olay2)
ENTRY(_start)

SPUプログラムのBFDフォーマットにはelf32-spuを指定する。
spu-objdump -fを使用して確認することが出来る。

% spu-objdump -f spu/spu_main
spu/spu_main: file format elf32-spu
architecture: spu:256K, flags 0x00000012:
EXEC_P, HAS_SYMS
start address 0x000000c0
%

NOCROSSREFSで指定したセクション間で参照があった際に、エラーが発生する。オーバーレイのテクニックを使用する際は、二つのコードが同時にメモリ上にロードされているかどうかはわからないのでこのオプションが有効。

オーバーレイに関するセクションを抜き出したもの。

PROVIDE(__executable_start = 0x0000080);

/**** overlay sections ****/
/* overlay load address */
__overlay_region = 0x000080;
__load_start_overlay1 = 0x4000000;
.overlay1 __overlay_region(NOLOAD) : AT (__load_start_overlay1)
{ mod1/*.o(.text) }
__overlay1_size = SIZEOF (.overlay1);
__load_stop_overlay1 = __load_start_overlay1 + __overlay1_size;

__load_start_overlay2 = (__load_stop_overlay1 + 0x80 - 1) & ~(0x7F); /* alinged by 2**7 */
.overlay2 __overlay_region(NOLOAD) : AT (__load_start_overlay2)
{ mod2/*.o(.text) }
__overlay2_size = SIZEOF(.overlay2);
__load_stop_overlay2 = __load_start_overlay2 + __overlay2_size;
__overlay_region_size = MAX(SIZEOF(.overlay1), SIZEOF(.overlay2));
/**** overlay sections ****/


__overlay_regionはオーバレイモジュールを読み込む領域で0x80に指定する。オーバレイモジュールを読み込む領域を二つ指定する事もできる。

__overlay_region1 = 0x000080;
__overlay_region2 = 0x010080;


オーバレイセクションの開始アドレスと終端アドレスはそれぞれ、__load_start_secname, __load_stop_secnameというシンボルで定義される。

オーバーレイのポイントは、出力セクション型にNOLOADを指定することでメモリ上にロードしないことである。次の設定は、olay1/*.oにマッチする全てのオブジェクトの.textセクションをLMA(ロードアドレス)とVMA(仮想アドレス)を等しく設定するATキーワードを付けて__load_start_overlay1をセクションのロードアドレスに指定する。__overaly_region(NODATA)で定義済みメモリ領域にセクションを割り当てるが、メモリ上にロードしない。

.olay1 __overlay_region (NOLOAD) : AT (__load_start_overlay1)
{ olay1/*.o(.text) }


SIZEOFでセクションのサイズを取得できる。セクション1の開始アドレスとサイズから終端ロードアドレスを算出し、その次の128byte境界のアドレスをセクション.overlay2の開始アドレスとする。

__overlay1_size = SIZEOF (.overlay1);
__load_stop_overlay1 = __load_start_overlay1 + __overlay1_size;
__load_start_overlay2 = (__load_stop_overlay1 + 0x80 - 1) & ~(0x7F); /* alinged by 2**7 */


位置カウンタ(. = で指定される)をオーバーレイをロードする領域のアドレスにセクションの大きい方のサイズを加算して設定する。

__overlay_region_size = MAX(SIZEOF(.overlay1), SIZEOF(.overlay2));
/**** overlay sections ****/
. = __overlay_region + __overlay_region_size;


Cell上でのリンカスクリプトはIBMが提供するCell SDKサンプルから利用することが出来る。

参考
3. リンカスクリプト

Tuesday, December 05, 2006

[Cell] SPE上でオーバーレイプログラムを書く

IT用語辞典バイナリより

オーバーレイとは、主にプログラムが大きすぎてメインメモリに格納できないような場合に、プログラムを機能ごとに分割し、それぞれのセグメントをプログラム自身が管理、制御することである。他にも、層を重ね合わせるといった意味で様々な場面に用いられる表現である。

メモリ容量の小さい昔は良く使われていた"らしい"テクニックだが、これまで低レイヤプログラミングの機会が無かったので使ったことのないテクニックだった。(組み込みぐらいでしか使わない?) SPU上では、LS(ローカルストレージ)内のプログラムを実行することが可能だが、LSには256KBという厳しい制限があるためこれより大きいサイズのプログラムを動かすためにオーバーレイというテクニックが有効となる。

ということでSPE上でオーバーレイを実現するプログラムを書いてみた。「どういう仕組みでオーバーレイが実現されるのか?」なんて全く知らなかったので仕組みを調べながら書いてみた。

ディレクトリ/ファイル構成は次の通り。

% ls -R
.:
Makefile ppu/ spu/

./ppu:
Makefile ppu_overlay.c

./spu:
Makefile ld.script mod1/ mod2/ offset.map spu_main.c toe_section.s

./spu/mod1:
Makefile func.c

./spu/mod2:
Makefile func.c
%

オーバレイを実現するには、オーバレイモジュール(プログラムの断片)とオーバーレイモジュールのロードを管理するプログラムが必要となる。上記のファイル構成との対応付けは次の通り。
  • モジュールのロードを管理するプログラム : spu/spu_main.c
  • オーバーレイモジュール : mod1/func.c, mod2/func.c
オーバーレイモジュールの作成
加算と乗算を行う関数を実装する。mod1/func.cにはint型の加算/乗算を行う関数、mod2/func.cにはfloat型の加算/乗算を行う関数を実装する。

spu/mod1/func.c

#include <stdio.h>

int mod1_add(int a, int b) {
int ret = a + b; // value may be overflowed
return ret;
}

int mod1_mul(int a, int b) {
int ret = a * b; // value may be overflowed
return ret;
}

spu/mod2/func.c
#include <stdio.h>

float mod2_fadd(float a, float b) {
float ret = a + b; // values maybe overflowed
return ret;
}

float mod2_fmul(float a, float b) {
float ret = a * b; // values maybe overflowed
return ret;
}
ここは特に説明なし。

spu/spu_main.c
次に、モジュールのロードを管理するプログラム(spu/spu_main.c)を実装する。ソースコード全体は次の通り。

#include <stdio.h>
#include <stdlib.h>
#include <libmisc.h> /* copy_to_ls and copy_from_ls */
#include <spu_mfcio.h>
#include <string.h>

/* value resolved by CESOF in the system memory */
extern unsigned long long _EAR_spu_main;
extern unsigned long long _EAR__overlay1_offset;
extern unsigned long long _EAR__overlay2_offset;

/* overlay symbols from linking process */
extern char __overlay_region;
extern char __overlay1_size;
extern char __overlay2_size;

/* these function is overlayed with overlay modules */
extern int mod1_add(int, int);
extern int mod1_mul(int, int);
extern float mod2_fadd(float, float);
extern float mod2_fmul(float, float);

/* SPE programe handle */
struct spe_program_handle {
unsigned int handle_size;
void *elf_image;
void *toe_shadow;
} __attribute__((aligned(128)));

int main(unsigned long long spuid __attribute__((unused)),
unsigned long long argp __attribute__((unused))) {

unsigned long long overlay1_ldaddr, overlay2_ldaddr;
struct spe_program_handle handle;

/* Get the spu_main image location and caluclate overlay modules address */
copy_to_ls((uint32_t)(&handle), _EAR_spu_main, sizeof(handle));
overlay1_ldaddr = (uint32_t) handle.elf_image + _EAR__overlay1_offset;
overlay2_ldaddr = (uint32_t) handle.elf_image + _EAR__overlay2_offset;

printf("__overlay_region: %p\n", &__overlay_region);
printf("__overlay1_size: %d\n", (int)&__overlay1_size);
printf("overlay1_ldaddr: 0x%llX\n", overlay1_ldaddr);

/* load overlay modules from system memory to .overlay section in LS */
copy_to_ls((uint32_t)(&__overlay_region), overlay1_ldaddr, (int)&__overlay1_size);
printf("mod1_add(2,3) result: %d\n", mod1_add(2, 3));
printf("mod1_mul(2,3) result: %d\n", mod1_mul(2, 3));

copy_to_ls((uint32_t)(&__overlay_region), overlay2_ldaddr, (int)&__overlay2_size);
printf("mod2_fadd(2.2,3.3) result: %.3f\n", mod2_fadd(2.2, 3.3));
printf("mod2_fmul(2.2,3.3) result: %.3f\n", mod2_fmul(2.2, 3.3));

return 0;
}
プログラムの解説。
次の変数はPPUプログラム上で宣言された変数(_EAR_を取り除いた名前の変数)を参照する。CESOF(Cell Embed SPU Object Format)でシンボル名がどのように解決されるかを[Cell] CESOFの仕組みで調べた。

_EAR_spu_mainは、システムメモリマップにマップされた実行可能SPEプログラムのアドレスである。
_EAR__overlay1_offset, _EAR__overlay2_offsetは、mod1/func.c, mod2/func.cは実行可能SPEプログラム中の.overlay1, .overlay2セクションの先頭バイトからのオフセットである。この値は、spu/offset.mapにて提供される。(後述)
/* value resolved by CESOF in the system memory */
extern unsigned long long _EAR_spu_main;
extern unsigned long long _EAR__overlay1_offset;
extern unsigned long long _EAR__overlay2_offset;
_EAR_spu_mainで提供されるアドレスから実行可能SPEイメージを読み込み、.overlay1, .overlay2セクションへのオフセットを加算してそれぞれのオーバーレイモジュールのアドレスを算出する。このアドレスを利用してメインメモリからLSへオーバーレイモジュールをDMA転送を利用して読み込む。


/* Get the spu_main image location and caluclate overlay modules address */
copy_to_ls((uint32_t)(&handle), _EAR_spu_main, sizeof(handle));
overlay1_ldaddr = (uint32_t) handle.elf_image + _EAR__overlay1_offset;
overlay2_ldaddr = (uint32_t) handle.elf_image + _EAR__overlay2_offset;


次の変数へのシンボルは、リンカスクリプト ld.script(後述)を使用してリンク時に解決される。このプログラムでは、オーバーレイモジュールをロードする領域を一つしか用意していないため、異なるオーバーレイモジュールに実装された関数を呼び出すには、ロードしなおす必要がある。

__overlay_regionはオーバーレイモジュールをロードする領域。
__overlay1_sizeは.overlay1セクションのサイズ。
__overlay2_sizeは.overlay2セクションのサイズ。
/* overlay symbols from linking process */
extern char __overlay_region;
extern char __overlay1_size;
extern char __overlay2_size;
オーバレイモジュールを読み込むには、低レベルのDMA転送をラップした便利な関数copy_to_lsを利用する。この関数は、libmisc.hで提供される。一つ目は、オーバーレイモジュールを読み込む領域に.overlay1セクションの値を読み込む。(サイズやオーバーレイを読み込む領域のアドレスはリンカスクリプトによって提供される。後述)
copy_to_ls((uint32_t)(&__overlay_region), overlay1_ldaddr, (int)&__overlay1_size);
copy_to_ls((uint32_t)(&__overlay_region), overlay2_ldaddr, (int)&__overlay2_size);


EAR変数のシンボルを解決するために、TOEセクション中にEARシンボルを定義したアセンブリspu/toe_section.sを書く。(参考: [Cell] CESOFを使用してPPU/SPU間で変数を相互に参照する)

spu/toe_section.s

.section .toe, "a", @progbits
.align 4

.global _EAR_spu_main
_EAR_spu_main:
.octa 0x0

.global _EAR__overlay1_offset
_EAR__overlay1_offset:
.octa 0x0

.global _EAR__overlay2_offset
_EAR__overlay2_offset:
.octa 0x0


リンカスクリプト
リンカスクリプトの雛形はCell SDKのなかでも提供されている。ここで定義する必要があるのは、**overlay sections**の間。上記の例で出てきた、__overlay_region等のシンボルの値はリンカ中で解決される。

オーバレイモジュールを読み込むアドレスは0x80にする必要がある。
.overlay1, .overlay2の出力セクションをNOLOADで指定することによりプログラムの実行時にロードされなくなる。.overlay1, .overlay2セクションのサイズを__overlay1_size, __overlay2_sizeというシンボルで参照できるようにして、SPUプログラム中からEARを利用して参照する。CellではオーバーレイはDMA転送を利用して行われるため、.overlay1, .overlay2ともに128byteアラインする。使用したリンカスクリプトの詳細な解説は別の記事で。

OUTPUT_FORMAT("elf32-spu", "elf32-spu", "elf32-spu")
OUTPUT_ARCH(spu)
NOCROSSREFS(.overlay1, .overlay2)
ENTRY(_start)
SECTIONS
{
PROVIDE(__executable_start = 0x0000080);

/**** overlay sections ****/
/* overlay load address */
__overlay_region = 0x000080;
__load_start_overlay1 = 0x4000000;
.overlay1 __overlay_region(NOLOAD) : AT (__load_start_overlay1)
{ mod1/*.o(.text) }
__overlay1_size = SIZEOF (.overlay1);
__load_stop_overlay1 = __load_start_overlay1 + __overlay1_size;

__load_start_overlay2 = (__load_stop_overlay1 + 0x80 - 1) & ~(0x7F); /* alinged by 2**7 */
.overlay2 __overlay_region(NOLOAD) : AT (__load_start_overlay2)
{ mod2/*.o(.text) }
__overlay2_size = SIZEOF(.overlay2);
__load_stop_overlay2 = __load_start_overlay2 + __overlay2_size;
__overlay_region_size = MAX(SIZEOF(.overlay1), SIZEOF(.overlay2));
/**** overlay sections ****/

. = __overlay_region + __overlay_region_size;
/* Read-only sections, merged into text segment: */
.init :
{
KEEP (*(.init))
} =0
.plt : { *(.plt) }
.text :
{
*(.text .stub .text.*)
} =0
.fini :
{
KEEP (*(.fini))
} =0
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
.rodata : { *(.rodata .rodata.*) }
.rodata1 : { *(.rodata1) }
.eh_frame_hdr : { *(.eh_frame_hdr) }
. = ALIGN(0x80);
PROVIDE (__preinit_array_start = .);
.preinit_array : { *(.preinit_array) }
PROVIDE (__preinit_array_end = .);
PROVIDE (__init_array_start = .);
.init_array : { *(.init_array) }
PROVIDE (__init_array_end = .);
PROVIDE (__fini_array_start = .);
.fini_array : { *(.fini_array) }
PROVIDE (__fini_array_end = .);
.data :
{
*(.data .data.*)
SORT(CONSTRUCTORS)
}
.data1 : { *(.data1) }
.tdata : { *(.tdata .tdata.* ) }
.tbss : { *(.tbss .tbss.*) *(.tcommon) }
.eh_frame : { KEEP (*(.eh_frame)) }
.gcc_except_table : { *(.gcc_except_table) }
.dynamic : { *(.dynamic) }
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin*.o(.ctors))
/* We don't want to include the .ctor section from
from the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin*.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr : { KEEP (*(.jcr)) }
.got : { *(.got.plt) *(.got) }
_edata = .;
PROVIDE (edata = .);
.toe ALIGN(128) : { *(.toe) } = 0
__bss_start = .;
.bss :
{
*(.dynbss)
*(.bss .bss.*)
*(COMMON)
. = ALIGN(16);
}
. = ALIGN(0x80);
/* stick the overlay section here */
_end = .;
PROVIDE (end = .);
PROVIDE (__stack = 0x3fff0);
}
ppu/ppu_overlay.c
次にSPUプログラムを呼び出すPPUプログラムを実装する。
#include <stdio.h>
#include <stdlib.h>
#include <libspe.h>
#include <errno.h>
#include <sched.h>

#define BUF_SIZE 256

extern spe_program_handle_t spu_main;

/* values proviede by offset.map, to be used by spu_main */
extern char _overlay1_offset;
extern char _overlay2_offset;

int main() {
int status;
speid_t spe_id;
spe_gid_t gid;

printf("%s overlay1_offset: %p\n", __FILE__, &_overlay1_offset);

/* Create SPE thread group */
gid = spe_create_group(SCHED_OTHER, 0, 1);
if(gid == 0) {
fprintf(stderr, "Failed spe_create_group(errno=%d)\n", errno);
return 1;
}

/* Create SPE thread */
spe_id = spe_create_thread(gid, &spu_main, NULL, NULL, -1, 0);
if(spe_id == 0) {
fprintf(stderr, "Failed spe_create_thread(errno=%d)\n", errno);
return 1;
}

/* Waiting SPE thread */
spe_wait(spe_id, &status, 0);

return 0;
}


次の変数は、spu/offset.map中で定義され.overlay1/.overlay2セクションのオフセットを持つ。
SPU中で変数名に_EAR_をつけることでこの変数を参照できる。(参考: [Cell] CESOFの仕組み)
/* values proviede by offset.map, to be used by spu_main */
extern char _overlay1_offset;
extern char _overlay2_offset;

spu/offset.map
オフセットの値は、コンパイルしてspu-readelfコマンドで確認してセクションへのオフセットの値を代入する。一度コンパイルしてからオフセットの値を参照し、再コンパイルした。
_overlay1_offset = 0x001850;
_overlay2_offset = 0x001888;
spu-readelfコマンドで.overlay1, .overlay2セクションのオフセットを確認して、offset.mapに代入する。プログラムを変更するとこの値も変わるのでその際は、offset.mapを書き直す。

% spu-readelf -S spu/spu_main | grep .overlay
[ 1] .overlay1 NOBITS 00000080 001850 000038 00 AX 0 0 8
[ 2] .overlay2 NOBITS 00000080 001888 000010 00 AX 0 0 8
%


コンパイルと実行
./Makefile
DIRS = spu ppu
include /opt/IBM/cell-sdk-1.1/make.footer

ppu/Makefile
IMPORTSでspuembededで作成したCESOFのリンク可能SPUを含む静的ライブラリを指定する。
LDFLAGSでリンカに-R ../spu/offset.mapをオプションを渡しシンボル名とアドレスのマップを与える。
PROGRAM_ppu = ppu_overlay
IMPORTS = ../spu/spu_overlay.a -lspe
LDFLAGS = -Wl,-R,../spu/offset.map
include /opt/IBM/cell-sdk-1.1/make.footer

spu/Makefile
LIBRARY_embedを作成する事で、リンク可能なCESOFを作成しspu-arで静的ライブラリを作成する。LDFLAGS-Wl,-T,ld.scriptオプションを指定することでリンカスクリプトとしてld.scriptを使用する。IMPORTでオーバーレイモジュールのfunc.oを組み込むがリンカスクリプト中でNODATAを指定しているためロードされない。


DIRS = mod1 mod2
PROGRAM_spu = spu_main
LIBRARY_embed = spu_overlay.a
IMPORTS = mod1/func.o \
mod2/func.o \
$(SDKLIB_spu)/libc.a \
$(SDKLIB_spu)/libmisc.a
LDFLAGS = -Wl,-T,ld.script
include /opt/IBM/cell-sdk-1.1/make.footer


spu/mod1/Makefile, spu/mod2/Makefile
TARGET_PROFESSOR  = spu
OBJS_ONLY = func.o
include /opt/IBM/cell-sdk-1.1/make.footer
コンパイルしシュミレータに転送し実行する。
# ./ppu_overlay
ppu_overlay.c overlay1_offset: 0x1850
__overlay_region: 0x80
__overlay1_size: 56
overlay1_laddr: 0x1803550
mod1_add(2,3) result: 5
mod2_mul(2,3) result: 6
mod2_fadd(2.2,2.3) result: 5.500
mod2_fmul(2.2,3.3) result: 7.260
#



実行できた。


参考
3. リンカスクリプト
Manpage of ld
[Cell] CESOFの仕組み
[Cell] CESOFを使用してPPU/SPU間で変数を相互に参照する

[Cell] CESOFの仕組み

PPUプログラムからSPUプログラムへのアクセス
PPUプログラムからSPUプログラムにアクセスする方法には、PPUプログラム内で明示的に行う方法と実行時OSによりロードする方法の二つがある。どちらの方法でも初めにSPUプログラムをグローバルな実効アドレスにメモリマップされなければならない。

マップされたアドレスはPPUから利用することが出来る。メモリマップされた実効可能SPUイメージはSPUローダーを通して、SPEのローカルストレージにDMA転送され実行される。そしてSPE実行イメージはPPU中のシンボルを通して参照可能になる。


SPUプログラムからPPUシンボルへのアクセス
SPEプログラムでは、共用されるシンボル領域を持たないため、SPUプログラム中からPPU中のシンボルを参照することは出来ない。このためSPUではPPUからEA(Effective Address)を受け取りDMAオペレーションを利用してデータを転送する。しかし、この操作は実行時オーバヘッドをうみやすく、エラーが混在しやすい。

そこでCESOF中では、EA(実効アドレス)を扱うEAR(Effective Address Reference)と呼ばれる新しいタイプの64bit変数(unsigned long long)を使用してPPU中のシンボルを参照可能にする。このシンボルはPPU中のグローバルスコープで宣言された変数名に_EAR_のプレフィックスを付けた名前にネームマングリングされる。

[Cell] CESOFを使用してPPU/SPU間で変数を相互に参照する

でEARを利用した簡単なプログラムを書いた。


実行可能SPUイメージがどのようにPPUプログラム内にロードされるか
PPUプログラム内でSPUイメージを明示的にメモリマップするには、libspe.hで提供されるspe_open_image関数を使用する方法である。次の場合、spe_handle_t型の変数spe_handleでSPU実行イメージの参照が可能である。


spe_program_handle_t *spe_handle;
speid_t spe_id;
spe_handle = spe_open_image(SPU_FILE_NAME);
spe_id = spe_create_thread(0, &spe_handle, NULL, NULL, -1, 0);


図 Separatted PPE and SPE code module life cycles.

OSによりメモリマップする方法は、CESOF(Cell Embeded SPU Object Format)を利用する。リンク可能なSPUオブジェクトを作成する方法は次の記事で紹介。

[Cell] PPUプログラムにSPUプログラムを組み込む

embeded-spuは実行可能SPUをリンク可能なCESOFに埋め込んでラップしたオブジェクトを作成する。リンク可能なCESOFは次のような構造を持つ。

図 CEOF linkable layout

先ほどのソース中のspe_program_handle_t型は、次のような構造体で.dataセクション中に配置される。

typedef struct spe_program_handle {
unsigned int handle_size;
void *elf_image;
void *toe_shadow;
} spe_program_handle_t;

  • ハンドラのサイズ
  • 実行イメージへのポインタ
  • Shadow SPU TOE領域(Table of EARs)
といった要素をもつ。(EAR: Effective Address Reference)
(Shadow SPU TOEの良い訳語がみつからなかったのでこのままで)

CESOFがどのように組み込まれて動作するのかを見ていく。

1. 実行可能SPUイメージのロードとシンボル作成
実行時に、SPU実行イメージがSPEローダーによりLS(ローカルストア)中の.rodata.speelfと呼ばれる特別なセクションにロードする。embedespuは、SPU実行イメージをポインタで参照するためのPPEシンボル(ファイル名と同じ)を作成する。構造体のメンバの(void *elf_image)は、.rodata.speelf中にロードされた実行可能SPUイメージへのポインタである。


% spu-readelf -S spu/spu_hello-embed.o | grep speelf
[ 2] .rodata.speelf PROGBITS 00000000 000080 004080 00 A 0 0 128
%

.rodata.speelfセクションは名前の通り読み込み専用である。これは、同一マシン上で実行中の複数のCBEアプリケーションによりSPE実行イメージが共用される可能性があるためである。

2. Shadow SPU TOE領域
Shadow SPU TOE領域は、実行可能SPU上に埋め込まれたTOEセグメントをミラーリングするための領域である。TOE(Table of EARs)セグメント中には、EARが集められていてembededspuはそれぞれのEARに一致するPPEシンボル参照を作成する。(_EAR_プレフィックスを取り除いたシンボル名)
また、spu_program_handle構造体は、図 CEOF linkable layoutへのポインタ(void *toe_shadow)を持つ。

3. SPEプログラムの扱い
CESOFはCESOF情報を持つデータ構造を実行時に提供する。このデータ構造は二つのポインタから構成されており、一つは組み込まれた実行可能SPEイメージを指し、もう一つはShadow領域を指す。この二つのポインタの値はPPEリンカーがリンク可能なCESOFを最終の実行イメージにリンクした場合のみ解決される。

embedspuがしてくれること
  1. .rodata.speelfと.data.spetoeへのポインタを持つspe_program_handle_t型の構造体の変数を定義する。
  2. .rodata.speelfにSPE実行イメージをロードする。(by .incbin)
  3. SPUバイナリ中の_EAR_を持つシンボル名を抜き出し、.data.spetoeに対応するPPEシンボルのアドレスを格納する。

CESOFの基本的な仕組みを学んだ。
低レベルのアーキテクチャから並列処理・SIMDにいたる深くて広い知識を理解したうえでプログラミングテクニックを展開しないと、ハイスピードなプログラムが書けないというエンジニアにとってとても挑戦的なプロセッサがCell. PPEリンカの動作などを理解しているというにはまだまだ程遠いが、次はCell上でのオーバーレイを試す。

参考
Programming the Cell Broadband Engine
IC 2006 新しいマルチコアプロセッサ Cell で遊ぼう

Monday, December 04, 2006

[Cell] CESOFを使用してPPU/SPU間で変数を相互に参照する

CESOF(Cell Embeded Object Format)は、実行可能SPEプログラムをPPEプログラムに埋め込むためのフォーマット。

前回の[Cell] PPUプログラムにSPUプログラムを組み込むでは、ppu-embedespuコマンドでCESOFオブジェクト(hello_spu-embed.o)を作成して静的ライブラリにしPPUプログラムのリンク時に合わせてリンクすることで、PPUプログラムからspu_program_handle_t型の外部変数としてSPUプログラムを参照可能になるようにした。

作成されたCEOSFのシンボル表には、hello_spuシンボルが定義されておりこれによりPPUプログラムでSPUプログラムを参照できるようになっている。


% nm spu/hello_spu-embed.o
00000000 D hello_spu
%

今回作成するプログラムについて
同じくCESOFを使用してPPU/SPU間で変数を相互に参照するコードを書く。PPU側で宣言された変数をSPU側で参照し出力する。その変数をSPU側書き換えてSPUの処理が完了するのを待ってからPPU側で出力する。

まずは、作業ディレクトリを作成し、その中に次のようにディレクトリとファイルを構成する。

% ls -R
.:
Makefile ppu/ spu/

./ppu:
Makefile ppu_hello.c

./spu:
Makefile spu_hello.c spu_hello_toe.s
%

ppu/ppu_hello.c
PPUプログラムではhello_stringというシンボル名を持つ変数を宣言する。この変数がSPU側から参照されることになる。

#include <stdio.h>
#include <stdlib.h>
#include <libspe.h>
#include <errno.h>
#include <sched.h>

#define BUF_SIZE 256

/* this symbol definded in spu_hello-embed.o */
extern spe_program_handle_t spu_hello;

char hello_string[BUF_SIZE] = "Hello, World!";

int main() {
int status;
speid_t spe_id;
spe_gid_t gid;

/* Create SPE thread group */
gid = spe_create_group(SCHED_OTHER, 0, 1);
if(gid == 0) {
fprintf(stderr, "Failed spe_create_group(errno=%d)\n", errno);
return 1;
}

/* Create SPE thread */
printf("hello_string: %p\n", &hello_string);
spe_id = spe_create_thread(gid, &spu_hello, NULL, NULL, -1, 0);
if(spe_id == 0) {
fprintf(stderr, "Failed spe_create_thread(errno=%d)\n", errno);
return 1;
}

/* Waiting SPE thread */
spe_wait(spe_id, &status, 0);

/* print hello_string */
printf("PPU: %s\n", hello_string);

return 0;
}


spu/spu_helllo.c
PPUで宣言したhello_stringをSPU側で参照し値を出力する。SPU側では、_EAR_というプレフィックスを付けることで、spuembedがCESOFオブジェクトを作成する際にシンボル名から_EAR_取り除いた値に変換してくれる。EARはEffective Address Referenceの略。
(シンボルについては、下記で説明)

#include <stdio.h>
#include <stdlib.h>
#include <libmisc.h> /* copy_to_ls and copy_from_ls */
#include <spu_mfcio.h>
#include <string.h>

static const int BUF_SIZE = 256;

/* to be resolved by CESOF in the system memory */
extern unsigned long long _EAR_hello_string;

int main(unsigned long long spuid __attribute__((unused)),
unsigned
long long argp __attribute__((unused))) {
char buf[BUF_SIZE];

/* Copy data from system memory to Local Storage */
printf("_EAR_hello_string: 0x%llX\n", _EAR_hello_string);
copy_to_ls((uint32_t)(&buf), _EAR_hello_string, sizeof(buf));
printf("SPU: %s\n", buf);

/* change buf */
strncpy(buf, "Welcome to SP program.", sizeof(buf));
copy_from_ls(_EAR_hello_string, (uint32_t)(&buf), sizeof(buf));

return 0;
}
LSとメインメモリ間のデータ転送については、copy_to_ls/copy_from_ls関数を使用する。それぞれ、libmisc.hで定義されている。ppu-embededにより作成されるCESOFオブジェクト(下記の例ではspu_hello-embbed.o)では、_EAR_がとりのぞかれhello_stringで参照するように変換され、PPEリンカがシンボル名を解決する。

% nm spu/spu_hello-embed.o
U hello_string
00000000 D spu_hello
% nm spu/spu_hello.o
00000000 r BUF_SIZE
U _EAR_hello_string
U copy_from_ls
U copy_to_ls
00000000 T main
U printf
U strncpy
%

このままでは、リンク時に_EAR_hello_stringシンボルの実効アドレスをリンカが解決できない。CESOFではEARは.toe(table of EARs)と呼ばれるセクションに置かれる。そこで、このセクションに関するアセンブリを書いて合わせてコンパイルする必要がある。(CESOFの詳細参照 <- 書いたらリンクに置き換える)

spu_hello_toe.s

.section .toe, "a", @progbits
.align 4
.global _EAR_hello_string
_EAR_hello_string:
.octa 0x0


作成したアセンブリはspu-asを使用することでオブジェクトファイルにコンパイルできる。コンパイルされたコードをリンク時に合わせてリンクすることで.toeセクションを追加できる。

% spu-as -I. -I /opt/IBM/cell-sdk-1.1/sysroot/usr/spu/include -o spu_hello_toe.o spu_hello_toe.s


CESOFの作成、静的ライブラリの作成、アセンブリコードのコンパイルといったこれらの処理も、下記に書くCell SDKが提供するMakefileを利用すれば全て自動的にやってくれる。

コンパイルに必要なMakefile達

Makefile
DIRS = spu ppu
include /opt/IBM/cell-sdk-1.1/make.footer
ppu/Makefile
PROGRAM_ppu = ppu_hello
IMPORTS = ../spu/spu_hello.a -lspe
include /opt/IBM/cell-sdk-1.1/make.footer
spu/Makefile
PROGRAM_spu   = spu_hello
LIBRARY_embed = spu_hello.a
IMPORTS = $(SDKLIB_spu)/libc.a $(SDKLIB_spu)/libmisc.a
include /opt/IBM/cell-sdk-1.1/make.footer


実行
# ./ppu_hello
hello_strint: 0x181688
_EAR_hello_string: 0x1816188
SPU: Hello, World!
PPU: Welocome to SPU Program.
#


TOEセクション

% spu-readelf -S spu/spu_hello | grep -w .toe
[ 6] .toe PROGBITS 00001680 0016c0 000010 00 A 0 0 16
%
% spu-objdump -s --start-address=0x1680 --stop-address=0x1690 spu/spu_hello

spu/spu_hello: file format elf32-spu

Contents of section .text:
Contents of section .rodata:
Contents of section .ctors:
Contents of section .dtors:
Contents of section .data:
Contents of section .toe:
1680 00000000 00000000 00000000 00000000 ................
Contents of section .comment:
Contents of section .debug_aranges:
Contents of section .debug_pubnames:
Contents of section .debug_info:
Contents of section .debug_abbrev:
Contents of section .debug_line:
Contents of section .debug_frame:
Contents of section .debug_str:
Contents of section .debug_loc:
Contents of section .note.spu_name:
%

TOEセクションの詳細とCESOFの動作原理の詳細については、あとで書く。

Sunday, December 03, 2006

[Cell] PPUプログラムにSPUプログラムを組み込む

SPUプログラムは静的ライブラリとしてPPUプログラムに組み込んでコンパイルすることができる。組み込みでコンパイルした際は、PPUプログラムからextern属性をつけて実行プログラム名でspe_program_handle_t型の変数として参照することが出来る。

組み込みSPEを作るには、オブジェクトファイルを作成してからppu-embedspuコマンドで組み込み用のオブジェクトファイルを作成し、ppu-arコマンドで静的ライブラリを作成する。次の例は、hello_spuというSPUプログラムから組み込み用の静的ライブラリを作成する。


% spu-gcc -o hello_spu hello_spu.o -Wl,-N /opt/IBM/cell-sdk-1.1/sysroot/usr/spu/lib/libc.a
% ppu-embedspu -m64 hello_spu hello_spu hello_spu-embed64.o
% ppu-ar -qcs hello_spu.a hello_spu-embed64.o


ppu-gccでリンクする際に作成した静的ライブラリも合わせてリンクするとPPUプログラム内で次のようにして実行イメージを参照可能になる。


extern spe_program_handle_t spe_program;


組み込みSPUを使用した、簡単なプログラムを作成した。
ファイル&ディレクトリの構成は次の通り。

% ls -R
.:
Makefile ppu/ ppu_time.h spu/

./ppu:
Makefile spu_program_embed.c

./spu:
Makefile hello_spu.c
%


ppu/spu_program_embed.c

#include <stdio.h>
#include <libspe.h>
#include <errno.h>
#include <sched.h>
#include <stdint.h>
#include "../ppu_time.h"

extern spe_program_handle_t hello_spu;

int main () {
int status;
spe_gid_t gid;
speid_t spe_id;
uint32_t ts, te;

StartTimer(ts);
puts("Starting PPE program.");
gid = spe_create_group(SCHED_OTHER, 0, 1);
if(gid == 0) {
fprintf(stderr, "Failed spe_create_group(errno=%d)\n", errno);
return 1;
}
if(spe_group_max(gid) < 1) {
fprintf(stderr, "System doesn't have a working SPE.\n");
return 1;
}

spe_id = spe_create_thread(gid, &hello_spu, NULL, NULL, -1, 0);
if(spe_id == 0) {
fprintf(stderr, "Failed spe_create_thread(errno=%d)\n", errno);
return 1;
}
printf("Waiting SPE thread...\n");
spe_wait(spe_id, &status, 0);
printf("done.\n");
StopTimer(te, ts);

PrintTimer(te);

return 0;
}


spu/hello_spu.c

#include <stdio.h>

int main(unsigned long long speid, unsigned long long argp) {
puts("SPE: Hello, world!");
return 0;
}


コンパイルはCell SDKで提供されているMakefile(make.footer)を使用すると簡単である。
./Makefile

DIRS = spu ppu
include /opt/IBM/cell-sdk-1.1/make.footer


ppu/Makefile
IMPORTSを設定して、静的ライブラリをリンク時に含めるようにする。

PROGRAM_ppu64 = ./spu_program_embed
IMPORTS = ../spu/hello_spu.a -lspe
CC_OPT_LEVEL = -O0
include /opt/IBM/cell-sdk-1.1/make.footer


spu/Makefile
LIBRARY_embed64に静的ライブラリ名を設定すると、自動的に作成してくれる。

PROGRAMS_spu := hello_spu
LIBRARY_embed64 := hello_spu.a
IMPORTS = $(SDKLIB_spu)/libc.a
include /opt/IBM/cell-sdk-1.1/make.footer


実行結果

% ./spu_program_embed
Starting PPE program.
Waiting SPE thread...
SPE: Hello, world!
done.
time: 0.83680(msec)
%

spe_open_imageでSPE実行プログラムを読み込む方法と組み込み方法の実行時間を比較してみた。





















回数組み込みspe_open_image
1回目0.881480(msec)0.840640(msec)
2回目0.835840(msec)0.838560(msec)
3回目0.833240(msec)0.838640(msec)

実行速度はほぼ同じだった。(シュミレータ上で実行した事が原因?)

Saturday, December 02, 2006

[Cell] SPUで分岐ヒント命令を明示的に書くが

うまく動作しない。

SPUは26段の深いパイプラインを持つため、分岐予測が外れた場合には15サイクルのペナルティが発生する。この分岐ペナルティへの対処として、SPUではハードウェア的な分岐予測回路ではなく、ソフトウェア的に分岐予測へのヒントを与える分岐ヒント命令を実装している。

分岐ヒント命令を与えた場合と与えない場合でどれほど実行速度に差が出るか計測してみた。SPE上で実行時間計測には、[Cell] SPE上での実行時間計測を利用した。


#include <stdio.h>
#include <spu_intrinsics.h>
#include "../spe_time.h"

#define likely(cond) __builtin_expect((int)(cond), 1)
#define unlikely(cond) __builtin_expect((int)(cond), 0)

static const int N = 100000;
void nope(void){}

int main() {
int i;
unsigned int t1, t2;

// 条件分岐を含むループƒ—
StartTimer(t1);
for(i=0;i<N;i++) {
if(i>100000) nope();
}
StopTimer(t1);

// 分岐ヒント命令を与えた条件分岐を含むループƒ—
StartTimer(t2);
for(i=0;i<N;i++) {
if(unlikely(i>100000)) nope();
}
StopTimer(t2);

PrintTimer(t1);
PrintTimer(t2);

return 0;
}


実行
#./spu.out
timer: 0.43780(msec)
timer: 0.468720(msec)
#

最適化なし。-O3で最適化すると実行速度はどちらも同じになる。
分岐ヒント命令を与えたほうが遅くなってるし。。
分岐ヒント命令を与えたループと与えないループのアセンブリコード。

42,44c42,45
< lqd $2,64($sp)
< ila $3,100000
< cgt $2,$2,$3
---
> lqd $3,64($sp)
> ila $2,100000
> cgt $2,$3,$2
> sfi $2,$2,0


unlikeyマクロで定義した(int)(cond)と0の比較を行うための、SFI $2, $2, 0という減算処理命令が追加されている。(下側が分岐ヒント命令を与えたアセンブリ)
その分実行速度が遅くなった様子。

分岐ヒント命令は、hbr, hbra, hbrrというニーモニックで与えられるのだが生成されたアセンブリコードには該当する命令はなかった。シュミレーター上では分岐ヒント命令は動作しない??

参考
Cell Broadband Engine Programming Tutorial Version 1.1

Friday, December 01, 2006

ゲストが言葉に出さない願いや要望をかなえる - リッツカールトン物語

本リッツカールトン物語中にこんなエピソードがある。


ザ・リッツカールトン・ネイプルズでビーチアテンダントがある日、砂浜に並んだビーチ・チェアを片付けていた。すると一人の男性がゲストから「ビーチ・チェアを一つだけ残しておいて欲しい」と頼まれた。理由を聞くと、ビーチに誰もいなくなった時にそこに恋人を連れてきて、プロポーズをしたいのだという。

「もちろんですよ」。ビーチ・アテンダントはそう答えると急いでポロシャツにショーツというユニフォームからタキシードに着替えた。そしてテーブルに花を飾ってシャンパンを用意し、男性がプロポーズのときにひざまづいても服が汚れないようにと、砂の上にタオルを敷いて二人が来るのを待った。男性の元々の要望は、ただ単に「砂浜にビーチチェアを一つだけ残しておいて欲しい」というものであったにもかかわらず。

予想もしなかったロマンティックな演出に二人は大喜び。男性のプロポーズは大成功だった。

これは、従業員に与えられた$2,000の決済権を利用したスタッフの判断であった。

リッツカールトングループのホテルにはもちろんまだ泊まった事がない。そのサービス哲学に興味を持ったのはごく最近だが、じゃあ実際リッツカールトン大阪の最高のおもてなしを受けようというわけにも行かないので、リッツカールトンに関する本を借りることにした。

リッツカールトンの全スタッフは、クルドと呼ばれるサービス精神がかかれたカードを常に携帯しており、そのカードの最後はこう結ばれているという。



Fulfill even the unexpressed wishes and needs of oour guests
(ゲストが言葉に表さない願いや要望もかなえること)


ホテル業や水商売などでは、サービスを商売にするところではこれらは当たり前のことなのかもしれない。電話一本「今から行くから席用意しといて」と名前も伝えず電話しただけで、店にきたときには、自分のボトルがセットされテーブルにはお気に入りのチャームが揃えられておりお客は満足する。自分の名前を伝えて、これ用意してといちいち要望を出すようなお店にはリピーターは集まらないだろう。

サービス哲学は、ホテル業などでは"当たり前"のことなのかもしれないが、それを組織として「リッツカールトンのサービス哲学」として体系化しているところに興味を持った。

コンシェルジェ始めホテルスタッフには、幅広い知識が求められる。
そしてその知識をフル活用した上で現場で最良の判断を出来るように決済権を与えられている。興味深かったことは、「リッツカールトンではスタッフ自身も紳士淑女として扱われ、それにふさわしいサービスを提供することが求められる」ことだ。ソフトウェア業界で言えばドックフードを食うみたいなものだろうか(違うけど)。
クルドには、次のような言葉も書かれているようだ。

「We are Ladies and Gentlemen Serving and Gentlmen.」
紳士淑女をおもてなしする私達もまた紳士淑女である。

ゲストに対して最良のサービスを提供できるように、ゲストのクレームさえも機会と捉えて次のよりよいサービスにつなげる。ゲストをよく観察し何を求めているか?何ができるか?ということを自発的にスタッフが考える文化がそこにある。

「ふーん。まぁそらそうだろうな。」と思えばそれで終わりだが、自分の世界観や自分のいるフィールドに置き換えて考えるとそういうサービス哲学が全然甘いことがよくわかる。どれほどゲスト(ユーザ)を観察しその言葉に出さない願いや要望を叶えられているだろうか。

異業種から学べることは、まだまだありそうだ。
リッツカールトンでは、顧客満足度と顧客不満足度を数値化するサービスの品質管理(Service Quality Indicator)というシステムがあるが、これは工業産業からヒントを得たものだという。そういった異業種への積極的な学びの精神からリッツカールトンのサービス哲学と理論は構築されている。

作ったサービスは、人が使うものであるのに顔が見えていなかったり、使う人にどういう経験や物語を提供するかといったことをサービス業に比べるとそれほど考えれていないと思う。


リッツカールトン物語には、リッツカールトンのサービス哲学に関しての説明は非常に少なかった。哲学については、リッツ・カールトンが大切にする サービスを超える瞬間がよさそうだ。

でも、世界中に展開するリッツカールトンの数々のホテルを多くのカラー写真付きで紹介している。その中でも特に魅力的に見えた三つ。

一つ目は世界一ロマンティックなホテル
二つ目は世界一セクシーなバスルームを持つホテル
三つ目はバリ

いやぁ、どれもすごい。贅沢でゆったりと流れる時間や空気といった最高の経験を体験できそう。
いつか泊まりたい。で、リッツカールトンの提供する最高のサービスを堪能したい。