月別アーカイブ: 2009年10月

Direct3D 11 / DirectX 11 UnorderedAccessView と RenderTarget の関係

DirectX10 で大きく仕様が変わったのは、より汎用化を進めるためでした。
つまり専用の機能が無くなったということ。
ライティングのための機能、マテリアルの設定などといった目的別の API は
完全に姿を消しています。

では CPU のように完全に何にでも使える、単純でフラットな空間が得られたのかと言えば必ずしも
そうではなく、特にリソースとバッファの扱いはかえって複雑になった印象を受けます。

汎用化や何にでも使えることが、そのまま単純化に繋がるとは限らないためです。
複数の機能の要素をマージしたこと、目的別の機能の代わりにより抽象的な用語や仕様が
増えたことがその要因といえるかもしれません。

DirectX11 はさらに CompteShader が使えるようになり、書き込み可能なバッファが
追加されています。

以下 FEATURE_LEVEL 11.0、 ShaderModel 5.0 を対象としています。

●リソース

Direct3D 11 では View も ShaderObject も種類が増えており何を見て良いのか
わからなくなります。
でもリソースを作る手段は二つだけ。CreateBuffer() か CreateTexture~() です。
Constant も Buffer だし、Vertex や Index もただの Buffer です。

● View

リソースをどう扱うか、それを決めるのが View です。
同じリソースを複数の View を通して見ることが可能で、テクスチャとして参照したり
レンダリングしたり、データとして読み書きするなど流用が可能となります。

View は次の 4種類だけです。

・読み込みのための View、テクスチャやバッファなど
  ShaderResourceView (SRV)

・Merger による書き込みのための View
  RenderTargetView (RTV)
  DepthStencilView (DSV)

・読み書きのための View
  UnorderedAccessView (UAV)

VertexBuffer/IndexBuffer/ConstantBuffer は View を用いずに設定します。

●タイプとシェーダーオブジェクト

データをアクセスする方法を決定するのが、バッファ作成時の細かい指定と
DXGI フォーマットタイプです。

Texture
Buffer
ByteAdddressBuffer
StructuredBuffer
AppendSturctutedBuffer
ConsumeStructuredBuffer
RWTexture
RWBuffer
RWSturcturedBuffer
RWByteAddressBuffer

これらのシェーダーオブジェクトは、シェーダーがアクセスする方法を決定しています。
シェーダー側の宣言だけではだめで、リソース生成時にもいろいろとフラグを指定
しておく必要があります。

● UnorderedAccessView

RW がついているものと AppendSturctutedBuffer/ConsumeStructuredBuffer は
書き込み可能なシェーダーオブジェクトです。
これらは UnorderedAccessView (UAV) を使います。

UAV が使えるのは PixelShader と CompteShader だけ。
Compte Shader は Output Merger が無いので、出力のために必ず何らかの UAV を
用いることになります。

実際に使ってみると、出力はすべて UnorderedAccessView に相当することがわかってきます。
つまり RenderTarget も出力先アドレスが固定された UnorderedAccessView 相当だということです。

UAV の設定 API は次の通り

CompteShader → CSSetUnorderedAccessViews()
PixelShader  → OMSetRenderTargetsAndUnorderedAccessViews()

OMSetRenderTargetsAndUnorderedAccessViews() で一見何のために存在する
パラメータなのかわかりにくいのが UAVStartSlot です。
これは RenderTarget との衝突を避けるためのものでした。

例えば RenderTarget を 2 枚設定した場合は、UnorderedAccessView は必ず
番号 2 から始めなければなりません。

下記のようなリソースを登録する場合

RenderTarget x2
UnorderedAccessView x3

OMSetRenderTargetsAndUnorderedAccessViews() には次のようなパラメータを
渡します。

NumViews = 2          // RTV (RenderTargetView) の個数
UAVStartSlot = 2      // UAV (UnorderedAccessView) の開始番号 == NumViews
NumUAVs = 3           // UAV の個数

※ August 2009 のマニュアル OMSetRenderTargetsAndUnorderedAccessViews()
の引数は若干間違いがあります。ヘッダファイルを見た方が正しい宣言です。

