本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

Assembly Visualizer を活用した SPU コードの高速化: 第 2 回 パイプラインを最適化する

asmVis を活用してパイプラインへの命令発行状況を確認しながら SPU プログラムを最適化する方法を解説します。

土居 意弘 (munepi@jp.ibm.com), IBM Systems & Technology Group, AKD48, IBM
土居 意弘
土居意弘は IBM で 5 年間、Cell/B.E. や Digital Signal Processor 上で画像処理システムなどを開発してきました。現在は日本 IBM のテクニカルエキスパートとして、Cell/B.E. 向けソフトウェアの実装やアーキテクト、トレーニング講師なども行っています。並列プログラミングでは多くのことを考えなければならないので、まず自分の思考を並列化できないか考えています。

概要: Cell Broadband Engine (Cell/B.E.) の能力を引き出すために、様々なツールが提供されています。 Assembly Visualizer (asmVis) は、SPUの実行パイプラインが十分埋まり性能が引き出されているかを調べるツールです。 asmVisを活用してSPUプログラムを最適化する方法を説明します。

このシリーズの他の記事を見る

日付:  2008年 8月 08日
レベル:  中級
アクティビティー: 2675 ビュー
お気軽にご意見・ご感想をお寄せください: 


よいプログラムに近づくために

前回は、asmVisの実行タイミングをグラフィカルに表示する機能、命令の実行順序を画面上で編集する機能、Enhanced DP対応のシミュレーション機能、JavaプログラムでありLinux以外にWindowsでも実行できる点を説明しました。

SPUはEven/Oddの2つの実行パイプラインを持つ2命令同時発行のスーパースカラプロセッサですが、EvenパイプとOddパイプでは実行できる命令が異なります。 Evenパイプは主に算術演算と比較演算を、Oddパイプではロード/ストアとブランチ命令を実行できます。 V字の実行タイミングを得るためには、EvenパイプとOddパイプの命令に均等に分散していることが重要になってきます。

また、ストールは効率を大きく引き下げるため、できる限り避けなくてはなりません。依存関係のない命令を多く配置する方法を考える必要があります。

この記事では、命令の実行タイミングがどのように変化していくか、実際にasmVisで確認しながらコードを最適化していきましょう。


はじまりはスカラーコード

List 1はLocal Store(LS)に置かれた幅_width、高さ_heightの2次元画像に対して、全体に定数を加算して明るさを増加させるという画像処理ルーチンです。画素は1ピクセルあたり8ビットで表現されていることとし、加算によって8ビットの値域を超える場合は255とする飽和処理を行っています。_outStrideおよび_in1Strideは画像のスキャンライン同士の距離で、ピクセル数単位で指定します。なお、uint8_tuint32_tなどは比較的新しいC言語標準のtypedef型で、inttypes.hで定義されています。


List 1. 画像定数加算プログラム
                
#include <inttypes.h>
#include <vec_types.h>

void  add_const(
        uint8_t* _outPtr,
        int32_t  _outStride,
        const uint8_t* _in1Ptr,
        int32_t  _in1Stride,
        uint8_t  _value,
        uint32_t _width,
        uint32_t _height)
{
    uint32_t x, y;
    
    for (y = 0; y < _height; y++)
    {
        uint8_t*       out = _outPtr;
        const uint8_t* in  = _in1Ptr;
        
        for (x = 0; x < _width; x++)
        {
            uint32_t  temp;
            temp = *in++ + _value;
            if ( temp > 255 ) temp = 255;
            *out++ = (uint8_t)temp;
        }
        
        _outPtr += _outStride;
        _in1Ptr += _in1Stride;
    }
}

さて、このプログラムを、asmVisでパイプラインを見てみましょう。全てはここから始まります。



一見して、ひどく隙間の多い結果になっていることが見て取れます。これは演算の依存関係があるために、前の命令が完了してからでないと後の命令を開始できない状態、つまりパイプラインストールが発生しているからです。

