HLSL では matrix のフォーマットとして、宣言時に row_major と column_major
を選ぶことができます。例えば float4x4 (matrix) と float4 (vector) の乗算
ではどちらで宣言しても
 ・水平演算 dp4 命令を使うか
 ・並列に積和展開するか
の違いになります。命令ステップ数どちらもは 4 で動作上は影響が出ません。
以前こちらのメモに書いた 2種類のコード展開がちょうどこの
row_major と columns_major の違いに相当します。
並列積和か水平か
(row_major, columns_major の違いだけでなく乗算順の設計にも依存する)

なので、エンジン側の設計に合わせて row_major にするか columns_major に
するか選ぶことができます。混在も可能でその辺は HLSL コンパイラがうまい
具合に合わせてくれます。

ただし入力が float3 になると少々話が違ってきます。

例えば VertexShader の入力頂点 float3 を、LocalToWorld float4x4 に
乗算する場合を考えます。

  float4 wpos= mul( LocalToWorld, float4( Input.Position.xyz, 1 ) );
  Output.Pos= mul( WorldToProjection, wpos );

このとき入力頂点の w に 1を代入し、matrix の translation 成分が
そのまま加算されるようにします。

このコードは、積和に水平展開されるとちょうど最後の積和が
単純な加算に置き換わるだけなのでコードのステップ数に影響が出ません。

 vs_4_0
 dcl_input v0.xyz
 dcl_input v1.xyz
 dcl_input v2.xy
 dcl_output_siv o0.xyzw , position
 dcl_output o1.xyz
 dcl_output o2.xy
 dcl_constantbuffer cb0[21], immediateIndexed
 dcl_constantbuffer cb1[4], immediateIndexed
 dcl_temps 2
 mul r0.xyzw, v0.yyyy, cb1[1].xyzw
 mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw
 mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw
 add r0.xyzw, r0.xyzw, cb1[3].xyzw     // w=1 のときの加算
 mul r1.xyzw, r0.yyyy, cb0[5].xyzw
 mad r1.xyzw, cb0[4].xyzw, r0.xxxx, r1.xyzw
 mad r1.xyzw, cb0[6].xyzw, r0.zzzz, r1.xyzw
 mad o0.xyzw, cb0[7].xyzw, r0.wwww, r1.xyzw // ここは積和のまま
 mov o1.xyz, v1.xyzx
 mov o2.xy, v2.xyxx
 ret
 // Approximately 11 instruction slots used

ところが水平加算の dp4 に展開されると

 vs_4_0
 dcl_input v0.xyz
 dcl_input v1.xyz
 dcl_input v2.xy
 dcl_output_siv o0.xyzw , position
 dcl_output o1.xyz
 dcl_output o2.xy
 dcl_constantbuffer cb0[21], immediateIndexed
 dcl_constantbuffer cb1[4], immediateIndexed
 dcl_temps 2
 mov r0.xyz, v0.xyzx // w を書き換えるためにテンポラリにコピー
 mov r0.w, l(1.000000) // w=1 に代入
 dp4 r1.x, cb1[0].xyzw, r0.xyzw
 dp4 r1.y, cb1[1].xyzw, r0.xyzw
 dp4 r1.z, cb1[2].xyzw, r0.xyzw
 dp4 r1.w, cb1[3].xyzw, r0.xyzw
 dp4 o0.x, cb0[4].xyzw, r1.xyzw
 dp4 o0.y, cb0[5].xyzw, r1.xyzw
 dp4 o0.z, cb0[6].xyzw, r1.xyzw
 dp4 o0.w, cb0[7].xyzw, r1.xyzw
 mov o1.xyz, v1.xyzx
 mov o2.xy, v2.xyxx
 ret
 // Approximately 13 instruction slots used

w=1 の代入のために 2命令増えてしまいます。
消費するテンポラリ変数の個数は変わらないので、スレッド数の差は出ていない
と思われます。

ShaderModel1 の nVIDIA 拡張では、dp4 の時に vector 側の最後の成分を
強制的に 1 とみなす dph 命令がありました。もし dph 命令があったら
w=1 代入は不要なので、命令数に差は出ません。

命令の依存関係で見た場合は、mad よりも dp4 の方が直前の命令との相関性が
薄いので並列化では有利に見えます。もし依存関係によるパイプラインストール
が存在しているのなら dp4 の方が良いのかもしれません。
(とはいってもこの例ではバイパスが利くケースなのでストールは限られると
思います)
スレッドによる並列化で、そもそもパイプラインストールが存在しないなら
純粋に命令数が少ない mad の方がきっと速いのでしょう。

また実際はドライバによってさらに GPU 毎にネイティブな命令にコードに
置き換わっているので、このような単純な比較はできないのかもしれません。