シェーダーレジスタはこのように割り当てられます。

o0 = RenderTarget
o1 = RenderTarget
u2 = UnorderedAccessView
u3 = UnorderedAccessView
u4 = UnorderedAccessView

つまり o レジスタと u レジスタは同時に同じ番号を使うことが出来ず、
使用可能な View はあわせて 8 個まで。この両者ほぼ同じ扱いです。

UnorderedAccessView は読み書き可能なバッファです。
よく考えると RenderTarget や DepthBuffer と同じこと。
UAV は読み込んだあとにブレンドしたり、判定した結果をまた書き戻すことが出来るわけです。
そのパス内で更新できる OM をプログラムできるなら、いろいろ面白いことが出来そうです。

実際試してみたらエラーで、RW で読み込めるのは sngle コンポーネントに限ると言われます。
時間がなかったので、このあたりは後ほどまた詳しく調べてみます。

昔わからないと書いた CSSetUnorderedAccessViews() の最後のパラメータは
Append/Consume buffer に関するものでした。
やはり当時のサンプルの使い方が間違っていたのだと思われます。

関連エントリ
DirectX 11 / Direct3D 11 と RADEON HD 5870 の caps
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
その他 Direct3D 11 関連

NetWalker PC-Z1 設定とキーの打ち方

Ubuntu のデスクトップ環境はよくできています。
設定とかウィンドウ上のダイアログだけで済んでしまうし、普通に使っている分には
Windows とあまり変わらない印象です。

もちろんコマンドシェルを使った方がやれることが多いだろうし、以前から Linux や Unix 系
OS を使っている方ならコマンドを打ち込んだ方が早いかもしれません。
そのため古くから使っている人だけでなく、ネット上に蓄積されている情報もコマンドで作業する
ものが多くなっています。
今後誰でも使える環境を目指すなら、コマンドに頼らない GUI だけで活用するノウハウも
必要になってくるのではないでしょうか。

● VPN の設定

通知スペースにある 無線LAN のアイコンをクリックすると「VPN 接続」という項目があります。
そのまま設定できそうですができませんでした。何らかのマネージャーを登録する必要があるようです。

ここでは PPTP を使っています。

・アプリケーションの追加と削除から PPTP で検索
・PPTP VPN Connection Manager が見つかったのでインストール
 (チェックボックスにチェックを入れて、変更の適用)

これで通知スペースのネットワークアイコンから「VPN 接続」を選べるようになりました。

・VPN の設定から [追加]
・VPN 接続タイプで PPTP を選択して [作成]
・必要な項目を書き込む

「VPN 接続」のメニューに書き込んだ項目が追加されました。
これでうまくいきそうに見えますがつながりません。

エラーが出ているようですが、右上の通知も一瞬で消えるので原因もよくわからないまま。
ログアウトして再ログインしてもだめでした。

しばらく悩んだものの、結局再起動したらあっさりつながりました。
ネットワークアイコンにもログイン状況を示すアニメーションや鍵マークが表示されて
VPN で繋がっています。

●クイックスタートボタンを無効化する

本体支える場所が少ないのでクイックスタートボタンをよく触ってしまいます。
下記の設定で無効化できることがわかりました。

設定 → キーボード・ショートカット → デスクトップ →「Run a defined commnd」

XF86HomePage, XF86WWW, XF86Mail, 0xca と書かれているのがそれぞれ
HOME, Web, Mail, ☆ ボタンに相当するようです。
個別に選択して [BS] キーを押すと「無効」になります。
設定を変更したら、いったんログアウトして再ログインし直すと反映されます。
元に戻すには、個別に選択して対応するキーを押せば OK です。

● Flash プラグイン

ブラウザが落ちやすくなったので結局無効にしてしまいました。
ツール → アドオン → プラグイン → Shockwave Flash 無効化

●片手だけタッチタイプで使う

(1) 両手とも5本指

テーブルに置いた状態では、両手でキーボードをたたいています。
パソコンと同じようにホームポジションに手を置きながら。
オプティカルポイントは人差し指で操作です。

(2) 親指のみ

