SpursEngineのSPEのDMA転送速度を計測しました。SPE1基のみでの計測です。
SPEのLSとSpursEngineローカルメモリ間で128KBのデータのDMA転送を行い、SPEのSPU Decrementerで転送時間の計測を行いました。
SpursEngineのSPEのSPU Decrementerのクロックは、SPEの動作周波数設定(*1)にかかわらず、100/6MHz(およそ16.667MHz)です。(SRKj_BasicSW10_AddFunc_Rev1-0.pdf 1.2.1より)
(*1)SpursEngineのSPEでは、消費電力節約のために動作クロックを動的に変更する機能があります。通常は1.5GHzで動作しますが、設定によりその1/2、1/4、1/8のクロックで動作させることが可能です。
実行方法
SpursEngine Linux SDKは「
PCにSpursEngine Linux SDKをインストールする」の方法でインストール済みであることとします。
将来のSpursEngine SDKのバージョンアップに簡単に対応させるため、今回からMakefileでのSpursEngineのSDKのTOPディレクトリの指定方法を変更しています。以下のspurs_topディレクトリの作成はSDKインストール後、1度だけ行ってください。
端末にコマンドを入力して作業を行います。
spurs_topディレクトリの作成
cd
mkdir spurs_top
cd spurs_top
ln -s ~/SpursEngine_Linux_SDK_v1_5_3_2/release release
(SpursEngine Linux SDK ver.1.5.3.2の場合)
テストプログラムをダウンロードします。
wget https://cellspe.matrix.jp/files/spurs_dma_single.tar.gz
解凍します。
tar xzf spurs_dma_single.tar.gz
コンパイルします。
cd spurs_dma_single
make
実行
./main
計測結果
DMA Read、DMA Write、DMA ReadとWrite同時の転送についてテストを行いました。
SpursEngineの理論メモリ帯域:12.8GB/s(3.2Gbps XDRメモリ x 16bit幅接続 x 2チップ)
(SRKj_Users_Guide_Rev1-0.pdf 6.1より。16bit幅接続 x 2チップ構成は基板を目視して確認。)
実測値
Read: 6.019246 GB/s
Write: 6.055075 GB/s
Read & Write: 6.001490 GB/s
テストプログラム ソースコード
ホストプログラム:main.cpp
#include <iostream>
#include <fstream>
#include <memory>
#include <stdlib.h>
#include <spurs/spha/spha.h>
#include <spurs/common/byte_order.h>
#include <spurs/sp3/sp3_cmdif.h>
#define NUM_OF_SPE 1
using namespace std;
// SPEプログラムファイル名
const char * spe_program_name = "spe.elf";
int main(int argc, char ** argv)
{
spha_session_t session;
// セッションの作成
spha_create_session(NULL, &session);
// 通信路を確立
spha_connect_session(session);
// SPEプログラムファイルをオープン
ifstream fp(spe_program_name, ios::binary);
// ファイルサイズを調べる
fp.seekg(0, ios::end);
uint32_t spe_program_size = fp.tellg();
fp.seekg(0, ios::beg);
// バッファサイズを128の倍数に揃える
uint32_t buffer_size = (spe_program_size + 127) & ~127;
// SpursEngine側ローカルメモリ領域の確保
// SPEプログラム格納用
spha_object_t buffer_obj;
spha_create_memory(session, 0, 0, 0, buffer_size, 0, &buffer_obj);
// データ格納用(64MB)
uint32_t buffer_size_data = 0x4000000;
spha_object_t buffer_obj_data;
spha_create_memory(session, 0, 0, 0, buffer_size_data, 0, &buffer_obj_data);
// メモリオブジェクトをマップ
uint32_t buffer_ea, buffer_ea_data;
spha_map_memory(session, buffer_obj, 0, 0, 0, buffer_size, 0, &buffer_ea);
spha_map_memory(session, buffer_obj_data, 0, 0, 0, buffer_size_data, 0, &buffer_ea_data);
// ホスト側バッファを確保(128バイト・アライン)
// SPEプログラム格納用
char * buffer;
int err = posix_memalign((void **)&buffer, 128, buffer_size);
if (err)
{
abort();
}
// SPEプログラムファイルをロード
fp.read(buffer, spe_program_size);
// SpursEngine側ローカルメモリに転送
uint32_t transfer_size = buffer_size;
spha_data_transfer_to(session, buffer_obj, 0, buffer, &transfer_size);
// ホスト側バッファを解放
free(buffer);
// SPEプログラムローダに与える引数をセット
// エンディアン変換して渡す
uint32_t loader_arg[4];
loader_arg[0] = spurs_convert_ui32(buffer_ea);
loader_arg[1] = spurs_convert_ui32(buffer_size);
loader_arg[2] = 0;
loader_arg[3] = 0;
spha_thread_t spe_th[NUM_OF_SPE];
for (int i = 0; i < NUM_OF_SPE; i++)
{
// SPEスレッドを作成
spha_create_spe_thread(session, ~0, 0, SP3_SPE_LOADER_DEFAULT, loader_arg, sizeof(loader_arg), &spe_th[i]);
// SPEのmain()に与えるパラメーター
uint32_t arg[12];
arg[0] = spurs_convert_ui32(buffer_ea_data);
// SPEスレッドを実行状態にする
spha_resume_spe_thread(session, spe_th[i], 0, SP3_SPE_THREAD_RESUME_DEFAULT_ENTRY, arg, sizeof(arg), NULL, NULL, NULL);
}
for (int i = 0; i < NUM_OF_SPE; i++)
{
// SPEスレッドの終了を待つ
spha_wait_spe_thread(session, spe_th[i], NULL, NULL, NULL);
// SPEスレッドの破棄
spha_delete_spe_thread (session, spe_th[i]);
}
// メモリオブジェクトをアンマップ
spha_unmap_memory(session, buffer_ea, 0);
spha_unmap_memory(session, buffer_ea_data, 0);
// SpursEngine側ローカルメモリ領域を解放
spha_delete_object(session, buffer_obj);
spha_delete_object(session, buffer_obj_data);
// セッションの通信路を切断
spha_close_session(session);
// セッションの破棄
spha_delete_session(session);
return 0;
}
SPEプログラム:spe.c
#include <stdio.h>
#include <spurs/spsa/spsa.h>
#include <spu_mfcio.h>
#include <spu_intrinsics.h>
#define DECREMENTER_INIT 0xffffffff
unsigned char buffer[0x20000] __attribute__((aligned(128)));
int main(vector unsigned int arg0, vector unsigned int arg1, vector unsigned int arg2)
{
unsigned int start, end;
unsigned int buffer_ea = spu_extract(arg0, 0);
// 実際に計測する前に一度バッファをなめておく
// 読み込みDMA実行(test)
unsigned int dma_tag = 0;
int i;
for (i = 0; i < 8; i++)
{
unsigned int addr = i * 0x4000;
mfc_get(buffer + addr, buffer_ea + addr, 0x4000, dma_tag, 0, 0);
}
// DMAが完了するまで待つ
mfc_write_tag_mask(1 << dma_tag);
mfc_write_tag_update_all();
mfc_read_tag_status();
// Read計測
// SPU Decrementerに初期値をセット
spu_writech(SPU_WrDec, DECREMENTER_INIT);
// 計測開始
start = spu_readch(SPU_RdDec);
// 読み込みDMA実行
for (i = 0; i < 8; i++)
{
unsigned int addr = i * 0x4000;
mfc_get(buffer + addr, buffer_ea + addr, 0x4000, dma_tag, 0, 0);
}
// DMAが完了するまで待つ
mfc_write_tag_mask(1 << dma_tag);
mfc_write_tag_update_all();
mfc_read_tag_status();
// 計測終了
end = spu_readch(SPU_RdDec);
// 結果表示
printf("Read: %f GB/s\n", (float)0x20000 / (float)(1024 * 1024 * 1024) / ((float)(start - end) / 16666666.7f));
// Write計測
spu_writech(SPU_WrDec, DECREMENTER_INIT);
start = spu_readch(SPU_RdDec);
for (i = 0; i < 8; i++)
{
unsigned int addr = i * 0x4000;
mfc_put(buffer + addr, buffer_ea + addr, 0x4000, dma_tag, 0, 0);
}
mfc_write_tag_mask(1 << dma_tag);
mfc_write_tag_update_all();
mfc_read_tag_status();
end = spu_readch(SPU_RdDec);
printf("Write: %f GB/s\n", (float)0x20000 / (float)(1024 * 1024 * 1024) / ((float)(start - end) / 16666666.7f));
// Read & Write計測
spu_writech(SPU_WrDec, DECREMENTER_INIT);
start = spu_readch(SPU_RdDec);
for (i = 0; i < 8; i++)
{
unsigned int addr = i * 0x4000;
if ((i & 1) == 1)
{
mfc_put(buffer + addr, buffer_ea + addr, 0x4000, dma_tag, 0, 0);
}
else
{
mfc_get(buffer + addr, buffer_ea + addr, 0x4000, dma_tag, 0, 0);
}
}
mfc_write_tag_mask(1 << dma_tag);
mfc_write_tag_update_all();
mfc_read_tag_status();
end = spu_readch(SPU_RdDec);
printf("Read & Write: %f GB/s\n", (float)0x20000 / (float)(1024 * 1024 * 1024) / ((float)(start - end) / 16666666.7f));
return 0;
}