前回はレジスタ・ペアをロード/ストアするLDPとSTP命令を練習しました。今回のLDNP/STNP、「表向きの機能」はLDPとSTPとクリソツです。アドレシングモードが少ないことを除けば見分けがつきません。しかしこの命令の真の機能は無駄にキャッシュラインをアサインしないことにあります。確かめるのはムツカシーです。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※実機動作確認には以下を使用しております。
-
- Raspberry Pi 4 model B、Cortex-A72コア(ARMv8-A)
- Raspberry Pi OS (64bit) bullseye
- gcc (Debian 10.2.1-6) 10.2.1 20210110
ARMv8もいろいろレベルがあり、Arm Cortex-A72はARMv8の中でもベーシックな(命令数の少ない)ARMv8p0です。
※A64の最新のマニュアルは以下でダウンロード可能です。
Arm Architecture Reference Manual for A-profile architecture
浮動小数LDNP/STNP
LDNP命令とSTNP命令は、表面的な動作はプリインデックスド/ポストインデックスド・アドレシングを欠いたLDP命令/STP命令にみえます。浮動小数点/SIMD(スカラー扱い)のレジスタ2本を一命令でロードまたはストアする命令です。
この命令の真の意味はLD”N”P/ST”N”Pの”N”に隠されとります。N=Non-temporal pairだそうな。Arm様は「時間局所性がない」と言っておられるようです。平易な言葉に翻訳すれば「使い捨てにするデータだからよ~、キャッシュしないでちょーよ」という感じでしょうか。
アルゴリズム的に同じアドレスのデータを何度か読み取ったり、読み書きしたりする必要のある場合(時間局所性あり)はままあります。そういう場合、キャッシュにデータを残して置くと御利益あり。それが普通。
しかし、みんな大好き音楽や動画の再生といった場合では、一度読んだデータは一期一会でそれっきりであることが多いじゃないかと思います。そのデータのためにキャッシュライン(数の限られた有限な資源です)を割り当ててしまうと、無駄なものに資源を浪費することになってヒット率など落ちることになります。そういうデータだと分かっているなら、キャッシュしないでね、とお願いしておくってもんでしょう。それでLDNP/STNPかと。知らんけど。
キャッシュのヒット、ミスを調べることは不可能ではないですが、とってもメンドイです。いつもの「手抜きでお楽な」ペースじゃできませぬ。そういうことで今回は「表面だけ」さらっとなでて、まあ動かしてみた(本質はみえてないけど)編。
今回実験のアセンブリ言語関数
「いつものように手抜きな」関数プロローグ無、エピローグ無の被テスト関数群が以下に。オペランドのサイズは簡単のため単精度浮動小数1種類に限ってます。前回と異なりインデックスアドレシングもないので、あるアドレスからロードしたデータを別なアドレスにストアするだけのコードです。一応、ロードとストアで別系統の命令でやってます。
.globl fldnpS, fstnpS .text .balign 4 fldnpS: ldnp s0, s1, [x0, #8] stp s0, s1, [x0] ret fstnpS: ldr s0, datalabel_1 ldr s1, datalabel_2 stnp s0, s1, [x0, #16] ret .balign 4 datalabel_1: .single 1.2345e3 datalabel_2: .single 2.2345e3
C言語記述のmain関数
前回同様(コピペともいう)「通り一遍さわるだけの手抜きな」C言語記述のテスト駆動部です。被テスト関数には引数として単精度配列(の先頭アドレス)を与えてます。テスト前後で配列に格納された値をダンプし、被テスト関数実行によるメモリの変化が見えるようにしてあります。
#include <stdio.h> #include <stdint.h> #define MAXMEM (6) float TargetMEM[MAXMEM]; //.globl fldnpS, fstnpS extern void fldnpS(float *); extern void fstnpS(float *); void initTGT() { for (int i=0; i < MAXMEM; i++) { TargetMEM[i] = 1.1111f * i; } } void dumpTGT(const char *arg) { printf("%s\n", arg); for (int i=0; i < MAXMEM; i++) { printf("%d: %f\n", i, TargetMEM[i]); } } int main(void) { initTGT(); dumpTGT("Initial value."); fldnpS(TargetMEM); dumpTGT("LDNP test."); fstnpS(TargetMEM); dumpTGT("STNP test."); return 0; }
ビルドして実行
ロード、ストア命令としては予定通りの動きをしておりますな。キャッシュがどう動いているのはは知らんけど。