これからこのプログラムに最適化テクニックを適用していきますが、最適化の成果を検討するために、性能評価のルールを決めておきましょう。

  • 最も内側のループ1周あたりに処理されるピクセル数:ループあたりにいくつのピクセルを処理していくかを検討しながら、SIMD命令やループアンローリングを適用していきます。List 1では、もちろん1です。
  • 最も内側のループのサイクル数:このルーチンの実行時間を支配するのは、主に変数xのループのサイクル数です。このループの最初の命令発行から、最後の命令発行までのサイクルを数えます。List 1では.L5ループが該当し、最初の命令は7クロック目、最後の命令は30クロック目で発行されていますから、24サイクルと数えます。
  • 最も内側のループの命令数:サイクル数と同様に命令数も数えましょう。このループの最初の命令発行から、最後の命令発行までの命令数を数えます。List 1の.sファイルを見ると、.L5ループの最初の命令は21:ai$25,$12,13、最後の命令は37:brnz$13,.L5で17行ありますが、.L15ラベルのために1行使っているため、16命令です。
  • ピクセルあたりのサイクル数:上記のサイクル数をピクセル数で割ったものです。少ないほどよいことになります。
  • ピクセルあたりの命令数:上記のサイクル数をピクセル数で割ったものです。少ないほどよいことになります。
  • サイクルあたりの命令数:上記の命令数をサイクル数で割ったものです。大きいほどよく、最大値は2です。

これらの指標を改めて表にしておきます。

ピクセル数サイクル数命令数サイクル/ピクセル命令/ピクセル命令/サイクル
List 11241624.0014.000.67


SIMDでまとめて処理する

次にSIMD命令を使用して、サイクルあたり16ピクセルずつ処理するように変更します。関数のインターフェースは変更しません。


List 2. 16ピクセルずつSIMD処理
                
#include <inttypes.h>
#include <vec_types.h>

void  add_const(
        uint8_t* _outPtr,
        int32_t  _outStride,
        const uint8_t* _in1Ptr,
        int32_t  _in1Stride,
        uint8_t  _value,
        uint32_t _width,
        uint32_t _height)
{
    uint32_t x, y;
    vec_ushort8  v_value    = spu_splats((uint16_t)_value);
    vec_ushort8  v_0x00ff   = spu_splats((uint16_t)0x00ff);
    vec_uchar16  v_zero     = (vec_uchar16)spu_splats(0);
    vec_uchar16  v_pattern1 = {  0, 16,  2, 18,  4, 20,  6, 22,
                                 8, 24, 10, 26, 12, 28, 14, 30 };
    vec_uchar16  v_pattern2 = {  1, 17,  3, 19,  5, 21,  7, 23, 
                                 9, 25, 11, 27, 13, 29, 15, 31 };

    for (y = 0; y < _height; y++)
    {
        vec_uchar16*       v_out = (vec_uchar16*)_outPtr;
        const vec_uchar16* v_in  = (const vec_uchar16*)_in1Ptr;
        
        for (x = 0; x < _width; x += 16)
        {
            vec_uchar16  v_src;
            vec_uchar16  v_dst;
            vec_ushort8  v_temp1, v_temp2;
            vec_ushort8  v_mask1, v_mask2;
            
            // Load
            v_src = *v_in++;
            
            // Convert to short vector
            v_temp1 = (vec_ushort8)spu_shuffle(v_zero, v_src, v_pattern1);
            v_temp2 = (vec_ushort8)spu_shuffle(v_zero, v_src, v_pattern2);
            
            // Add
            v_temp1 = spu_add(v_temp1, v_value);
            v_temp2 = spu_add(v_temp2, v_value);
            
            // Saturate
            v_mask1 = spu_cmpgt(v_temp1, v_0x00ff);
            v_mask2 = spu_cmpgt(v_temp2, v_0x00ff);
            
            v_temp1 = spu_sel(v_temp1, v_0x00ff, v_mask1);
            v_temp2 = spu_sel(v_temp2, v_0x00ff, v_mask2);
            
            // Gather to uchar vector
            v_dst = (vec_uchar16)spu_shuffle(v_temp1, v_temp2, v_pattern2);
            
            // Store
            *v_out++ = v_dst;
        }
        
        _outPtr += _outStride;
        _in1Ptr += _in1Stride;
    }
}

さて、asmVisでパイプラインを見てみましょう。



命令間の依存関係があるため前の演算が終わるのを待ってから次の命令を実行しなければならずにパイプラインが頻繁にストールしている状況は変わっていません。

