プロが教えるわが家の防犯対策術!

今あるソースを書いていますが、計算時間が多くを占めるのか、メモリアクセスが多くを占めるのかを調べたいです。その方法としてアセンブラで記述し、load/store命令、あるいはadd等計算命令をそれぞれ計って足し合わせる、という方法が考えられますが、大きなソースコードだと大変なので、定量的に算出したいと思っています。

例えば、以下のようなコードの
//----- ~ //-----
の部分に対しては、計算に対してメモリアクセスがほとんどを占めるため、計算時間はほぼ0であると考えることで、メモリアクセスの時間を概略計測することはできると思います。しかし、レジスタにデータを置いてひたすら計算してからメモリに戻す、という場合には同手法は使えないと思いますが、そのような場合にはどうやって計測すれば良いでしょうか?


#include <stdio.h>
#define NUM_ITER (1000)
float a[NUM_ITER], b[NUM_ITER], c[NUM_ITER];
int i;
//-----------------------
for(i=0; i<NUM_ITER; i++){
c[i] = a[i] + b[i];
}
//-----------------------

int j;
for(j=0; j<NUM_ITER; j++){
printf("%f\n", c[i]);
}

A 回答 (7件)

> 5×12=60clk


> の誤りでしょうか?

すんません。間違えてました。(^_^;;;
#さいきん、誤字が多くて自分でもまいっています(×o×;)
    • good
    • 0
この回答へのお礼

安心しました、有難うございます。

お礼日時:2009/10/20 01:12

そーいえば、おもしろいツールを思い出しました。


午後ベンチと言われるものですが、これはベンチの他、アクセスに要した
クロック数も確認できます。
因みに私の環境はこーなります。
=======================================
--- GogoWinBench 1.28 [2009/10/18 23:29:03] ---
[OS] Windows NT 6.1 (6.1.7100)
[CPU] AMD Phenom(tm) II X4 940 Processor * 4 / 3010.2MHz
AuthenticAMD
AMD Phenom(tm) II X4 940 Processor
1/0/0/15/4/2
FPU TSC MSR CMOV MMX MMXE SSE SSE2 3DN 3DNE HT(4)
L1CodeCache 64KB, 2Way
L1DataCache 64KB, 2Way, 89732.76MB/s, 3clk
L2Cache 512KB, 16Way, 22515.62MB/s, 12clk
Memory 4096MB, 9234.48MB/s, 49clk
[DLL] GOGO DLL ver. 2.39b for only bench (Feb 28 2002)
=======================================
core系、athlon 64以降は大体 IPC限界は2.5程度とらしいので
とりあえず、命令クロック数=1÷2.5×命令数、メモリはローカル変数で
扱うのであればキャッシュに全て入る(規模にもよりますが)として
2次キャッシュアクセスクロック数×アクセス回数で計算してみては
如何でしょうか?

-------------------------------------------
for(i=0; i<NUM_ITER; i++){
c[i] = a[i] + b[i]; → メモリアクセス3回、命令1回(計算)
} → メモリアクセス2回、命令3回(計算、判断、分岐)
-------------------------------------------
アセンブリコード見ていないので、適当なんですが、上記のような
アクセスに分類されるとすれば、1ループで起こるメモリアクセスは5回、
命令は4回、でそれぞれ必要なクロック数は以下のようになるのでは
ないでしょうか?

メモリ(キャッシュ)アクセス時間:5×14=70clk
命令実行時間         :4÷2.5=1.6clk

命令:メモリ(キャッシュ)アクセス時間=1:43.75

# L1データキャッシュは3clkなのでもっと速いんですが、64kBの2wayってことは
# 32kB×2ってことで、そこにユーザプログラムがのっかることってないような気がするので
# 考えないことにしました。(^^;

IPCが2.5っていうのは限界値であって、もっと低いかも?
詳しく乗っているサイトもなかったので2.5をそのまま採用しましたが、
いずれにしろ、メモリアクセスが例えキャッシュに乗っていても
それなりにかかるのは見えたかと思います。

尚、ローカル変数であれば、その領域はスタック上に取得されるため、
比較的キャッシュに乗りやすいところであると考えていますが、
allocによる取得の場合は少なくともスタック上とは別のエリア
ですので、キャッシュに乗らない可能性があります。
その場合はまぁメモリ応答時間もふまえて計算してみては
如何でしょうか?

尚、キャッシュの応答時間はCPUによって異なるかと思いますので
ご自身でも試されてみるといいかと思います。
#intelだと、キャッシュは若干速かったような気もします。
    • good
    • 0
この回答へのお礼

追加アドバイス有難う御座います。これもかなり参考になります。
一応確認なんですが、おっしゃるツールの表示結果で、
>L2Cache 512KB, 16Way, 22515.62MB/s, 12clk
とあります、あら、
>メモリ(キャッシュ)アクセス時間:5×14=70clk
の部分は、
5×12=60clk
の誤りでしょうか? 御指摘頂いたのにこちらから逆指摘するのは失礼な気もしますが、せっかく頂いたアドバイスを今後に役立てたいので、しっかり理解しておきたいのです。
お時間あるときで構いませんのでご回答頂けると幸いです。

お礼日時:2009/10/19 09:26

> //--- ~ ---//の計算時間


> = 計算時間 + メモリアクセス時間 + 分岐処理時間
>
> となると思いますが、分岐処理時間はNUM_ITERを大きくすれば
> 無視できると思います。あとは計算時間とメモリアクセス時間を
> 分離したいということです(それぞれの時間を概算でいいので出したい)

「 =(計算時間 + メモリアクセス時間 + 分岐処理時間)×NUM_ITER」でしょうから
分岐処理時間は特に小さくなったりしませんよ。
分岐処理=カウンタアップ+条件付きジャンプですので、カウンタが
メモリから取得なのかレジスタから取得かで大幅に実行時間が異なります。

レジスタ、キャッシュメモリ、メインメモリに対するウェイトはそれぞれ
0,10~30,100クロック以上(何となく、感覚的に)ですが、全てローカル変数と
して扱うのであれば キャッシュメモリへのアクセスとして考えればいいのでは
ないでしょうか?

計算の定義をint数値同士の足し算をいうのであれば 演算はパイプライン効果により
(理想的とはなりますが)1クロックと定義できるのではないでしょうか?
メモリアクセスの定義を2つの計算要素の取得及び結果の格納で3回と定義するなら
全てキャッシュへのアクセスであると期待しても100クロック程度かと思います。
で、分岐処理は30+1クロック(適当に30とします)と考えます。
非常に大まかで少々適当な感じもしますが、私なら1:100:30の実行時間割合
と考え、実行に要した時間から上記比率で時間を割り出してみます。
    • good
    • 0
この回答へのお礼

>分岐処理時間は特に小さくなったりしませんよ
おっしゃる通りですね。すいません。
for(i=0; i<10; i++){
MyFunc(); //MyFuncは何もしない空の関数
}
をアセンブラに直せば
xor ebx, ebx
@4 call _MyFunc
inc ebx
cmp ebx, 10
jl short @4
のようになるでしょうから、おっしゃる通りだと思います、御指摘有難う御座います。

あと、アドバイスはかなり参考になります。有難う御座いました。

お礼日時:2009/10/19 09:18

 組み込みマイコンレベルでの話をします。


 空いているポートにHやLを書き込む命令を追加し、オシロスコープで波形観測をすれば計測できます。
 ルネサスのH8マイコンは、システム・クロックでカウントする16ビットのカウンタが有ります。
 これを読めば経過時間が分かります。16ビットではオーバーフローするならば、システムクロックを分周したクロックで動作する内部カウンタを使うなどすれば、オシロスコープによらずカウンタ値の読み込みで計測出来ます。
 
    • good
    • 0
この回答へのお礼

有難う御座います。
実は、私、H8/3048で組込みプログラムを組んだことがあるので(というか、H8/3048ボード自体を所有しています)、ITUを動かしてカウンタからの出力ピンの出力をオシロでとれば計測できることは一応理解できております。
ただ、今回はPCのメモリアクセス時間を調べたいと思っておりますので、厳しいかなと。

とはいえ、参考になる意見、感謝致します。

お礼日時:2009/10/16 22:52

単純な方法は無いと思います。


今のパソコンのメモリは1次キャッシュ、2次キャッシュ、メインメモリ、仮想記憶のような階層構造になっています。
1次キャッシュは大抵はCPUに内蔵されていて高速ですが容量は少ないです。
2次キャッシュ、メインメモリ、仮想記憶の順に低速で大容量になります。
人間にたとえると1次キャッシュは頭の中の記憶、2次キャッシュは机の上のメモ、メインメモリは本棚の書類、仮想記憶は図書館の本のようなものです。

小さいプログラム、小さいデータでは全てがキャッシュに収まるのでメインメモリへのアクセスがなくなるので高速で動作しますが
プログラムやデータサイズが大きくなるとキャッシュの内容をメインメモリと入れ替える必要があり動作が遅くなります。
さらに大きくなると仮想記憶との入れ替えが必要になり動作が極めて遅くなります。
プログラムやデータのサイズが徐々に大きくなるとある値を超えた時に急激に遅くなることがあるのです。
このある値というのは環境で変わりますのでどういう環境で測定するかを指定しないと意味がありません。
同一のパソコンでメインメモリのスピードの違うもので実際のプログラムで比較をすればメモリアクセスの影響を見ることは出来るでしょう。

なお最近の高性能のCPUではアセンブラの記述だけでは動作を予測することは出来ません。
参考URLを見てください。

参考URL:http://d.hatena.ne.jp/hyoshiok/20070916#p1
    • good
    • 0
この回答へのお礼

有難う御座います。紹介していただいたURL拝見しました。
最後の方で
>というようなことで昨今のプロセッサではメモリのコピーと言う非常に単純な操作ですら、外部からは命令の実行順を保障した形では観測できないのである。シングルプロセッサでは、そーゆめんどうな事は、通常は観測できない。しかし、マルチコアになると、あるコアで書き込んだものを別のコアで利用するなんていうことが日常茶飯事で発生するのであるが、それについては別途なにがしかの方法が必要になってくるのである。

というところですね。
一応、自分並列処理をかじっているもので、out-of-order実行が絡みますから単純には予測できないですし、(紹介URLにもある通り)実行回毎に動作が変わることも理解できます。しかし、平均をとればどうでしょう?上記コードを何回か実行して、その平均をとれば動作の詳細はわからずとも、計算をfloatからintにすれば、かなり実際のメモリアクセス時間に近い数字は算出できると思います。
また、キャッシュの影響は曲者ですが、キャッシュに乗り切らないような大量のデータ(つまりスワップが頻繁に起こるような、ある程度の大きさのデータ)に対して、int和にした上記コードを何回か実行してその平均をとれば、プロセッサ~メインメモリのアクセス時間がとれる、と思います。

見当違いであれば、御指摘をお願いしたく、お暇なときでもご意見頂けると幸いです。

お礼日時:2009/10/16 14:04

試してみて申し訳ないのですが、私的には浮動小数点の計算って結構


重いのではないか?と思います。
目的のプログラムとはそれてしまうと思いますが、a[],b[],c[]を
int型に変更し、実行時間を比較されてはどうでしょうか?

尚、レジスタの話題が出ていますが、レジスタは決まった、数少ない
int型の変数のようなもので、配列みたいに要素を設定し使用することは
できません。
#(レジスタ+要素)でメモリアクセスを行うことは普通にあります。(CPUによるかもしれませんが)

a[],b[],c[]が扱う数値について、小数点を含む実数であっても
たとえば小数点第2位までしか使用しない、って決まっているのであれば
計算中は全て100倍し、最後の最後で浮動小数点にすると(私の感覚では)
劇的に速くなるかと思います。
もちろん、オーバーフローしないのが大前提ですが。
    • good
    • 0
この回答へのお礼

有難う御座います。
御指摘の通り、float同士の和だと計算時間が多くを占める気がするので、intの方がいいな、と考えを改めました。

とりあえず、上記の計算を速くする、ということが目的なのではなく、計算時間と、メモリアクセスの時間と分離したいのです。

つまり、
//--- ~ ---//の計算時間
= 計算時間 + メモリアクセス時間 + 分岐処理時間

となると思いますが、分岐処理時間はNUM_ITERを大きくすれば無視できると思います。あとは計算時間とメモリアクセス時間を分離したいということです(それぞれの時間を概算でいいので出したい)

お礼日時:2009/10/16 14:10

開発環境による依存度が違うので厳密な測定ができるかは疑問だけど、


registerの指定子を使用するか、
インラインアセンブラでダイレクトにレジスタ制御処理を記載するとか。

尚、
>例えば、以下のようなコードの
>計算に対してメモリアクセスがほとんどを占めるため、計算時間はほぼ0であると考えることで
さて、どうでしょう?
ダイレクトにメモリ間の計算をしているのでしょうか?
一度該当メモリの内容をレジスタに投入してレジスタで計算したりはしていないでしょうか?
これは、環境依存が大きすぎでどちらとも言えませんよ。
    • good
    • 0
この回答へのお礼

回答有難う御座います。厳密な測定は期待しておらず、とりあえず概略の計測ができれば良いです。アドバイスの部分は参考になります、有難うございます。

なお、ご指摘の部分に関してはコンパイラによって実行コードが変わる可能性があるのは承知しています。その際はvolatileをつけたり、最終的にはgcc -sでアセンブラで吐き出してチェックすることもできると思いますし、a, b, cは全てメモリに乗っているものとしてお考えいただけると幸いです(説明足らずですいません)
NUM_ITERが極めて大きければ、a, b, cがレジスタに乗るはずがないので、メモリから逐一レジスタにロードしていると思うんですが…、そこはアセンブラで確認することにします。

お礼日時:2009/10/15 22:31

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!