前提・実現したいこと
C言語で配列へのアクセス順序による処理速度の違いを調べるために、2次配列のコピーを行うコード内のfor文のi,jを入れ替えたものをそれぞれ実行し、timeコマンドで実行時間を調べ、格納されている場所が近いほどキャッシュの恩恵を受けられて実行速度は速くなるということは分かったのですが、今度は用意する配列の要素数を1ずつ増やして実行してみると、実行時間は増やす前と比べて速くなりました。一見参照する数が増えた分遅くなりそうな気がするのですが、処理速度が速くなるのはなぜでしょうか?
該当のソースコード
【C言語】
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #define N 32768 char a[N+1][N+1]; //ここがa[N][N]の時と実行時間の比較 char b[N+1][N+1]; //ここがb[N][N]の時と実行時間の比較 main(){ int i, j; for (j = 0; j < N; j++) for (i = 0; i < N; i++) b[i][j] = a[i][j]; return 0; }
速さの違いとはどの程度でしょう?ミリ秒単位であれば誤差の世界です。
コンパイラの最適化オプションや測定時の環境で実行時間は変わりますので、
それらの環境も記載ください。
すいません!
[N][N]の時で15.77秒
[N+1][N+1]の時で10.82秒
と大きな誤差でした。
ちなみに
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
の場合は圧倒的に速く2.11秒で終わりました。
初心者の為、最適化オプション?などはよくわからないのですが、結果はこうなると保証されている中で理由を考える問題でした。ヒントは「メモリ番地」とのことで、自分でよく考えてみたのですが、結局分からなかったので質問しました。
メモリ配置上jは連続しているので、ポインタのインクリメントで次を参照出来るので早くなります。場合によっては専用命令に置き換わります。また、連続したメモリアクセスをしたほうが、よりキャッシュに入り易くなります。
本当に細かくつめるなら、他の方が言ってるように平均を取ったり、メモリアライメントを揃えたり、出力されたアセンブリコードまで確認するくらいまでやるべきです。
それは[0][0],[0][1],[0][2]…と参照していく場合(内側for文がj)と[0][0],[1][0],[2][0]…と参照していく場合(内側for文がi)を比較したときに出る差の原理だと認識していたのですが、今回のような[0][0],[1][0],[2][0]…と参照していく(内側for文がi)という条件は一緒で、最初に定義する配列の要素数を上記のように増やした場合についてはどうでしょうか? 理解せず変なことを言っていたらすいません。。
要素数増やして早くなるのはちょっと理由判らないですね。平均とっても早い感じです?
<[N][N]の場合1>
$ /usr/bin/time ./a.out
16.27user 0.04system 0:16.33elapsed 99%CPU (0avgtext+0avgdata 1051032maxresident)k
0inputs+0outputs (0major+1681minor)pagefaults 0swaps
<[N][N]の場合2>
$ /usr/bin/time ./a.out
16.05user 0.05system 0:16.10elapsed 99%CPU (0avgtext+0avgdata 1051032maxresident)k
0inputs+0outputs (0major+1682minor)pagefaults 0swaps
<[N+1][N+1]の場合1>
$ /usr/bin/time ./a.out
11.09user 0.05system 0:11.14elapsed 99%CPU (0avgtext+0avgdata 1051032maxresident)k
0inputs+0outputs (0major+1705minor)pagefaults 0swaps
<[N+1][N+1]の場合2>
$ /usr/bin/time ./a.out
10.95user 0.05system 0:11.01elapsed 99%CPU (0avgtext+0avgdata 1051032maxresident)k
0inputs+0outputs (0major+1705minor)pagefaults 0swaps
何回も実行したところ、やはり[N+1][N+1]の方が5秒ほど速いという結果は変わりませんでした。。
ページサイズとか割り当てメモリのアラインメントが関係あるとして、今はN=32768でページサイズの等倍ですが、N=32769とかにして、NとN+1の計測をすると状況が変わったりしませんでしょうか。
<[N][N]でN=32768の場合>
$ cat n.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define N 32768
char a[N][N];
char b[N][N];
main()
{
int i, j;
for (j = 0; j < N; j++)
for (i = 0; i < N; i++)
b[i][j] = a[i][j];
return 0;
}
$cc n.c
$ /usr/bin/time ./a.out
15.43user 0.05system 0:15.48elapsed 99%CPU (0avgtext+0avgdata 1051032maxresident
0inputs+0outputs (0major+1681minor)pagefaults 0swaps
<[N][N]でN=32769の場合>
$ cat n.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define N 32769 //32769に変えました
char a[N][N];
char b[N][N];
main()
{
int i, j;
for (j = 0; j < N; j++)
for (i = 0; i < N; i++)
b[i][j] = a[i][j];
return 0;
}
$cc n.c
$ /usr/bin/time ./a.out
10.79user 0.04system 0:10.84elapsed 99%CPU (0avgtext+0avgdata 1051032maxresident
0inputs+0outputs (0major+1714minor)pagefaults 0swaps
<[N+1][N+1]でN=32768の場合>
$ cat n1.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define N 32768
char a[N+1][N+1];
char b[N+1][N+1];
main()
{
int i, j;
for (j = 0; j < N; j++)
for (i = 0; i < N; i++)
b[i][j] = a[i][j];
return 0;
}
$cc n1.c
$ /usr/bin/time ./a.out
10.98user 0.04system 0:11.03elapsed 99%CPU (0avgtext+0avgdata 1051028maxresident
0inputs+0outputs (0major+1705minor)pagefaults 0swaps
<[N+1][N+1]でN=32769の場合>
$ cat n1.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define N 32769 //32769に書き換えた
char a[N+1][N+1];
char b[N+1][N+1];
main()
{
int i, j;
for (j = 0; j < N; j++)
for (i = 0; i < N; i++)
b[i][j] = a[i][j];
return 0;
}
$cc n1.c
$ /usr/bin/time ./a.out
11.00user 0.04system 0:11.05elapsed 99%CPU (0avgtext+0avgdata 1051032maxresident
0inputs+0outputs (0major+1738minor)pagefaults 0swaps
結果は以上のようになりました。書き換えの際間違いがあるといけないのでcatで表示して確認してから実行しました。どうやら配列の要素数が32768の時と32769の時に大きな時間差が出るようで、32770になる時と32769の時の差はないようでした。
思い付きでのコメントですが、CPUキャッシュメモリの問題もあるかもしれませんね。それと、32768と32769は4096を単位とすると境界をまたいでいますが、32769,32770はそうではありません。何か関係ありそうです。(あくまで思い付きです)
なるほど!気づきませんでした。なにか関係ありそうですね。ちょっと調べてみます。
回答2件
あなたの回答
tips
プレビュー