しかし、SIMD命令は活用してループあたり16ピクセル処理していますから、総合的な性能は向上しているはずです。指標を調べてみましょう。

ピクセル数サイクル数命令数サイクル/ピクセル命令/ピクセル命令/サイクル
List 21626141.630.880.54

ループアンローリングで待ち時間を有効活用する

パイプラインストールを解消するには、ループアンローリングが有効です。ループアンローリングとは、forなどのループ文で書かれている反復コードを、ループを使わずに(あるいは数回のループごとの単位で)書き下してしまうことです。

ループアンローリングをすることで1回のループに含まれる依存関係のない命令が増えます。それを活用して依存関係のない命令を次々と発行してしまい、演算完了待ちをしている時間を隠蔽してしまうことができます。ループの周回間に演算の依存関係がない場合には、一般に有効な方法と言えるでしょう。

ループアンローリングを実施したものが次のコードです。


List 3. ループアンローリングを適用
                
#include <inttypes.h>
#include <vec_types.h>

void  add_const(
        uint8_t* _outPtr,
        int32_t  _outStride,
        const uint8_t* _in1Ptr,
        int32_t  _in1Stride,
        uint8_t  _value,
        uint32_t _width,
        uint32_t _height)
{
    uint32_t x, y;
    vec_ushort8  v_value    = spu_splats((uint16_t)_value);
    vec_ushort8  v_0x00ff   = spu_splats((uint16_t)0x00ff);
    vec_uchar16  v_zero     = (vec_uchar16)spu_splats(0);
    vec_uchar16  v_pattern1 = {  0, 16,  2, 18,  4, 20,  6, 22,
                                 8, 24, 10, 26, 12, 28, 14, 30 };
    vec_uchar16  v_pattern2 = {  1, 17,  3, 19,  5, 21,  7, 23, 
                                 9, 25, 11, 27, 13, 29, 15, 31 };

    for (y = 0; y < _height; y++)
    {
        vec_uchar16*       v_out = (vec_uchar16*)_outPtr;
        const vec_uchar16* v_in  = (const vec_uchar16*)_in1Ptr;
        
        for (x = 0; x < _width; x += 64)
        {
            vec_uchar16  v_srcA, v_srcB, v_srcC, v_srcD;
            vec_uchar16  v_dstA, v_dstB, v_dstC, v_dstD;
            vec_ushort8  v_tempA1, v_tempA2, v_tempB1, v_tempB2,
                         v_tempC1, v_tempC2, v_tempD1, v_tempD2;
            vec_ushort8  v_maskA1, v_maskA2, v_maskB1, v_maskB2,
                         v_maskC1, v_maskC2, v_maskD1, v_maskD2;
            
            // Load
            v_srcA = *v_in++;
            v_srcB = *v_in++;
            v_srcC = *v_in++;
            v_srcD = *v_in++;
            
            // Convert to short vector
            v_tempA1 = (vec_ushort8)spu_shuffle(v_zero, v_srcA, v_pattern1);
            v_tempA2 = (vec_ushort8)spu_shuffle(v_zero, v_srcA, v_pattern2);
            v_tempB1 = (vec_ushort8)spu_shuffle(v_zero, v_srcB, v_pattern1);
            v_tempB2 = (vec_ushort8)spu_shuffle(v_zero, v_srcB, v_pattern2);
            v_tempC1 = (vec_ushort8)spu_shuffle(v_zero, v_srcC, v_pattern1);
            v_tempC2 = (vec_ushort8)spu_shuffle(v_zero, v_srcC, v_pattern2);
            v_tempD1 = (vec_ushort8)spu_shuffle(v_zero, v_srcD, v_pattern1);
            v_tempD2 = (vec_ushort8)spu_shuffle(v_zero, v_srcD, v_pattern2);
            
            // Add
            v_tempA1 = spu_add(v_tempA1, v_value);
            v_tempA2 = spu_add(v_tempA2, v_value);
            v_tempB1 = spu_add(v_tempB1, v_value);
            v_tempB2 = spu_add(v_tempB2, v_value);
            v_tempC1 = spu_add(v_tempC1, v_value);
            v_tempC2 = spu_add(v_tempC2, v_value);
            v_tempD1 = spu_add(v_tempD1, v_value);
            v_tempD2 = spu_add(v_tempD2, v_value);
            
            // Saturate
            v_maskA1 = spu_cmpgt(v_tempA1, v_0x00ff);
            v_maskA2 = spu_cmpgt(v_tempA2, v_0x00ff);
            v_maskB1 = spu_cmpgt(v_tempB1, v_0x00ff);
            v_maskB2 = spu_cmpgt(v_tempB2, v_0x00ff);
            v_maskC1 = spu_cmpgt(v_tempC1, v_0x00ff);
            v_maskC2 = spu_cmpgt(v_tempC2, v_0x00ff);
            v_maskD1 = spu_cmpgt(v_tempD1, v_0x00ff);
            v_maskD2 = spu_cmpgt(v_tempD2, v_0x00ff);
            
            v_tempA1 = spu_sel(v_tempA1, v_0x00ff, v_maskA1);
            v_tempA2 = spu_sel(v_tempA2, v_0x00ff, v_maskA2);
            v_tempB1 = spu_sel(v_tempB1, v_0x00ff, v_maskB1);
            v_tempB2 = spu_sel(v_tempB2, v_0x00ff, v_maskB2);
            v_tempC1 = spu_sel(v_tempC1, v_0x00ff, v_maskC1);
            v_tempC2 = spu_sel(v_tempC2, v_0x00ff, v_maskC2);
            v_tempD1 = spu_sel(v_tempD1, v_0x00ff, v_maskD1);
            v_tempD2 = spu_sel(v_tempD2, v_0x00ff, v_maskD2);
            
            // Gather to uchar vector
            v_dstA = (vec_uchar16)spu_shuffle(v_tempA1, v_tempA2, v_pattern2);
            v_dstB = (vec_uchar16)spu_shuffle(v_tempB1, v_tempB2, v_pattern2);
            v_dstC = (vec_uchar16)spu_shuffle(v_tempC1, v_tempC2, v_pattern2);
            v_dstD = (vec_uchar16)spu_shuffle(v_tempD1, v_tempD2, v_pattern2);
            
            // Store
            *v_out++ = v_dstA;
            *v_out++ = v_dstB;
            *v_out++ = v_dstC;
            *v_out++ = v_dstD;
        }
        
        _outPtr += _outStride;
        _in1Ptr += _in1Stride;
    }
}



