● ToolBox API徒然草(2006/08/28)

  このニュースは、MOSAの会員にのみ配布されているデベロッパー向けの
  デジタルマガジンMOSADeNのに掲載された記事です。ほぼ一ヶ月遅れで
  ここに掲載されて行きます

 〜 アプリケーションのUniversal Binary化(その12) 〜


今回は、AltiVecからSSE/SSE2へのコード変換における実作業についてお話します。例題を取り上げることでコード変換作業(少々やっかい)について解説します。

以前にも紹介しましたが、Universal Binary化においてAltiVecコードをSSE/SSE2コードに変換する作業を行う場合には、まず以下のドキュメントを参照してください。

「AltiVec/SSE Migration Guide」「Accelerate_sse_migration.pdf」(PDF 44ページ)

現在のSSE/SSE2はAltiVecよりも命令数が少なく、その処理速度もかなり低速です。次世代のIntel版CPU(Core 2 Duo)では、SSE/SSE2に新たな命令が追加され(SSE4と呼ばれる?)、演算ユニット自体の処理速度も向上すると言われています。しかし、実機が存在しない現段階では何とも判断できません。よって、変換後のソースコードの記述が下手だと、PowerPCではAltiVecを利用することで高速化していた整数処理が、SSE/SSE2に書き換えた途端に、CPU自身の整数処理を使うより遅くなってしまう可能性もあります。大規模なコード書き換えの前に、まずは小規模なコードを書き換えて、処理速度の詳細な比較を行うことが肝要でしょう。

XcodeプロジェクトででSSE/SSE2コードを利用するには、Accelerate.frameworkをリンクし、ソースファイルの最初にヘッダファイルとしてAccelerate.hを定義します。

#include <Accelerate/Accelerate.h>

SSE/SSE2に関する様々な定義はAccelerate.hが間接的に参照している以下のヘッダファイルに記述されています。

・SSEについては「xmmintrin.h」
・SSE2については「emmintrin.h」
・SSE3については「pmmintrin.h」

また、上記ファイルはDeveloperフォルダの以下の場所に保存されています。

/Developer/SDKs/MacOSX10.4u.sdk/usr/lib/gcc/i686-apple-darwin8/4.0.1/include/

上記3つのファイルに加えてMMX用(懐かしい)のヘッダファイルとしてmmintrin.hがありますが、MMXについてはSSE/SSE2の登場で時代遅れになってしまいましたので、今後使用を検討する必要はありません。SSE/SSE2で用いるルーチン名は、上記ヘッダーファイルにアゼンブラコードをインライン展開する形式で定義されています。例えば4つの浮動小数点2ペアーをかけ算するルーチンは、ソースファイル上には_mm_mul_ps()と記述することになります。これは、xmmintrin.hにおいて以下の様に定義されています。

static __inline __m128 __attribute__((__always_inline__, __nodebug__))
_mm_mul_ps (__m128 __A, __m128 __B)
{
return (__m128) __builtin_ia32_mulps ((__v4sf)__A, (__v4sf)__B);
}

SSE/SSE2のアゼンブラコードは__builtin_ia32_mulpsですが、それをC言語の関数風に記述できるよう考慮されているわけです。次にベクトル化した数値データ(変数や引数)の記述方法です。こちらは、AltiVecとSSE/SSE2でそれぞれ異なる形式として定義されています。しかし、VecLib.hから呼ばれているvecLibTypes.hでは、両方の定義の差異を吸収するための再定義がされており、利用者の便宜を図っています。例えば、128bitのレジスタに4つのfloat値(32bit)を代入したい場合には、AltiVecではvector floatと定義するのに対してSSE/SSE2では__m128(先頭のアンダーラインは2本...よく間違える)と定義しますが、以下のtypedefを利用していれば、どちらもvFloatと記述できるわけです。



この定義を用いれば、Universal Binary化において#ifや#endifなどを使いソースコードを切り分ける必要がなくなりますので非常に便利です。上記定義を見ると、SSE/SSE2ではユニット内でlong long値(64bit)やdouble値(64bit)同士の演算が可能だと言うことが理解できます。残念ながら、AltiVecでは64bit長の数値は取り扱うことができません。

以下は、は原点(0,0)からある座標(x,y)までの距離を、4座標まとめて同時に計算するルーチンです。最初は、AltiVec用ルーチンです。



AltiVecには単純に2ペアーをかけ算するルーチンはありません。その代わり、vec_madd()は一発で「A*B+C」という演算を行います。そのため、最初のvec_madd()では3番目の引数に0.0を渡してかけ算のみの実行としています。次が、SSE/SSE2用ルーチンです。



AltiVecと異なり、SSE/SSE2には「A*B+C」という演算を一発でこなす命令はありません。よって、この部分はかけ算_mm_mul_ps()と足し算_mm_add_ps()を併用します。また、最後にvsqrtf()を用いてルート値を得ていますが、この関数はAccelerate.frameworkに定義されており、AltiVecとSSE/SSE2共通で利用できます。また、Accelerate.frameworkを用いることで、単純な演算であれば以下のような「ごく普通」の表記をすることも可能です。この表記は、AltiVecとSSE/SSE2共通で利用できますので、やはり#ifや#endifでソースコードを切り分ける必要がなくなり大変便利です。



今回をもって、長らく続けてきた「アプリケーションのUniversal Binary化」についての話を終了したいと思います。次回からは、新しいテーマにそったCarbon API関連の最新の話題を取り上げていきたいと考えています。お楽しみに!


copyright 2006 Ottimo, Inc. All rights reserved
無断転載・引用禁止
Contact us: koike@ottimo.co.jp