// bench.c
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <stdarg.h>
#include <pthread.h>
typedef int (*core_fn)(int index,int count,void* data);
typedef struct {
pthread_t thread;
int index,count;
void *data;
core_fn core;
int rc,trc;
struct timespec ts_start;
double t_begin,t_end;
} run_ncore_t;
static int fatal_error(const char *msg) {
fprintf(stderr,"FATAL_ERROR: %s\n",msg);
exit(1);
}
static double run_ncore_ts(run_ncore_t *prm) {
struct timespec ts[1];
clock_gettime(CLOCK_REALTIME,ts) && fatal_error("clock_gettime");
ts->tv_sec-=prm->ts_start.tv_sec;
if (ts->tv_nsec<prm->ts_start.tv_nsec) {
ts->tv_sec--;
ts->tv_nsec=1000000000-prm->ts_start.tv_nsec+ts->tv_nsec;
} else {
ts->tv_nsec-=prm->ts_start.tv_nsec;
}
return ts->tv_sec+1e-9*ts->tv_nsec;
}
static void* run_ncore_main(void* ctx) {
struct timespec ts;
run_ncore_t *prm=(run_ncore_t*)ctx;
prm->t_begin=run_ncore_ts(prm);
prm->rc=prm->core(prm->index,prm->count,prm->data);
prm->t_end=run_ncore_ts(prm);
return 0;
}
int run_ncore_en_stat=0;
int run_ncore(int count,core_fn core,void *data,double *dt) {
run_ncore_t *cores;
unsigned size,i,nerr;
struct timespec ts[1];
double ts_start, ts_done;
if (count<1) return 1; // invalid argument
size=sizeof(run_ncore_t)*count;
cores=(run_ncore_t*)malloc(size);
if (!cores) return 2; // unable to allocate memory
nerr=0; memset(cores,0,size);
clock_gettime(CLOCK_REALTIME,ts) && fatal_error("clock_gettime");
for(i=0;i<count;i++) {
cores[i].index=i;
cores[i].count=count;
cores[i].data=data;
cores[i].core=core;
cores[i].ts_start=*ts;
cores[i].trc=i>0 ? pthread_create(&cores[i].thread,0,run_ncore_main,&cores[i]) : 0;
if (cores[i].trc) nerr++;
}
ts_start=run_ncore_ts(cores);
run_ncore_main(cores);
for(i=1;i<count;i++) {
void* tret=0;
if (cores[i].trc==0) pthread_join(cores[i].thread,&tret);
}
ts_done=run_ncore_ts(cores);
if (run_ncore_en_stat) {
printf("creating threads dt=%.3fms\n",ts_start*1e3);
printf("working time ts=%.3fms\n",ts_done*1e3);
for(i=0;i<count;i++) {
printf("%d. start=%.3fms done=%.6fms\n",i,
cores[i].t_begin*1e3,cores[i].t_end*1e3);
}
}
free((void*)cores); cores=0;
if (nerr) return 3; // has problems
if (dt) *dt=ts_done;
return 0;
}
typedef struct {
void *buf;
size_t buf_size;
} memtest1_t;
int memtest1_init(memtest1_t *t,size_t buf_size) {
t->buf=malloc(buf_size);
t->buf_size=buf_size;
return t->buf ? 0 : 1;
}
void memtest1_done(memtest1_t *t) {
if (t->buf) { free(t->buf); t->buf=0; }
}
int memtest1_memset(int index,int count,void *data) {
size_t h,t,sz; void *p;
memtest1_t *mt=(memtest1_t*)data;
h=index*mt->buf_size/count; h=(h+63)/64*64;
t=(index+1)*mt->buf_size/count; t=(t+63)/64*64;
p=(void*)((char*)mt->buf+h); sz=t-h;
memset(p,255,sz);
return 0;
}
void my_memset8(void* data,size_t value,size_t size) {
size=(size+7)>>3;
asm ("rep stosq\n\t"
:
: "D"(data), "c"(size), "a"(value)
);
}
int memtest1_memset8(int index,int count,void *data) {
size_t h,t,sz; void *p;
memtest1_t *mt=(memtest1_t*)data;
h=index*mt->buf_size/count; h=(h+63)/64*64;
t=(index+1)*mt->buf_size/count; t=(t+63)/64*64;
p=(void*)((char*)mt->buf+h); sz=t-h;
my_memset8(p,255,sz);
return 0;
}
int memtest1_memmove(int index,int count,void *data) {
size_t h,t,m,sz; void *p1, *p2;
memtest1_t *mt=(memtest1_t*)data;
h=index*mt->buf_size/count; h=(h+63)/64*64;
t=(index+1)*mt->buf_size/count; t=(t+63)/64*64;
m=(h+t)/2;
p1=(void*)((char*)mt->buf+h); sz=m-h;
p2=(void*)((char*)mt->buf+m);
memmove(p1,p2,sz);
return 0;
}
int memtest1_memcpy(int index,int count,void *data) {
size_t h,t,m,sz; void *p1, *p2;
memtest1_t *mt=(memtest1_t*)data;
h=index*mt->buf_size/count; h=(h+63)/64*64;
t=(index+1)*mt->buf_size/count; t=(t+63)/64*64;
m=(h+t)/2;
p1=(void*)((char*)mt->buf+h); sz=m-h;
p2=(void*)((char*)mt->buf+m);
memcpy(p1,p2,sz);
return 0;
}
enum { KB=1024, MB=1024*KB, GB=1024*MB };
void test_write(int n,size_t size,core_fn core,const char* name) {
int i,j; double dt,rate;
memtest1_t mt[1];
memtest1_init(mt,size);
printf("%s %.3fMb\n",name,(double)size/MB);
for(i=0;i<=n;i++) {
run_ncore(i<1?1:i,core,mt,&dt);
rate=size/dt;
if (i==0)printf("\tfirst time one thread %.1fMb/s\n",rate/MB);
else printf("\t%d\t%.1fMb/s\t%.2fms\n",i,rate/MB,dt*1e3);
}
memtest1_done(mt);
}
void test_copy(int n,size_t size,core_fn core,const char* name) {
int i,j; double dt,rate;
memtest1_t mt[1];
memtest1_init(mt,2*size);
printf("%s %.3fMb->%.3fMb\n",name,(double)size/MB,(double)size/MB);
for(i=0;i<=n;i++) {
run_ncore(i<1?1:i,core,mt,&dt);
rate=size/dt;
if (i==0)printf("\tfirst time one thread %.1fMb/s\n",rate/MB);
else printf("\t%d\t%.1fMb/s\t%.2fms\n",i,rate/MB,dt*1e3);
}
memtest1_done(mt);
}
typedef struct {
size_t count;
} test0_t;
int test0_core0(size_t x) {
int i,r=0;
for(i=0;i<1900;i++) r^=i-x;
return r;
}
int test0_core(int index,int count,void* data) {
size_t h,t,i; int r=0;
test0_t *ts=(test0_t*)data;
h=index*ts->count/count;
t=(index+1)*ts->count/count;
for(i=h;i<t;i++) {
r+=test0_core0(i);
}
return r;
}
void test0(int n,size_t count) {
test0_t ts[1]; int i;
double dt,rate,rate1,alpha,k;
ts->count=count;
printf("threading speedup test\n");
for(i=1;i<=n;i++) {
run_ncore(i,test0_core,ts,&dt);
rate=count/dt;
if (i==1) rate1=rate;
k=rate1/rate;
alpha=i>1 ? (k*i-1)/(i-1) : 0;
printf("\t%d\t%.1fMHz/s\t%.2fms\t%.4f\n",i,rate/1000000,dt*1e3,alpha);
}
}
int main(int argc,char** argv) {
int n=8; enum { N=512, M=200000 };
if (argc>1) {
n=atoi(argv[1]);
if (n<1) n=1;
if (n>256) n=256;
}
//run_ncore_en_stat=1;
test0(n,M);
test_write(n,N*MB,memtest1_memset,"memset");
test_write(n,N*MB,memtest1_memset8,"rep stosq");
test_copy(n,N*MB,memtest1_memcpy,"memcpy");
test_copy(n,N*MB,memtest1_memmove,"memmove");
return 0;
}
Ly8gYmVuY2guYwojaW5jbHVkZSA8c3RkaW8uaD4KI2luY2x1ZGUgPHN0cmluZy5oPgojaW5jbHVkZSA8dGltZS5oPgojaW5jbHVkZSA8c3RkbGliLmg+CiNpbmNsdWRlIDxzdGRhcmcuaD4KI2luY2x1ZGUgPHB0aHJlYWQuaD4KCnR5cGVkZWYgaW50ICgqY29yZV9mbikoaW50IGluZGV4LGludCBjb3VudCx2b2lkKiBkYXRhKTsKCnR5cGVkZWYgc3RydWN0IHsKCXB0aHJlYWRfdCB0aHJlYWQ7CglpbnQgaW5kZXgsY291bnQ7Cgl2b2lkICpkYXRhOwoJY29yZV9mbiBjb3JlOwoJaW50IHJjLHRyYzsKCXN0cnVjdCB0aW1lc3BlYyB0c19zdGFydDsKCWRvdWJsZSB0X2JlZ2luLHRfZW5kOwp9IHJ1bl9uY29yZV90OwoKc3RhdGljIGludCBmYXRhbF9lcnJvcihjb25zdCBjaGFyICptc2cpIHsKCWZwcmludGYoc3RkZXJyLCJGQVRBTF9FUlJPUjogJXNcbiIsbXNnKTsKCWV4aXQoMSk7Cn0Kc3RhdGljIGRvdWJsZSBydW5fbmNvcmVfdHMocnVuX25jb3JlX3QgKnBybSkgewoJc3RydWN0IHRpbWVzcGVjIHRzWzFdOwoJY2xvY2tfZ2V0dGltZShDTE9DS19SRUFMVElNRSx0cykgJiYgZmF0YWxfZXJyb3IoImNsb2NrX2dldHRpbWUiKTsKCXRzLT50dl9zZWMtPXBybS0+dHNfc3RhcnQudHZfc2VjOwoJaWYgKHRzLT50dl9uc2VjPHBybS0+dHNfc3RhcnQudHZfbnNlYykgewoJCXRzLT50dl9zZWMtLTsKCQl0cy0+dHZfbnNlYz0xMDAwMDAwMDAwLXBybS0+dHNfc3RhcnQudHZfbnNlYyt0cy0+dHZfbnNlYzsKCX0gZWxzZSB7CgkJdHMtPnR2X25zZWMtPXBybS0+dHNfc3RhcnQudHZfbnNlYzsKCX0KCXJldHVybiB0cy0+dHZfc2VjKzFlLTkqdHMtPnR2X25zZWM7Cn0Kc3RhdGljIHZvaWQqIHJ1bl9uY29yZV9tYWluKHZvaWQqIGN0eCkgewoJc3RydWN0IHRpbWVzcGVjIHRzOwoJcnVuX25jb3JlX3QgKnBybT0ocnVuX25jb3JlX3QqKWN0eDsKCXBybS0+dF9iZWdpbj1ydW5fbmNvcmVfdHMocHJtKTsKCXBybS0+cmM9cHJtLT5jb3JlKHBybS0+aW5kZXgscHJtLT5jb3VudCxwcm0tPmRhdGEpOwoJcHJtLT50X2VuZD1ydW5fbmNvcmVfdHMocHJtKTsKCXJldHVybiAwOwp9CmludCBydW5fbmNvcmVfZW5fc3RhdD0wOwppbnQgcnVuX25jb3JlKGludCBjb3VudCxjb3JlX2ZuIGNvcmUsdm9pZCAqZGF0YSxkb3VibGUgKmR0KSB7CglydW5fbmNvcmVfdCAqY29yZXM7Cgl1bnNpZ25lZCBzaXplLGksbmVycjsKCXN0cnVjdCB0aW1lc3BlYyB0c1sxXTsKCWRvdWJsZSB0c19zdGFydCwgdHNfZG9uZTsKCglpZiAoY291bnQ8MSkgcmV0dXJuIDE7IC8vIGludmFsaWQgYXJndW1lbnQKCXNpemU9c2l6ZW9mKHJ1bl9uY29yZV90KSpjb3VudDsKCWNvcmVzPShydW5fbmNvcmVfdCopbWFsbG9jKHNpemUpOwoJaWYgKCFjb3JlcykgcmV0dXJuIDI7IC8vIHVuYWJsZSB0byBhbGxvY2F0ZSBtZW1vcnkKCW5lcnI9MDsgbWVtc2V0KGNvcmVzLDAsc2l6ZSk7CgljbG9ja19nZXR0aW1lKENMT0NLX1JFQUxUSU1FLHRzKSAmJiBmYXRhbF9lcnJvcigiY2xvY2tfZ2V0dGltZSIpOwoJZm9yKGk9MDtpPGNvdW50O2krKykgewoJCWNvcmVzW2ldLmluZGV4PWk7CgkJY29yZXNbaV0uY291bnQ9Y291bnQ7CgkJY29yZXNbaV0uZGF0YT1kYXRhOwoJCWNvcmVzW2ldLmNvcmU9Y29yZTsKCQljb3Jlc1tpXS50c19zdGFydD0qdHM7CgkJY29yZXNbaV0udHJjPWk+MCA/IHB0aHJlYWRfY3JlYXRlKCZjb3Jlc1tpXS50aHJlYWQsMCxydW5fbmNvcmVfbWFpbiwmY29yZXNbaV0pIDogMDsKCQlpZiAoY29yZXNbaV0udHJjKSBuZXJyKys7Cgl9Cgl0c19zdGFydD1ydW5fbmNvcmVfdHMoY29yZXMpOwoJcnVuX25jb3JlX21haW4oY29yZXMpOwoJZm9yKGk9MTtpPGNvdW50O2krKykgewoJCXZvaWQqIHRyZXQ9MDsKCQlpZiAoY29yZXNbaV0udHJjPT0wKSBwdGhyZWFkX2pvaW4oY29yZXNbaV0udGhyZWFkLCZ0cmV0KTsKCX0KCXRzX2RvbmU9cnVuX25jb3JlX3RzKGNvcmVzKTsKCWlmIChydW5fbmNvcmVfZW5fc3RhdCkgewoJCXByaW50ZigiY3JlYXRpbmcgdGhyZWFkcyBkdD0lLjNmbXNcbiIsdHNfc3RhcnQqMWUzKTsKCQlwcmludGYoIndvcmtpbmcgdGltZSB0cz0lLjNmbXNcbiIsdHNfZG9uZSoxZTMpOwoJCWZvcihpPTA7aTxjb3VudDtpKyspIHsKCQkJcHJpbnRmKCIlZC4gc3RhcnQ9JS4zZm1zIGRvbmU9JS42Zm1zXG4iLGksCgkJCQljb3Jlc1tpXS50X2JlZ2luKjFlMyxjb3Jlc1tpXS50X2VuZCoxZTMpOwoJCX0KCX0KCWZyZWUoKHZvaWQqKWNvcmVzKTsgY29yZXM9MDsKCWlmIChuZXJyKSByZXR1cm4gMzsgLy8gaGFzIHByb2JsZW1zCglpZiAoZHQpICpkdD10c19kb25lOwoJcmV0dXJuIDA7Cn0KCnR5cGVkZWYgc3RydWN0IHsKCXZvaWQgKmJ1ZjsKCXNpemVfdCBidWZfc2l6ZTsKfSBtZW10ZXN0MV90OwoKaW50IG1lbXRlc3QxX2luaXQobWVtdGVzdDFfdCAqdCxzaXplX3QgYnVmX3NpemUpIHsKCXQtPmJ1Zj1tYWxsb2MoYnVmX3NpemUpOwoJdC0+YnVmX3NpemU9YnVmX3NpemU7CglyZXR1cm4gdC0+YnVmID8gMCA6IDE7Cn0Kdm9pZCBtZW10ZXN0MV9kb25lKG1lbXRlc3QxX3QgKnQpIHsKCWlmICh0LT5idWYpIHsgZnJlZSh0LT5idWYpOyB0LT5idWY9MDsgfQp9CgppbnQgbWVtdGVzdDFfbWVtc2V0KGludCBpbmRleCxpbnQgY291bnQsdm9pZCAqZGF0YSkgewoJc2l6ZV90IGgsdCxzejsgdm9pZCAqcDsKCW1lbXRlc3QxX3QgKm10PShtZW10ZXN0MV90KilkYXRhOwoJaD1pbmRleCptdC0+YnVmX3NpemUvY291bnQ7ICAgICBoPShoKzYzKS82NCo2NDsKCXQ9KGluZGV4KzEpKm10LT5idWZfc2l6ZS9jb3VudDsgdD0odCs2MykvNjQqNjQ7CglwPSh2b2lkKikoKGNoYXIqKW10LT5idWYraCk7IHN6PXQtaDsKCW1lbXNldChwLDI1NSxzeik7CglyZXR1cm4gMDsKfQp2b2lkIG15X21lbXNldDgodm9pZCogZGF0YSxzaXplX3QgdmFsdWUsc2l6ZV90IHNpemUpIHsKCXNpemU9KHNpemUrNyk+PjM7Cglhc20gKCJyZXAgc3Rvc3Fcblx0IgoJCToKCQk6ICJEIihkYXRhKSwgImMiKHNpemUpLCAiYSIodmFsdWUpCgkpOwp9CmludCBtZW10ZXN0MV9tZW1zZXQ4KGludCBpbmRleCxpbnQgY291bnQsdm9pZCAqZGF0YSkgewoJc2l6ZV90IGgsdCxzejsgdm9pZCAqcDsKCW1lbXRlc3QxX3QgKm10PShtZW10ZXN0MV90KilkYXRhOwoJaD1pbmRleCptdC0+YnVmX3NpemUvY291bnQ7ICAgICBoPShoKzYzKS82NCo2NDsKCXQ9KGluZGV4KzEpKm10LT5idWZfc2l6ZS9jb3VudDsgdD0odCs2MykvNjQqNjQ7CglwPSh2b2lkKikoKGNoYXIqKW10LT5idWYraCk7IHN6PXQtaDsKCW15X21lbXNldDgocCwyNTUsc3opOwoJcmV0dXJuIDA7Cn0KaW50IG1lbXRlc3QxX21lbW1vdmUoaW50IGluZGV4LGludCBjb3VudCx2b2lkICpkYXRhKSB7CglzaXplX3QgaCx0LG0sc3o7IHZvaWQgKnAxLCAqcDI7CgltZW10ZXN0MV90ICptdD0obWVtdGVzdDFfdCopZGF0YTsKCWg9aW5kZXgqbXQtPmJ1Zl9zaXplL2NvdW50OyAgICAgaD0oaCs2MykvNjQqNjQ7Cgl0PShpbmRleCsxKSptdC0+YnVmX3NpemUvY291bnQ7IHQ9KHQrNjMpLzY0KjY0OwoJbT0oaCt0KS8yOwoJcDE9KHZvaWQqKSgoY2hhciopbXQtPmJ1ZitoKTsgc3o9bS1oOwoJcDI9KHZvaWQqKSgoY2hhciopbXQtPmJ1ZittKTsKCW1lbW1vdmUocDEscDIsc3opOwoJcmV0dXJuIDA7Cn0KaW50IG1lbXRlc3QxX21lbWNweShpbnQgaW5kZXgsaW50IGNvdW50LHZvaWQgKmRhdGEpIHsKCXNpemVfdCBoLHQsbSxzejsgdm9pZCAqcDEsICpwMjsKCW1lbXRlc3QxX3QgKm10PShtZW10ZXN0MV90KilkYXRhOwoJaD1pbmRleCptdC0+YnVmX3NpemUvY291bnQ7ICAgICBoPShoKzYzKS82NCo2NDsKCXQ9KGluZGV4KzEpKm10LT5idWZfc2l6ZS9jb3VudDsgdD0odCs2MykvNjQqNjQ7CgltPShoK3QpLzI7CglwMT0odm9pZCopKChjaGFyKiltdC0+YnVmK2gpOyBzej1tLWg7CglwMj0odm9pZCopKChjaGFyKiltdC0+YnVmK20pOwoJbWVtY3B5KHAxLHAyLHN6KTsKCXJldHVybiAwOwp9CgplbnVtIHsgS0I9MTAyNCwgTUI9MTAyNCpLQiwgR0I9MTAyNCpNQiB9Owp2b2lkIHRlc3Rfd3JpdGUoaW50IG4sc2l6ZV90IHNpemUsY29yZV9mbiBjb3JlLGNvbnN0IGNoYXIqIG5hbWUpIHsKCWludCBpLGo7IGRvdWJsZSBkdCxyYXRlOwoJbWVtdGVzdDFfdCBtdFsxXTsKCW1lbXRlc3QxX2luaXQobXQsc2l6ZSk7CglwcmludGYoIiVzICUuM2ZNYlxuIixuYW1lLChkb3VibGUpc2l6ZS9NQik7Cglmb3IoaT0wO2k8PW47aSsrKSB7CgkJcnVuX25jb3JlKGk8MT8xOmksY29yZSxtdCwmZHQpOwoJCXJhdGU9c2l6ZS9kdDsKCQlpZiAoaT09MClwcmludGYoIlx0Zmlyc3QgdGltZSBvbmUgdGhyZWFkICUuMWZNYi9zXG4iLHJhdGUvTUIpOwoJCWVsc2UgcHJpbnRmKCJcdCVkXHQlLjFmTWIvc1x0JS4yZm1zXG4iLGkscmF0ZS9NQixkdCoxZTMpOwoJfQoJbWVtdGVzdDFfZG9uZShtdCk7Cn0KCnZvaWQgdGVzdF9jb3B5KGludCBuLHNpemVfdCBzaXplLGNvcmVfZm4gY29yZSxjb25zdCBjaGFyKiBuYW1lKSB7CglpbnQgaSxqOyBkb3VibGUgZHQscmF0ZTsKCW1lbXRlc3QxX3QgbXRbMV07CgltZW10ZXN0MV9pbml0KG10LDIqc2l6ZSk7CglwcmludGYoIiVzICUuM2ZNYi0+JS4zZk1iXG4iLG5hbWUsKGRvdWJsZSlzaXplL01CLChkb3VibGUpc2l6ZS9NQik7Cglmb3IoaT0wO2k8PW47aSsrKSB7CgkJcnVuX25jb3JlKGk8MT8xOmksY29yZSxtdCwmZHQpOwoJCXJhdGU9c2l6ZS9kdDsKCQlpZiAoaT09MClwcmludGYoIlx0Zmlyc3QgdGltZSBvbmUgdGhyZWFkICUuMWZNYi9zXG4iLHJhdGUvTUIpOwoJCWVsc2UgcHJpbnRmKCJcdCVkXHQlLjFmTWIvc1x0JS4yZm1zXG4iLGkscmF0ZS9NQixkdCoxZTMpOwoJfQoJbWVtdGVzdDFfZG9uZShtdCk7Cn0KCnR5cGVkZWYgc3RydWN0IHsKCXNpemVfdCBjb3VudDsKfSB0ZXN0MF90OwoKaW50IHRlc3QwX2NvcmUwKHNpemVfdCB4KSB7CglpbnQgaSxyPTA7Cglmb3IoaT0wO2k8MTkwMDtpKyspIHJePWkteDsKCXJldHVybiByOwp9CmludCB0ZXN0MF9jb3JlKGludCBpbmRleCxpbnQgY291bnQsdm9pZCogZGF0YSkgewoJc2l6ZV90IGgsdCxpOyBpbnQgcj0wOwoJdGVzdDBfdCAqdHM9KHRlc3QwX3QqKWRhdGE7CgoJaD1pbmRleCp0cy0+Y291bnQvY291bnQ7Cgl0PShpbmRleCsxKSp0cy0+Y291bnQvY291bnQ7Cglmb3IoaT1oO2k8dDtpKyspIHsKCQlyKz10ZXN0MF9jb3JlMChpKTsKCX0KCXJldHVybiByOwp9Cgp2b2lkIHRlc3QwKGludCBuLHNpemVfdCBjb3VudCkgewoJdGVzdDBfdCB0c1sxXTsgaW50IGk7Cglkb3VibGUgZHQscmF0ZSxyYXRlMSxhbHBoYSxrOwoJdHMtPmNvdW50PWNvdW50OwoJcHJpbnRmKCJ0aHJlYWRpbmcgc3BlZWR1cCB0ZXN0XG4iKTsKCWZvcihpPTE7aTw9bjtpKyspIHsKCQlydW5fbmNvcmUoaSx0ZXN0MF9jb3JlLHRzLCZkdCk7CgkJcmF0ZT1jb3VudC9kdDsKCQlpZiAoaT09MSkgcmF0ZTE9cmF0ZTsKCQlrPXJhdGUxL3JhdGU7CgkJYWxwaGE9aT4xID8gKGsqaS0xKS8oaS0xKSA6IDA7CgkJcHJpbnRmKCJcdCVkXHQlLjFmTUh6L3NcdCUuMmZtc1x0JS40ZlxuIixpLHJhdGUvMTAwMDAwMCxkdCoxZTMsYWxwaGEpOwoJfQp9CgppbnQgbWFpbihpbnQgYXJnYyxjaGFyKiogYXJndikgewoJaW50IG49ODsgZW51bSB7IE49NTEyLCBNPTIwMDAwMCB9OwoJaWYgKGFyZ2M+MSkgewoJCW49YXRvaShhcmd2WzFdKTsKCQlpZiAobjwxKSBuPTE7CgkJaWYgKG4+MjU2KSBuPTI1NjsKCX0KCS8vcnVuX25jb3JlX2VuX3N0YXQ9MTsKCXRlc3QwKG4sTSk7Cgl0ZXN0X3dyaXRlKG4sTipNQixtZW10ZXN0MV9tZW1zZXQsIm1lbXNldCIpOwoJdGVzdF93cml0ZShuLE4qTUIsbWVtdGVzdDFfbWVtc2V0OCwicmVwIHN0b3NxIik7Cgl0ZXN0X2NvcHkobixOKk1CLG1lbXRlc3QxX21lbWNweSwibWVtY3B5Iik7Cgl0ZXN0X2NvcHkobixOKk1CLG1lbXRlc3QxX21lbW1vdmUsIm1lbW1vdmUiKTsKCXJldHVybiAwOwp9Cgo=