ピクセル数サイクル数命令数サイクル/ピクセル命令/ピクセル命令/サイクル
List 36443590.670.921.37

4周のループを展開したためループあたりの命令数はほぼ4倍に増えているにも関わらず、ループに必要なサイクル数は2倍以下に収まっています。 ループアンローリングにより依存関係のない演算を判断しやすい状況が促進され、複数の命令をパイプライン処理できるようになったためです。

この劇的な性能向上を見るともっと多くの周回をアンローリングしたいところですが、アンローリングすることにより処理単位を制約してしまうというトレードオフもあります。例えばList 3では64ピクセルの倍数に制約されています。許容される制約と求める性能の間で適切な値を見つけましょう。

またこの記事では詳しく議論していませんが、処理単位以外にもトレードオフを考えるべきポイントがあります。例えばコードサイズとレジスタ消費量です。ループアンローリングを行う場合は一般にコードサイズが大きくなります。貴重なLocal Storeの消費量に配慮する必要があります。また、SPEでは128本ものレジスタを使用できますが、ループアンローリングする周回を大きくした場合には枯渇する可能性にも十分注意を払うべきです。

さて、サイクル数/ピクセルの値を比べるとすでに元のスカラーコードに比べて30倍以上の高速化を果たしていますが、まだ終わりではありません。List 3のパイプラインをasmVisで見てみると命令が右側のOddパイプに偏っていることがわかります。もしEvenパイプの命令で置き換えられる演算があれば、より理想的なV字型に近づけることができるでしょう。


最高の"V"を目指して

List 3では3分の1程度がOddパイプで実行されるshuffle命令となっています。これはSPUには8ビットデータ用の演算命令がほとんどないため、16ビット整数に変換してから演算を行っているためです。

この変換を行っている最初のブロックと最後のブロックを工夫して、Evenパイプのand命令とor命令に置き換えることが可能です。