電車の中など立って使う場合は、両手で持って親指でオプティカルポイントを操作しています。
この場合キーボードも親指打ちになります。

でもこれだと中央のキーを打ちづらいしあまり速く打てないので、文章を打つ場合は
右手だけ 5本指を使うようになりました。

(3) 親指+5本指

左手は本体のホールド兼用で、左側のキーを親指で打ちます。
右手はホームポジションに乗せて、そのまま (1) と同じようにタッチタイプします。
オプティカルポイントは人差し指や中指で。

この打ち方のメリットは右手側のキーだけ見なくても速く打てることと、左手の親指が届かない
真ん中のキーを右手でカバーできることです。
デメリットは本体を支える左手が疲れること。
左右逆でもよいかもしれません。

●NetWalker

個人的にはタッチタイプできる絶妙なサイズのキーボードで、予想に反して手放せないものと
なりつつあります。

親指打ちに特化されたキーボードだと、上のような組み合わせた持ち方&打ち方は出来ないし、
これ以上キーボードが大きくなるとおそらく本体も重くて片手で支えられなくなります。
複数の打ち方を使い分けられる別のジャンルと割り切ってよいのではないでしょうか。
Netbook が買える価格帯であることを考えると決して万人には勧められませんし、それぞれ
個別に見ていくと中途半端に見えるかもしれません。
だけど今まで無かったこの中途半端なサイズを求める人にはぴったりかもしれません。

今後キーの質が改良されるのは望むべきことですが、サイズとか他のところはあまり大きく
変えないで欲しいというのが今の感想です。

関連エントリ
NetWalker PC-Z1 と親指シフト入力
NetWalker PC-Z1 Bluetooth とキーカスタムその他
NetWalker PC-Z1 意外にいける
Netwalker PC-Z1 買いました

DirectX 11 / Direct3D 11 と RADEON HD 5870 の caps

RADEON HD 5870 手に入れました。
とりあえず各機能の対応状況は以下の通り。(caps viewer より)

Direct3D 11
   Feature Level                   D3D_FEATURE_LEVEL_11_0
   Driver Concurrent Creates       No
   Driver Command Lists            No
   Double-precision Shaders        Yes
   Compute Shader 4.x              Yes
D3D_FEATURE_LEVEL_11_0
   Shader Model                    5.0
   Geometry Shader                 Yes
   Stream Out                      Yes
   Compute Shader                  Yes
   Hull & Domain Shaders           Yes
   Texture Resource Arrays         Yes
   Cubemap Resource Arrays         Yes
   BC4/BC5 Compression             Yes
   BC6H/BC7 Compression            Yes
   Alpha-to-coverage               Yes
   Extended Formats (BGRA, etc.)   Yes
   10-bit XR High Color Format     Yes

ShaderModel 5.0 も、ComputeShader も、BC6H/BC7 も、Yes です。
去年末くらいに実験していたテセレータ周りのプログラムもそのまま動きました。
Compute Shader 4~5 も動いてます。OpenCL の方は不明。

唯一 Thread 周りが未対応で、Concurrent Creates と Command Lists が No に
なっています。ドライバがまだ完全ではないのかもしれません。
使用したドライバは Radeon_HD5800_8.66RC6_Vista_Win7_Sep21 。

倍精度演算のコンパイルを試しました。hlsl の出力は下記の通り。

cs_5_0
dcl_globalFlags refactoringAllowed | enableDoublePrecisionFloatOps
dcl_uav_structured u0, 4
dcl_input vThreadIDInGroupFlattened
dcl_input vThreadGroupID.xy
dcl_input vThreadIDInGroup.xy
dcl_input vThreadID.xy
dcl_temps 2
~
add r0.y, r0.z, r0.y
add r0.y, r0.w, r0.y
ftod r0.zw, r0.y
dmul r0.zw, d(0.000000, 100000.000000), r0.zwzw
dadd r0.zw, d(0.000000, 0.000000), r0.zwzw
imul null, r1.xy, l(10000, 1000, 0, 0), vThreadIDInGroup.xyxx
utof r1.xy, r1.xyxx
ftod r1.xyzw, r1.xyxx
dadd r0.zw, r0.zwzw, r1.xyxy

