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