List 4. shuffle命令の一部をEvenパイプ命令に置き換え
                
#include <inttypes.h>
#include <vec_types.h>

void  add_const(
        uint8_t* _outPtr,
        int32_t  _outStride,
        const uint8_t* _in1Ptr,
        int32_t  _in1Stride,
        uint8_t  _value,
        uint32_t _width,
        uint32_t _height)
{
    uint32_t x, y;
    vec_ushort8  v_value    = spu_splats((uint16_t)_value);
    vec_ushort8  v_0x00ff   = spu_splats((uint16_t)0x00ff);
    vec_uchar16  v_zero     = (vec_uchar16)spu_splats(0);
    vec_uchar16  v_pattern1 = { 0, 16,  2, 18,  4, 20,  6, 22, 
                                8, 24, 10, 26, 12, 28, 14, 30 };
    vec_uchar16  v_pattern2 = { 1, 17,  3, 19,  5, 21,  7, 23, 
                                9, 25, 11, 27, 13, 29, 15, 31 };
    vec_uchar16  v_pattern3 = { 0x80, 0, 0x80,  2, 0x80,  4, 0x80,  6, 
                                0x80, 8, 0x80, 10, 0x80, 12, 0x80, 14 };

    for (y = 0; y < _height; y++)
    {
        vec_uchar16*       v_out = (vec_uchar16*)_outPtr;
        const vec_uchar16* v_in  = (const vec_uchar16*)_in1Ptr;
        
        for (x = 0; x < _width; x += 64)
        {
            vec_uchar16  v_srcA, v_srcB, v_srcC, v_srcD;
            vec_uchar16  v_dstA, v_dstB, v_dstC, v_dstD;
            vec_uchar16  v_maskA, v_maskB, v_maskC, v_maskD;
            vec_ushort8  v_tempA1, v_tempA2, v_tempB1, v_tempB2, 
                         v_tempC1, v_tempC2, v_tempD1, v_tempD2;
            
            // Load
            v_srcA = *v_in++;
            v_srcB = *v_in++;
            v_srcC = *v_in++;
            v_srcD = *v_in++;
            
            // Convert to short vector
            v_tempA1 = (vec_ushort8)spu_shuffle(v_srcA, v_srcA, v_pattern3);
            v_tempA2 = (vec_ushort8)spu_and((vec_ushort8)v_srcA, v_0x00ff);
            v_tempB1 = (vec_ushort8)spu_shuffle(v_srcB, v_srcB, v_pattern3);
            v_tempB2 = (vec_ushort8)spu_and((vec_ushort8)v_srcB, v_0x00ff);
            v_tempC1 = (vec_ushort8)spu_shuffle(v_srcC, v_srcC, v_pattern3);
            v_tempC2 = (vec_ushort8)spu_and((vec_ushort8)v_srcC, v_0x00ff);
            v_tempD1 = (vec_ushort8)spu_shuffle(v_srcD, v_srcD, v_pattern3);
            v_tempD2 = (vec_ushort8)spu_and((vec_ushort8)v_srcD, v_0x00ff);
            
            // Add
            v_tempA1 = spu_add(v_tempA1, v_value);
            v_tempA2 = spu_add(v_tempA2, v_value);
            v_tempB1 = spu_add(v_tempB1, v_value);
            v_tempB2 = spu_add(v_tempB2, v_value);
            v_tempC1 = spu_add(v_tempC1, v_value);
            v_tempC2 = spu_add(v_tempC2, v_value);
            v_tempD1 = spu_add(v_tempD1, v_value);
            v_tempD2 = spu_add(v_tempD2, v_value);
            
            // Saturate
            v_maskA = (vec_uchar16)spu_shuffle(v_tempA1, v_tempA2, v_pattern1);
            v_maskB = (vec_uchar16)spu_shuffle(v_tempB1, v_tempB2, v_pattern1);
            v_maskC = (vec_uchar16)spu_shuffle(v_tempC1, v_tempC2, v_pattern1);
            v_maskD = (vec_uchar16)spu_shuffle(v_tempD1, v_tempD2, v_pattern1);

            v_maskA = spu_cmpgt(v_maskA, v_zero);
            v_maskB = spu_cmpgt(v_maskB, v_zero);
            v_maskC = spu_cmpgt(v_maskC, v_zero);
            v_maskD = spu_cmpgt(v_maskD, v_zero);
            
            // Gather
            v_dstA  = (vec_uchar16)spu_shuffle(v_tempA1, v_tempA2, v_pattern2);
            v_dstB  = (vec_uchar16)spu_shuffle(v_tempB1, v_tempB2, v_pattern2);
            v_dstC  = (vec_uchar16)spu_shuffle(v_tempC1, v_tempC2, v_pattern2);
            v_dstD  = (vec_uchar16)spu_shuffle(v_tempD1, v_tempD2, v_pattern2);

            v_dstA  = spu_or(v_dstA, v_maskA);
            v_dstB  = spu_or(v_dstB, v_maskB);
            v_dstC  = spu_or(v_dstC, v_maskC);
            v_dstD  = spu_or(v_dstD, v_maskD);
            
            // Store
            *v_out++ = v_dstA;
            *v_out++ = v_dstB;
            *v_out++ = v_dstC;
            *v_out++ = v_dstD;
        }
        
        _outPtr += _outStride;
        _in1Ptr += _in1Stride;
    }
}