d がついているのが double 命令です。
倍精度演算はレジスタを 2 個使って表現していることがわかります。
レジスタのペアを {} で表現すると

ftod r0.zw, r0.y
  ↓
r0.{zw} = FloatToDouble( r0.y )


utof r1.xy, r1.xyxx
ftod r1.xyzw, r1.xyxx
dadd r0.zw, r0.zwzw, r1.xyxy
  ↓
r0.x = UIntToFloat( r1.x )
r0.y = UIntToFloat( r1.y )
r1.{xy} = FloatToDouble( r0.x )
r1.{zw} = FloatToDouble( r0.y )
r0.{zw} = r0.{zw} + r1.{xy}

でも実際に走らせると落ちました。
単純な加算や型変換だけなら動くのですが、まだ何らかの問題があるようです。

関連エントリ
RADEON HD 5870 と DirectX 11
Direct3D11/DirectX11 (18) GPU を使ったアウトラインフォントの描画の(6)

ARM Cortex-A8 の NEON と浮動小数演算最適化

ARM も x86 と同じように、これまで様々な命令セットの拡張が行われてきました。
例えば乗算一つとっても多数の命令が存在しており、どれを使えばよいかわからなくなる
ことがあります。
x86 に FPU と SSE 命令が共存しているように、ARM にも VFP や NEON 命令があります。
どちらの命令を使っても、単精度の浮動小数演算が可能です。

Cortex-A8 (ARM v7) の場合: 浮動小数演算

VFP    低速  倍精度演算対応  IEEE754 準拠
NEON   高速  単精度のみ      SIMD

NEON 命令は VFP 命令も混在しており区別が付きにくいことから、それぞれ演算速度を
調べてみました。

下記の表は気になる命令のみピックアップして調べたものです。
4 G 個 (40億個) の命令を実行した場合の時間の実測値です。

01:  vld1.32  {d[0]},[r]     20.16 sec *1
02:  vld1.32  {d[0]},[r]     15.14 sec *2
03:  vldr.32  s,[r]          15.06 sec *1
04:  vldr.32  s,[r]          10.04 sec *2
05:  vldr.64  d,[r]          10.18 sec
06:  vdup.32  d,r             5.90 sec
07:  vdup.32  q,r             5.90 sec
08:  vdup.32  d,d[0]          5.02 sec
09:  vdup.32  q,d[0]          5.02 sec
10:  vmov.32  d[0],r         15.10 sec *1
11:  vmov.32  d[0],r         10.08 sec *2
12:  vmov.32  r,d[0]          7.03 sec
13:  vmov  d,r,r             11.79 sec
14:  vmov  r,r,d             12.05 sec
15:  vcvt.s32.f32  d,d        5.01 sec
16:  vcvt.s32.f32  q,q       10.03 sec
17:  vcvt.f32.s32  s,s       45.30 sec VFP
18:  vcvt.f32.s32  d,d        3.76 sec
19:  vcvt.f32.s32  q,q       10.04 sec
20:  vadd.f32  d,d,d          5.02 sec
21:  vadd.f32  q,q,q         10.04 sec
22:  vmla.f32  d,d,d          5.02 sec
23:  vmla.f32  q,q,q         10.04 sec
24:  vrecpe.f32  d,d          5.02 sec
25:  vrecpe.f32  q,q         10.10 sec
26:  vrecps.f32  d,d,d        5.08 sec
27:  vrecps.f32  q,q,q       10.09 sec

r = ARM レジスタ r0~
s = VFP レジスタ s0~s31
d = NEON/VFP レジスタ d0~d31
q = NEON レジスタ q0~q15

*1 = インターリーブ無し
*2 = インターリーブあり

ARM レジスタから NEON/VFP レジスタへのデータ転送はいくつかの手段が考えられます。

13:  vmov  d,r,r             11.79 sec
14:  vmov  r,r,d             12.05 sec

この命令は NEON の 64bit d0~d31 レジスタと、ARM レジスタ r0~ 2個を相互に
転送するものです。ARM 側は必ず 2個セットの 64bit でなければならず、動作時間も
比較的かかっています。

