PS3でTLBレイテンシを計測

Handbookによると、PPEのTLBは1024エントリもあって、4MB分。で、512KBのL2キャッシュの8倍もの領域をカバーする。(なので、キャッシュに乗らない影響なのかTLBが足りない影響なのかがわかりにくいのだが…)

とりあえず3000ページほど読ませたところ↓このぐらい。(時間はmftbの値を40倍している。PS3では+-1ぐらいの誤差でサイクル数と同じになるはず)

$ ./a.out 3000 3010 0 # hugetlb無し
3000: 590.528125
3001: 591.494606
3002: 590.975053
3003: 591.010071
3004: 591.499888
3005: 591.094205
3006: 591.120480
3007: 590.863311
3008: 591.832548
3009: 591.158010
$ ./a.out 3000 3010 1 # hugetlb有り
3000: 444.871432
3001: 444.612902
3002: 444.610143
3003: 444.606500
3004: 444.732154
3005: 444.541714
3006: 444.599772
3007: 444.594319
3008: 444.619245
3009: 444.625589

TLBミスった時のペナルティは150cycleぐらいか…

ついでにSPEも測っとこう。

$ ./tlb 1 16 0 1 # hugetlb無し
0001: 595.939941
0002: 577.701416
0003: 571.557617
0004: 570.562744
0005: 569.965332
0006: 569.510905
0007: 569.212123
0008: 569.064026
0009: 569.189996
0010: 568.953369
0011: 568.426403
0012: 568.741455
0013: 568.719764
0014: 568.782610
0015: 568.745280

$ ./tlb 1 16 1 1 # hugetlb有り
0001: 572.163086
0002: 558.167725
0003: 553.348796
0004: 550.966187
0005: 549.134766
0006: 548.186442
0007: 547.724609
0008: 546.977539
0009: 546.493327
0010: 545.870850
0011: 545.509588
0012: 545.153402
0013: 545.004132
0014: 544.826486
0015: 544.730143

TLBが当たってるときは、540ぐらい。この時点で違いがあるのはなんで?

SPEのTLBは256個なので、512個ぐらいアクセスして、

$ ./tlb 512 520 0 1  # hugetlb無し
0512: 1040.265656
0513: 1038.673246
0514: 1038.606290
0515: 1039.193871
0516: 1040.109163
0517: 1038.715320
0518: 1038.623914
0519: 1039.168322
$ ./tlb 512 520 1 1  # hugetlb有り
0512: 544.081879
0513: 543.906022
0514: 543.887935
0515: 543.974894
0516: 543.947584
0517: 543.920303
0518: 543.921785
0519: 543.926195

こんな感じ。ほぼ倍だなー。


特に結論は無いが、所感を書いておくと…

  • x86はページテーブルがL2にキャッシュされるのでTLBミスってもペナルティは20cycle程度になる。
  • PPCは多分ページテーブルがキャッシュされないので、ペナルティ150cycleぐらいになる。
  • (上のはPPEだが、多分、PowerPC全体的にそうなってんじゃないかという気がする。根拠は無い)
  • そのかわりPPCに比べるとx86はTLBエントリの数が少ない。あとPhoenomのTLBエラッタは多分ここらへんの複雑さが原因なんかも。
  • PPEはキャッシュ少ないのでTLBミスよりキャッシュミスのほうが影響大きいが、キャッシュが数MBぐらいに大きいCPUだと、アプリケーションによっては全体で数十%ぐらい変わる可能性はあるかも。

コードは以下

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <ppu_intrinsics.h>
#include <libspe2.h>

#ifdef __x86__
static unsigned int
rdtsc()
{
	int a, d;
	asm volatile ("rdtsc"
		      :"=a"(a), "=d"(d));
	return a;
}
#else
static unsigned int
rdtsc()
{
	return __mftb()*40;
}
#endif

char *
alloc16M(int huge)
{
	if (! huge) {
		return malloc(4096*4096);
	} else {
		int fd = open("/hugetlb/16M", O_CREAT|O_RDWR, 0755);
		char *ptr;
		ptr = mmap(0, 4096*4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
		unlink("/hugetlb/16M");
		return ptr;
	}
}

int
main(int argc, char **argv)
{
	int begin = atoi(argv[1]);
	int end = atoi(argv[2]);
	int huge = atoi(argv[3]);
	int spu = atoi(argv[4]);

	char *data = alloc16M(huge);
	int i, j, k;

	memset(data, 0, 4096*4096);

	if (spu) {
		unsigned int mbox_data;
		spe_context_ptr_t spe = spe_context_create(0,0);
		spe_program_handle_t *h;
		unsigned int entry = SPE_DEFAULT_ENTRY;

		mbox_data = begin;
		spe_in_mbox_write(spe, &mbox_data, 1, SPE_MBOX_ALL_BLOCKING);
		mbox_data = end;
		spe_in_mbox_write(spe, &mbox_data, 1, SPE_MBOX_ALL_BLOCKING);

		h = spe_image_open("tlb-spu");
		spe_program_load(spe, h);

		spe_context_run(spe, &entry, 0, data, NULL, NULL);
	} else {
		for (i=begin; i<end; i+=1) {
			int l;
			int x = rdtsc(), y;
			for (l=0; l<1024; l++) {
				for (j=0; j<i; j++) {
					if (1) {
						char c = data[j*4096+(j*64)%4096];
						asm("#"::"r"(c));
					} else {
						data[j*4096+(j*64)%4096] = 0;
					}
				}
			}
			y = rdtsc();
			printf("%04d: %f\n", i, (y-x)/(double)(l*i));
		}
	}

	return 0;
}
#include <spu_mfcio.h>

typedef unsigned long long ea_t;
static unsigned char ls_buffer[128] __attribute__((aligned(128)));

int main(unsigned long long spe,
	 unsigned long long argv)
{
	ea_t buffer = argv;
	int begin = spu_read_in_mbox();
	int end = spu_read_in_mbox();
	int i,j;

	spu_write_decrementer(~0);

	for (i=begin; i<end; i++) {
		int l;
		int x = spu_read_decrementer()*40, y;

		for (l=0; l<1024; l++) {
			for (j=0; j<i; j++) {
				ea_t addr = buffer + j*4096;

				mfc_get(ls_buffer, addr, 128, 0, 0, 0);

				mfc_write_tag_mask(~0);
				mfc_read_tag_status_all();
			}
		}

		y = spu_read_decrementer()*40;

		printf("%04d: %f\n", i, (x-y)/(double)(l*i));
	}

	return 0;
}