asmVisで見てみましょう。



ピクセル数サイクル数命令数サイクル/ピクセル命令/ピクセル命令/サイクル
List 46432510.500.801.59

ループ周回あたりの命令数も少し減りましたが、サイクル数はさらに減っています。shuffle命令がEvenパイプのand/or命令に置き換わり、ほとんどの行で左右のパイプに命令が発行されているより理想的なV字を描く状況に近づいたことがわかります。


まとめ

もう一度実験結果を見比べてみましょう。

オリジナルから比較すると、ピクセルあたりのサイクル数は24サイクルから0.5サイクルにまで削減することができ、48倍の高速化を果たしました。実際はY方向のループの分のオーバーヘッドがありますから、spu-gcc 4.1.1でSPUプログラムとしてビルドして実際の実行した場合の時間も記しておきます。使用した画像サイズは1024x32です。

数値はお使いのコンパイラやシステムによって変化することがあります。コンパイルスイッチを変えてみたり、コンパイラそのものを変えてみるとどうなるか、ぜひみなさん自身で試してみてください。

ピクセル数サイクル数命令数サイクル/ピクセル命令/ピクセル命令/サイクル実行時間
[us]
add_const.o
のサイズ
[byte]
対List 1 速度比
[us/us]
List 11241624.0014.000.67246.177431.00
List 21626141.630.880.5417.7890813.85
List 36443590.670.921.377.41111633.23
List 46432510.500.801.595.64112043.69
参考12853930.410.731.754.95125649.77

List 4までの最適化で、最終的なサイクルあたりの命令数は1.59となりました。最大値である2にはもう一息といったところですが、演算に先立って必ず多くのロード/ストア命令が必要なことが主な要因となっています。もっと多くのループをアンロールしたり、先ほどのshuffle命令をand/or命令に置き換えたようなトリックを探すことで、さらに改善することができるでしょう。

サインは"V"!!

命令がSPUのパイプラインにどのように実行されるかをasmVisで見ながら、簡単な画像処理コードの最適化をしてみました。 SIMD命令やループアンローリングなどのテクニックで劇的に速度を改善させることができることを、asmVisを用いて視覚的に確認しながら作業できることをご紹介しました。


参考文献

学ぶために

製品や技術を入手するために

議論するために

著者について

土居 意弘

土居意弘は IBM で 5 年間、Cell/B.E. や Digital Signal Processor 上で画像処理システムなどを開発してきました。現在は日本 IBM のテクニカルエキスパートとして、Cell/B.E. 向けソフトウェアの実装やアーキテクト、トレーニング講師なども行っています。並列プログラミングでは多くのことを考えなければならないので、まず自分の思考を並列化できないか考えています。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Multicore acceleration
ArticleID=325262
ArticleTitle=Assembly Visualizer を活用した SPU コードの高速化: 第 2 回 パイプラインを最適化する
publish-date=08082008
author1-email=munepi@jp.ibm.com
author1-email-cc=

タグ

Help
このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。

スライダーバーを使用することで、より多く(少なく)タグを表示します。

人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。

マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。

このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。