10:  vmov.32  d[0],r         15.10 sec *1
11:  vmov.32  d[0],r         10.08 sec *2
12:  vmov.32  r,d[0]          7.03 sec

上の vmov.32 は NEON の d0~d31 レジスタのうち半分、32bit 分のみ転送する命令です。
NEON レジスタは下記のように割り当てられています。

NEON ビュー
+-------------------------------+
| q0                            |  128bit   ( ~ q15 , 16個 )
+---------------+---------------+
| d0            | d1            |   64bit   ( ~ d31 , 32個 )
+-------+-------+-------+-------+
| d0[0] | d0[1] | d1[0] | d1[1] |   32bit   ( ~ d31[1] , 64個 )
+-------+-------+-------+-------+

全く同じ領域を VFP レジスタとしてもアクセスすることが出来ます。

VFP ビュー
+---------------+---------------+
| d0            | d1            |   64bit   ( ~ d31 , 32個 )
+-------+-------+-------+-------+
| s0    | s1    | s2    | s3    |   32bit   ( ~ s31 , 32個 )
+-------+-------+-------+-------+

VFP ビューでは 32bit のデータを sレジスタとして個別にアクセスできます。
64bit dレジスタの扱いはほぼ同じです。
命令フィールドの制限から、レジスタ番号は 0~31 の範囲でなければなりません。
つまり 32bit レジスタが 64 個あるにもかかわらず、s レジスタとしてアクセス出来る
のは半分の s0~s31 だけです。

NEON ビューの 32bit スカラ要素では、このようなアクセス制限が無いことがわかります。

NEON ビューと VFP ビューの重要かつ大きな違いがもう一つあります。
NEON では dレジスタの 64bit 単位でデータを扱うということ。
VFP は sレジスタ単位、つまり 32bit 単位でデータを扱います。

よって上の命令

10:  vmov.32  d[0],r         15.10 sec *1

は NEON d レジスタの 32bit 分、半分の領域にしか書き込みを行いません。
このとき d レジスタは、残り半分のデータを保存しなければならなくなるため
デステネーションレジスタにも依存が発生します。
いわゆるパーシャルレジスタストールです。
SSE で SS 命令よりも、完全に置き換える PS 命令の方が速いのと同じです。

実際に測定してみると、下記のようにデスティネーション側レジスタに同じ d レジスタ
を指定するとパイプラインストールが発生します。これが 10: の vmov.32 の値です。

; 同じ d0 に部分書き込みを行うためストールする。10: vmov.32 ~ 15.06 sec
vmov.32  d0[0],r2
vmov.32  d0[1],r3
vmov.32  d0[0],r2
vmov.32  d0[0],r3
 ~

; 異なるレジスタへ交互に書き込む場合。11: vmov.32 ~ 10.04 sec
vmov.32  d0[0],r2
vmov.32  d1[0],r2
vmov.32  d2[0],r2
vmov.32  d3[0],r2
 ~

レジスタを置き換えた 11: の方では 15 sec → 10 sec と速くなるため、1cycle 分の
遅延が発生していることがわかります。

全く同じことが 03: の vldr.32 s,[r] でも発生していることがわかりました。
03: vldr.32 s,[r] は VFP 命令のはずですが、実行時間を見ると NEON の演算ユニットで
実行しているようです。
NEON には即値アドレスのメモリから 32bit スカラを読み込む命令がないので
この命令を多用しても大丈夫そうです。

s レジスタへの書き込みは上の d[x] と同じように 32bit の部分書き込みに相当します。
実際に下記の通りストールが発生しました。

; ストールする ( s0 も s1 も同じ d0 レジスタに相当するため)
vldr.32   s0,[r1]
vldr.32   s1,[r1]
vldr.32   s0,[r1]
vldr.32   s1,[r1]

; ストールしない
vldr.32   s0,[r1]
vldr.32   s2,[r1]
vldr.32   s4,[r1]
vldr.32   s6,[r1]

また sレジスタなので、後半 d16~d31 エリアへ直接 32bit の値をロードすることができません。

