diff --git a/MPI_benchmark/compile-command b/MPI_benchmark/compile-command index 20f26a3cb..a2ed87472 100644 --- a/MPI_benchmark/compile-command +++ b/MPI_benchmark/compile-command @@ -1 +1,2 @@ -mpicxx -fsycl halo_mpi.cc -o halo_mpi \ No newline at end of file +mpicxx -fsycl halo_mpi.cc -o halo_mpi +mpicxx -O2 -std=c++11 io_mpi.cc -o io_mpi diff --git a/MPI_benchmark/io_frontier.slurm b/MPI_benchmark/io_frontier.slurm new file mode 100644 index 000000000..498a4d536 --- /dev/null +++ b/MPI_benchmark/io_frontier.slurm @@ -0,0 +1,95 @@ +#!/bin/bash -l + +# Standalone MPI-only I/O reproducer on Frontier. No Grid, no accelerator, +# so no GCD/NUMA wrapper is needed -- the point of the exercise is that this +# depends on nothing but an MPI installation and a filesystem. +# +# mpicxx -O2 -std=c++11 io_mpi.cc -o io_mpi +# +# Weak scan: the local volume, and therefore the file view structure, is held +# identical at every rung and only the number of Lustre clients changes: +# +# local volume 8.8.32.128 = 262144 sites x 576 B = 151 MB/rank +# file view 32768 contiguous runs of 4608 B per rank, at every rung +# aggregate k=2, row of 16, 8 extents of 18 MB, at every rung +# +# so any change in the relative bandwidth of the two lexicographic paths is a +# property of the client count alone. + +#SBATCH --job-name=ioMPI +#SBATCH --nodes=128 +#SBATCH --ntasks-per-node=8 +#SBATCH --cpus-per-task=7 +#SBATCH --time=02:00:00 +#SBATCH --account=phy157_dwf +#SBATCH --exclusive +#SBATCH --mem=0 + +module load cce/21.0.0 +module load cpe/26.03 + +WORK=/lustre/orion/phy157/proj-shared/phy157_dwf/paboyle/iompi.$SLURM_JOB_ID +mkdir -p $WORK +cd $WORK + +# Do NOT stripe by default. Wide striping is what rescues the collective at +# scale and costs every other path a factor of 1.2-2.2; the default layout is +# what a user gets without knowing to ask. Uncomment to reproduce that +# interaction, and record which one you ran. +#lfs setstripe -c -1 -S 8M $WORK +lfs getstripe -d $WORK + +BIN=$SLURM_SUBMIT_DIR/io_mpi +ARGS="--target 4194304 --reps 3" + +# ROMIO's own view of what it did. Verbose, but the first thing anyone +# reading the report will ask for. +# export MPICH_MPIIO_STATS=1 +# export MPICH_MPIIO_TIMERS=1 + +run () { # run [extra args...] + local nodes=$1 gr=$2 mp=$3 note=$4 + local nranks=$(( nodes * 8 )) + shift 4 + echo + echo "===================================================================" + echo "=== nodes=$nodes ranks=$nranks grid=$gr mpi=$mp $note" + echo "=== extra: $@" + echo "===================================================================" + srun -N$nodes -n$nranks --ntasks-per-node=8 $BIN --grid $gr --mpi $mp $ARGS "$@" + echo "=== exit $?" +} + +##################################################################### +# Phase 0. Correctness, including the non-uniform Alltoallv branch +# (odd process factor in an un-split dimension). Small, and the +# whole-file crc32 is serial, so keep the volume down here. +##################################################################### +run 1 12.12.8.8 2.2.2.1 "correctness, uniform counts" --reps 0 --serial-crc +run 3 24.12.8.8 3.2.2.2 "correctness, NON-uniform counts" --reps 0 --serial-crc +run 4 16.16.16.32 2.2.2.4 "correctness, non-zero offset" --reps 0 --serial-crc --offset 1024 + +##################################################################### +# Phase 1. Weak scan, 151 MB/rank. Timing only. +##################################################################### +run 4 32.32.64.128 4.4.2.1 "4.8 GB" --no-validate +run 8 32.32.64.256 4.4.2.2 "9.7 GB" --no-validate +run 16 32.32.64.512 4.4.2.4 "19.3 GB" --no-validate +run 32 32.32.128.512 4.4.4.4 "38.6 GB" --no-validate +run 64 32.32.128.1024 4.4.4.8 "77.3 GB" --no-validate +run 128 32.32.256.1024 4.4.8.8 "154.6 GB" --no-validate + +##################################################################### +# Phase 2. Answer the two questions a reviewer will ask immediately. +##################################################################### +# Is the gap an artefact of measuring cache rather than the filesystem? +run 128 32.32.256.1024 4.4.8.8 "154.6 GB, durable" --no-validate --fsync --drop-cache +# Does the collective recover if it is given the hints it wants? +run 128 32.32.256.1024 4.4.8.8 "154.6 GB, cb hints" --no-validate \ + --hints romio_cb_write=enable,romio_cb_read=enable,cb_nodes=128,cb_buffer_size=16777216 +# Does the degenerate memory subarray matter? +run 128 32.32.256.1024 4.4.8.8 "154.6 GB, mem subarray" --no-validate --mem-subarray + +echo +echo "=== done. Files left in $WORK" +ls -l $WORK diff --git a/MPI_benchmark/io_mpi.cc b/MPI_benchmark/io_mpi.cc new file mode 100644 index 000000000..29202034d --- /dev/null +++ b/MPI_benchmark/io_mpi.cc @@ -0,0 +1,1017 @@ +/************************************************************************** + * MPI only parallel file I/O reproducer -- no Grid, no accelerator. + * + * Writes an Nd lattice of NWORD-double site objects to a single shared file + * in *global lexicographic* order (dimension 0 fastest), which is the order + * that makes a file independent of the process decomposition that wrote it. + * Three paths produce that file, and are timed and validated against each + * other: + * + * raw each rank writes one disjoint contiguous segment with pwrite. + * NOT lexicographic -- the file depends on the decomposition. + * This is the zero-transposition control: the bandwidth the + * filesystem and the clients can sustain with no reordering. + * + * mpiio MPI_Type_create_subarray + MPI_File_set_view + + * MPI_File_write_all / MPI_File_read_all. The canonical + * two-phase collective. This is the path under test. + * + * aggregate user level two-phase: MPI_Alltoallv within a row + * subcommunicator to give each rank a few large contiguous + * file extents, then independent pwrite / pread of those. + * Produces a byte identical file to mpiio. + * + * Validation. Site content is a deterministic function of the *global* + * lexicographic site index, so a byte that ends up in the wrong place is + * detectable, not merely a checksum difference. Three independent checks: + * + * 1. crc32 per site, combined across sites with rotate-and-xor keyed by + * the global index (so the combination is order independent) and + * reduced with MPI_BXOR. Proves the right sites with the right + * contents are present. Does NOT prove they are in the right place. + * 2. On read, every rank recomputes the expected content of each global + * site it believes it owns and memcmps. Proves placement, in + * parallel, at any volume. Reports the first offending global site. + * 3. Cross validation: write with mpiio, read with aggregate, and vice + * versa. Both paths must agree on the same file, so a shared + * misunderstanding of the layout cannot hide. + * 4. Optionally (--serial-crc) rank 0 streams the whole file and crc32s + * it. Slow, incontrovertible, and the two lexicographic files must + * give the same value. + * + * Build: mpicxx -O2 -std=c++11 io_mpi.cc -o io_mpi + * Run: mpirun -n 32 ./io_mpi --grid 32.32.64.128 --mpi 4.4.2.1 + **************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/************************************************************** + * Globals + **************************************************************/ +static MPI_Comm WorldComm; +static int WorldRank, WorldSize; +static int ShmRank, ShmSize; + +static int Nd = 4; +static int64_t NWORD = 72; /* doubles per site; 72 -> 576 B, a LorentzColourMatrixD */ +static int64_t fobjSize = 72*sizeof(double); + +#define CHECK(ierr) do { \ + int _e = (ierr); \ + if ( _e != MPI_SUCCESS ) { \ + char _s[MPI_MAX_ERROR_STRING]; int _l; \ + MPI_Error_string(_e,_s,&_l); \ + printf("rank %d: MPI error at %s:%d : %s\n",WorldRank,__FILE__,__LINE__,_s); \ + fflush(stdout); MPI_Abort(WorldComm,1); \ + } } while(0) + +#define SYSCHECK(cond,what) do { \ + if ( !(cond) ) { \ + printf("rank %d: %s failed at %s:%d : %s\n",WorldRank,what,__FILE__,__LINE__,strerror(errno)); \ + fflush(stdout); MPI_Abort(WorldComm,1); \ + } } while(0) + +static inline double usecond(void) { + struct timeval tv; gettimeofday(&tv,NULL); + return 1.0e6*tv.tv_sec + 1.0*tv.tv_usec; +} + +/************************************************************** + * crc32 (IEEE 802.3, reflected), table built once. Deliberately + * the slow obvious implementation: this is a correctness tool and + * we would rather it be readable than fast. + **************************************************************/ +static uint32_t crc_table[256]; +static void crc32_init(void) +{ + for(uint32_t n=0;n<256;n++){ + uint32_t c = n; + for(int k=0;k<8;k++) c = (c&1) ? (0xEDB88320u ^ (c>>1)) : (c>>1); + crc_table[n] = c; + } +} +static inline uint32_t crc32_buf(const void *buf,size_t len,uint32_t crc) +{ + const unsigned char *p = (const unsigned char *)buf; + crc = crc ^ 0xFFFFFFFFu; + for(size_t i=0;i>8); + return crc ^ 0xFFFFFFFFu; +} +static inline uint32_t rotl32(uint32_t x,int n){ n &= 31; return n ? ((x<>(32-n))) : x; } + +/************************************************************** + * Lexicographic index <-> coordinate, dimension 0 fastest. + * This is MPI_ORDER_FORTRAN, and it is the file order. + **************************************************************/ +static inline void CoorFromIndex(int64_t *coor,int64_t idx,const int64_t *dims,int nd) +{ + for(int d=0;d exactly + * representable doubles, so byte comparison is well defined. + **************************************************************/ +static inline void FillSite(double *s,uint64_t g) +{ + for(int64_t w=0;w>30; x *= 0xBF58476D1CE4E5B9ull; + x ^= x>>27; x *= 0x94D049BB133111EBull; + x ^= x>>31; + s[w] = (double)(int64_t)(x>>11) * (1.0/9007199254740992.0); + } +} + +/************************************************************** + * Layout. Everything the three paths need to agree on. + **************************************************************/ +struct Layout { + std::vector gLattice, lLattice, psizes, pcoor, gStart; + int64_t lsites, gsites; + uint64_t rawOffset; /* this rank's byte offset for the raw control */ +}; + +static void BuildLayout(Layout &L,const std::vector &g,const std::vector &p) +{ + L.gLattice = g; L.psizes = p; + L.lLattice.resize(Nd); L.pcoor.resize(Nd); L.gStart.resize(Nd); + + /* process coordinate: dimension 0 fastest, matching the lattice order. + Note we do NOT use MPI_Cart_create -- it ranks with the last dimension + fastest, and mixing the two conventions is the classic way to get a + subtly transposed file. */ + int64_t r = WorldRank; + for(int d=0;d 0, since + * B[d] >= lLattice[d] for every d. + * + * so the Alltoallv send buffer is the untouched local array and only the + * per-destination counts are needed. That is the whole trick. + **************************************************************/ +struct AggregationPlan { + int nunsplit, rowsize, rowrank; + int64_t lsites, chunk; + MPI_Comm rowcomm; + std::vector sendcounts, senddispls, recvcounts, recvdispls; /* in sites */ + std::vector scatter; + std::vector extentGsite, extentLocal, extentSites; +}; + +static void BuildAggregationPlan(const Layout &L,uint64_t targetBytes,AggregationPlan &p) +{ + const int64_t *g = &L.gLattice[0], *l = &L.lLattice[0], *ps = &L.psizes[0]; + int64_t lsites = L.lsites; + p.lsites = lsites; + + int k = Nd-1; + for(int trial=1; trial= targetBytes ) { k = trial; break; } + } + p.nunsplit = k; + + std::vector B(Nd), S(Nd); + for(int d=0;d lcoor(Nd), bcoor(Nd), gcoor(Nd); + + /* send side: counts only, buffer stays in local order (monotonicity above) */ + p.sendcounts.assign(p.rowsize,0); + p.senddispls.assign(p.rowsize,0); + for(int64_t Lx=0; Lx source(lsites); + for(int64_t pos=0; pos fill(p.rowsize,0); + for(int64_t pos=0; pos back(p.rowsize,0); + CHECK(MPI_Alltoall(&p.sendcounts[0],1,MPI_INT,&back[0],1,MPI_INT,p.rowcomm)); + for(int c=0;c aggregated (lexicographic order) */ +static void AggregateExchange(AggregationPlan &p,double *iodata,double *aggregated, + MPI_Datatype siteType,int forward,double *t_comm,double *t_perm) +{ + int64_t lsites = p.lsites; + std::vector tmp(lsites*NWORD); + double t0,t1,t2; + + if ( forward ) { + t0 = usecond(); + CHECK(MPI_Alltoallv(iodata, &p.sendcounts[0],&p.senddispls[0],siteType, + &tmp[0],&p.recvcounts[0],&p.recvdispls[0],siteType,p.rowcomm)); + t1 = usecond(); + for(int64_t s=0;s want(NWORD); + for(int64_t i=0;i=0,"open for serial crc"); + const size_t bufsz = 8*1024*1024; + std::vector buf(bufsz); + uint64_t done=0; + while ( done < bytes ) { + size_t want = (size_t)std::min((uint64_t)bufsz,bytes-done); + ssize_t got = pread(fd,&buf[0],want,(off_t)(offset+done)); + SYSCHECK(got==(ssize_t)want,"pread for serial crc"); + crc = crc32_buf(&buf[0],want,crc); + done += want; + } + close(fd); + } + CHECK(MPI_Bcast(&crc,1,MPI_UINT32_T,0,WorldComm)); + return crc; +} + +/************************************************************** + * The three write / read paths. Each returns seconds, measured as the + * maximum over ranks of the interval spanning open..close, with a + * barrier before the clock starts. + **************************************************************/ +static int do_fsync = 0; +static int do_memsub = 0; +static int use_stdio = 0; +static int reuse_plan = 0; +static uint64_t file_offset = 0; +static MPI_Info io_hints = MPI_INFO_NULL; +static int hints_reported = 0; + +/* Grid's bracket, exactly: barrier, start, work, barrier, stop, and quote + the boss rank's own stopwatch. The trailing barrier makes that the + slowest rank plus the barrier itself, which is what BinaryIO.h reports; + an allreduced max of per-rank spans is a slightly different -- and + slightly smaller -- number, and the difference would show up as the two + tools disagreeing for no reason to do with I/O. */ +static double Elapsed(double t0) +{ + CHECK(MPI_Barrier(WorldComm)); + double dt = (usecond()-t0)/1.0e6; + CHECK(MPI_Bcast(&dt,1,MPI_DOUBLE,0,WorldComm)); + return dt; +} + +/************************************************************** + * File sink / source. + * + * Grid writes through std::ofstream (buffered) and this reproducer + * defaults to pwrite (unbuffered). --stdio selects the former, so that + * if the two tools disagree on the POSIX paths the buffering can be + * ruled in or out without editing anything. + **************************************************************/ +struct Sink { int fd; std::ofstream *os; }; +struct Source { int fd; std::ifstream *is; }; + +/* boss creates, everyone else only seeks and writes into a file that + already exists. No rank but 0 ever uses O_CREAT/O_TRUNC or ios::out: + an unsynchronised truncation from a late opener silently erases an + earlier rank's segment. */ +static Sink SinkOpen(const char *file) +{ + Sink s; s.fd = -1; s.os = NULL; + if ( !WorldRank ) { + int fd = open(file,O_WRONLY|O_CREAT,0644); + SYSCHECK(fd>=0,"create"); + close(fd); + } + CHECK(MPI_Barrier(WorldComm)); + if ( use_stdio ) { + s.os = new std::ofstream(file,std::ios::binary|std::ios::out|std::ios::in); + SYSCHECK(s.os->is_open(),"ofstream open for write"); + } else { + s.fd = open(file,O_WRONLY); + SYSCHECK(s.fd>=0,"open for write"); + } + return s; +} +static void SinkWrite(Sink &s,const char *buf,uint64_t bytes,uint64_t off) +{ + if ( s.os ) { + s.os->seekp((std::streamoff)off); + s.os->write(buf,(std::streamsize)bytes); + SYSCHECK(!s.os->fail(),"ofstream write"); + return; + } + uint64_t done=0; + while ( done < bytes ) { + ssize_t w = pwrite(s.fd,buf+done,bytes-done,(off_t)(off+done)); + SYSCHECK(w>0,"pwrite"); + done += (uint64_t)w; + } +} +static void SinkClose(Sink &s,const char *file,uint64_t need) +{ + if ( s.os ) { + s.os->flush(); + if ( do_fsync ) { /* no portable fd from ofstream; reopen to sync */ + int fd = open(file,O_WRONLY); if ( fd>=0 ) { fsync(fd); close(fd); } + } + s.os->close(); delete s.os; s.os = NULL; + } else { + if ( do_fsync ) SYSCHECK(fsync(s.fd)==0,"fsync"); + SYSCHECK(close(s.fd)==0,"close"); + s.fd = -1; + } + CHECK(MPI_Barrier(WorldComm)); /* every extent must be on its way */ + if ( !WorldRank ) { + struct stat sb; + SYSCHECK(stat(file,&sb)==0,"stat"); + if ( (uint64_t)sb.st_size != need ) /* only when a longer record preceded us */ + SYSCHECK(truncate(file,(off_t)need)==0,"truncate"); + } + CHECK(MPI_Barrier(WorldComm)); +} + +static Source SourceOpen(const char *file) +{ + Source s; s.fd = -1; s.is = NULL; + if ( use_stdio ) { + s.is = new std::ifstream(file,std::ios::binary|std::ios::in); + SYSCHECK(s.is->is_open(),"ifstream open for read"); + } else { + s.fd = open(file,O_RDONLY); + SYSCHECK(s.fd>=0,"open for read"); + } + return s; +} +static void SourceRead(Source &s,char *buf,uint64_t bytes,uint64_t off) +{ + if ( s.is ) { + s.is->seekg((std::streamoff)off); + s.is->read(buf,(std::streamsize)bytes); + SYSCHECK(!s.is->fail(),"ifstream read"); + return; + } + uint64_t done=0; + while ( done < bytes ) { + ssize_t r = pread(s.fd,buf+done,bytes-done,(off_t)(off+done)); + SYSCHECK(r>0,"pread"); + done += (uint64_t)r; + } +} +static void SourceClose(Source &s) +{ + if ( s.is ) { s.is->close(); delete s.is; s.is = NULL; } + else { close(s.fd); s.fd = -1; } +} + +/*-------------------- raw: one contiguous segment per rank ----------------*/ +static double WriteRaw(const Layout &L,const double *data,const char *file) +{ + uint64_t bytes = (uint64_t)L.lsites*fobjSize; + CHECK(MPI_Barrier(WorldComm)); + double t0 = usecond(); + Sink s = SinkOpen(file); + SinkWrite(s,(const char *)data,bytes,file_offset+L.rawOffset); + SinkClose(s,file,file_offset+(uint64_t)L.gsites*fobjSize); + return Elapsed(t0); +} +static double ReadRaw(const Layout &L,double *data,const char *file) +{ + uint64_t bytes = (uint64_t)L.lsites*fobjSize; + CHECK(MPI_Barrier(WorldComm)); + double t0 = usecond(); + Source s = SourceOpen(file); + SourceRead(s,(char *)data,bytes,file_offset+L.rawOffset); + SourceClose(s); + return Elapsed(t0); +} + +/*-------------------- mpiio: subarray view + collective -------------------*/ +static void ReportHints(MPI_File fh) +{ + if ( WorldRank || hints_reported ) return; + hints_reported = 1; + MPI_Info info; + if ( MPI_File_get_info(fh,&info) != MPI_SUCCESS ) return; + int nkeys; MPI_Info_get_nkeys(info,&nkeys); + printf("= ROMIO hints actually in force (%d):\n",nkeys); + for(int i=0;i g(Nd),l(Nd),s(Nd),z(Nd,0); + for(int d=0;d aggregated(L.lsites*NWORD); + + if ( write ) { + AggregateExchange(p,data,&aggregated[0],siteType,1,t_comm,t_perm); + Sink s = SinkOpen(file); + for(size_t e=0;e=0 ) { +#ifdef POSIX_FADV_DONTNEED + posix_fadvise(fd,0,0,POSIX_FADV_DONTNEED); +#endif + close(fd); + } + CHECK(MPI_Barrier(WorldComm)); +} + +/************************************************************** + * Command line + **************************************************************/ +static std::string CmdPayload(char **b,char **e,const std::string &opt) +{ + char **itr = std::find(b,e,opt); + if ( itr != e && ++itr != e ) return std::string(*itr); + return std::string(""); +} +static bool CmdExists(char **b,char **e,const std::string &opt){ return std::find(b,e,opt) != e; } +static void CmdIntVector(const std::string &str,std::vector &vec) +{ + vec.resize(0); + std::stringstream ss(str); + long long i; + while ( ss >> i ) { vec.push_back((int64_t)i); if ( ispunct(ss.peek()) ) ss.ignore(); } +} +static void ParseHints(const std::string &str) +{ + if ( str.empty() ) return; + CHECK(MPI_Info_create(&io_hints)); + std::stringstream ss(str); + std::string kv; + while ( std::getline(ss,kv,',') ) { + size_t eq = kv.find('='); + if ( eq == std::string::npos ) continue; + std::string k = kv.substr(0,eq), v = kv.substr(eq+1); + CHECK(MPI_Info_set(io_hints,(char *)k.c_str(),(char *)v.c_str())); + if ( !WorldRank ) printf("= hint %s = %s\n",k.c_str(),v.c_str()); + } +} + +/************************************************************** + * Reporting + **************************************************************/ +struct Samples { std::vector s; }; + +/* MiB/s, dividing by 1024*1024 -- which is what BinaryIO.h computes for + lastPerf.mbytesPerSecond and prints as "MB/s". Quoting true decimal MB/s + here would make this tool read 4.86% faster than Grid on identical work, + and that discrepancy would be blamed on something real. */ +static void Record(Samples &S,double bytes,double secs){ S.s.push_back(bytes/1024.0/1024.0/secs); } + +static void ReportSamples(const char *what,const char *path,const Samples &S) +{ + if ( WorldRank || S.s.empty() ) return; + double best=0, sum=0; + for(size_t i=0;i g,p; + if ( CmdExists(argv,argv+argc,"--grid") ) CmdIntVector(CmdPayload(argv,argv+argc,"--grid"),g); + if ( CmdExists(argv,argv+argc,"--mpi") ) CmdIntVector(CmdPayload(argv,argv+argc,"--mpi"), p); + if ( g.size()==0 || p.size()==0 || g.size()!=p.size() ) { + if ( !WorldRank ) { + printf("usage: io_mpi --grid n1.n2.n3.n4 --mpi p1.p2.p3.p4 [options]\n"); + printf(" --words N doubles per site (default 72, = 576 B)\n"); + printf(" --target BYTES aggregate extent target (default 4194304)\n"); + printf(" --reps N timed repetitions (default 3)\n"); + printf(" --offset BYTES record displacement in the file (default 0)\n"); + printf(" --hints k=v,k=v MPI_Info passed to open and set_view\n"); + printf(" --fsync include fsync / MPI_File_sync in the timed region\n"); + printf(" --drop-cache posix_fadvise(DONTNEED) between write and read\n"); + printf(" --mem-subarray use a degenerate memory subarray, as Grid does\n"); + printf(" --stdio POSIX paths via std::ofstream/ifstream, as Grid does\n"); + printf(" --reuse-plan build the aggregation plan once, outside the timed region\n"); + printf(" --serial-crc rank 0 streams and crc32s each file (slow)\n"); + printf(" --no-validate skip correctness, timing only\n"); + } + MPI_Finalize(); return 0; + } + Nd = (int)g.size(); + + int64_t reps = 3; + uint64_t target = 4*1024*1024; + int validate = !CmdExists(argv,argv+argc,"--no-validate"); + int serialcrc = CmdExists(argv,argv+argc,"--serial-crc"); + int dropcache = CmdExists(argv,argv+argc,"--drop-cache"); + do_fsync = CmdExists(argv,argv+argc,"--fsync"); + do_memsub = CmdExists(argv,argv+argc,"--mem-subarray"); + use_stdio = CmdExists(argv,argv+argc,"--stdio"); + reuse_plan = CmdExists(argv,argv+argc,"--reuse-plan"); + if ( CmdExists(argv,argv+argc,"--words") ) NWORD = atoll(CmdPayload(argv,argv+argc,"--words").c_str()); + if ( CmdExists(argv,argv+argc,"--target") ) target = strtoull(CmdPayload(argv,argv+argc,"--target").c_str(),NULL,0); + if ( CmdExists(argv,argv+argc,"--reps") ) reps = atoll(CmdPayload(argv,argv+argc,"--reps").c_str()); + if ( CmdExists(argv,argv+argc,"--offset") ) file_offset = strtoull(CmdPayload(argv,argv+argc,"--offset").c_str(),NULL,0); + fobjSize = NWORD*sizeof(double); + + Layout L; + BuildLayout(L,g,p); + + { int64_t prod=1; for(int d=0;d gindex(L.lsites); + { std::vector lc(Nd), gc(Nd); + for(int64_t i=0;i data(L.lsites*NWORD), back(L.lsites*NWORD); + for(int64_t i=0;i 0 ) { + if ( !WorldRank ) { printf("\n= PERFORMANCE (%lld reps)\n",(long long)reps); fflush(stdout); } + + Samples wraw,wmpi,wagg,rraw,rmpi,ragg; + double bytes = (double)payload; + double tc=0,tp=0,tb=0; + + for(int64_t n=0;n