サイクルタイムを測定しよう (7) [PSoC]
サイクルタイムを測定した結果、プログラムの配置や分岐先のアドレスによって実行時間が異なっている事がわかりました。 今回は、もっと詳しくデータをとります。
実験に使ったソフトウェア
これまでと同じように関数に "nop" 命令を並べて、プログラムの配置位置を変更します。
// Measure the execution cycle time uint32 measure(reg32 *reg) __attribute__((aligned(256))); uint32 measure(reg32 *reg) { uint32 s; uint32 e; asm( "nop\n" // 10 "nop\n" // 9 "nop\n" // 8 "nop\n" // 7 "nop\n" // 6 "nop\n" // 5 "nop\n" // 4 "nop\n" // 3 "nop\n" // 2 "nop\n" // 1 "label_1:\n" ); s = *reg; asm( "b label_2\n" "nop\n" // 10 "nop\n" // 9 "nop\n" // 8 "nop\n" // 7 "nop\n" // 6 "nop\n" // 5 "nop\n" // 4 "nop\n" // 3 "nop\n" // 2 "nop\n" // 1 "label_2:\n" ); e = *reg; return e - s; }
前半のアセンブラ表記で "nop" の数を変えると、分岐命令の配置アドレスが2バイト単位で変えられます。 さらに、後半のアセンブラ表記で "nop" の数を変えると、分岐先アドレスを2バイト単位で変えられます。 分岐先のアドレスは、分岐命令のアドレスにも依存します。 "nop" の数を変えながらサイクル数を測定し、分岐命令の位置と分岐先アドレスによってで測定されたサイクル数を並べると、以下の表のようになります。
分岐命令 | ||||||||||||
02 | 04 | 06 | 08 | 0A | 0C | 0E | 10 | 12 | 14 | 16 | ||
分岐先 | 04 | 7/7/7 | ||||||||||
06 | 7/7/7 | 7/7/7 | ||||||||||
08 | 7/7/7 | 7/7/7 | 7/7/7 | |||||||||
0A | 7/7/7 | 7/7/7 | 7/7/7 | 7/7/7 | ||||||||
0C | 7/7/7 | 7/7/7 | 7/7/7 | 7/7/7 | 9/8/7 | |||||||
0E | 8/7/7 | 8/7/7 | 8/7/7 | 8/7/7 | 9/8/7 | 9/8/7 | ||||||
10 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 7/7/7 | 7/7/7 | 7/7/7 | |||||
12 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 7/7/7 | 7/7/7 | 7/7/7 | 7/7/7 | ||||
14 | A/8/7 | A/8/7 | A/8/7 | A/8/7 | 7/7/7 | 7/7/7 | 7/7/7 | 7/7/7 | 9/8/7 | |||
16 | B/9/7 | B/9/7 | B/9/7 | B/9/7 | 8/7/7 | 8/7/7 | 8/7/7 | 8/7/7 | 9/8/7 | 9/8/7 | ||
18 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 7/7/7 | 7/7/7 | 7/7/7 | |
1A | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 7/7/7 | 7/7/7 | 7/7/7 | ||
1C | A/8/7 | A/8/7 | A/8/7 | A/8/7 | A/8/7 | A/8/7 | 7/7/7 | 7/7/7 | 7/7/7 | |||
1E | B/9/7 | B/9/7 | B/9/7 | B/9/7 | B/9/7 | 8/7/7 | 8/7/7 | 8/7/7 | ||||
20 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | |||||
22 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | 9/8/7 | ||||||
24 | A/8/7 | A/8/7 | A/8/7 | A/8/7 | A/8/7 | |||||||
26 | B/9/7 | B/9/7 | B/9/7 | B/9/7 | ||||||||
28 | 9/8/7 | 9/8/7 | 9/8/7 | |||||||||
2A | 9/8/7 | 9/8/7 | ||||||||||
2C | A/8/7 |
この表からは、以下の事がわかります。
- 分岐先が分岐命令に近く、アドレスの下3ビットが 000 から 100 である場合、サイクル数は最小を保ちます。 これは、追加でプリフェッチを必要とするためです。
- アドレスの下3ビットが 110 であるアドレスに分岐する場合、どの条件であってもコストが最大になります。 これは、分岐先の8バイトブロックに続いて次の8バイトブロックもプリフェッチする必要があるためと考えられます。
- その次にコストが高いのは、アドレスの下3ビットが 100 である場合です。 プリフェッチのタイミングを1クロックだけ遅らせる事ができるので、一回分のプリフェッチが見えなくなるためと考えられます。
- 分岐先が遠い場合、分岐命令のアドレスとは無関係に、分岐先のアドレスに依存したサイクル数を要します。 これにより、純粋にプリフェッチの時間が見えているのだとわかります。
以上の考察より、サイクル数を減らしたければ、分岐先のアドレスが8バイトブロックの前半になるように配置を考える必要があります。 関数内で分岐先アドレスを制御するのは困難ですが、せめて関数の入り口アドレスを8バイト境界の前半に配置するようにオプションを付けると処理時間が短くなりそうです。
コメント 0