VFP 命令が遅いのは、このようなアーキテクチャの違いも一つの要因かもしれません。
レジスタへのアクセス単位が異なるので、パイプラインが矛盾しないように実行が終わるのを
待っている可能性があります。

最初の測定結果から、ARM レジスタから NEON レジスタへのスカラ転送は vdup を
使うのが最も効率がよいことがわかります。
実行速度も速く d レジスタを完全に置き換えるために不要な依存が発生しません。

メモリからの読み込みは vldr.32 s を用います。後半 d16~d31 エリアへの代入が
必要なら vld1.32 を使うことが出来ますが、この場合利用できるアドレッシングモードに
制限があります。

これらの測定データを元に、VFP 命令を NEON 命令に置換する簡単なスクリプト
作ってみました。命令置換は下記の方針で処理しています。

・s0~s31 レジスタは d0~d31 レジスタのスカラにマップする。

・ARM レジスタとの相互転送は下記の命令を使う。
     R ← D    vmov.32  r,d[0]
     D ← R    vdup.32  d,r

・一般の演算や浮動小数と整数の変換などは NEON の 64bit (float x2) 演算を用いる。

・メモリからのロードは vldr.32 s,addr

・d レジスタの奇数要素 d0[1]~d31[1] は一時的なテンポラリに使える。

・どうしても s レジスタを使わなければならない命令ではレジスタ番号を 2倍する。
  例えば double への変換や vldr 命令時。
  s1 は d1 = (s2,s3) にマッピングされるため。

・s レジスタの番号が 32 を超える場合は、テンポラリを経由した複数命令に展開する。やむなし

・倍精度演算命令は置き換えない。

gcc は VFP でコンパイルし、出力したアセンブラコード (*.s) をいったんスクリプトに通して
可能な部分を NEON 命令に置き換えます。例えば Makefile は下記のような感じで、
opt_neon.pl を通しています。

%.o: %.cpp
	$(CC) $(CFLAGS) $< -S -o $*._temp.s
	$(PERL) opt_neon.pl $*._temp.s > $*.s
	$(CC) $(CFLAGS) $*.s -c -o $@

void Loop_main( float dd )
{
    TimerClass  timer;
    timer.Begin();
    float   sum= 0.0f;
    for( int i= 0 ; i< 100000000 ; i++ ){
        sum= sum * dd + dd;
    }
    timer.End( "end" );
    printf( "%f\n", sum );
}

上のプログラムを実行した結果は次の通り (NetWalker Cortex-A8 800MHz)

そのまま VFP を使用した場合    2.39 秒
オプティマイザを通した場合     1.01 秒

スクリプト opt_neon.pl は上のプログラムを正しく変換できるように必要な命令の分しか
作っていません。つまりまだ未完成です。対応してない命令があるのでどんなプログラムでも
変換できるわけではありません。
それでもきちんと効果があるので、このまま対応命令を増やせば浮動小数演算を多用した
アプリケーションも大幅な高速化が期待できそうです。

opt_neon.pl

将来的にはおそらく gcc の方に、スカラ演算でも NEON コードだけを生成するオプションが
追加されるのではないでしょうか。
試していませんが iPhone 3GS や iPod Touch 3G でももしかしたら NEON 最適化が
有効かもしれません。

NetWalker    i.MX515  Cortex-A8 (ARM v7-A) 800MHz   AMD Z430
iPhone 3GS   S5PC100  Cortex-A8 (ARM v7-A) 600MHz   PowerVR SGX 535

関連エントリ
NetWalker PC-Z1 Cortex-A8 の NEON 命令とメモリ速度
SSE の浮動小数演算速度
NetWalker PC-Z1 Cortex-A8 浮動小数演算の実行速度
NetWalker PC-Z1 Atom と速度比較

NetWalker PC-Z1 Cortex-A8 の NEON 命令とメモリ速度

最善ケースばかりでなく、実際に使えるプログラムで試してみます。
使っているのは i.MX51 系 i.MX515 を搭載した NetWalker PC-Z1 ですが、
おそらく同じ Cortex-A8 (ARM v7-A) の CPU core を使った iPhone 3GS や
iPod touch 3G でも全く同様ではないかと思います。

NEON のプログラミングは比較的簡単です。
例えば 4×4 matrix の乗算だとこんな感じで書けます。

inline void Mul_NEON( Float4x4* p3, const Float4x4* p1, const Float4x4* p2 )
{
    __asm__ __volatile__ ( "\
        vldmia  %0, {d0-d7}     \n\
        vldmia  %1, {d8-d15}    \n\
\n\
        vmul.f32    q8,q0,d8[0]     \n\
        vmla.f32    q8,q1,d8[1]     \n\
        vmla.f32    q8,q2,d9[0]     \n\
        vmla.f32    q8,q3,d9[1]     \n\
        vstmia  %2!, {d16,d17}      \n\
\n\
        vmul.f32    q8,q0,d10[0]    \n\
        vmla.f32    q8,q1,d10[1]    \n\
        vmla.f32    q8,q2,d11[0]    \n\
        vmla.f32    q8,q3,d11[1]    \n\
        vstmia  %2!, {d16,d17}      \n\
\n\
        vmul.f32    q8,q0,d12[0]    \n\
        vmla.f32    q8,q1,d12[1]    \n\
        vmla.f32    q8,q2,d13[0]    \n\
        vmla.f32    q8,q3,d13[1]    \n\
        vstmia  %2!, {d16,d17}      \n\
\n\
        vmul.f32    q8,q0,d14[0]    \n\
        vmla.f32    q8,q1,d14[1]    \n\
        vmla.f32    q8,q2,d15[0]    \n\
        vmla.f32    q8,q3,d15[1]    \n\
        vstmia  %2!, {d16,d17}      \n\
    "
    : "=&r"( p1 ), "=&r"( p2 ), "=&r"( p3 )
    : "0"( p1 ), "1"( p2 ), "2"( p3 )
    : "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "cc", "memory"
    );
}

かなり少ない命令で記述できます。

・3オペランドの命令フォーマットでレジスタの転送が不要
・積和命令がある
・ベクタへのスカラ乗算機能が用意されているため、スカラ要素の複製が不要
・レジスタの数が多い

d0~d31 : 64bit レジスタ (float x2) 32本
q1~q16 : 128bit レジスタ (float x4) 16本 (d0~d31 と共有)

上のプログラムで配列のような記述をしている d8[0], d8[1] は、d0レジスタに含まれる
2つの float に個別にアクセスしています。

s0~s31 は 32bit の single float レジスタですが、この形式で記述する命令は
VFP 命令なので要注意です。NEON View では d または q レジスタのみ扱い、
single 要素は d レジスタの一部として扱います。
なお VFP 命令も倍精度演算で d レジスタを扱うことがあります。

vmla は積和命令で vmla.f32 q8,q0,d8[1] はシェーダー風の記述を用いるなら

q8.xyzw = q0.xyzw * d8.xxxx + q8.xyzw

となります。ベクタへのスカラ乗算を簡単に記述できるのは便利です。
SSE だと 3~4命令くらいかかります。

これが SSE なら

void __fastcall Mul_SSE( Float4x4* p3, const Float4x4* p1, const Float4x4* p2 )
{
    __asm {
    mov     eax, dword ptr[esp+4]	; p2

    movaps  xmm4, xmmword ptr[edx]	; p1->_11_12_13_14
    movss   xmm0, xmmword ptr[eax]	; p2->_11
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm4
    movaps  xmm1, xmm0

    movaps  xmm5, xmmword ptr[edx+16]	; p1->_21_22_23_24
    movss   xmm0, xmmword ptr[eax+4]	; p2->_12
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm5
    addps   xmm1, xmm0

    movaps  xmm6, xmmword ptr[edx+32]	; p1->_31_32_33_34
    movss   xmm0, xmmword ptr[eax+8]	; p2->_13
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm6
    addps   xmm1, xmm0

    movaps  xmm7, xmmword ptr[edx+48]	; p1->_41_42_43_44
    movss   xmm0, xmmword ptr[eax+12]	; p2->_14
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm7
    addps   xmm1, xmm0

    movaps  xmmword ptr[ecx], xmm1	; p3->_11_12_13_14

    movss   xmm0, xmmword ptr[eax+16]	; p2->_21
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm4
    movaps  xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+20]	; p2->_22
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm5
    addps   xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+24]	; p2->_23
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm6
    addps   xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+28]	; p2->_24
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm7
    addps   xmm1, xmm0

    movaps  xmmword ptr[ecx+16], xmm1	; p3->_21_22_23_24

    movss   xmm0, xmmword ptr[eax+32]	; p2->_31
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm4
    movaps  xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+36]	; p2->_32
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm5
    addps   xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+40]	; p2->_33
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm6
    addps   xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+44]	; p2->_34
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm7
    addps   xmm1, xmm0

    movaps  xmmword ptr[ecx+32], xmm1	; p3->_31_32_33_34

    movss   xmm0, xmmword ptr[eax+48]	; p2->_41
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm4
    movaps  xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+52]	; p2->_42
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm5
    addps   xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+56]	; p2->_43
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm6
    addps   xmm1, xmm0

    movss   xmm0, xmmword ptr[eax+60]	; p2->_44
    shufps  xmm0, xmm0, 00000000b
    mulps   xmm0, xmm7
    addps   xmm1, xmm0

    movaps  xmmword ptr[ecx+48], xmm1	; p3->_41_42_43_44
    };
}

長いです。

その代わり Cortex-A8 / NEON はインオーダー実行 HT 無しなので、最適化を考えて
書く必要があります。
最初の NEON の例も、レジスタが多いことを利用すれば次のようにできます。

    vldmia  %0, {d0-d7}
    vldmia  %1, {d8-d15}

    vmul.f32    q8,q0,d8[0]
    vmul.f32    q9,q0,d10[0]
    vmul.f32    q10,q0,d12[0]
    vmul.f32    q11,q0,d14[0]

    vmla.f32    q8,q1,d8[1]
    vmla.f32    q9,q1,d10[1]
    vmla.f32    q10,q1,d12[1]
    vmla.f32    q11,q1,d14[1]

    vmla.f32    q8,q2,d9[0]
    vmla.f32    q9,q2,d11[0]
    vmla.f32    q10,q2,d13[0]
    vmla.f32    q11,q2,d15[0]

    vmla.f32    q8,q3,d9[1]
    vmla.f32    q9,q3,d11[1]
    vmla.f32    q10,q3,d13[1]
    vmla.f32    q11,q3,d15[1]
    vstmia  %2, {d16-d23}

完全にキャッシュがヒットする前提なら、こちらのコードの方が 1.7倍くらい速くなります。
ただし、実際のアプリケーションで使うとここまで差が出ません。
メモリアクセスの方がずっと低速だからです。

キャッシュがほとんど利かない条件でテストすると、Cortex-A8 + NEON は Atom よりも
かなり低速でした。Matrix 演算ではなく、ただのメモリ転送だけテストしてみたのが下の表です。

Atom Z540 1.86GHz  2.98GB/sec
Atom Z540  800MHz  1.91GB/sec (省電力機能で制限をかけたもの)
Cortex-A8  800MHz   255MB/sec

Atom Z540 は FSB 533MHz で 64bit 幅あるため、DDR2-533 とするとメモリの転送速度は
最大 4.2GB/sec (PC2-4200) と考えられます。
i.MX515 は mDDR1 or DDR2 200MHz の 16/32bit らしいですが、この辺の具体的な値は
明らかになっていません。ただ考えられる数値よりも測定結果はかなり低いです。
省電力との兼ね合いでしょうか。L2 cache 容量も Atom の半分です。

プロセッサ自体の演算能力はそれなりにあるものの、メモリ上のデータを大量にストリーム処理
するようなケースでは、ほとんど生かし切れていない可能性があります。

同様の core を持つ iPhone 3GS でどの程度動くか試してみたいところです。

関連エントリ
SSE の浮動小数演算速度
NetWalker PC-Z1 Cortex-A8 浮動小数演算の実行速度
NetWalker PC-Z1 Atom と速度比較