From 9b961ae47ec3218f878dd7d7c5fec82885ebcaf0 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Thu, 23 Feb 2017 11:53:45 -0700 Subject: [PATCH 01/20] Rename 'n' dimension to 'w'. Generic interfaces now use TWXYZ indices. --- Makefile | 32 +- docs/YASK-intro.pdf | Bin 634946 -> 634985 bytes src/foldBuilder/stencils/AveStencil.hpp | 13 +- src/realv.hpp | 41 +-- src/realv_grids.cpp | 36 +-- src/realv_grids.hpp | 374 ++++++++++++------------ src/stencil.hpp | 32 +- src/stencil_calc.cpp | 301 +++++++++---------- src/stencil_calc.hpp | 141 ++++----- src/stencil_main.cpp | 9 +- stencil-tuner.pl | 68 +++-- 11 files changed, 529 insertions(+), 518 deletions(-) diff --git a/Makefile b/Makefile index b371f687..8996d0ef 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ mpi = 0 # Defaults based on stencil type. ifeq ($(stencil),) -$(error Stencil not specified; use stencil=iso3dfd, 3axis, 9axis, 3plane, cube, ave, stream, awp, awp_elastic, ssg , fsg or fsg_abc) +$(error Stencil not specified; use stencil=iso3dfd, 3axis, 9axis, 3plane, cube, ave, stream, awp, awp_elastic, ssg, fsg, or fsg_abc) else ifeq ($(stencil),ave) radius ?= 1 @@ -207,8 +207,8 @@ def_thread_divisor ?= 1 real_bytes ?= 4 layout_xyz ?= Layout_123 layout_txyz ?= Layout_2314 -layout_nxyz ?= Layout_1234 -layout_tnxyz ?= Layout_23415 +layout_wxyz ?= Layout_1234 +layout_twxyz ?= Layout_23415 def_rank_size ?= 128 def_block_size ?= 64 def_pad ?= 1 @@ -276,8 +276,8 @@ endif MACROS += REAL_BYTES=$(real_bytes) MACROS += LAYOUT_XYZ=$(layout_xyz) MACROS += LAYOUT_TXYZ=$(layout_txyz) -MACROS += LAYOUT_NXYZ=$(layout_nxyz) -MACROS += LAYOUT_TNXYZ=$(layout_tnxyz) +MACROS += LAYOUT_WXYZ=$(layout_wxyz) +MACROS += LAYOUT_TWXYZ=$(layout_twxyz) MACROS += DEF_RANK_SIZE=$(def_rank_size) MACROS += DEF_BLOCK_SIZE=$(def_block_size) MACROS += DEF_BLOCK_THREADS=$(def_block_threads) @@ -344,19 +344,19 @@ endif # grouped, serpentine, square-wave) may not be used here when # using temporal wavefronts. The time loop may be found # in StencilEquations::calc_rank(). -RANK_LOOP_OPTS = -dims 'dn,dx,dy,dz' -RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop(dn,dx,dy,dz) \ +RANK_LOOP_OPTS = -dims 'dw,dx,dy,dz' +RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop(dw,dx,dy,dz) \ { $(RANK_LOOP_INNER_MODS) calc(region(start_dt, stop_dt, eqGroup_ptr)); } # Region loops break up a region using OpenMP threading into blocks. # The region time loops are not coded here to allow for proper # spatial skewing for temporal wavefronts. The time loop may be found # in StencilEquations::calc_region(). -REGION_LOOP_OPTS = -dims 'rn,rx,ry,rz' \ +REGION_LOOP_OPTS = -dims 'rw,rx,ry,rz' \ -ompConstruct '$(omp_par_for) schedule($(omp_schedule)) proc_bind(spread)' \ -calcPrefix 'eg->calc_' REGION_LOOP_OUTER_MODS ?= grouped omp -REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rn,rx,ry,rz) \ +REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) \ { $(REGION_LOOP_INNER_MODS) calc(block(rt)); } # Block loops break up a block into vector clusters. @@ -364,21 +364,21 @@ REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rn,rx,ry,rz) \ # this is indicated by the 'v' suffix. # The 'omp' modifier creates a nested OpenMP loop. # There is no time loop here because threaded temporal blocking is not yet supported. -BLOCK_LOOP_OPTS = -dims 'bnv,bxv,byv,bzv' \ +BLOCK_LOOP_OPTS = -dims 'bwv,bxv,byv,bzv' \ -ompConstruct '$(omp_par_for) schedule($(omp_block_schedule)) proc_bind(close)' BLOCK_LOOP_INNER_MODS ?= prefetch(L2) BLOCK_LOOP_OUTER_MODS ?= omp -BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bnv,bxv) { loop(byv) \ +BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bwv,bxv) { loop(byv) \ { $(BLOCK_LOOP_INNER_MODS) loop(bzv) { calc(cluster(bt)); } } } # Halo pack/unpack loops break up a region face, edge, or corner into vectors. # The indices at this level are by vector instead of element; # this is indicated by the 'v' suffix. # Nested OpenMP is not used here because there is no sharing between threads. -HALO_LOOP_OPTS = -dims 'nv,xv,yv,zv' \ +HALO_LOOP_OPTS = -dims 'wv,xv,yv,zv' \ -ompConstruct '$(omp_par_for) schedule($(omp_halo_schedule)) proc_bind(spread)' HALO_LOOP_OUTER_MODS ?= omp -HALO_LOOP_CODE ?= $(HALO_LOOP_OUTER_MODS) loop(nv,xv,yv,zv) \ +HALO_LOOP_CODE ?= $(HALO_LOOP_OUTER_MODS) loop(wv,xv,yv,zv) \ $(HALO_LOOP_INNER_MODS) { calc(halo(t)); } # compile with model_cache=1 or 2 to check prefetching. @@ -421,8 +421,8 @@ echo-settings: @echo real_bytes=$(real_bytes) @echo layout_xyz=$(layout_xyz) @echo layout_txyz=$(layout_txyz) - @echo layout_nxyz=$(layout_nxyz) - @echo layout_tnxyz=$(layout_tnxyz) + @echo layout_wxyz=$(layout_wxyz) + @echo layout_twxyz=$(layout_twxyz) @echo streaming_stores=$(streaming_stores) @echo def_block_threads=$(def_block_threads) @echo omp_schedule=$(omp_schedule) @@ -519,7 +519,7 @@ clean: rm -fv src/*.[io] *.optrpt src/*.optrpt *.s $(GEN_HEADERS) $(MAKE_REPORT_FILE) realclean: clean - rm -fv stencil*.exe foldBuilder TAGS + rm -fv stencil*.exe foldBuilder TAGS $(MAKE_REPORT_FILE) $(CXXFLAGS_FILE) $(LFLAGS_FILE) find . -name '*~' | xargs -r rm -v help: diff --git a/docs/YASK-intro.pdf b/docs/YASK-intro.pdf index e0df54698102cd8f074789132810d5f11348cb17..5a8e5562f9dacc4e02a44cd25c8a8e85beccab0d 100755 GIT binary patch delta 26392 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 LFiA?P>*4+n5_DUp diff --git a/src/foldBuilder/stencils/AveStencil.hpp b/src/foldBuilder/stencils/AveStencil.hpp index 5fcbe620..4d359749 100644 --- a/src/foldBuilder/stencils/AveStencil.hpp +++ b/src/foldBuilder/stencils/AveStencil.hpp @@ -25,26 +25,27 @@ IN THE SOFTWARE. // Simple stencil that calculates an average of the points in a cube. // Similar to MG_STENCIL_3D27PT heat-transfer stencil from miniGhost benchmark. +// Uses the 'w' dimension to index independent grids. #include "StencilBase.hpp" class AveStencil : public StencilRadiusBase { protected: - Grid multi_grid; // N time-varying 3D grids. + Grid heat; // W time-varying 3D grids. public: AveStencil(StencilList& stencils, int radius=2) : StencilRadiusBase("ave", stencils, radius) { - INIT_GRID_5D(multi_grid, t, n, x, y, z); + INIT_GRID_5D(heat, t, w, x, y, z); } // Define equation for grid n at t as average of - // (2*radius+1)^3 cube of values from grid n at t-1. + // (2*radius+1)^3 cube of values from grid w at t-1. virtual void define(const IntTuple& offsets) { GET_OFFSET(t); - GET_OFFSET(n); + GET_OFFSET(w); GET_OFFSET(x); GET_OFFSET(y); GET_OFFSET(z); @@ -57,7 +58,7 @@ class AveStencil : public StencilRadiusBase { for (int rx = rBegin; rx <= rEnd; rx++) for (int ry = rBegin; ry <= rEnd; ry++) for (int rz = rBegin; rz <= rEnd; rz++) { - v += multi_grid(t, n, x+rx, y+ry, z+rz); + v += heat(t, w, x+rx, y+ry, z+rz); nPts++; } @@ -65,7 +66,7 @@ class AveStencil : public StencilRadiusBase { v *= 1.0 / double(nPts); // define the grid value at t+1 to be equivalent to v. - multi_grid(t+1, n, x, y, z) IS_EQUIV_TO v; + heat(t+1, w, x, y, z) IS_EQUIV_TO v; } }; diff --git a/src/realv.hpp b/src/realv.hpp index 432c71a6..fbf2f5d8 100644 --- a/src/realv.hpp +++ b/src/realv.hpp @@ -117,8 +117,8 @@ namespace yask { #ifndef VLEN_T #define VLEN_T (1) #endif -#ifndef VLEN_N -#define VLEN_N (1) +#ifndef VLEN_W +#define VLEN_W (1) #endif #ifndef VLEN_X #define VLEN_X (1) @@ -130,7 +130,7 @@ namespace yask { #define VLEN_Z (1) #endif #ifndef VLEN -#define VLEN ((VLEN_T) * (VLEN_N) * (VLEN_X) * (VLEN_Y) * (VLEN_Z)) +#define VLEN ((VLEN_T) * (VLEN_W) * (VLEN_X) * (VLEN_Y) * (VLEN_Z)) #endif #if VLEN_T != 1 @@ -210,7 +210,7 @@ namespace yask { // Type for a vector block. // The union 'u' is a 4-dimensional "folded" vector - // of size VLEN_N * VLEN_X * VLEN_Y * VLEN_Z. + // of size VLEN_W * VLEN_X * VLEN_Y * VLEN_Z. struct real_vec_t { // union of data types. @@ -292,32 +292,33 @@ namespace yask { return u.r[l]; } - // access a real_t by n,x,y,z vector-block indices. - ALWAYS_INLINE const real_t& operator()(idx_t n, idx_t i, idx_t j, idx_t k) const { - assert(n >= 0); - assert(n < VLEN_N); - assert(i >= 0); - assert(i < VLEN_X); - assert(j >= 0); - assert(j < VLEN_Y); - assert(k >= 0); - assert(k < VLEN_Z); + // access a real_t by w,x,y,z element indices. + ALWAYS_INLINE const real_t& operator()(idx_t w, idx_t x, idx_t y, idx_t z) const { + assert(w >= 0); + assert(w < VLEN_W); + assert(x >= 0); + assert(x < VLEN_X); + assert(y >= 0); + assert(y < VLEN_Y); + assert(z >= 0); + assert(z < VLEN_Z); + // TODO: move this to stencil compiler. #if VLEN_FIRST_DIM_IS_UNIT_STRIDE - // n dim is unit stride, followed by x, y, z. - idx_t l = LAYOUT_4321(n, i, j, k, VLEN_N, VLEN_X, VLEN_Y, VLEN_Z); + // w dim is unit stride, followed by x, y, z. + idx_t l = LAYOUT_4321(w, x, y, z, VLEN_W, VLEN_X, VLEN_Y, VLEN_Z); #else - // z dim is unit stride, followed by y, x, n. - idx_t l = LAYOUT_1234(n, i, j, k, VLEN_N, VLEN_X, VLEN_Y, VLEN_Z); + // z dim is unit stride, followed by y, x, w. + idx_t l = LAYOUT_1234(w, x, y, z, VLEN_W, VLEN_X, VLEN_Y, VLEN_Z); #endif return u.r[l]; } - ALWAYS_INLINE real_t& operator()(idx_t n, idx_t i, idx_t j, idx_t k) { + ALWAYS_INLINE real_t& operator()(idx_t w, idx_t x, idx_t y, idx_t z) { const real_vec_t* ct = const_cast(this); - const real_t& cr = (*ct)(n, i, j, k); + const real_t& cr = (*ct)(w, x, y, z); return const_cast(cr); } diff --git a/src/realv_grids.cpp b/src/realv_grids.cpp index 3b0fdd40..88ea97bb 100644 --- a/src/realv_grids.cpp +++ b/src/realv_grids.cpp @@ -46,7 +46,7 @@ namespace yask { // TODO: fix '*'s w/o z dim. os << get_num_dims() << "D ("; if (got_t()) os << "t=" << get_tdim() << " * "; - if (got_n()) os << "n=" << get_dn() << " * "; + if (got_w()) os << "w=" << get_dw() << " * "; if (got_x()) os << "x=" << get_dx() << " * "; if (got_y()) os << "y=" << get_dy() << " * "; if (got_z()) os << "z=" << get_dz(); @@ -76,22 +76,22 @@ namespace yask { errs = 0; for (int ti = 0; ti <= get_tdim(); ti++) { - for (int ni = get_first_n(); ni <= get_last_n(); ni++) { + for (int wi = get_first_w(); wi <= get_last_w(); wi++) { for (int xi = get_first_x(); xi <= get_last_x(); xi++) { for (int yi = get_first_y(); yi <= get_last_y(); yi++) { for (int zi = get_first_z(); zi <= get_last_z(); zi++) { - real_t te = readElem_TNXYZ(ti, ni, xi, yi, zi, __LINE__); - real_t re = ref.readElem_TNXYZ(ti, ni, xi, yi, zi, __LINE__); + real_t te = readElem_TWXYZ(ti, wi, xi, yi, zi, __LINE__); + real_t re = ref.readElem_TWXYZ(ti, wi, xi, yi, zi, __LINE__); if (!within_tolerance(te, re, epsilon)) { errs++; if (errs < maxPrint) { - printElem_TNXYZ(os, "** mismatch", - ti, ni, xi, yi, zi, + printElem_TWXYZ(os, "** mismatch", + ti, wi, xi, yi, zi, te, 0, false); - printElem_TNXYZ(os, " != reference", - ti, ni, xi, yi, zi, + printElem_TWXYZ(os, " != reference", + ti, wi, xi, yi, zi, re, 0, true); } else if (errs == maxPrint) @@ -107,8 +107,8 @@ namespace yask { } // Print one element. - void RealVecGridBase::printElem_TNXYZ(std::ostream& os, const std::string& m, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + void RealVecGridBase::printElem_TWXYZ(std::ostream& os, const std::string& m, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, real_t e, int line, bool newline) const { @@ -118,7 +118,7 @@ namespace yask { os << m << ": "; os << _name << "["; if (got_t()) os << "t=" << t << ", "; - if (got_n()) os << "n=" << n << ", "; + if (got_w()) os << "w=" << w << ", "; if (got_x()) os << "x=" << x << ", "; if (got_y()) os << "y=" << y << ", "; if (got_z()) os << "z=" << z; @@ -131,11 +131,11 @@ namespace yask { // Print one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - void RealVecGridBase::printVecNorm_TNXYZ(std::ostream& os, const std::string& m, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + void RealVecGridBase::printVecNorm_TWXYZ(std::ostream& os, const std::string& m, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, const real_vec_t& v, int line) const { - idx_t n = nv * VLEN_N; + idx_t w = wv * VLEN_W; idx_t x = xv * VLEN_X; idx_t y = yv * VLEN_Y; idx_t z = zv * VLEN_Z; @@ -144,12 +144,12 @@ namespace yask { for (int zi = 0; zi < VLEN_Z; zi++) { for (int yi = 0; yi < VLEN_Y; yi++) { for (int xi = 0; xi < VLEN_X; xi++) { - for (int ni = 0; ni < VLEN_N; ni++) { - real_t e = v(ni, xi, yi, zi); + for (int wi = 0; wi < VLEN_W; wi++) { + real_t e = v(wi, xi, yi, zi); #ifdef CHECK_VEC_ELEMS - real_t e2 = readElem_TNXYZ(t, n+ni, x+xi, y+yi, z+zi, line); + real_t e2 = readElem_TWXYZ(t, w+wi, x+xi, y+yi, z+zi, line); #endif - printElem_TNXYZ(os, m, t, n+ni, x+xi, y+yi, z+zi, e, line); + printElem_TWXYZ(os, m, t, w+wi, x+xi, y+yi, z+zi, e, line); #ifdef CHECK_VEC_ELEMS // compare to per-element read. if (e == e2) diff --git a/src/realv_grids.hpp b/src/realv_grids.hpp index 445fe761..a416fe19 100644 --- a/src/realv_grids.hpp +++ b/src/realv_grids.hpp @@ -51,16 +51,16 @@ namespace yask { RealVecGrid* _gp; // real_t sizes for up to 4 spatial dims. - idx_t _dn=VLEN_N, _dx=VLEN_X, _dy=VLEN_Y, _dz=VLEN_Z; // domain sizes. - idx_t _hn=0, _hx=0, _hy=0, _hz=0; // halo sizes. - idx_t _pn=0, _px=0, _py=0, _pz=0; // halo + extra-pad sizes. - idx_t _on=0, _ox=0, _oy=0, _oz=0; // offsets into global problem domain. + idx_t _dw=VLEN_W, _dx=VLEN_X, _dy=VLEN_Y, _dz=VLEN_Z; // domain sizes. + idx_t _hw=0, _hx=0, _hy=0, _hz=0; // halo sizes. + idx_t _pw=0, _px=0, _py=0, _pz=0; // halo + extra-pad sizes. + idx_t _ow=0, _ox=0, _oy=0, _oz=0; // offsets into global problem domain. // real_vec_t sizes for up to 4 spatial dims. // halo vector-sizes are not given here, because they are not rounded up. - idx_t _dnv=1, _dxv=1, _dyv=1, _dzv=1; - idx_t _pnv=0, _pxv=0, _pyv=0, _pzv=0; - idx_t _onv=0, _oxv=0, _oyv=0, _ozv=0; + idx_t _dwv=1, _dxv=1, _dyv=1, _dzv=1; + idx_t _pwv=0, _pxv=0, _pyv=0, _pzv=0; + idx_t _owv=0, _oxv=0, _oyv=0, _ozv=0; // Dynamic data. bool _is_updated = false; // data has been received from neighbors' halos. @@ -96,8 +96,8 @@ namespace yask { idx_t vec_ofs) const { return vec_index + vec_pad - vec_ofs; } - ALWAYS_INLINE idx_t get_index_n(idx_t vec_index) const { - return get_index(vec_index, _pnv, _onv); + ALWAYS_INLINE idx_t get_index_w(idx_t vec_index) const { + return get_index(vec_index, _pwv, _owv); } ALWAYS_INLINE idx_t get_index_x(idx_t vec_index) const { return get_index(vec_index, _pxv, _oxv); @@ -123,13 +123,13 @@ namespace yask { // Determine what dims are actually used for derived type. virtual bool got_t() const { return false; } - virtual bool got_n() const { return false; } + virtual bool got_w() const { return false; } virtual bool got_x() const { return false; } virtual bool got_y() const { return false; } virtual bool got_z() const { return false; } virtual int get_num_dims() const { return (got_t() ? 1 : 0) + - (got_n() ? 1 : 0) + + (got_w() ? 1 : 0) + (got_x() ? 1 : 0) + (got_y() ? 1 : 0) + (got_z() ? 1 : 0); @@ -139,13 +139,13 @@ namespace yask { virtual inline idx_t get_tdim() const { return 1; } // Get domain-size for this rank after round-up. - inline idx_t get_dn() const { return _dn; } + inline idx_t get_dw() const { return _dw; } inline idx_t get_dx() const { return _dx; } inline idx_t get_dy() const { return _dy; } inline idx_t get_dz() const { return _dz; } // Get halo-size (NOT rounded up). - inline idx_t get_halo_n() const { return _hn; } + inline idx_t get_halo_w() const { return _hw; } inline idx_t get_halo_x() const { return _hx; } inline idx_t get_halo_y() const { return _hy; } inline idx_t get_halo_z() const { return _hz; } @@ -153,26 +153,26 @@ namespace yask { // Get extra-padding-size after round-up. // Since the extra pad is in addition to the halo, these // values may not be multiples of the vector lengths. - inline idx_t get_pad_n() const { return _pn - _hn; } + inline idx_t get_pad_w() const { return _pw - _hw; } inline idx_t get_pad_x() const { return _px - _hx; } inline idx_t get_pad_y() const { return _py - _hy; } inline idx_t get_pad_z() const { return _pz - _hz; } // Get first logical index in domain on this rank. - inline idx_t get_first_n() const { return _on; } + inline idx_t get_first_w() const { return _ow; } inline idx_t get_first_x() const { return _ox; } inline idx_t get_first_y() const { return _oy; } inline idx_t get_first_z() const { return _oz; } // Get last logical index in domain on this rank. - inline idx_t get_last_n() const { return _on + _dn - 1; } + inline idx_t get_last_w() const { return _ow + _dw - 1; } inline idx_t get_last_x() const { return _ox + _dx - 1; } inline idx_t get_last_y() const { return _oy + _dy - 1; } inline idx_t get_last_z() const { return _oz + _dz - 1; } // Set domain-size for this rank and round-up. - inline void set_dn(idx_t dn) { - _dn = ROUND_UP(dn, VLEN_N); _dnv = _dn / VLEN_N; resize_g(); } + inline void set_dw(idx_t dw) { + _dw = ROUND_UP(dw, VLEN_W); _dwv = _dw / VLEN_W; resize_g(); } inline void set_dx(idx_t dx) { _dx = ROUND_UP(dx, VLEN_X); _dxv = _dx / VLEN_X; resize_g(); } inline void set_dy(idx_t dy) { @@ -182,8 +182,8 @@ namespace yask { // Set halo sizes. // Increase padding if needed. - inline void set_halo_n(idx_t hn) { - _hn = hn; _pn = ROUND_UP(std::max(_pn, hn), VLEN_N); resize_g(); } + inline void set_halo_w(idx_t hw) { + _hw = hw; _pw = ROUND_UP(std::max(_pw, hw), VLEN_W); resize_g(); } inline void set_halo_x(idx_t hx) { _hx = hx; _px = ROUND_UP(std::max(_px, hx), VLEN_X); resize_g(); } inline void set_halo_y(idx_t hy) { @@ -193,8 +193,8 @@ namespace yask { // Set padding and round-up to encompass halo. // To get minimum padding, set halo first. - inline void set_pad_n(idx_t pn) { - _pn = ROUND_UP(pn + _hn, VLEN_N); _pnv = _pn / VLEN_N; resize_g(); } + inline void set_pad_w(idx_t pw) { + _pw = ROUND_UP(pw + _hw, VLEN_W); _pwv = _pw / VLEN_W; resize_g(); } inline void set_pad_x(idx_t px) { _px = ROUND_UP(px + _hx, VLEN_X); _pxv = _px / VLEN_X; resize_g(); } inline void set_pad_y(idx_t py) { @@ -203,8 +203,8 @@ namespace yask { _pz = ROUND_UP(pz + _hz, VLEN_Z); _pzv = _pz / VLEN_Z; resize_g(); } // Set offset and round-up. - inline void set_ofs_n(idx_t on) { - _on = ROUND_UP(on, VLEN_N); _onv = _on / VLEN_N; } + inline void set_ofs_w(idx_t ow) { + _ow = ROUND_UP(ow, VLEN_W); _owv = _ow / VLEN_W; } inline void set_ofs_x(idx_t ox) { _ox = ROUND_UP(ox, VLEN_X); _oxv = _ox / VLEN_X; } inline void set_ofs_y(idx_t oy) { @@ -253,8 +253,8 @@ namespace yask { // Normalize element indices to vector indices and element offsets. ALWAYS_INLINE - void normalize_n(idx_t n, idx_t& vec_index, idx_t& elem_ofs) const { - normalize(n, vec_index, elem_ofs, VLEN_N, _pnv, _pn); + void normalize_w(idx_t w, idx_t& vec_index, idx_t& elem_ofs) const { + normalize(w, vec_index, elem_ofs, VLEN_W, _pwv, _pw); } ALWAYS_INLINE void normalize_x(idx_t x, idx_t& vec_index, idx_t& elem_ofs) const { @@ -270,34 +270,34 @@ namespace yask { } // Read one element. - virtual real_t readElem_TNXYZ(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual real_t readElem_TWXYZ(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) const =0; // Write one element. - virtual void writeElem_TNXYZ(real_t val, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual void writeElem_TWXYZ(real_t val, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) =0; // Read one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual real_vec_t readVecNorm_TNXYZ(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual real_vec_t readVecNorm_TWXYZ(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const =0; // Write one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual void writeVecNorm_TNXYZ(const real_vec_t& v, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual void writeVecNorm_TWXYZ(const real_vec_t& v, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) =0; // Print one element. - virtual void printElem_TNXYZ(std::ostream& os, const std::string& m, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual void printElem_TWXYZ(std::ostream& os, const std::string& m, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, real_t e, int line, bool newline = true) const; // Print one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual void printVecNorm_TNXYZ(std::ostream& os, const std::string& m, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual void printVecNorm_TWXYZ(std::ostream& os, const std::string& m, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, const real_vec_t& v, int line) const; @@ -393,16 +393,16 @@ namespace yask { ALWAYS_INLINE const real_t* getElemPtr(idx_t x, idx_t y, idx_t z, bool checkBounds=true) const { - idx_t xv, ie, yv, je, zv, ke; - normalize_x(x, xv, ie); - normalize_y(y, yv, je); - normalize_z(z, zv, ke); + idx_t xv, xe, yv, ye, zv, ze; + normalize_x(x, xv, xe); + normalize_y(y, yv, ye); + normalize_z(z, zv, ze); // Get vector. const real_vec_t* vp = getVecPtrNorm(xv, yv, zv, checkBounds); // Extract point from vector. - return &(*vp)(0, ie, je, ke); + return &(*vp)(0, xe, ye, ze); } // non-const version. @@ -493,38 +493,38 @@ namespace yask { } // Read one element. - virtual real_t readElem_TNXYZ(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual real_t readElem_TWXYZ(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) const { assert(t == 0); - assert(n == 0); + assert(w == 0); return readElem(x, y, z, line); } // Write one element. - virtual void writeElem_TNXYZ(real_t val, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual void writeElem_TWXYZ(real_t val, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) { assert(t == 0); - assert(n == 0); + assert(w == 0); writeElem(val, x, y, z, line); } // Read one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual real_vec_t readVecNorm_TNXYZ(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual real_vec_t readVecNorm_TWXYZ(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const { assert(t == 0); - assert(nv == 0); + assert(wv == 0); return readVecNorm(xv, yv, zv, line); } // Write one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual void writeVecNorm_TNXYZ(const real_vec_t& val, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual void writeVecNorm_TWXYZ(const real_vec_t& val, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) { assert(t == 0); - assert(nv == 0); + assert(wv == 0); writeVecNorm(val, xv, yv, zv, line); } @@ -532,20 +532,20 @@ namespace yask { void printVecNorm(std::ostream& os, const std::string& m, idx_t xv, idx_t yv, idx_t zv, const real_vec_t& v, int line) const { - printVecNorm_TNXYZ(0, 0, xv, yv, zv, v, line); + printVecNorm_TWXYZ(0, 0, xv, yv, zv, v, line); } // Print one element. void printElem(std::ostream& os, const std::string& m, idx_t x, idx_t y, idx_t z, real_t e, int line) const { - printElem_TNXYZ(0, 0, x, y, z, e, line); + printElem_TWXYZ(0, 0, x, y, z, e, line); } }; - // A 4D (n, x, y, z) collection of real_vec_t elements. + // A 4D (w, x, y, z) collection of real_vec_t elements. // Supports symmetric padding in each dimension. - template class RealVecGrid_NXYZ : + template class RealVecGrid_WXYZ : public RealVecGridBase { protected: @@ -553,7 +553,7 @@ namespace yask { GenericGrid4d _data; virtual void resize_g() { - _data.set_d1(_dnv + 2 * _pnv); + _data.set_d1(_dwv + 2 * _pwv); _data.set_d2(_dxv + 2 * _pxv); _data.set_d3(_dyv + 2 * _pyv); _data.set_d4(_dzv + 2 * _pzv); @@ -562,34 +562,34 @@ namespace yask { public: // Ctor. - RealVecGrid_NXYZ(const std::string& name) : + RealVecGrid_WXYZ(const std::string& name) : RealVecGridBase(name, &_data) { } // Determine what dims are defined. - virtual bool got_n() const { return true; } + virtual bool got_w() const { return true; } virtual bool got_x() const { return true; } virtual bool got_y() const { return true; } virtual bool got_z() const { return true; } - // Get pointer to the real_vec_t at vector offset nv, xv, yv, zv. + // Get pointer to the real_vec_t at vector offset wv, xv, yv, zv. // Indices must be normalized, i.e., already divided by VLEN_*. ALWAYS_INLINE - const real_vec_t* getVecPtrNorm(idx_t nv, idx_t xv, idx_t yv, idx_t zv, + const real_vec_t* getVecPtrNorm(idx_t wv, idx_t xv, idx_t yv, idx_t zv, bool checkBounds=true) const { #ifdef TRACE_MEM - std::cout << _name << "." << "RealVecGrid_NXYZ::getVecPtrNorm(" << - nv << "," << xv << "," << yv << "," << zv << ")"; + std::cout << _name << "." << "RealVecGrid_WXYZ::getVecPtrNorm(" << + wv << "," << xv << "," << yv << "," << zv << ")"; #endif // adjust for padding and offset. #if USE_GET_INDEX - nv = get_index_n(nv); + wv = get_index_w(wv); xv = get_index_x(xv); yv = get_index_y(yv); zv = get_index_z(zv); #else - nv += _pnv - _onv; + wv += _pwv - _owv; xv += _pxv - _oxv; yv += _pyv - _oyv; zv += _pzv - _ozv; @@ -597,86 +597,86 @@ namespace yask { #ifdef TRACE_MEM if (checkBounds) - std::cout << " => " << _data.get_index(nv, xv, yv, zv); + std::cout << " => " << _data.get_index(wv, xv, yv, zv); std::cout << std::endl << flush; #endif - return &_data(nv, xv, yv, zv, checkBounds); + return &_data(wv, xv, yv, zv, checkBounds); } // Non-const version. ALWAYS_INLINE - real_vec_t* getVecPtrNorm(idx_t nv, idx_t xv, idx_t yv, idx_t zv, + real_vec_t* getVecPtrNorm(idx_t wv, idx_t xv, idx_t yv, idx_t zv, bool checkBounds=true) { const real_vec_t* vp = - const_cast(this)->getVecPtrNorm(nv, xv, yv, zv, + const_cast(this)->getVecPtrNorm(wv, xv, yv, zv, checkBounds); return const_cast(vp); } // Get a pointer to one real_t. ALWAYS_INLINE - const real_t* getElemPtr(idx_t n, idx_t x, idx_t y, idx_t z, + const real_t* getElemPtr(idx_t w, idx_t x, idx_t y, idx_t z, bool checkBounds=true) const { - idx_t nv, ne, xv, ie, yv, je, zv, ke; - normalize_n(n, nv, ne); - normalize_x(x, xv, ie); - normalize_y(y, yv, je); - normalize_z(z, zv, ke); + idx_t wv, we, xv, xe, yv, ye, zv, ze; + normalize_w(w, wv, we); + normalize_x(x, xv, xe); + normalize_y(y, yv, ye); + normalize_z(z, zv, ze); // Get vector. - const real_vec_t* vp = getVecPtrNorm(nv, xv, yv, zv, checkBounds); + const real_vec_t* vp = getVecPtrNorm(wv, xv, yv, zv, checkBounds); // Extract point from vector. - return &(*vp)(ne, ie, je, ke); + return &(*vp)(we, xe, ye, ze); } // non-const version. ALWAYS_INLINE - real_t* getElemPtr(idx_t n, idx_t x, idx_t y, idx_t z, + real_t* getElemPtr(idx_t w, idx_t x, idx_t y, idx_t z, bool checkBounds=true) { - const real_t* p = const_cast(this)->getElemPtr(n, x, y, z, + const real_t* p = const_cast(this)->getElemPtr(w, x, y, z, checkBounds); return const_cast(p); } // Read one element. ALWAYS_INLINE - real_t readElem(idx_t n, idx_t x, idx_t y, idx_t z, + real_t readElem(idx_t w, idx_t x, idx_t y, idx_t z, int line) const { - const real_t* ep = getElemPtr(n, x, y, z); + const real_t* ep = getElemPtr(w, x, y, z); real_t e = *ep; #ifdef TRACE_MEM - printElem(std::cout, "readElem", n, x, y, z, e, line); + printElem(std::cout, "readElem", w, x, y, z, e, line); #endif return e; } // Write one element. ALWAYS_INLINE - void writeElem(real_t val, idx_t n, idx_t x, idx_t y, idx_t z, + void writeElem(real_t val, idx_t w, idx_t x, idx_t y, idx_t z, int line) { - real_t* ep = getElemPtr(n, x, y, z); + real_t* ep = getElemPtr(w, x, y, z); *ep = val; #ifdef TRACE_MEM - printElem(std::cout, "writeElem", n, x, y, z, val, line); + printElem(std::cout, "writeElem", w, x, y, z, val, line); #endif } - // Read one vector at vector offset nv, xv, yv, zv. + // Read one vector at vector offset wv, xv, yv, zv. // Indices must be normalized, i.e., already divided by VLEN_*. ALWAYS_INLINE - real_vec_t readVecNorm(idx_t nv, idx_t xv, idx_t yv, idx_t zv, + real_vec_t readVecNorm(idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const { #ifdef TRACE_MEM - std::cout << "readVecNorm(" << nv << "," << xv << "," << yv << "," << zv << ")..." << std::endl; + std::cout << "readVecNorm(" << wv << "," << xv << "," << yv << "," << zv << ")..." << std::endl; #endif - const real_vec_t* p = getVecPtrNorm(nv, xv, yv, zv); + const real_vec_t* p = getVecPtrNorm(wv, xv, yv, zv); __assume_aligned(p, CACHELINE_BYTES); real_vec_t v; v.loadFrom(p); #ifdef TRACE_MEM - printVecNorm(std::cout, "readVec", nv, xv, yv, zv, v, line); + printVecNorm(std::cout, "readVec", wv, xv, yv, zv, v, line); #endif #ifdef MODEL_CACHE cache_model.read(p, line); @@ -684,33 +684,33 @@ namespace yask { return v; } - // Write one vector at vector offset nv, xv, yv, zv. + // Write one vector at vector offset wv, xv, yv, zv. // Indices must be normalized, i.e., already divided by VLEN_*. ALWAYS_INLINE - void writeVecNorm(const real_vec_t& v, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + void writeVecNorm(const real_vec_t& v, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) { - real_vec_t* p = getVecPtrNorm(nv, xv, yv, zv); + real_vec_t* p = getVecPtrNorm(wv, xv, yv, zv); __assume_aligned(p, CACHELINE_BYTES); v.storeTo(p); #ifdef TRACE_MEM - printVecNorm(std::cout, "writeVec", nv, xv, yv, zv, v, line); + printVecNorm(std::cout, "writeVec", wv, xv, yv, zv, v, line); #endif #ifdef MODEL_CACHE cache_model.write(p, line); #endif } - // Prefetch one vector at vector offset nv, xv, yv, zv. + // Prefetch one vector at vector offset wv, xv, yv, zv. // Indices must be normalized, i.e., already divided by VLEN_*. template ALWAYS_INLINE - void prefetchVecNorm(idx_t nv, idx_t xv, idx_t yv, idx_t zv, + void prefetchVecNorm(idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const { #ifdef TRACE_MEM std::cout << "prefetchVecNorm<" << level << ">(" << - nv << "," << xv << "," << yv << "," << zv << ")..." << std::endl; + wv << "," << xv << "," << yv << "," << zv << ")..." << std::endl; #endif - const char* p = (const char*)getVecPtrNorm(nv, xv, yv, zv, false); + const char* p = (const char*)getVecPtrNorm(wv, xv, yv, zv, false); __assume_aligned(p, CACHELINE_BYTES); _mm_prefetch (p, level); #ifdef MODEL_CACHE @@ -719,49 +719,49 @@ namespace yask { } // Read one element. - virtual real_t readElem_TNXYZ(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual real_t readElem_TWXYZ(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) const { assert(t == 0); - return readElem(n, x, y, z, line); + return readElem(w, x, y, z, line); } // Write one element. - virtual void writeElem_TNXYZ(real_t val, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual void writeElem_TWXYZ(real_t val, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) { assert(t == 0); - writeElem(val, n, x, y, z, line); + writeElem(val, w, x, y, z, line); } // Read one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual real_vec_t readVecNorm_TNXYZ(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual real_vec_t readVecNorm_TWXYZ(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const { assert(t == 0); - return readVecNorm(nv, xv, yv, zv, line); + return readVecNorm(wv, xv, yv, zv, line); } // Write one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual void writeVecNorm_TNXYZ(const real_vec_t& val, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual void writeVecNorm_TWXYZ(const real_vec_t& val, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) { assert(t == 0); - writeVecNorm(val, nv, xv, yv, zv, line); + writeVecNorm(val, wv, xv, yv, zv, line); } // Print one vector. void printVecNorm(std::ostream& os, const std::string& m, - idx_t nv, idx_t xv, idx_t yv, idx_t zv, const real_vec_t& v, + idx_t wv, idx_t xv, idx_t yv, idx_t zv, const real_vec_t& v, int line) const { - printVecNorm_TNXYZ(0, nv, xv, yv, zv, v, line); + printVecNorm_TWXYZ(0, wv, xv, yv, zv, v, line); } // Print one element. void printElem(std::ostream& os, const std::string& m, - idx_t n, idx_t x, idx_t y, idx_t z, real_t e, + idx_t w, idx_t x, idx_t y, idx_t z, real_t e, int line) const { - printElem_TNXYZ(0, n, x, y, z, e, line); + printElem_TWXYZ(0, w, x, y, z, e, line); } }; @@ -844,7 +844,7 @@ namespace yask { bool checkBounds=true) const { #ifdef TRACE_MEM - std::cout << _name << "." << "RealVecGrid_TNXYZ::getVecPtrNorm(" << + std::cout << _name << "." << "RealVecGrid_TWXYZ::getVecPtrNorm(" << t << "," << << xv << "," << yv << "," << zv << ")"; #endif @@ -883,16 +883,16 @@ namespace yask { ALWAYS_INLINE const real_t* getElemPtr(idx_t t, idx_t x, idx_t y, idx_t z, bool checkBounds=true) const { - idx_t xv, ie, yv, je, zv, ke; - this->normalize_x(x, xv, ie); - this->normalize_y(y, yv, je); - this->normalize_z(z, zv, ke); + idx_t xv, xe, yv, ye, zv, ze; + this->normalize_x(x, xv, xe); + this->normalize_y(y, yv, ye); + this->normalize_z(z, zv, ze); // Get vector. const real_vec_t* vp = getVecPtrNorm(t, xv, yv, zv, checkBounds); // Extract point from vector. - return &(*vp)(0, ie, je, ke); + return &(*vp)(0, xe, ye, ze); } // non-const version. @@ -987,34 +987,34 @@ namespace yask { } // Read one element. - virtual real_t readElem_TNXYZ(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual real_t readElem_TWXYZ(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) const { - assert(n == 0); + assert(w == 0); return readElem(t, x, y, z, line); } // Write one element. - virtual void writeElem_TNXYZ(real_t val, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual void writeElem_TWXYZ(real_t val, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) { - assert(n == 0); + assert(w == 0); writeElem(val, t, x, y, z, line); } // Read one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual real_vec_t readVecNorm_TNXYZ(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual real_vec_t readVecNorm_TWXYZ(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const { - assert(nv == 0); + assert(wv == 0); return readVecNorm(t, xv, yv, zv, line); } // Write one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual void writeVecNorm_TNXYZ(const real_vec_t& val, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual void writeVecNorm_TWXYZ(const real_vec_t& val, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) { - assert(nv == 0); + assert(wv == 0); writeVecNorm(val, t, xv, yv, zv, line); } @@ -1023,7 +1023,7 @@ namespace yask { idx_t t, idx_t xv, idx_t yv, idx_t zv, const real_vec_t& v, int line) const { - printVecNorm_TNXYZ(t, 0, xv, yv, zv, v, line); + printVecNorm_TWXYZ(t, 0, xv, yv, zv, v, line); } // Print one element. @@ -1031,13 +1031,13 @@ namespace yask { idx_t t, idx_t x, idx_t y, idx_t z, real_t e, int line) const { - printElem_TNXYZ(t, 0, x, y, z, e, line); + printElem_TWXYZ(t, 0, x, y, z, e, line); } }; - // A 5D (t, n, x, y, z) collection of real_vec_t elements. + // A 5D (t, w, x, y, z) collection of real_vec_t elements. // Supports symmetric padding in each dimension. - template class RealVecGrid_TNXYZ : + template class RealVecGrid_TWXYZ : public RealVecGridTemplate<_tdim> { protected: @@ -1046,7 +1046,7 @@ namespace yask { virtual void resize_g() { _data.set_d1(_tdim); - _data.set_d2(this->_dnv + 2 * this->_pnv); + _data.set_d2(this->_dwv + 2 * this->_pwv); _data.set_d3(this->_dxv + 2 * this->_pxv); _data.set_d4(this->_dyv + 2 * this->_pyv); _data.set_d5(this->_dzv + 2 * this->_pzv); @@ -1055,36 +1055,36 @@ namespace yask { public: // Ctor. - RealVecGrid_TNXYZ(const std::string& name) : + RealVecGrid_TWXYZ(const std::string& name) : RealVecGridTemplate<_tdim>(name, &_data) { } // Determine what dims are defined. virtual bool got_t() const { return true; } - virtual bool got_n() const { return true; } + virtual bool got_w() const { return true; } virtual bool got_x() const { return true; } virtual bool got_y() const { return true; } virtual bool got_z() const { return true; } - // Get pointer to the real_vec_t at vector offset t, nv, xv, yv, zv. + // Get pointer to the real_vec_t at vector offset t, wv, xv, yv, zv. // Indices must be normalized, i.e., already divided by VLEN_*. ALWAYS_INLINE - const real_vec_t* getVecPtrNorm(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + const real_vec_t* getVecPtrNorm(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, bool checkBounds=true) const { #ifdef TRACE_MEM - std::cout << _name << "." << "RealVecGrid_TNXYZ::getVecPtrNorm(" << - t << "," << nv << "," << xv << "," << yv << "," << zv << ")"; + std::cout << _name << "." << "RealVecGrid_TWXYZ::getVecPtrNorm(" << + t << "," << wv << "," << xv << "," << yv << "," << zv << ")"; #endif // adjust for padding and offset. t = this->get_index_t(t); #if USE_GET_INDEX - nv = this->get_index_n(nv); + wv = this->get_index_w(wv); xv = this->get_index_x(xv); yv = this->get_index_y(yv); zv = this->get_index_z(zv); #else - nv += this->_pnv - this->_onv; + wv += this->_pwv - this->_owv; xv += this->_pxv - this->_oxv; yv += this->_pyv - this->_oyv; zv += this->_pzv - this->_ozv; @@ -1092,58 +1092,58 @@ namespace yask { #ifdef TRACE_MEM if (checkBounds) - std::cout << " => " << _data.get_index(t, nv, xv, yv, zv); + std::cout << " => " << _data.get_index(t, wv, xv, yv, zv); std::cout << std::endl << flush; #endif - return &_data(t, nv, xv, yv, zv, checkBounds); + return &_data(t, wv, xv, yv, zv, checkBounds); } // Non-const version. ALWAYS_INLINE - real_vec_t* getVecPtrNorm(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + real_vec_t* getVecPtrNorm(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, bool checkBounds=true) { const real_vec_t* vp = - const_cast(this)->getVecPtrNorm(t, nv, xv, yv, zv, + const_cast(this)->getVecPtrNorm(t, wv, xv, yv, zv, checkBounds); return const_cast(vp); } // Get a pointer to one real_t. ALWAYS_INLINE - const real_t* getElemPtr(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + const real_t* getElemPtr(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, bool checkBounds=true) const { - idx_t nv, ne, xv, ie, yv, je, zv, ke; - this->normalize_n(n, nv, ne); - this->normalize_x(x, xv, ie); - this->normalize_y(y, yv, je); - this->normalize_z(z, zv, ke); + idx_t wv, we, xv, xe, yv, ye, zv, ze; + this->normalize_w(w, wv, we); + this->normalize_x(x, xv, xe); + this->normalize_y(y, yv, ye); + this->normalize_z(z, zv, ze); // Get vector. - const real_vec_t* vp = getVecPtrNorm(t, nv, xv, yv, zv, checkBounds); + const real_vec_t* vp = getVecPtrNorm(t, wv, xv, yv, zv, checkBounds); // Extract point from vector. - return &(*vp)(ne, ie, je, ke); + return &(*vp)(we, xe, ye, ze); } // non-const version. ALWAYS_INLINE - real_t* getElemPtr(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + real_t* getElemPtr(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, bool checkBounds=true) { const real_t* p = - const_cast(this)->getElemPtr(t, n, x, y, z, + const_cast(this)->getElemPtr(t, w, x, y, z, checkBounds); return const_cast(p); } // Read one element. ALWAYS_INLINE - real_t readElem(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + real_t readElem(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) const { - const real_t* ep = getElemPtr(t, n, x, y, z); + const real_t* ep = getElemPtr(t, w, x, y, z); real_t e = *ep; #ifdef TRACE_MEM - printElem(std::cout, "readElem", t, n, x, y, z, e, line); + printElem(std::cout, "readElem", t, w, x, y, z, e, line); #endif return e; } @@ -1151,30 +1151,30 @@ namespace yask { // Write one element. ALWAYS_INLINE void writeElem(real_t val, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) { - real_t* ep = getElemPtr(t, n, x, y, z); + real_t* ep = getElemPtr(t, w, x, y, z); *ep = val; #ifdef TRACE_MEM - printElem(std::cout, "writeElem", t, n, x, y, z, val, line); + printElem(std::cout, "writeElem", t, w, x, y, z, val, line); #endif } - // Read one vector at vector offset t, nv, xv, yv, zv. + // Read one vector at vector offset t, wv, xv, yv, zv. // Indices must be normalized, i.e., already divided by VLEN_*. ALWAYS_INLINE - real_vec_t readVecNorm(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + real_vec_t readVecNorm(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const { #ifdef TRACE_MEM - std::cout << "readVecNorm(" << t "," << nv << "," << xv << + std::cout << "readVecNorm(" << t "," << wv << "," << xv << "," << yv << "," << zv << ")..." << std::endl; #endif - const real_vec_t* p = getVecPtrNorm(t, nv, xv, yv, zv); + const real_vec_t* p = getVecPtrNorm(t, wv, xv, yv, zv); __assume_aligned(p, CACHELINE_BYTES); real_vec_t v; v.loadFrom(p); #ifdef TRACE_MEM - printVecNorm(std::cout, "readVec", t, nv, xv, yv, zv, v, line); + printVecNorm(std::cout, "readVec", t, wv, xv, yv, zv, v, line); #endif #ifdef MODEL_CACHE cache_model.read(p, line); @@ -1182,34 +1182,34 @@ namespace yask { return v; } - // Write one vector at vector offset t, nv, xv, yv, zv. + // Write one vector at vector offset t, wv, xv, yv, zv. // Indices must be normalized, i.e., already divided by VLEN_*. ALWAYS_INLINE void writeVecNorm(const real_vec_t& v, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) { - real_vec_t* p = getVecPtrNorm(t, nv, xv, yv, zv); + real_vec_t* p = getVecPtrNorm(t, wv, xv, yv, zv); __assume_aligned(p, CACHELINE_BYTES); v.storeTo(p); #ifdef TRACE_MEM - printVecNorm(std::cout, "writeVec", t, nv, xv, yv, zv, v, line); + printVecNorm(std::cout, "writeVec", t, wv, xv, yv, zv, v, line); #endif #ifdef MODEL_CACHE cache_model.write(p, line); #endif } - // Prefetch one vector at vector offset t, nv, xv, yv, zv. + // Prefetch one vector at vector offset t, wv, xv, yv, zv. // Indices must be normalized, i.e., already divided by VLEN_*. template ALWAYS_INLINE - void prefetchVecNorm(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + void prefetchVecNorm(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const { #ifdef TRACE_MEM std::cout << "prefetchVecNorm<" << level << ">(" << t << "," << - nv << "," << xv << "," << yv << "," << zv << ")..." << std::endl; + wv << "," << xv << "," << yv << "," << zv << ")..." << std::endl; #endif - const char* p = (const char*)getVecPtrNorm(t, nv, xv, yv, zv, false); + const char* p = (const char*)getVecPtrNorm(t, wv, xv, yv, zv, false); __assume_aligned(p, CACHELINE_BYTES); _mm_prefetch (p, level); #ifdef MODEL_CACHE @@ -1218,47 +1218,47 @@ namespace yask { } // Read one element. - virtual real_t readElem_TNXYZ(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual real_t readElem_TWXYZ(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) const { - return readElem(t, n, x, y, z, line); + return readElem(t, w, x, y, z, line); } // Write one element. - virtual void writeElem_TNXYZ(real_t val, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + virtual void writeElem_TWXYZ(real_t val, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, int line) { - writeElem(val, t, n, x, y, z, line); + writeElem(val, t, w, x, y, z, line); } // Read one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual real_vec_t readVecNorm_TNXYZ(idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual real_vec_t readVecNorm_TWXYZ(idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) const { - return readVecNorm(t, nv, xv, yv, zv, line); + return readVecNorm(t, wv, xv, yv, zv, line); } // Write one vector at *vector* offset. // Indices must be normalized, i.e., already divided by VLEN_*. - virtual void writeVecNorm_TNXYZ(const real_vec_t& val, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + virtual void writeVecNorm_TWXYZ(const real_vec_t& val, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, int line) { - writeVecNorm(val, t, nv, xv, yv, zv, line); + writeVecNorm(val, t, wv, xv, yv, zv, line); } // Print one vector. void printVecNorm(std::ostream& os, const std::string& m, - idx_t t, idx_t nv, idx_t xv, idx_t yv, idx_t zv, + idx_t t, idx_t wv, idx_t xv, idx_t yv, idx_t zv, const real_vec_t& v, int line) const { - printVecNorm_TNXYZ(t, nv, xv, yv, zv, v, line); + printVecNorm_TWXYZ(t, wv, xv, yv, zv, v, line); } // Print one element. void printElem(std::ostream& os, const std::string& m, - idx_t t, idx_t n, idx_t x, idx_t y, idx_t z, + idx_t t, idx_t w, idx_t x, idx_t y, idx_t z, real_t e, int line) const { - printElem_TNXYZ(t, n, x, y, z, e, line); + printElem_TWXYZ(t, w, x, y, z, e, line); } }; diff --git a/src/stencil.hpp b/src/stencil.hpp index d589532f..49483fbe 100644 --- a/src/stencil.hpp +++ b/src/stencil.hpp @@ -60,8 +60,8 @@ IN THE SOFTWARE. #ifndef CLEN_T #define CLEN_T (1) #endif -#ifndef CLEN_N -#define CLEN_N (1) +#ifndef CLEN_W +#define CLEN_W (1) #endif #ifndef CLEN_X #define CLEN_X (1) @@ -78,21 +78,21 @@ IN THE SOFTWARE. // call to the calc_vector function(s) generated by foldBuilder // in stencil_code.hpp. #define CPTS_T (CLEN_T * VLEN_T) -#define CPTS_N (CLEN_N * VLEN_N) +#define CPTS_W (CLEN_W * VLEN_W) #define CPTS_X (CLEN_X * VLEN_X) #define CPTS_Y (CLEN_Y * VLEN_Y) #define CPTS_Z (CLEN_Z * VLEN_Z) -#define CPTS (CLEN_T * CPTS_N * CPTS_X * CPTS_Y * CPTS_Z) +#define CPTS (CLEN_T * CPTS_W * CPTS_X * CPTS_Y * CPTS_Z) // default sizes. #ifndef DEF_PROBLEM_SIZE -#define DEF_PROBLEM_SIZE (1024) +#define DEF_PROBLEM_SIZE (256) #endif #ifndef DEF_WAVEFRONT_REGION_SIZE -#define DEF_WAVEFRONT_REGION_SIZE (512) +#define DEF_WAVEFRONT_REGION_SIZE (128) #endif #ifndef DEF_BLOCK_SIZE -#define DEF_BLOCK_SIZE (64) +#define DEF_BLOCK_SIZE (32) #endif // Memory-accessing code. @@ -108,32 +108,32 @@ namespace yask { // Default grid layouts. // Last number in 'Layout' name has unit stride, e.g., - // LAYOUT_NXYZ Layout_1234 => unit-stride in z. - // LAYOUT_NXYZ Layout_1243 => unit-stride in y. + // LAYOUT_WXYZ Layout_1234 => unit-stride in z. + // LAYOUT_WXYZ Layout_1243 => unit-stride in y. #ifndef LAYOUT_XYZ #define LAYOUT_XYZ Layout_123 #endif -#ifndef LAYOUT_NXYZ -#define LAYOUT_NXYZ Layout_1234 +#ifndef LAYOUT_WXYZ +#define LAYOUT_WXYZ Layout_1234 #endif #ifndef LAYOUT_TXYZ #define LAYOUT_TXYZ Layout_1234 #endif -#ifndef LAYOUT_TNXYZ -#define LAYOUT_TNXYZ Layout_12345 +#ifndef LAYOUT_TWXYZ +#define LAYOUT_TWXYZ Layout_12345 #endif // RealVecGrids using layouts defined above. using Grid_XYZ = RealVecGrid_XYZ; - using Grid_NXYZ = RealVecGrid_NXYZ; + using Grid_WXYZ = RealVecGrid_WXYZ; template using Grid_TXYZ = RealVecGrid_TXYZ; template - using Grid_TNXYZ = RealVecGrid_TNXYZ; + using Grid_TWXYZ = RealVecGrid_TWXYZ; // RealGrids using traditional C layout. typedef GenericGrid3d RealGrid_XYZ; - typedef GenericGrid4d RealGrid_NXYZ; + typedef GenericGrid4d RealGrid_WXYZ; } #endif diff --git a/src/stencil_calc.cpp b/src/stencil_calc.cpp index a8285efa..6d267221 100644 --- a/src/stencil_calc.cpp +++ b/src/stencil_calc.cpp @@ -116,16 +116,16 @@ namespace yask { // Loop through 4D space within the bounding-box of this // equation set. #pragma omp parallel for collapse(4) - for (idx_t n = eg->begin_bbn; n < eg->end_bbn; n++) + for (idx_t w = eg->begin_bbw; w < eg->end_bbw; w++) for (idx_t x = eg->begin_bbx; x < eg->end_bbx; x++) for (idx_t y = eg->begin_bby; y < eg->end_bby; y++) for (idx_t z = eg->begin_bbz; z < eg->end_bbz; z++) { // Update only if point is in sub-domain for this eq group. - if (eg->is_in_valid_domain(t, n, x, y, z)) { + if (eg->is_in_valid_domain(t, w, x, y, z)) { // Evaluate the reference scalar code. - eg->calc_scalar(t, n, x, y, z); + eg->calc_scalar(t, w, x, y, z); } } @@ -154,25 +154,25 @@ namespace yask { #endif // Problem begin points. - idx_t begin_dn = begin_bbn; + idx_t begin_dw = begin_bbw; idx_t begin_dx = begin_bbx; idx_t begin_dy = begin_bby; idx_t begin_dz = begin_bbz; // Problem end-points. - idx_t end_dn = end_bbn; + idx_t end_dw = end_bbw; idx_t end_dx = end_bbx; idx_t end_dy = end_bby; idx_t end_dz = end_bbz; // Steps are based on region sizes. - idx_t step_dn = _opts->rn; + idx_t step_dw = _opts->rw; idx_t step_dx = _opts->rx; idx_t step_dy = _opts->ry; idx_t step_dz = _opts->rz; // Groups in rank loops are set to smallest size. - const idx_t group_size_dn = 1; + const idx_t group_size_dw = 1; const idx_t group_size_dx = 1; const idx_t group_size_dy = 1; const idx_t group_size_dz = 1; @@ -199,13 +199,13 @@ namespace yask { // (orig) (after extension) // idx_t nshifts = (idx_t(eqGroups.size()) * _opts->rt) - 1; - end_dn += angle_n * nshifts; + end_dw += angle_w * nshifts; end_dx += angle_x * nshifts; end_dy += angle_y * nshifts; end_dz += angle_z * nshifts; TRACE_MSG("extended domain after wave-front adjustment: " << "t=" << begin_dt << ".." << (end_dt-1) << - ", n=" << begin_dn << ".." << (end_dn-1) << + ", w=" << begin_dw << ".." << (end_dw-1) << ", x=" << begin_dx << ".." << (end_dx-1) << ", y=" << begin_dy << ".." << (end_dy-1) << ", z=" << begin_dz << ".." << (end_dz-1) << @@ -282,11 +282,11 @@ namespace yask { // equations and evaluate the blocks in the region. void StencilContext::calc_region(idx_t start_dt, idx_t stop_dt, EqGroupSet* eqGroup_set, - idx_t start_dn, idx_t start_dx, idx_t start_dy, idx_t start_dz, - idx_t stop_dn, idx_t stop_dx, idx_t stop_dy, idx_t stop_dz) + idx_t start_dw, idx_t start_dx, idx_t start_dy, idx_t start_dz, + idx_t stop_dw, idx_t stop_dx, idx_t stop_dy, idx_t stop_dz) { TRACE_MSG("calc_region(t=" << start_dt << ".." << (stop_dt-1) << - ", n=" << start_dn << ".." << (stop_dn-1) << + ", n=" << start_dw << ".." << (stop_dw-1) << ", x=" << start_dx << ".." << (stop_dx-1) << ", y=" << start_dy << ".." << (stop_dy-1) << ", z=" << start_dz << ".." << (stop_dz-1) << @@ -294,13 +294,13 @@ namespace yask { // Steps within a region are based on block sizes. const idx_t step_rt = _opts->bt; - const idx_t step_rn = _opts->bn; + const idx_t step_rw = _opts->bw; const idx_t step_rx = _opts->bx; const idx_t step_ry = _opts->by; const idx_t step_rz = _opts->bz; // Groups in region loops are based on group sizes. - const idx_t group_size_rn = _opts->gn; + const idx_t group_size_rw = _opts->gw; const idx_t group_size_rx = _opts->gx; const idx_t group_size_ry = _opts->gy; const idx_t group_size_rz = _opts->gz; @@ -339,8 +339,8 @@ namespace yask { // based on the BB. // Actual region boundaries must stay within BB for this eq group. - idx_t begin_rn = max(start_dn, eg->begin_bbn); - idx_t end_rn = min(stop_dn, eg->end_bbn); + idx_t begin_rw = max(start_dw, eg->begin_bbw); + idx_t end_rw = min(stop_dw, eg->end_bbw); idx_t begin_rx = max(start_dx, eg->begin_bbx); idx_t end_rx = min(stop_dx, eg->end_bbx); idx_t begin_ry = max(start_dy, eg->begin_bby); @@ -354,7 +354,7 @@ namespace yask { // start outside the domain but enter the domain as time // progresses and their boundaries shift. So, we don't // want to return if this condition isn't met. - if (end_rn > begin_rn && + if (end_rw > begin_rw && end_rx > begin_rx && end_ry > begin_ry && end_rz > begin_rz) { @@ -377,8 +377,8 @@ namespace yask { // backward, so region loops must increment. They may do // so in any order. TODO: shift only what is needed by // this eq-group, not the global max. - start_dn -= angle_n; - stop_dn -= angle_n; + start_dw -= angle_w; + stop_dw -= angle_w; start_dx -= angle_x; stop_dx -= angle_x; start_dy -= angle_y; @@ -403,7 +403,7 @@ namespace yask { os << "This rank index: " << my_rank << endl; // Check ranks. - idx_t req_ranks = _opts->nrn * _opts->nrx * _opts->nry * _opts->nrz; + idx_t req_ranks = _opts->nrw * _opts->nrx * _opts->nry * _opts->nrz; if (req_ranks != num_ranks) { cerr << "error: " << req_ranks << " rank(s) requested, but " << num_ranks << " rank(s) are active." << endl; @@ -414,12 +414,12 @@ namespace yask { // Determine my coordinates if not provided already. // TODO: do this more intelligently based on proximity. if (_opts->find_loc) { - Layout_4321 rank_layout(_opts->nrn, _opts->nrx, _opts->nry, _opts->nrz); + Layout_4321 rank_layout(_opts->nrw, _opts->nrx, _opts->nry, _opts->nrz); rank_layout.unlayout((idx_t)my_rank, - _opts->rin, _opts->rix, _opts->riy, _opts->riz); + _opts->riw, _opts->rix, _opts->riy, _opts->riz); } os << "Logical coordinates of this rank: " << - _opts->rin << ", " << + _opts->riw << ", " << _opts->rix << ", " << _opts->riy << ", " << _opts->riz << endl; @@ -429,7 +429,7 @@ namespace yask { idx_t coords[num_ranks][num_dims]; // Init coords for this rank. - coords[my_rank][0] = _opts->rin; + coords[my_rank][0] = _opts->riw; coords[my_rank][1] = _opts->rix; coords[my_rank][2] = _opts->riy; coords[my_rank][3] = _opts->riz; @@ -438,7 +438,7 @@ namespace yask { idx_t rsizes[num_ranks][num_dims]; // Init sizes for this rank. - rsizes[my_rank][0] = _opts->dn; + rsizes[my_rank][0] = _opts->dw; rsizes[my_rank][1] = _opts->dx; rsizes[my_rank][2] = _opts->dy; rsizes[my_rank][3] = _opts->dz; @@ -453,25 +453,25 @@ namespace yask { } #endif - ofs_n = ofs_x = ofs_y = ofs_z = 0; - tot_n = tot_x = tot_y = tot_z = 0; + ofs_w = ofs_x = ofs_y = ofs_z = 0; + tot_w = tot_x = tot_y = tot_z = 0; int num_neighbors = 0, num_exchanges = 0; for (int rn = 0; rn < num_ranks; rn++) { // Get coordinates of rn. - idx_t rnn = coords[rn][0]; + idx_t rnw = coords[rn][0]; idx_t rnx = coords[rn][1]; idx_t rny = coords[rn][2]; idx_t rnz = coords[rn][3]; // Coord offset of rn from me: prev => negative, self => 0, next => positive. - idx_t rdn = rnn - _opts->rin; + idx_t rdw = rnw - _opts->riw; idx_t rdx = rnx - _opts->rix; idx_t rdy = rny - _opts->riy; idx_t rdz = rnz - _opts->riz; // Get sizes of rn; - idx_t rsn = rsizes[rn][0]; + idx_t rsw = rsizes[rn][0]; idx_t rsx = rsizes[rn][1]; idx_t rsy = rsizes[rn][2]; idx_t rsz = rsizes[rn][3]; @@ -481,28 +481,28 @@ namespace yask { // Adjust my offset in the global problem by adding all domain // sizes from prev ranks only. if (rdx == 0 && rdy == 0 && rdz == 0) { - tot_n += rsn; - if (rdn < 0) - ofs_n += rsn; + tot_w += rsw; + if (rdw < 0) + ofs_w += rsw; } - if (rdn == 0 && rdy == 0 && rdz == 0) { + if (rdw == 0 && rdy == 0 && rdz == 0) { tot_x += rsx; if (rdx < 0) ofs_x += rsx; } - if (rdn == 0 && rdx == 0 && rdz == 0) { + if (rdw == 0 && rdx == 0 && rdz == 0) { tot_y += rsy; if (rdy < 0) ofs_y += rsy; } - if (rdn == 0 && rdx == 0 && rdy == 0) { + if (rdw == 0 && rdx == 0 && rdy == 0) { tot_z += rsz; if (rdz < 0) ofs_z += rsz; } // Manhattan distance. - int mdist = abs(rdn) + abs(rdx) + abs(rdy) + abs(rdz); + int mdist = abs(rdw) + abs(rdx) + abs(rdy) + abs(rdz); // Myself. if (rn == my_rank) { @@ -526,15 +526,15 @@ namespace yask { // every dim. Assume we do not need to exchange halos except // with immediate neighbor. TODO: validate domain size is larger // than halo. - if (abs(rdn) > 1 || abs(rdx) > 1 || abs(rdy) > 1 || abs(rdz) > 1) + if (abs(rdw) > 1 || abs(rdx) > 1 || abs(rdy) > 1 || abs(rdz) > 1) continue; // Save rank of this neighbor. // Add one to -1..+1 dist to get 0..2 range for my_neighbors indices. - my_neighbors[rdn+1][rdx+1][rdy+1][rdz+1] = rn; + my_neighbors[rdw+1][rdx+1][rdy+1][rdz+1] = rn; num_neighbors++; os << "Neighbor #" << num_neighbors << " at " << - rnn << ", " << rnx << ", " << rny << ", " << rnz << + rnw << ", " << rnx << ", " << rny << ", " << rnz << " is rank " << rn << endl; // Check against max dist needed. TODO: determine max dist @@ -559,12 +559,12 @@ namespace yask { // Size of buffer in each direction: if dist to neighbor is // zero in given direction (i.e., is perpendicular to this // rank), use domain size; otherwise, use halo size. - idx_t bsn = ROUND_UP((rdn == 0) ? _opts->dn : gp->get_halo_n(), VLEN_N); + idx_t bsw = ROUND_UP((rdw == 0) ? _opts->dw : gp->get_halo_w(), VLEN_W); idx_t bsx = ROUND_UP((rdx == 0) ? _opts->dx : gp->get_halo_x(), VLEN_X); idx_t bsy = ROUND_UP((rdy == 0) ? _opts->dy : gp->get_halo_y(), VLEN_Y); idx_t bsz = ROUND_UP((rdz == 0) ? _opts->dz : gp->get_halo_z(), VLEN_Z); - if (bsn * bsx * bsy * bsz == 0) { + if (bsw * bsx * bsy * bsz == 0) { os << " No halo exchange needed for grid '" << gname << "' with rank " << rn << '.' << endl; } @@ -580,9 +580,9 @@ namespace yask { else oss << "_get_halo_to_" << my_rank << "_from_" << rn; - Grid_NXYZ* bp = mpiBufs[gname].makeBuf(bd, - rdn+1, rdx+1, rdy+1, rdz+1, - bsn, bsx, bsy, bsz, + Grid_WXYZ* bp = mpiBufs[gname].makeBuf(bd, + rdw+1, rdx+1, rdy+1, rdz+1, + bsw, bsx, bsy, bsz, oss.str()); num_bytes += bp->get_num_bytes(); num_exchanges++; @@ -595,7 +595,7 @@ namespace yask { } os << "Number of halo exchanges from this rank: " << num_exchanges << endl; os << "Problem-domain offsets of this rank: " << - ofs_n << ", " << ofs_x << ", " << ofs_y << ", " << ofs_z << endl; + ofs_w << ", " << ofs_x << ", " << ofs_y << ", " << ofs_z << endl; } // Allocate memory for grids, params, and MPI bufs. @@ -613,15 +613,15 @@ namespace yask { for (auto gp : gridPtrs) { // set grid sizes from settings. - gp->set_dn(_opts->dn); + gp->set_dw(_opts->dw); gp->set_dx(_opts->dx); gp->set_dy(_opts->dy); gp->set_dz(_opts->dz); - gp->set_pad_n(_opts->pn); + gp->set_pad_w(_opts->pw); gp->set_pad_x(_opts->px); gp->set_pad_y(_opts->py); gp->set_pad_z(_opts->pz); - gp->set_ofs_n(ofs_n); + gp->set_ofs_w(ofs_w); gp->set_ofs_x(ofs_x); gp->set_ofs_y(ofs_y); gp->set_ofs_z(ofs_z); @@ -664,10 +664,10 @@ namespace yask { // Visit buffers for each neighbor for this grid. mpiBufs[gname].visitNeighbors (*this, false, - [&](idx_t nn, idx_t nx, idx_t ny, idx_t nz, + [&](idx_t nw, idx_t nx, idx_t ny, idx_t nz, int rank, - Grid_NXYZ* sendBuf, - Grid_NXYZ* rcvBuf) + Grid_WXYZ* sendBuf, + Grid_WXYZ* rcvBuf) { // Send. if (_data_buf) @@ -760,15 +760,6 @@ namespace yask { exit_yask(1); } - // TODO: check all dims. -#ifndef USING_DIM_N - if (_opts->dn > 1) { - cerr << "error: dn = " << _opts->dn << ", but stencil '" - YASK_STENCIL_NAME "' doesn't use dimension 'n'." << endl; - exit_yask(1); - } -#endif - os << endl; os << "Num grids: " << gridPtrs.size() << endl; os << "Num grids to be updated: " << outputGridPtrs.size() << endl; @@ -788,32 +779,32 @@ namespace yask { // Report some stats. idx_t dt = _opts->dt; - os << "\nSizes in points per grid (t*n*x*y*z):\n" + os << "\nSizes in points per grid (t*w*x*y*z):\n" " vector-size: " << - VLEN_T << '*' << VLEN_N << '*' << VLEN_X << '*' << VLEN_Y << '*' << VLEN_Z << endl << + VLEN_T << '*' << VLEN_W << '*' << VLEN_X << '*' << VLEN_Y << '*' << VLEN_Z << endl << " cluster-size: " << - CPTS_T << '*' << CPTS_N << '*' << CPTS_X << '*' << CPTS_Y << '*' << CPTS_Z << endl << + CPTS_T << '*' << CPTS_W << '*' << CPTS_X << '*' << CPTS_Y << '*' << CPTS_Z << endl << " block-size: " << - _opts->bt << '*' << _opts->bn << '*' << _opts->bx << '*' << _opts->by << '*' << _opts->bz << endl << + _opts->bt << '*' << _opts->bw << '*' << _opts->bx << '*' << _opts->by << '*' << _opts->bz << endl << " block-group-size: 1*" << - _opts->gn << '*' << _opts->gx << '*' << _opts->gy << '*' << _opts->gz << endl << + _opts->gw << '*' << _opts->gx << '*' << _opts->gy << '*' << _opts->gz << endl << " region-size: " << - _opts->rt << '*' << _opts->rn << '*' << _opts->rx << '*' << _opts->ry << '*' << _opts->rz << endl << + _opts->rt << '*' << _opts->rw << '*' << _opts->rx << '*' << _opts->ry << '*' << _opts->rz << endl << " rank-domain-size: " << - dt << '*' << _opts->dn << '*' << _opts->dx << '*' << _opts->dy << '*' << _opts->dz << endl << + dt << '*' << _opts->dw << '*' << _opts->dx << '*' << _opts->dy << '*' << _opts->dz << endl << " problem-size: " << - dt << '*' << tot_n << '*' << tot_x << '*' << tot_y << '*' << tot_z << endl << + dt << '*' << tot_w << '*' << tot_x << '*' << tot_y << '*' << tot_z << endl << endl << "Other settings:\n" " stencil-name: " YASK_STENCIL_NAME << endl << " num-ranks: " << - _opts->nrn << '*' << _opts->nrx << '*' << _opts->nry << '*' << _opts->nrz << endl << + _opts->nrw << '*' << _opts->nrx << '*' << _opts->nry << '*' << _opts->nrz << endl << " vector-len: " << VLEN << endl << " extra-padding: " << - _opts->pn << '+' << _opts->px << '+' << _opts->py << '+' << _opts->pz << endl << + _opts->pw << '+' << _opts->px << '+' << _opts->py << '+' << _opts->pz << endl << " max-wave-front-angles: " << - angle_n << '+' << angle_x << '+' << angle_y << '+' << angle_z << endl << - " max-halos: " << hn << '+' << hx << '+' << hy << '+' << hz << endl << + angle_w << '+' << angle_x << '+' << angle_y << '+' << angle_z << endl << + " max-halos: " << hw << '+' << hx << '+' << hy << '+' << hz << endl << " manual-L1-prefetch-distance: " << PFDL1 << endl << " manual-L2-prefetch-distance: " << PFDL2 << endl << endl; @@ -828,7 +819,7 @@ namespace yask { rank_numFpOps_1t += fpops_domain; os << "Stats for equation-group '" << eg->get_name() << "':\n" << " sub-domain size: " << - eg->len_bbn << '*' << eg->len_bbx << '*' << eg->len_bby << '*' << eg->len_bbz << endl << + eg->len_bbw << '*' << eg->len_bbx << '*' << eg->len_bby << '*' << eg->len_bbz << endl << " valid points in sub domain: " << printWithPow10Multiplier(eg->bb_num_points) << endl << " grid-updates per point: " << updates1 << endl << " grid-updates in sub-domain: " << printWithPow10Multiplier(updates_domain) << endl << @@ -853,7 +844,7 @@ namespace yask { tot_numFpOps_1t = sumOverRanks(rank_numFpOps_1t, comm); tot_numFpOps_dt = tot_numFpOps_1t * dt; - rank_domain_1t = _opts->dn * _opts->dx * _opts->dy * _opts->dz; + rank_domain_1t = _opts->dw * _opts->dx * _opts->dy * _opts->dz; rank_domain_dt = rank_domain_1t * dt; tot_domain_1t = sumOverRanks(rank_domain_1t, comm); tot_domain_dt = tot_domain_1t * dt; @@ -889,7 +880,7 @@ namespace yask { printWithPow10Multiplier(tot_numFpOps_dt) << endl << endl << "Notes:\n" << - " problem-size is based on rank-domain sizes specified in command-line (dn * dx * dy * dz).\n" << + " problem-size is based on rank-domain sizes specified in command-line (dw * dx * dy * dz).\n" << " grid-points-updated is based sum of grid-updates-in-sub-domain across equation-group(s).\n" << " est-FP-ops is based on sum of est-FP-ops-in-sub-domain across equation-group(s).\n" << endl; @@ -954,7 +945,7 @@ namespace yask { // Init overall BB. // Init min vars w/max val and vice-versa. - begin_bbn = idx_max; end_bbn = idx_min; + begin_bbw = idx_max; end_bbw = idx_min; begin_bbx = idx_max; end_bbx = idx_min; begin_bby = idx_max; end_bby = idx_min; begin_bbz = idx_max; end_bbz = idx_min; @@ -963,11 +954,11 @@ namespace yask { for (auto eg : eqGroups) { eg->find_bounding_box(); - begin_bbn = min(begin_bbn, eg->begin_bbn); + begin_bbw = min(begin_bbw, eg->begin_bbw); begin_bbx = min(begin_bbx, eg->begin_bbx); begin_bby = min(begin_bby, eg->begin_bby); begin_bbz = min(begin_bbz, eg->begin_bbz); - end_bbn = max(end_bbn, eg->end_bbn); + end_bbw = max(end_bbw, eg->end_bbw); end_bbx = max(end_bbx, eg->end_bbx); end_bby = max(end_bby, eg->end_bby); end_bbz = max(end_bbz, eg->end_bbz); @@ -979,13 +970,13 @@ namespace yask { bb_simple = false; // Adjust region size to be within BB. - _opts->rn = min(_opts->rn, len_bbn); + _opts->rw = min(_opts->rw, len_bbw); _opts->rx = min(_opts->rx, len_bbx); _opts->ry = min(_opts->ry, len_bby); _opts->rz = min(_opts->rz, len_bbz); // Adjust block size to be within region. - _opts->bn = min(_opts->bn, _opts->rn); + _opts->bw = min(_opts->bw, _opts->rw); _opts->bx = min(_opts->bx, _opts->rx); _opts->by = min(_opts->by, _opts->ry); _opts->bz = min(_opts->bz, _opts->rz); @@ -996,7 +987,7 @@ namespace yask { // if the region size is less than the rank size, i.e., if the // region covers the whole rank in a given dimension, no wave-front // is needed in thar dim. - angle_n = (_opts->rn < len_bbn) ? ROUND_UP(hn, CPTS_N) : 0; + angle_w = (_opts->rw < len_bbw) ? ROUND_UP(hw, CPTS_W) : 0; angle_x = (_opts->rx < len_bbx) ? ROUND_UP(hx, CPTS_X) : 0; angle_y = (_opts->ry < len_bby) ? ROUND_UP(hy, CPTS_Y) : 0; angle_z = (_opts->rz < len_bbz) ? ROUND_UP(hz, CPTS_Z) : 0; @@ -1010,7 +1001,7 @@ namespace yask { ostream& os = context.get_ostr(); // Init min vars w/max val and vice-versa. - idx_t minn = idx_max, maxn = idx_min; + idx_t minw = idx_max, maxw = idx_min; idx_t minx = idx_max, maxx = idx_min; idx_t miny = idx_max, maxy = idx_min; idx_t minz = idx_max, maxz = idx_min; @@ -1023,18 +1014,18 @@ namespace yask { // Loop through 4D space. // Find the min and max valid points in this space. #pragma omp parallel for collapse(4) \ - reduction(min:minn,minx,miny,minz) \ - reduction(max:maxn,maxx,maxy,maxz) \ + reduction(min:minw,minx,miny,minz) \ + reduction(max:maxw,maxx,maxy,maxz) \ reduction(+:npts) - for (idx_t n = context.ofs_n; n < context.ofs_n + opts.dn; n++) + for (idx_t w = context.ofs_w; w < context.ofs_w + opts.dw; w++) for(idx_t x = context.ofs_x; x < context.ofs_x + opts.dx; x++) for(idx_t y = context.ofs_y; y < context.ofs_y + opts.dy; y++) for(idx_t z = context.ofs_z; z < context.ofs_z + opts.dz; z++) { // Update only if point in domain for this eq group. - if (is_in_valid_domain(t, n, x, y, z)) { - minn = min(minn, n); - maxn = max(maxn, n); + if (is_in_valid_domain(t, w, x, y, z)) { + minw = min(minw, w); + maxw = max(maxw, w); minx = min(minx, x); maxx = max(maxx, x); miny = min(miny, y); @@ -1047,8 +1038,8 @@ namespace yask { // Set begin vars to min indices and end vars to one beyond max indices. if (npts) { - begin_bbn = minn; - end_bbn = maxn + 1; + begin_bbw = minw; + end_bbw = maxw + 1; begin_bbx = minx; end_bbx = maxx + 1; begin_bby = miny; @@ -1056,7 +1047,7 @@ namespace yask { begin_bbz = minz; end_bbz = maxz + 1; } else { - begin_bbn = end_bbn = 0; + begin_bbw = end_bbw = 0; begin_bbx = end_bbx = 0; begin_bby = end_bby = 0; begin_bbz = end_bbz = 0; @@ -1075,7 +1066,7 @@ namespace yask { } // Lengths are cluster-length multiples? - else if (len_bbn % CLEN_N || + else if (len_bbw % CLEN_W || len_bbx % CLEN_X || len_bby % CLEN_Y || len_bbz % CLEN_Z) { @@ -1086,7 +1077,7 @@ namespace yask { } // Edges are cluster-length multiples? - else if (begin_bbn % CLEN_N || + else if (begin_bbw % CLEN_W || begin_bbx % CLEN_X || begin_bby % CLEN_Y || begin_bbz % CLEN_Z) { @@ -1111,8 +1102,8 @@ namespace yask { // These vars control blocking within halo packing. // Currently, only zv has a loop in the calc_halo macros below. - // Thus, step_{n,x,y}v must be 1. - const idx_t step_nv = 1; + // Thus, step_{w,x,y}v must be 1. + const idx_t step_wv = 1; const idx_t step_xv = 1; const idx_t step_yv = 1; #ifndef HALO_STEP_ZV @@ -1121,7 +1112,7 @@ namespace yask { const idx_t step_zv = HALO_STEP_ZV; // Groups in halo loops are set to smallest size. - const idx_t group_size_nv = 1; + const idx_t group_size_wv = 1; const idx_t group_size_xv = 1; const idx_t group_size_yv = 1; const idx_t group_size_zv = 1; @@ -1163,7 +1154,7 @@ namespace yask { // to vector lengths because the halo exchange only works with // whole vectors. TODO: make this more efficient for halos that // are not vector-length multiples. - idx_t ghn = ROUND_UP(gp->get_halo_n(), VLEN_N); + idx_t ghw = ROUND_UP(gp->get_halo_w(), VLEN_W); idx_t ghx = ROUND_UP(gp->get_halo_x(), VLEN_X); idx_t ghy = ROUND_UP(gp->get_halo_y(), VLEN_Y); idx_t ghz = ROUND_UP(gp->get_halo_z(), VLEN_Z); @@ -1172,10 +1163,10 @@ namespace yask { int ni = 0; mpiBufs[gname].visitNeighbors (*this, false, - [&](idx_t nn, idx_t nx, idx_t ny, idx_t nz, + [&](idx_t nw, idx_t nx, idx_t ny, idx_t nz, int neighbor_rank, - Grid_NXYZ* sendBuf, - Grid_NXYZ* rcvBuf) + Grid_WXYZ* sendBuf, + Grid_WXYZ* rcvBuf) { ni++; @@ -1189,16 +1180,16 @@ namespace yask { } // Common code for pack (and send) and unpack. - else { + if (hi == halo_isend || hi == halo_unpack) { // Set begin/end vars to indicate what part // of main grid to read from or write to. // Init range to whole rank domain (inside halos). - idx_t begin_n = 0; + idx_t begin_w = 0; idx_t begin_x = 0; idx_t begin_y = 0; idx_t begin_z = 0; - idx_t end_n = opts.dn; + idx_t end_w = opts.dw; idx_t end_x = opts.dx; idx_t end_y = opts.dy; idx_t end_z = opts.dz; @@ -1207,10 +1198,10 @@ namespace yask { if (hi == halo_isend) { // Modify begin and/or end based on direction. - if (nn == idx_t(MPIBufs::rank_prev)) // neighbor is prev N. - end_n = ghn; // read first halo-width only. - if (nn == idx_t(MPIBufs::rank_next)) // neighbor is next N. - begin_n = opts.dn - ghn; // read last halo-width only. + if (nw == idx_t(MPIBufs::rank_prev)) // neighbor is prev W. + end_w = ghw; // read first halo-width only. + if (nw == idx_t(MPIBufs::rank_next)) // neighbor is next W. + begin_w = opts.dw - ghw; // read last halo-width only. if (nx == idx_t(MPIBufs::rank_prev)) // neighbor is on left. end_x = ghx; if (nx == idx_t(MPIBufs::rank_next)) // neighbor is on right. @@ -1229,13 +1220,13 @@ namespace yask { else if (hi == halo_unpack) { // Modify begin and/or end based on direction. - if (nn == idx_t(MPIBufs::rank_prev)) { // neighbor is prev N. - begin_n = -ghn; // begin at outside of halo. - end_n = 0; // end at inside of halo. + if (nw == idx_t(MPIBufs::rank_prev)) { // neighbor is prev W. + begin_w = -ghw; // begin at outside of halo. + end_w = 0; // end at inside of halo. } - if (nn == idx_t(MPIBufs::rank_next)) { // neighbor is next N. - begin_n = opts.dn; // begin at inside of halo. - end_n = opts.dn + ghn; // end of outside of halo. + if (nw == idx_t(MPIBufs::rank_next)) { // neighbor is next W. + begin_w = opts.dw; // begin at inside of halo. + end_w = opts.dw + ghw; // end of outside of halo. } if (nx == idx_t(MPIBufs::rank_prev)) { // neighbor is on left. begin_x = -ghx; @@ -1265,11 +1256,11 @@ namespace yask { // Add offsets and divide indices by vector // lengths. Use idiv_flr() because indices may be neg (in halo). - idx_t begin_nv = idiv_flr(ofs_n + begin_n, VLEN_N); + idx_t begin_wv = idiv_flr(ofs_w + begin_w, VLEN_W); idx_t begin_xv = idiv_flr(ofs_x + begin_x, VLEN_X); idx_t begin_yv = idiv_flr(ofs_y + begin_y, VLEN_Y); idx_t begin_zv = idiv_flr(ofs_z + begin_z, VLEN_Z); - idx_t end_nv = idiv_flr(ofs_n + end_n, VLEN_N); + idx_t end_wv = idiv_flr(ofs_w + end_w, VLEN_W); idx_t end_xv = idiv_flr(ofs_x + end_x, VLEN_X); idx_t end_yv = idiv_flr(ofs_y + end_y, VLEN_Y); idx_t end_zv = idiv_flr(ofs_z + end_z, VLEN_Z); @@ -1294,25 +1285,25 @@ namespace yask { // Define calc_halo to copy data between main grid and MPI buffer. // Add a short loop in z-dim to increase work done in halo loop. #define calc_halo(t, \ - start_nv, start_xv, start_yv, start_zv, \ - stop_nv, stop_xv, stop_yv, stop_zv) do { \ - idx_t nv = start_nv; \ + start_wv, start_xv, start_yv, start_zv, \ + stop_wv, stop_xv, stop_yv, stop_zv) do { \ + idx_t wv = start_wv; \ idx_t xv = start_xv; \ idx_t yv = start_yv; \ idx_t izv = index_zv * step_zv; \ if (hi == halo_isend) { \ for (idx_t zv = start_zv; zv < stop_zv; zv++) { \ - real_vec_t hval = gp->readVecNorm_TNXYZ(t, nv, xv, yv, zv, \ + real_vec_t hval = gp->readVecNorm_TWXYZ(t, wv, xv, yv, zv, \ __LINE__); \ - sendBuf->writeVecNorm(hval, index_nv, index_xv, index_yv, izv++, \ + sendBuf->writeVecNorm(hval, index_wv, index_xv, index_yv, izv++, \ __LINE__); \ } \ } else if (hi == halo_unpack) { \ for (idx_t zv = start_zv; zv < stop_zv; zv++) { \ real_vec_t hval = \ - rcvBuf->readVecNorm(index_nv, index_xv, index_yv, izv++, \ + rcvBuf->readVecNorm(index_wv, index_xv, index_yv, izv++, \ __LINE__); \ - gp->writeVecNorm_TNXYZ(hval, t, nv, xv, yv, zv, \ + gp->writeVecNorm_TWXYZ(hval, t, wv, xv, yv, zv, \ __LINE__); \ } } } while(0) @@ -1368,21 +1359,21 @@ namespace yask { // The send and receive buffer pointers may be null if 'null_ok' is true. void MPIBufs::visitNeighbors(StencilContext& context, bool null_ok, - std::function visitor) + Grid_WXYZ* sendBuf, + Grid_WXYZ* rcvBuf)> visitor) { - for (idx_t nn = 0; nn < num_neighbors; nn++) + for (idx_t nw = 0; nw < num_neighbors; nw++) for (idx_t nx = 0; nx < num_neighbors; nx++) for (idx_t ny = 0; ny < num_neighbors; ny++) for (idx_t nz = 0; nz < num_neighbors; nz++) - if (context.my_neighbors[nn][nx][ny][nz] != MPI_PROC_NULL) { - Grid_NXYZ* sendBuf = bufs[bufSend][nn][nx][ny][nz]; - Grid_NXYZ* rcvBuf = bufs[bufRec][nn][nx][ny][nz]; + if (context.my_neighbors[nw][nx][ny][nz] != MPI_PROC_NULL) { + Grid_WXYZ* sendBuf = bufs[bufSend][nw][nx][ny][nz]; + Grid_WXYZ* rcvBuf = bufs[bufRec][nw][nx][ny][nz]; if (null_ok || (sendBuf && rcvBuf)) { - visitor(nn, nx, ny, nz, - context.my_neighbors[nn][nx][ny][nz], + visitor(nw, nx, ny, nz, + context.my_neighbors[nw][nx][ny][nz], sendBuf, rcvBuf); } } @@ -1390,18 +1381,18 @@ namespace yask { // Create new buffer in given direction and size. // Does not yet allocate space in it. - Grid_NXYZ* MPIBufs::makeBuf(int bd, - idx_t nn, idx_t nx, idx_t ny, idx_t nz, - idx_t dn, idx_t dx, idx_t dy, idx_t dz, + Grid_WXYZ* MPIBufs::makeBuf(int bd, + idx_t nw, idx_t nx, idx_t ny, idx_t nz, + idx_t dw, idx_t dx, idx_t dy, idx_t dz, const std::string& name) { TRACE_MSG0(cout, "making MPI buffer '" << name << "' at " << - nn << ", " << nx << ", " << ny << ", " << nz << " with size " << - dn << " * " << dx << " * " << dy << " * " << dz); - auto** gp = getBuf(bd, nn, nx, ny, nz); - *gp = new Grid_NXYZ(name); + nw << ", " << nx << ", " << ny << ", " << nz << " with size " << + dw << " * " << dx << " * " << dy << " * " << dz); + auto** gp = getBuf(bd, nw, nx, ny, nz); + *gp = new Grid_WXYZ(name); assert(*gp); - (*gp)->set_dn(dn); + (*gp)->set_dw(dw); (*gp)->set_dx(dx); (*gp)->set_dy(dy); (*gp)->set_dz(dz); @@ -1429,18 +1420,18 @@ namespace yask { #define ADD_TXYZ_OPTION(name, help, var) \ ADD_XYZ_OPTION(name, help, var); \ ADD_1_OPTION(name, help, " (number of time steps)", var, t) -#define ADD_NXYZ_OPTION(name, help, var) \ +#define ADD_WXYZ_OPTION(name, help, var) \ ADD_XYZ_OPTION(name, help, var); \ - ADD_1_OPTION(name, help, "", var, n) -#define ADD_TNXYZ_OPTION(name, help, var) \ + ADD_1_OPTION(name, help, "", var, w) +#define ADD_TWXYZ_OPTION(name, help, var) \ ADD_TXYZ_OPTION(name, help, var); \ - ADD_1_OPTION(name, help, "", var, n) + ADD_1_OPTION(name, help, "", var, w) -#ifdef USING_DIM_N +#ifdef USING_DIM_W #define ADD_T_DIM_OPTION(name, help, var) \ - ADD_TNXYZ_OPTION(name, help, var) + ADD_TWXYZ_OPTION(name, help, var) #define ADD_DIM_OPTION(name, help, var) \ - ADD_NXYZ_OPTION(name, help, var) + ADD_WXYZ_OPTION(name, help, var) #else #define ADD_T_DIM_OPTION(name, help, var) \ ADD_TXYZ_OPTION(name, help, var) @@ -1543,7 +1534,7 @@ namespace yask { // Round up domain size as needed. dt = roundUp(os, dt, CPTS_T, "rank domain size in t (time steps)"); - dn = roundUp(os, dn, CPTS_N, "rank domain size in n"); + dw = roundUp(os, dw, CPTS_W, "rank domain size in w"); dx = roundUp(os, dx, CPTS_X, "rank domain size in x"); dy = roundUp(os, dy, CPTS_Y, "rank domain size in y"); dz = roundUp(os, dz, CPTS_Z, "rank domain size in z"); @@ -1552,26 +1543,26 @@ namespace yask { // Also fix up region sizes as needed. os << "\nRegions:" << endl; idx_t nrgt = findNumRegions(os, rt, dt, CPTS_T, "t"); - idx_t nrgn = findNumRegions(os, rn, dn, CPTS_N, "n"); + idx_t nrgw = findNumRegions(os, rw, dw, CPTS_W, "w"); idx_t nrgx = findNumRegions(os, rx, dx, CPTS_X, "x"); idx_t nrgy = findNumRegions(os, ry, dy, CPTS_Y, "y"); idx_t nrgz = findNumRegions(os, rz, dz, CPTS_Z, "z"); - idx_t nrg = nrgt * nrgn * nrgx * nrgy * nrgz; + idx_t nrg = nrgt * nrgw * nrgx * nrgy * nrgz; os << " num-regions-per-rank: " << nrg << endl; // Determine num blocks. // Also fix up block sizes as needed. os << "\nBlocks:" << endl; idx_t nbt = findNumBlocks(os, bt, rt, CPTS_T, "t"); - idx_t nbn = findNumBlocks(os, bn, rn, CPTS_N, "n"); + idx_t nbw = findNumBlocks(os, bw, rw, CPTS_W, "w"); idx_t nbx = findNumBlocks(os, bx, rx, CPTS_X, "x"); idx_t nby = findNumBlocks(os, by, ry, CPTS_Y, "y"); idx_t nbz = findNumBlocks(os, bz, rz, CPTS_Z, "z"); - idx_t nb = nbt * nbn * nbx * nby * nbz; + idx_t nb = nbt * nbw * nbx * nby * nbz; os << " num-blocks-per-region: " << nb << endl; // Adjust defaults for block-groups. - if (!gn) gn = bn; + if (!gw) gw = bw; if (!gx) gx = bx; if (!gy) gy = by; if (!gz) gz = bz; @@ -1579,11 +1570,11 @@ namespace yask { // Determine num groups. // Also fix up group sizes as needed. os << "\nBlock-groups:" << endl; - idx_t ngn = findNumGroups(os, gn, rn, bn, "n"); + idx_t ngw = findNumGroups(os, gw, rw, bw, "w"); idx_t ngx = findNumGroups(os, gx, rx, bx, "x"); idx_t ngy = findNumGroups(os, gy, ry, by, "y"); idx_t ngz = findNumGroups(os, gz, rz, bz, "z"); - idx_t ng = ngn * ngx * ngy * ngz; + idx_t ng = ngw * ngx * ngy * ngz; os << " num-block-groups-per-region: " << ng << endl; } diff --git a/src/stencil_calc.hpp b/src/stencil_calc.hpp index 9d42555b..eb0588cc 100644 --- a/src/stencil_calc.hpp +++ b/src/stencil_calc.hpp @@ -33,12 +33,12 @@ IN THE SOFTWARE. // Stencil types. #include "stencil.hpp" -// Macro for automatically adding N dimension's arg. +// Macro for automatically adding W dimension's arg. // TODO: make all args programmatic. -#if USING_DIM_N -#define ARG_N(n) n, +#if USING_DIM_W +#define ARG_W(w) w, #else -#define ARG_N(n) +#define ARG_W(w) #endif namespace yask { @@ -61,7 +61,7 @@ namespace yask { enum BufDir { bufSend, bufRec, nBufDirs }; // A type to store buffers for all possible neighbors. - typedef Grid_NXYZ* NeighborBufs[nBufDirs][num_neighbors][num_neighbors][num_neighbors][num_neighbors]; + typedef Grid_WXYZ* NeighborBufs[nBufDirs][num_neighbors][num_neighbors][num_neighbors][num_neighbors]; NeighborBufs bufs; MPIBufs() { @@ -69,35 +69,36 @@ namespace yask { } // Access a buffer by direction and 4D neighbor indices. - Grid_NXYZ** getBuf(int bd, - idx_t nn, idx_t nx, idx_t ny, idx_t nz) { + Grid_WXYZ** getBuf(int bd, + idx_t nw, idx_t nx, idx_t ny, idx_t nz) { assert(bd >= 0); assert(bd < nBufDirs); - assert(nn >= 0); - assert(nn < num_neighbors); + assert(nw >= 0); + assert(nw < num_neighbors); assert(nx >= 0); assert(nx < num_neighbors); assert(ny >= 0); assert(ny < num_neighbors); assert(nz >= 0); - assert(nn < num_neighbors); - return &bufs[bd][nn][nx][ny][nz]; + assert(nz < num_neighbors); + return &bufs[bd][nw][nx][ny][nz]; } // Apply a function to each neighbor rank. // Called visitor function will contain the rank index of the neighbor. // The send and receive buffer pointers may be null if 'null_ok' is true. + // TODO: remove 'null_ok' when non-symmetrical halos are supported. virtual void visitNeighbors(StencilContext& context, bool null_ok, - std::function visitor); + Grid_WXYZ* sendBuf, + Grid_WXYZ* rcvBuf)> visitor); // Create new buffer in given direction and size. - virtual Grid_NXYZ* makeBuf(int bd, - idx_t nn, idx_t nx, idx_t ny, idx_t nz, - idx_t dn, idx_t dx, idx_t dy, idx_t dz, + virtual Grid_WXYZ* makeBuf(int bd, + idx_t nw, idx_t nx, idx_t ny, idx_t nz, + idx_t dw, idx_t dx, idx_t dy, idx_t dz, const std::string& name); }; @@ -106,17 +107,17 @@ namespace yask { // Sizes in elements (points). // - time sizes (t) are in steps to be done. - // - spatial sizes (n, x, y, z) are in elements (not vectors). + // - spatial sizes (w, x, y, z) are in elements (not vectors). // Sizes are the same for all grids. TODO: relax this restriction. - idx_t dt=1, dn=0, dx=0, dy=0, dz=0; // rank size (without halos). - idx_t rt=1, rn=0, rx=0, ry=0, rz=0; // region size (used for wave-front tiling). - idx_t bt=1, bn=0, bx=0, by=0, bz=0; // block size (used for cache locality). - idx_t gn=0, gx=0, gy=0, gz=0; // group-of-blocks size (only used for 'grouped' loop paths). - idx_t pn=0, px=0, py=0, pz=0; // spatial padding (in addition to halos, to avoid aliasing). + idx_t dt=1, dw=0, dx=0, dy=0, dz=0; // rank size (without halos). + idx_t rt=1, rw=0, rx=0, ry=0, rz=0; // region size (used for wave-front tiling). + idx_t bt=1, bw=0, bx=0, by=0, bz=0; // block size (used for cache locality). + idx_t gw=0, gx=0, gy=0, gz=0; // group-of-blocks size (only used for 'grouped' loop paths). + idx_t pw=0, px=0, py=0, pz=0; // spatial padding (in addition to halos, to avoid aliasing). // MPI settings. - idx_t nrn=1, nrx=1, nry=1, nrz=1; // number of ranks in each dim. - idx_t rin=0, rix=0, riy=0, riz=0; // my rank index in each dim. + idx_t nrw=1, nrx=1, nry=1, nrz=1; // number of ranks in each dim. + idx_t riw=0, rix=0, riy=0, riz=0; // my rank index in each dim. bool find_loc=true; // whether my rank index needs to be calculated. int msg_rank=0; // rank that prints informational messages. @@ -127,9 +128,9 @@ namespace yask { // Ctor. StencilSettings() : - dt(50), dn(1), dx(DEF_RANK_SIZE), dy(DEF_RANK_SIZE), dz(DEF_RANK_SIZE), - bt(1), bn(1), bx(DEF_BLOCK_SIZE), by(DEF_BLOCK_SIZE), bz(DEF_BLOCK_SIZE), - pn(0), px(DEF_PAD), py(DEF_PAD), pz(DEF_PAD), + dt(50), dw(1), dx(DEF_RANK_SIZE), dy(DEF_RANK_SIZE), dz(DEF_RANK_SIZE), + bt(1), bw(1), bx(DEF_BLOCK_SIZE), by(DEF_BLOCK_SIZE), bz(DEF_BLOCK_SIZE), + pw(0), px(DEF_PAD), py(DEF_PAD), pz(DEF_PAD), thread_divisor(DEF_THREAD_DIVISOR), num_block_threads(DEF_BLOCK_THREADS) { @@ -153,9 +154,9 @@ namespace yask { // A 4D bounding-box. struct BoundingBox { - idx_t begin_bbn=0, begin_bbx=0, begin_bby=0, begin_bbz=0; - idx_t end_bbn=1, end_bbx=1, end_bby=1, end_bbz=1; // one past last value. - idx_t len_bbn=1, len_bbx=1, len_bby=1, len_bbz=1; + idx_t begin_bbw=0, begin_bbx=0, begin_bby=0, begin_bbz=0; + idx_t end_bbw=1, end_bbx=1, end_bby=1, end_bbz=1; // one past last value. + idx_t len_bbw=1, len_bbx=1, len_bby=1, len_bbz=1; idx_t bb_size=1; // points in the entire box. idx_t bb_num_points=1; // valid points within the box. bool bb_simple=true; // full box with vector-length sizes. @@ -165,11 +166,11 @@ namespace yask { // Find lengths and set valid to true. virtual void update_lengths() { - len_bbn = end_bbn - begin_bbn; + len_bbw = end_bbw - begin_bbw; len_bbx = end_bbx - begin_bbx; len_bby = end_bby - begin_bby; len_bbz = end_bbz - begin_bbz; - bb_size = len_bbn * len_bbx * len_bby * len_bbz; + bb_size = len_bbw * len_bbx * len_bby * len_bbz; bb_valid = true; } }; @@ -248,13 +249,13 @@ namespace yask { ParamPtrMap paramMap; // Some calculated sizes. - idx_t ofs_t=0, ofs_n=0, ofs_x=0, ofs_y=0, ofs_z=0; // Index offsets for this rank. - idx_t tot_n=0, tot_x=0, tot_y=0, tot_z=0; // Total of rank domains over all ranks. + idx_t ofs_t=0, ofs_w=0, ofs_x=0, ofs_y=0, ofs_z=0; // Index offsets for this rank. + idx_t tot_w=0, tot_x=0, tot_y=0, tot_z=0; // Total of rank domains over all ranks. // Maximum halos and skewing angles over all grids and // equations. Used for calculating worst-case minimum regions. - idx_t hn=0, hx=0, hy=0, hz=0; // spatial halos. - idx_t angle_n=0, angle_x=0, angle_y=0, angle_z=0; // temporal skewing angles. + idx_t hw=0, hx=0, hy=0, hz=0; // spatial halos. + idx_t angle_w=0, angle_x=0, angle_y=0, angle_z=0; // temporal skewing angles. // Various metrics calculated in allocAll(). // 'rank_' prefix indicates for this rank. @@ -424,8 +425,8 @@ namespace yask { // TODO: create a public interface w/a more logical index ordering. virtual void calc_region(idx_t start_dt, idx_t stop_dt, EqGroupSet* eqGroup_set, - idx_t start_dn, idx_t start_dx, idx_t start_dy, idx_t start_dz, - idx_t stop_dn, idx_t stop_dx, idx_t stop_dy, idx_t stop_dz); + idx_t start_dw, idx_t start_dx, idx_t start_dy, idx_t start_dz, + idx_t stop_dw, idx_t stop_dx, idx_t stop_dy, idx_t stop_dz); // Exchange halo data needed by eq-group 'eg' at the given time. virtual void exchange_halos(idx_t start_dt, idx_t stop_dt, EqGroupBase& eg); @@ -479,15 +480,15 @@ namespace yask { virtual void find_bounding_box(); // Determine whether indices are in [sub-]domain. - virtual bool is_in_valid_domain(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z) =0; + virtual bool is_in_valid_domain(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) =0; // Calculate one scalar result at time t. - virtual void calc_scalar(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z) =0; + virtual void calc_scalar(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) =0; // Calculate one block of results from begin to end-1 on each dimension. virtual void calc_block(idx_t bt, - idx_t begin_bn, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, - idx_t end_bn, idx_t end_bx, idx_t end_by, idx_t end_bz) =0; + idx_t begin_bw, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, + idx_t end_bw, idx_t end_bx, idx_t end_by, idx_t end_bz) =0; }; // Define a method named cfn to prefetch a cluster by calling vfn. @@ -495,16 +496,16 @@ namespace yask { template \ ALWAYS_INLINE void \ cfn (idx_t ct, \ - idx_t begin_cnv, idx_t begin_cxv, idx_t begin_cyv, idx_t begin_czv, \ - idx_t end_cnv, idx_t end_cxv, idx_t end_cyv, idx_t end_czv) { \ + idx_t begin_cwv, idx_t begin_cxv, idx_t begin_cyv, idx_t begin_czv, \ + idx_t end_cwv, idx_t end_cxv, idx_t end_cyv, idx_t end_czv) { \ TRACE_MSG2(get_name() << "." #cfn "<" << level << ">(" \ "t=" << ct << \ - ", nv=" << begin_cnv << \ + ", wv=" << begin_cwv << \ ", xv=" << begin_cxv << \ ", yv=" << begin_cyv << \ ", zv=" << begin_czv << ")"); \ _eqGroup.vfn(*_context, ct, \ - ARG_N(begin_cnv) begin_cxv, begin_cyv, begin_czv); \ + ARG_W(begin_cwv) begin_cxv, begin_cyv, begin_czv); \ } // A template that provides wrappers around a stencil-equation class @@ -567,18 +568,18 @@ namespace yask { } // Determine whether indices are in [sub-]domain for this eq group. - virtual bool is_in_valid_domain(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z) { + virtual bool is_in_valid_domain(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) { return _eqGroup.is_in_valid_domain(*_context, t, - ARG_N(n) x, y, z); + ARG_W(w) x, y, z); } // Calculate one scalar result. // This function implements the interface in the base class. - virtual void calc_scalar(idx_t t, idx_t n, idx_t x, idx_t y, idx_t z) { + virtual void calc_scalar(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) { TRACE_MSG2(get_name() << ".calc_scalar(t=" << t << - ", n=" << n << ", x=" << x << + ", w=" << w << ", x=" << x << ", y=" << y << ", z=" << z << ")"); - _eqGroup.calc_scalar(*_context, t, ARG_N(n) x, y, z); + _eqGroup.calc_scalar(*_context, t, ARG_W(w) x, y, z); } // Calculate results within a cluster of vectors. @@ -586,11 +587,11 @@ namespace yask { // The begin/end_c* vars are the start/stop_b* vars from the block loops. ALWAYS_INLINE void calc_cluster(idx_t ct, - idx_t begin_cnv, idx_t begin_cxv, idx_t begin_cyv, idx_t begin_czv, - idx_t end_cnv, idx_t end_cxv, idx_t end_cyv, idx_t end_czv) + idx_t begin_cwv, idx_t begin_cxv, idx_t begin_cyv, idx_t begin_czv, + idx_t end_cwv, idx_t end_cxv, idx_t end_cyv, idx_t end_czv) { TRACE_MSG2("calc_cluster(t=" << ct << - ", nv=" << begin_cnv << ".." << (end_cnv-1) << + ", wv=" << begin_cwv << ".." << (end_cwv-1) << ", xv=" << begin_cxv << ".." << (end_cxv-1) << ", yv=" << begin_cyv << ".." << (end_cyv-1) << ", zv=" << begin_czv << ".." << (end_czv-1) << @@ -600,21 +601,21 @@ namespace yask { // never be a partial step at this level. So, we can assume one var and // exactly CLEN_d steps in each given direction d are calculated in this // function. Thus, we can ignore the end_* vars in the calc function. - assert(end_cnv == begin_cnv + CLEN_N); + assert(end_cwv == begin_cwv + CLEN_W); assert(end_cxv == begin_cxv + CLEN_X); assert(end_cyv == begin_cyv + CLEN_Y); assert(end_czv == begin_czv + CLEN_Z); // Calculate results. - _eqGroup.calc_cluster(*_context, ct, ARG_N(begin_cnv) begin_cxv, begin_cyv, begin_czv); + _eqGroup.calc_cluster(*_context, ct, ARG_W(begin_cwv) begin_cxv, begin_cyv, begin_czv); } // Prefetch a cluster. // Separate methods for full cluster and each direction. // TODO: handle pre-fetching correctly for non-simple BBs. PREFETCH_CLUSTER_METHOD(prefetch_cluster, prefetch_cluster) -#if USING_DIM_N - PREFETCH_CLUSTER_METHOD(prefetch_cluster_bnv, prefetch_cluster_n) +#if USING_DIM_W + PREFETCH_CLUSTER_METHOD(prefetch_cluster_bwv, prefetch_cluster_w) #endif PREFETCH_CLUSTER_METHOD(prefetch_cluster_bxv, prefetch_cluster_x) PREFETCH_CLUSTER_METHOD(prefetch_cluster_byv, prefetch_cluster_y) @@ -626,11 +627,11 @@ namespace yask { // The begin/end_b* vars are the start/stop_r* vars from the region loops. virtual void calc_block(idx_t bt, - idx_t begin_bn, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, - idx_t end_bn, idx_t end_bx, idx_t end_by, idx_t end_bz) + idx_t begin_bw, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, + idx_t end_bw, idx_t end_bx, idx_t end_by, idx_t end_bz) { TRACE_MSG2(get_name() << ".calc_block(t=" << bt << - ", n=" << begin_bn << ".." << (end_bn-1) << + ", w=" << begin_bw << ".." << (end_bw-1) << ", x=" << begin_bx << ".." << (end_bx-1) << ", y=" << begin_by << ".." << (end_by-1) << ", z=" << begin_bz << ".." << (end_bz-1) << @@ -641,7 +642,7 @@ namespace yask { if (!bb_simple) { TRACE_MSG2("...using scalar code."); - for (idx_t n = begin_bn; n < end_bn; n++) + for (idx_t w = begin_bw; w < end_bw; w++) for (idx_t x = begin_bx; x < end_bx; x++) for (idx_t y = begin_by; y < end_by; y++) { @@ -650,15 +651,15 @@ namespace yask { for (idx_t z = begin_bz; z < end_bz; z++) { // Update only if point is in sub-domain for this eq group. - if (is_in_valid_domain(bt, n, x, y, z)) - calc_scalar(bt, n, x, y, z); + if (is_in_valid_domain(bt, w, x, y, z)) + calc_scalar(bt, w, x, y, z); } } // If no holes, don't need to check domain. else { for (idx_t z = begin_bz; z < end_bz; z++) { - calc_scalar(bt, n, x, y, z); + calc_scalar(bt, w, x, y, z); } } } @@ -668,24 +669,24 @@ namespace yask { // Divide indices by vector lengths. Use idiv_flr() instead of '/' // because begin/end vars may be negative (if in halo). - const idx_t begin_bnv = idiv_flr(begin_bn, VLEN_N); + const idx_t begin_bwv = idiv_flr(begin_bw, VLEN_W); const idx_t begin_bxv = idiv_flr(begin_bx, VLEN_X); const idx_t begin_byv = idiv_flr(begin_by, VLEN_Y); const idx_t begin_bzv = idiv_flr(begin_bz, VLEN_Z); - const idx_t end_bnv = idiv_flr(end_bn, VLEN_N); + const idx_t end_bwv = idiv_flr(end_bw, VLEN_W); const idx_t end_bxv = idiv_flr(end_bx, VLEN_X); const idx_t end_byv = idiv_flr(end_by, VLEN_Y); const idx_t end_bzv = idiv_flr(end_bz, VLEN_Z); // Vector-size steps are based on cluster lengths. // Using CLEN_* instead of CPTS_* because we want multiples of vector lengths. - const idx_t step_bnv = CLEN_N; + const idx_t step_bwv = CLEN_W; const idx_t step_bxv = CLEN_X; const idx_t step_byv = CLEN_Y; const idx_t step_bzv = CLEN_Z; // Groups in block loops are set to smallest size. - const idx_t group_size_bnv = 1; + const idx_t group_size_bwv = 1; const idx_t group_size_bxv = 1; const idx_t group_size_byv = 1; const idx_t group_size_bzv = 1; diff --git a/src/stencil_main.cpp b/src/stencil_main.cpp index e09bd4dc..2f0024e5 100644 --- a/src/stencil_main.cpp +++ b/src/stencil_main.cpp @@ -57,7 +57,11 @@ struct AppSettings : public StencilSettings { public: ValOption(AppSettings& as) : OptionBase("v", - "Shortcut for -validate -no-warmup -t 1 -dt 1 -d 64 -b 24."), + "Shortcut for '-validate -no-warmup -t 1 -dt 1 -d 64" +#if USING_DIM_W + " -dw 3" +#endif + " -b 24'."), _as(as) { } // Set multiple vars. @@ -68,6 +72,9 @@ struct AppSettings : public StencilSettings { _as.doWarmup = false; _as.num_trials = 1; _as.dt = 1; +#if USING_DIM_W + _as.dw = 3; +#endif _as.dx = _as.dy = _as.dz = 64; _as.bx = _as.by = _as.bz = 24; return true; diff --git a/stencil-tuner.pl b/stencil-tuner.pl index 7caabc93..92bdd32e 100755 --- a/stencil-tuner.pl +++ b/stencil-tuner.pl @@ -63,6 +63,7 @@ my $zVec = 0; # Force 1D vectorization in 'z' direction. my $folding = 1; # 2D & 3D folding allowed. my $dp; # double precision. +my $dw = 1; # 'w' dimension (fixed). my $makeArgs = ''; # extra make arguments. my $makePrefix = ''; # prefix for make. my $runArgs = ''; # extra run arguments. @@ -115,6 +116,8 @@ sub usage { " See the notes above on specification.\n". " -folds= Comma separated list of folds to use.\n". " Examples: '-folds=4 4 1', '-folds=1 1 16, 4 4 1, 1 4 4'.\n". + " Can only specify 3D folds.\n". + " -dw= Set size of 'w' dim to (only for 4D problems).\n". " -mem=- Set allowable est. memory usage between and GiB (default is $minGB-$maxGB).\n". " -maxVecsInCluster= Maximum vectors allowed in cluster (default is $maxVecsInCluster).\n". " -noPrefetch Disable any prefetching (shortcut for '-pfdl1=0 -pfdl2=0').\n". @@ -195,6 +198,9 @@ sub usage { $minGB = $1; $maxGB = $2; } + elsif ($opt =~ '^-dw=(\d+)$') { + $dw = $1; + } elsif ($opt =~ '^-radius=(\d+)$') { $radius = $1; } @@ -286,8 +292,8 @@ sub usage { # radius. $radius = $isAve ? 1 : 8 if !defined $radius; -# n dimension. -my $vars = $isAve ? 40 : 1; +# w dimension. +$dw = $isAve ? 40 : 1 if !defined $dw; # 40 grids in miniGhost. # disable folding for DP MIC (no valignq). $folding = 0 if (defined $mic && $dp); @@ -384,17 +390,17 @@ sub usage { @layouts = grep /4$/, @layouts if $zLayout; # list of possible loop orders. -# start with n on outer loop only. +# start with w on outer loop only. my @loopOrders = - ('nxyz', 'nxzy', 'nyxz', 'nyzx', 'nzxy', 'nzyx'); + ('wxyz', 'wxzy', 'wyxz', 'wyzx', 'wzxy', 'wzyx'); -# add more options if there are >1 var, i.e., 'n' +# add more options if there are >1 var, i.e., 'w' # is meaningful. push @loopOrders, - ('xnyz', 'xnzy', 'xynz', 'xyzn', 'xzny', 'xzyn', - 'ynxz', 'ynzx', 'yxnz', 'yxzn', 'yznx', 'yzxn', - 'znxy', 'znyx', 'zxny', 'zxyn', 'zynx', 'zyxn', ) - if $vars > 1; + ('xwyz', 'xwzy', 'xywz', 'xyzw', 'xzwy', 'xzyw', + 'ywxz', 'ywzx', 'yxwz', 'yxzw', 'yzwx', 'yzxw', + 'zwxy', 'zwyx', 'zxwy', 'zxyw', 'zywx', 'zyxw', ) + if $dw > 1; # only allow z in inner loop if requested. @loopOrders = grep /z$/, @loopOrders if $zLoop; @@ -466,7 +472,7 @@ sub usage { push @folds, ("1 $velems 1", "$velems 1 1") if !$zVec; # add remaining options if folding. -# TODO: add n-dim folding. +# TODO: add w-dim folding. push @folds, ($velems == 8) ? ("4 2 1", "4 1 2", "2 4 1", "2 1 4", @@ -801,12 +807,13 @@ ($$$) my $pads = shift; # ref to pad array. my $mults = shift; # ref to array of multiples. - # need to determine how many grids will be allocated for this stencil. + # need to determine how many XYZ grids will be allocated for this stencil. if (!$numSpatialGrids) { my $makeCmd = getMakeCmd('', 'EXTRA_CXXFLAGS=-O0'); my $runCmd = getRunCmd(); $runCmd .= ' -t 0 -d 1'; + $runCmd .= " -dw $dw" if $dw > 1; my $cmd = "$makeCmd 2>&1 && $runCmd"; my $timeDim = 0; @@ -821,11 +828,17 @@ ($$$) # E.g., # 4D (t=1 * x=8 * y=1 * z=1) 'vel_x' data is at 0x7fce08200000: 1.176K element(s) of 4 byte(s) each, 147 vector(s), 4.59375KiB. # 3D (x=8 * y=1 * z=1) 'lambda' data is at 0x7fce0820f880: 600 element(s) of 4 byte(s) each, 75 vector(s), 2.34375KiB. - if (/^\s*4D.*t=(\d+)/) { - $numSpatialGrids += $1; + if (/^\s*5D.*t=(\d+).*w=(\d+)/) { + $numSpatialGrids += $1 * $2; # twxyz + } + elsif (/^\s*4D.*w=(\d+)/) { + $numSpatialGrids += $1; # wxyz + } + elsif (/^\s*4D.*t=(\d+)/) { + $numSpatialGrids += $1; # txyz. } elsif (/^\s*3D.*x=/) { - $numSpatialGrids += 1; + $numSpatialGrids += 1; # xyz. } } close CMD; @@ -833,11 +846,10 @@ ($$$) map { print ">> $_"; } @cmdOut; die "error: no grids defined in '$cmd'.\n"; } - print "Determined that $numSpatialGrids spatial grids are allocated.\n"; + print "Determined that $numSpatialGrids XYZ grids are allocated.\n"; } - # actual dimensions of allocated memory. - # TODO: fix over-estimate due to assumption of max halo size. + # estimate each dim of allocated memory as size + 2 * (halo + pad). my @sizes = map { roundUp($sizes->[$_], $mults->[$_]) + 2 * roundUp($radius + $pads->[$_], $mults->[$_]) } 0..$#dirs; @@ -891,13 +903,13 @@ ($$) my $val = $1; # adjust for suffixes. - if ($val =~ /^([0-9.e+-]+)Ki$/) { + if ($val =~ /^([0-9.e+-]+)KiB?$/) { $val = $1 * $oneKi; - } elsif ($val =~ /^([0-9.e+-]+)Mi$/) { + } elsif ($val =~ /^([0-9.e+-]+)MiB?$/) { $val = $1 * $oneMi; - } elsif ($val =~ /^([0-9.e+-]+)Gi$/) { + } elsif ($val =~ /^([0-9.e+-]+)GiB?$/) { $val = $1 * $oneGi; - } elsif ($val =~ /^([0-9.e+-]+)Ti$/) { + } elsif ($val =~ /^([0-9.e+-]+)TiB?$/) { $val = $1 * $oneTi; } elsif ($val =~ /^([0-9.e+-]+)K$/) { $val = $1 * $oneK; @@ -1104,8 +1116,8 @@ ($$$$$) my $order = readHash($h, $nameLong."LoopOrder", 1); my $type = readHash($h, $nameLong."Loop", 1); - my $dims = $loopOrders[$order]; # e.g., 'nyxz' (outer-to-inner). - my @dims = split '',$dims; # e.g., ('n', 'y', 'x', 'z'); + my $dims = $loopOrders[$order]; # e.g., 'wyxz' (outer-to-inner). + my @dims = split '',$dims; # e.g., ('w', 'y', 'x', 'z'); my $code = $types->[$type]; # e.g., 'loop(D0) { ... }'. for my $ld (0..$#dims) { $dims[$ld] = "$varPrefix$dims[$ld]$varSuffix"; # e.g., 'bnv'. @@ -1378,9 +1390,7 @@ sub fitness { $g3d =~ s/3/2/; # move 'y' from posn 3 to 2. $g3d =~ s/4/3/; # move 'z' from posn 4 to 3. $mvars .= " layout_xyz=Layout_$g3d layout_txyz=Layout_$g4d"; - if ($vars > 1) { - $mvars .= " layout_nxyz=Layout_4$g3d layout_tnxyz=Layout_5$g4d"; - } + $mvars .= " layout_wxyz=Layout_4$g3d layout_twxyz=Layout_5$g4d" if $dw > 1; # prefetch distances. if ($pfdl1 > 0 && $pfdl2 > 0) { @@ -1419,7 +1429,7 @@ sub fitness { $args .= " -block_threads ".(1 << $bthreads_exp); # sizes. - $args .= " -dn $vars" if $vars > 1; + $args .= " -dw $dw" if $dw > 1; $args .= " -dx $ds[0] -dy $ds[1] -dz $ds[2]"; $args .= " -rx $rs[0] -ry $rs[1] -rz $rs[2]"; $args .= " -bx $bs[0] -by $bs[1] -bz $bs[2]"; @@ -1427,8 +1437,8 @@ sub fitness { $args .= " -px $ps[0] -py $ps[1] -pz $ps[2]"; # num of iterations and trials. - my $shortIters = ($vars > 10) ? 2 : 5; - my $longIters = ($vars > 10) ? 15 : 30; + my $shortIters = 5; + my $longIters = 30; my $longTrials = min($gen, 2); # various commands. From 487b9c4beb7e9a5efe94317e9deb806915471765 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Wed, 1 Mar 2017 12:26:17 -0700 Subject: [PATCH 02/20] auto-generate stencil include file. --- Makefile | 13 ++++++++++--- src/foldBuilder/main.cpp | 10 ++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 8996d0ef..ed4afed9 100644 --- a/Makefile +++ b/Makefile @@ -250,6 +250,7 @@ EXTRA_FB_CXXFLAGS = FB_FLAGS += -st $(stencil) -cluster $(cluster) -fold $(fold) ST_MACRO_FILE := stencil_macros.hpp ST_CODE_FILE := stencil_code.hpp +FB_STENCIL_LIST := src/foldBuilder/stencils.hpp GEN_HEADERS = $(addprefix src/, \ stencil_rank_loops.hpp \ stencil_region_loops.hpp \ @@ -486,9 +487,15 @@ src/layouts.hpp: gen-layouts.pl # Compile the stencil compiler. # TODO: move this to its own makefile. -foldBuilder: src/foldBuilder/*.*pp src/foldBuilder/stencils/*.*pp +foldBuilder: src/foldBuilder/*.*pp src/foldBuilder/stencils/*.*pp $(FB_STENCIL_LIST) $(FB_CXX) $(FB_CXXFLAGS) -Isrc/foldBuilder/stencils -o $@ src/foldBuilder/*.cpp $(EXTRA_FB_CXXFLAGS) +$(FB_STENCIL_LIST): src/foldBuilder/stencils/*.hpp + @- rm -f $@ + for sfile in $(^F); do \ + echo '#include "'$$sfile'"' >> $@; \ + done + # Run the stencil compiler and post-process its output files. # Use the gmake pattern-rule trick to specify simultaneous targets. %/$(ST_MACRO_FILE) %/$(ST_CODE_FILE): foldBuilder @@ -503,7 +510,7 @@ foldBuilder: src/foldBuilder/*.*pp src/foldBuilder/stencils/*.*pp indent -fca $*/$(ST_CODE_FILE) || \ echo "note:" $*/$(ST_CODE_FILE) "not formatted." -headers: $(GEN_HEADERS) +headers: $(GEN_HEADERS) $(FB_STENCIL_LIST) @ echo 'Header files generated.' %.$(arch).o: %.cpp src/*.hpp src/foldBuilder/*.hpp $(GEN_HEADERS) @@ -519,7 +526,7 @@ clean: rm -fv src/*.[io] *.optrpt src/*.optrpt *.s $(GEN_HEADERS) $(MAKE_REPORT_FILE) realclean: clean - rm -fv stencil*.exe foldBuilder TAGS $(MAKE_REPORT_FILE) $(CXXFLAGS_FILE) $(LFLAGS_FILE) + rm -fv stencil*.exe foldBuilder TAGS $(MAKE_REPORT_FILE) $(CXXFLAGS_FILE) $(LFLAGS_FILE) $(FB_STENCIL_LIST) find . -name '*~' | xargs -r rm -v help: diff --git a/src/foldBuilder/main.cpp b/src/foldBuilder/main.cpp index 4ca4655d..5a58f7c6 100644 --- a/src/foldBuilder/main.cpp +++ b/src/foldBuilder/main.cpp @@ -35,15 +35,9 @@ StencilList stencils; #define REGISTER_STENCIL(Class) static Class registered_ ## Class(stencils) // Stencils. -#include "ExampleStencil.hpp" -#include "Iso3dfdStencil.hpp" -#include "AveStencil.hpp" -#include "AwpStencil.hpp" -#include "AwpElasticStencil.hpp" -#include "StreamStencil.hpp" -#include "SSGElasticStencil.hpp" -#include "FSGElasticStencil.hpp" +#include "stencils.hpp" +// Misc headers. #include // output streams. From c4981418014fbaff027f8a1b9f3b705bc19476c1 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Wed, 8 Mar 2017 11:01:45 -0700 Subject: [PATCH 03/20] Change IS_EQUIV_TO to simply EQUALS. IS_EQUIV_TO kept for backward-compat. Also, some comment cleanup in idiv. --- src/foldBuilder/Expr.cpp | 10 ++-- src/foldBuilder/Expr.hpp | 15 +++--- src/foldBuilder/Print.hpp | 2 +- src/foldBuilder/stencils/AveStencil.hpp | 2 +- .../stencils/AwpElasticStencil.hpp | 54 +++++++++---------- src/foldBuilder/stencils/AwpStencil.hpp | 30 +++++------ .../ElasticStencil/ElasticStencil.hpp | 4 +- src/foldBuilder/stencils/ExampleStencil.hpp | 2 +- .../stencils/FSGElasticStencil.hpp | 38 ++++++------- src/foldBuilder/stencils/Iso3dfdStencil.hpp | 49 +++++++++-------- src/foldBuilder/stencils/StreamStencil.hpp | 2 +- src/idiv.hpp | 5 ++ 12 files changed, 113 insertions(+), 100 deletions(-) diff --git a/src/foldBuilder/Expr.cpp b/src/foldBuilder/Expr.cpp index 3c266b16..d94c4864 100644 --- a/src/foldBuilder/Expr.cpp +++ b/src/foldBuilder/Expr.cpp @@ -198,7 +198,7 @@ IfExprPtr operator IF_OPER(EqualsExprPtr expr, const BoolExprPtr cond) { } // Define the value of a grid point. -EqualsExprPtr operator EQUIV_OPER(GridPointPtr gpp, const NumExprPtr rhs) { +EqualsExprPtr operator EQUALS_OPER(GridPointPtr gpp, const NumExprPtr rhs) { // Get grid referenced by the expr. assert(gpp); @@ -220,8 +220,8 @@ EqualsExprPtr operator EQUIV_OPER(GridPointPtr gpp, const NumExprPtr rhs) { return expr; } -EqualsExprPtr operator EQUIV_OPER(GridPointPtr gpp, double rhs) { - return gpp EQUIV_OPER constNum(rhs); +EqualsExprPtr operator EQUALS_OPER(GridPointPtr gpp, double rhs) { + return gpp EQUALS_OPER constNum(rhs); } // Visitor acceptors. @@ -807,8 +807,8 @@ void Grids::findDeps(IntTuple& pts, // may or may not be legal. // // Example: - // eq1: a(t+1, x, ...) IS_EQUIV_TO ... IF ... - // eq2: b(t+1, x, ...) IS_EQUIV_TO a(t+1, x+5, ...) ... IF ... + // eq1: a(t+1, x, ...) EQUALS ... IF ... + // eq2: b(t+1, x, ...) EQUALS a(t+1, x+5, ...) ... IF ... // // TODO: be much smarter about this and find only real // dependencies--use a polyhedral library? diff --git a/src/foldBuilder/Expr.hpp b/src/foldBuilder/Expr.hpp index 739e44e1..00558559 100644 --- a/src/foldBuilder/Expr.hpp +++ b/src/foldBuilder/Expr.hpp @@ -97,11 +97,12 @@ void operator/=(NumExprPtr& lhs, const NumExprPtr rhs); void operator/=(NumExprPtr& lhs, double rhs); // The '==' operator used for defining a grid value. -#define EQUIV_OPER == -EqualsExprPtr operator EQUIV_OPER(GridPointPtr gpp, const NumExprPtr rhs); -EqualsExprPtr operator EQUIV_OPER(GridPointPtr gpp, double rhs); -#define IS_EQUIV_TO EQUIV_OPER -#define IS_EQUIVALENT_TO EQUIV_OPER +#define EQUALS_OPER == +EqualsExprPtr operator EQUALS_OPER(GridPointPtr gpp, const NumExprPtr rhs); +EqualsExprPtr operator EQUALS_OPER(GridPointPtr gpp, double rhs); +#define EQUALS EQUALS_OPER +#define IS_EQUIV_TO EQUALS_OPER +#define IS_EQUIVALENT_TO EQUALS_OPER // The '==' operator for comparing values. BoolExprPtr operator==(const NumExprPtr lhs, const NumExprPtr rhs); @@ -1142,7 +1143,7 @@ class Grids : public vector_set { // Aliases for parameters. // Even though these are just typedefs for now, don't interchange them. -// TODO: enforce the difference between grids and parameters. +// TODO: make params just a special case of grids. typedef Grid Param; typedef Grids Params; @@ -1436,7 +1437,7 @@ typedef NumExprPtr GridValue; #define SET_VALUE_FROM_EXPR(lhs, rhs) do { \ ostringstream oss; \ oss << setprecision(17) << scientific; \ - oss << "(" << rhs << ")"; \ + oss << "(" << rhs << ")"; \ lhs make_shared(oss.str()); \ } while(0) diff --git a/src/foldBuilder/Print.hpp b/src/foldBuilder/Print.hpp index 9e4f05eb..5933f316 100644 --- a/src/foldBuilder/Print.hpp +++ b/src/foldBuilder/Print.hpp @@ -120,7 +120,7 @@ class PrintHelper { // The 'os' parameter is provided for derived types that // need to write intermediate code to a stream. virtual string writeToPoint(ostream& os, const GridPoint& gp, const string& val) { - return gp.makeStr() + " IS_EQUIV_TO " + val; + return gp.makeStr() + " EQUALS " + val; } }; diff --git a/src/foldBuilder/stencils/AveStencil.hpp b/src/foldBuilder/stencils/AveStencil.hpp index 4d359749..65e23c43 100644 --- a/src/foldBuilder/stencils/AveStencil.hpp +++ b/src/foldBuilder/stencils/AveStencil.hpp @@ -66,7 +66,7 @@ class AveStencil : public StencilRadiusBase { v *= 1.0 / double(nPts); // define the grid value at t+1 to be equivalent to v. - heat(t+1, w, x, y, z) IS_EQUIV_TO v; + heat(t+1, w, x, y, z) EQUALS v; } }; diff --git a/src/foldBuilder/stencils/AwpElasticStencil.hpp b/src/foldBuilder/stencils/AwpElasticStencil.hpp index 7a1af0b0..d713d1a9 100644 --- a/src/foldBuilder/stencils/AwpElasticStencil.hpp +++ b/src/foldBuilder/stencils/AwpElasticStencil.hpp @@ -119,9 +119,9 @@ class AwpElasticStencil : public StencilBase { // This equation does NOT have a special case at surface, but the // formula is replicated to unify the sub-domains. Eventually, // YASK should be able to do this automatically. - vel_x(t+1, x, y, z) IS_EQUIV_TO next_vel_x + vel_x(t+1, x, y, z) EQUALS next_vel_x IF !at_last_z; - vel_x(t+1, x, y, z) IS_EQUIV_TO next_vel_x + vel_x(t+1, x, y, z) EQUALS next_vel_x IF at_last_z; } void define_vel_y(GridIndex t, GridIndex x, GridIndex y, GridIndex z, @@ -144,9 +144,9 @@ class AwpElasticStencil : public StencilBase { // This equation does NOT have a special case at surface, but the // formula is replicated to unify the sub-domains. Eventually, // YASK should be able to do this automatically. - vel_y(t+1, x, y, z) IS_EQUIV_TO next_vel_y + vel_y(t+1, x, y, z) EQUALS next_vel_y IF !at_last_z; - vel_y(t+1, x, y, z) IS_EQUIV_TO next_vel_y + vel_y(t+1, x, y, z) EQUALS next_vel_y IF at_last_z; } void define_vel_z(GridIndex t, GridIndex x, GridIndex y, GridIndex z, @@ -169,9 +169,9 @@ class AwpElasticStencil : public StencilBase { // This equation does NOT have a special case at surface, but the // formula is replicated to unify the sub-domains. Eventually, // YASK should be able to do this automatically. - vel_z(t+1, x, y, z) IS_EQUIV_TO next_vel_z + vel_z(t+1, x, y, z) EQUALS next_vel_z IF !at_last_z; - vel_z(t+1, x, y, z) IS_EQUIV_TO next_vel_z + vel_z(t+1, x, y, z) EQUALS next_vel_z IF at_last_z; } @@ -202,11 +202,11 @@ class AwpElasticStencil : public StencilBase { (2.0 / mu(x, y, z) + 1.0 / lambda(x, y, z)))); // Define equivalencies to be valid only when z == last value in domain. - vel_x(t+1, x, y, z+1) IS_EQUIV_TO plus1_vel_x + vel_x(t+1, x, y, z+1) EQUALS plus1_vel_x IF at_last_z; - vel_y(t+1, x, y, z+1) IS_EQUIV_TO plus1_vel_y + vel_y(t+1, x, y, z+1) EQUALS plus1_vel_y IF at_last_z; - vel_z(t+1, x, y, z+1) IS_EQUIV_TO plus1_vel_z + vel_z(t+1, x, y, z+1) EQUALS plus1_vel_z IF at_last_z; } @@ -232,9 +232,9 @@ class AwpElasticStencil : public StencilBase { // This equation does NOT have a special case at surface, but the // formula is replicated to unify the sub-domains. Eventually, // YASK should be able to do this automatically. - stress_xx(t+1, x, y, z) IS_EQUIV_TO next_stress_xx + stress_xx(t+1, x, y, z) EQUALS next_stress_xx IF !at_last_z; - stress_xx(t+1, x, y, z) IS_EQUIV_TO next_stress_xx + stress_xx(t+1, x, y, z) EQUALS next_stress_xx IF at_last_z; } void define_stress_yy(GridIndex t, GridIndex x, GridIndex y, GridIndex z, @@ -251,9 +251,9 @@ class AwpElasticStencil : public StencilBase { // This equation does NOT have a special case at surface, but the // formula is replicated to unify the sub-domains. Eventually, // YASK should be able to do this automatically. - stress_yy(t+1, x, y, z) IS_EQUIV_TO next_stress_yy + stress_yy(t+1, x, y, z) EQUALS next_stress_yy IF !at_last_z; - stress_yy(t+1, x, y, z) IS_EQUIV_TO next_stress_yy + stress_yy(t+1, x, y, z) EQUALS next_stress_yy IF at_last_z; } void define_stress_zz(GridIndex t, GridIndex x, GridIndex y, GridIndex z, @@ -270,9 +270,9 @@ class AwpElasticStencil : public StencilBase { // This equation does NOT have a special case at surface, but the // formula is replicated to unify the sub-domains. Eventually, // YASK should be able to do this automatically. - stress_zz(t+1, x, y, z) IS_EQUIV_TO next_stress_zz + stress_zz(t+1, x, y, z) EQUALS next_stress_zz IF !at_last_z; - stress_zz(t+1, x, y, z) IS_EQUIV_TO next_stress_zz + stress_zz(t+1, x, y, z) EQUALS next_stress_zz IF at_last_z; } void define_stress_xy(GridIndex t, GridIndex x, GridIndex y, GridIndex z, @@ -297,9 +297,9 @@ class AwpElasticStencil : public StencilBase { // This equation does NOT have a special case at surface, but the // formula is replicated to unify the sub-domains. Eventually, // YASK should be able to do this automatically. - stress_xy(t+1, x, y, z) IS_EQUIV_TO next_stress_xy + stress_xy(t+1, x, y, z) EQUALS next_stress_xy IF !at_last_z; - stress_xy(t+1, x, y, z) IS_EQUIV_TO next_stress_xy + stress_xy(t+1, x, y, z) EQUALS next_stress_xy IF at_last_z; } void define_stress_xz(GridIndex t, GridIndex x, GridIndex y, GridIndex z, @@ -321,9 +321,9 @@ class AwpElasticStencil : public StencilBase { adjust_for_sponge(next_stress_xz, x, y, z); // define the value at t+1 (special case: zero at surface). - stress_xz(t+1, x, y, z) IS_EQUIV_TO next_stress_xz + stress_xz(t+1, x, y, z) EQUALS next_stress_xz IF !at_last_z; - stress_xz(t+1, x, y, z) IS_EQUIV_TO 0.0 + stress_xz(t+1, x, y, z) EQUALS 0.0 IF at_last_z; } void define_stress_yz(GridIndex t, GridIndex x, GridIndex y, GridIndex z, @@ -345,9 +345,9 @@ class AwpElasticStencil : public StencilBase { adjust_for_sponge(next_stress_yz, x, y, z); // define the value at t+1 (special case: zero at surface). - stress_yz(t+1, x, y, z) IS_EQUIV_TO next_stress_yz + stress_yz(t+1, x, y, z) EQUALS next_stress_yz IF !at_last_z; - stress_yz(t+1, x, y, z) IS_EQUIV_TO 0.0 + stress_yz(t+1, x, y, z) EQUALS 0.0 IF at_last_z; } @@ -358,19 +358,19 @@ class AwpElasticStencil : public StencilBase { // Define equivalencies to be valid only when z == last value in domain. // Note that values beyond the last index are updated, i.e., in the halo. - stress_zz(t+1, x, y, z+1) IS_EQUIV_TO -stress_zz(t+1, x, y, z) + stress_zz(t+1, x, y, z+1) EQUALS -stress_zz(t+1, x, y, z) IF at_last_z; - stress_zz(t+1, x, y, z+2) IS_EQUIV_TO -stress_zz(t+1, x, y, z-1) + stress_zz(t+1, x, y, z+2) EQUALS -stress_zz(t+1, x, y, z-1) IF at_last_z; - stress_xz(t+1, x, y, z+1) IS_EQUIV_TO -stress_xz(t+1, x, y, z-1) + stress_xz(t+1, x, y, z+1) EQUALS -stress_xz(t+1, x, y, z-1) IF at_last_z; - stress_xz(t+1, x, y, z+2) IS_EQUIV_TO -stress_xz(t+1, x, y, z-2) + stress_xz(t+1, x, y, z+2) EQUALS -stress_xz(t+1, x, y, z-2) IF at_last_z; - stress_yz(t+1, x, y, z+1) IS_EQUIV_TO -stress_yz(t+1, x, y, z-1) + stress_yz(t+1, x, y, z+1) EQUALS -stress_yz(t+1, x, y, z-1) IF at_last_z; - stress_yz(t+1, x, y, z+2) IS_EQUIV_TO -stress_yz(t+1, x, y, z-2) + stress_yz(t+1, x, y, z+2) EQUALS -stress_yz(t+1, x, y, z-2) IF at_last_z; } diff --git a/src/foldBuilder/stencils/AwpStencil.hpp b/src/foldBuilder/stencils/AwpStencil.hpp index 55f38208..71ede4c6 100644 --- a/src/foldBuilder/stencils/AwpStencil.hpp +++ b/src/foldBuilder/stencils/AwpStencil.hpp @@ -137,7 +137,7 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_vel_x, x, y, z); // define the value at t+1. - vel_x(t+1, x, y, z) IS_EQUIV_TO next_vel_x; + vel_x(t+1, x, y, z) EQUALS next_vel_x; } void define_vel_y(GridIndex t, GridIndex x, GridIndex y, GridIndex z) { GridValue rho_val = (rho(x, y, z ) + @@ -155,7 +155,7 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_vel_y, x, y, z); // define the value at t+1. - vel_y(t+1, x, y, z) IS_EQUIV_TO next_vel_y; + vel_y(t+1, x, y, z) EQUALS next_vel_y; } void define_vel_z(GridIndex t, GridIndex x, GridIndex y, GridIndex z) { GridValue rho_val = (rho(x, y, z) + @@ -173,7 +173,7 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_vel_z, x, y, z); // define the value at t+1. - vel_z(t+1, x, y, z) IS_EQUIV_TO next_vel_z; + vel_z(t+1, x, y, z) EQUALS next_vel_z; } // Stress-grid define functions. For each D in xx, yy, zz, xy, xz, yz, @@ -204,8 +204,8 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_stress_xx, x, y, z); // define the value at t+1. - stress_mem_xx(t+1, x, y, z) IS_EQUIV_TO next_stress_mem_xx; - stress_xx(t+1, x, y, z) IS_EQUIV_TO next_stress_xx; + stress_mem_xx(t+1, x, y, z) EQUALS next_stress_mem_xx; + stress_xx(t+1, x, y, z) EQUALS next_stress_xx; } void define_stress_yy(GridIndex t, GridIndex x, GridIndex y, GridIndex z, GridValue lambda_val, GridValue mu_val, @@ -227,8 +227,8 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_stress_yy, x, y, z); // define the value at t+1. - stress_mem_yy(t+1, x, y, z) IS_EQUIV_TO next_stress_mem_yy; - stress_yy(t+1, x, y, z) IS_EQUIV_TO next_stress_yy; + stress_mem_yy(t+1, x, y, z) EQUALS next_stress_mem_yy; + stress_yy(t+1, x, y, z) EQUALS next_stress_yy; } void define_stress_zz(GridIndex t, GridIndex x, GridIndex y, GridIndex z, GridValue lambda_val, GridValue mu_val, @@ -250,8 +250,8 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_stress_zz, x, y, z); // define the value at t+1. - stress_mem_zz(t+1, x, y, z) IS_EQUIV_TO next_stress_mem_zz; - stress_zz(t+1, x, y, z) IS_EQUIV_TO next_stress_zz; + stress_mem_zz(t+1, x, y, z) EQUALS next_stress_mem_zz; + stress_zz(t+1, x, y, z) EQUALS next_stress_zz; } void define_stress_xy(GridIndex t, GridIndex x, GridIndex y, GridIndex z, GridValue tau1) { @@ -280,8 +280,8 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_stress_xy, x, y, z); // define the value at t+1. - stress_mem_xy(t+1, x, y, z) IS_EQUIV_TO next_stress_mem_xy; - stress_xy(t+1, x, y, z) IS_EQUIV_TO next_stress_xy; + stress_mem_xy(t+1, x, y, z) EQUALS next_stress_mem_xy; + stress_xy(t+1, x, y, z) EQUALS next_stress_xy; } void define_stress_xz(GridIndex t, GridIndex x, GridIndex y, GridIndex z, GridValue tau1) { @@ -310,8 +310,8 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_stress_xz, x, y, z); // define the value at t+1. - stress_mem_xz(t+1, x, y, z) IS_EQUIV_TO next_stress_mem_xz; - stress_xz(t+1, x, y, z) IS_EQUIV_TO next_stress_xz; + stress_mem_xz(t+1, x, y, z) EQUALS next_stress_mem_xz; + stress_xz(t+1, x, y, z) EQUALS next_stress_xz; } void define_stress_yz(GridIndex t, GridIndex x, GridIndex y, GridIndex z, GridValue tau1) { @@ -340,8 +340,8 @@ class AwpStencil : public StencilBase { adjust_for_sponge(next_stress_yz, x, y, z); // define the value at t+1. - stress_mem_yz(t+1, x, y, z) IS_EQUIV_TO next_stress_mem_yz; - stress_yz(t+1, x, y, z) IS_EQUIV_TO next_stress_yz; + stress_mem_yz(t+1, x, y, z) EQUALS next_stress_mem_yz; + stress_yz(t+1, x, y, z) EQUALS next_stress_yz; } // Call all the define_* functions. diff --git a/src/foldBuilder/stencils/ElasticStencil/ElasticStencil.hpp b/src/foldBuilder/stencils/ElasticStencil/ElasticStencil.hpp index 79c72fd6..3d9ae2aa 100644 --- a/src/foldBuilder/stencils/ElasticStencil/ElasticStencil.hpp +++ b/src/foldBuilder/stencils/ElasticStencil/ElasticStencil.hpp @@ -220,9 +220,9 @@ class ElasticStencilBase : public StencilBase { // define the value at t+1. if ( hasBoundaryCondition ) { Condition not_at_bc = bc->is_not_at_boundary(t,x,y,z); - v(t+1, x, y, z) IS_EQUIV_TO next_v IF not_at_bc; + v(t+1, x, y, z) EQUALS next_v IF not_at_bc; } else - v(t+1, x, y, z) IS_EQUIV_TO next_v; + v(t+1, x, y, z) EQUALS next_v; } GridValue stencil_O2_Z( GridIndex t, GridIndex x, GridIndex y, GridIndex z, Grid &g, const int offset ) diff --git a/src/foldBuilder/stencils/ExampleStencil.hpp b/src/foldBuilder/stencils/ExampleStencil.hpp index f1931097..62078b27 100644 --- a/src/foldBuilder/stencils/ExampleStencil.hpp +++ b/src/foldBuilder/stencils/ExampleStencil.hpp @@ -70,7 +70,7 @@ class ExampleStencil : public StencilRadiusBase { addPoints(v, t, x, y, z); // define the value at t+1 to be equivalent to v. - data(t+1, x, y, z) IS_EQUIV_TO v; + data(t+1, x, y, z) EQUALS v; } }; diff --git a/src/foldBuilder/stencils/FSGElasticStencil.hpp b/src/foldBuilder/stencils/FSGElasticStencil.hpp index 669d92ef..2bc670e7 100644 --- a/src/foldBuilder/stencils/FSGElasticStencil.hpp +++ b/src/foldBuilder/stencils/FSGElasticStencil.hpp @@ -304,19 +304,19 @@ class FSGElasticStencilBase : public ElasticStencilBase { // define the value at t+1. if ( hasBoundaryCondition ) { Condition not_at_bc = bc->is_not_at_boundary(t,x,y,z); - sxx(t+1, x, y, z) IS_EQUIV_TO next_sxx IF not_at_bc; - syy(t+1, x, y, z) IS_EQUIV_TO next_syy IF not_at_bc; - szz(t+1, x, y, z) IS_EQUIV_TO next_szz IF not_at_bc; - syz(t+1, x, y, z) IS_EQUIV_TO next_syz IF not_at_bc; - sxz(t+1, x, y, z) IS_EQUIV_TO next_sxz IF not_at_bc; - sxy(t+1, x, y, z) IS_EQUIV_TO next_sxy IF not_at_bc; + sxx(t+1, x, y, z) EQUALS next_sxx IF not_at_bc; + syy(t+1, x, y, z) EQUALS next_syy IF not_at_bc; + szz(t+1, x, y, z) EQUALS next_szz IF not_at_bc; + syz(t+1, x, y, z) EQUALS next_syz IF not_at_bc; + sxz(t+1, x, y, z) EQUALS next_sxz IF not_at_bc; + sxy(t+1, x, y, z) EQUALS next_sxy IF not_at_bc; } else { - sxx(t+1, x, y, z) IS_EQUIV_TO next_sxx; - syy(t+1, x, y, z) IS_EQUIV_TO next_syy; - szz(t+1, x, y, z) IS_EQUIV_TO next_szz; - syz(t+1, x, y, z) IS_EQUIV_TO next_syz; - sxz(t+1, x, y, z) IS_EQUIV_TO next_sxz; - sxy(t+1, x, y, z) IS_EQUIV_TO next_sxy; + sxx(t+1, x, y, z) EQUALS next_sxx; + syy(t+1, x, y, z) EQUALS next_syy; + szz(t+1, x, y, z) EQUALS next_szz; + syz(t+1, x, y, z) EQUALS next_syz; + sxz(t+1, x, y, z) EQUALS next_sxz; + sxy(t+1, x, y, z) EQUALS next_sxy; } } @@ -432,7 +432,7 @@ class FSG_ABC : public FSGBoundaryCondition next_v *= abc_sq_x(x,y,z) * abc_sq_y(x,y,z) * abc_sq_z(x,y,z); // define the value at t+1. - v(t+1, x, y, z) IS_EQUIV_TO next_v IF at_abc; + v(t+1, x, y, z) EQUALS next_v IF at_abc; } void velocity (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) @@ -512,12 +512,12 @@ class FSG_ABC : public FSGBoundaryCondition // define the value at t+1. Condition at_abc = is_at_boundary(t,x,y,z); - sxx(t+1, x, y, z) IS_EQUIV_TO next_sxx IF at_abc; - syy(t+1, x, y, z) IS_EQUIV_TO next_syy IF at_abc; - szz(t+1, x, y, z) IS_EQUIV_TO next_szz IF at_abc; - syz(t+1, x, y, z) IS_EQUIV_TO next_syz IF at_abc; - sxz(t+1, x, y, z) IS_EQUIV_TO next_sxz IF at_abc; - sxy(t+1, x, y, z) IS_EQUIV_TO next_sxy IF at_abc; + sxx(t+1, x, y, z) EQUALS next_sxx IF at_abc; + syy(t+1, x, y, z) EQUALS next_syy IF at_abc; + szz(t+1, x, y, z) EQUALS next_szz IF at_abc; + syz(t+1, x, y, z) EQUALS next_syz IF at_abc; + sxz(t+1, x, y, z) EQUALS next_sxz IF at_abc; + sxy(t+1, x, y, z) EQUALS next_sxy IF at_abc; } void stress (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) diff --git a/src/foldBuilder/stencils/Iso3dfdStencil.hpp b/src/foldBuilder/stencils/Iso3dfdStencil.hpp index bca392fd..bd791cf6 100644 --- a/src/foldBuilder/stencils/Iso3dfdStencil.hpp +++ b/src/foldBuilder/stencils/Iso3dfdStencil.hpp @@ -23,8 +23,8 @@ IN THE SOFTWARE. *****************************************************************************/ -// Implement isotropic 3D finite-difference stencil. -// 2nd-order in time. +// Implement isotropic 3D finite-difference (FD) stencil, nth-order accurate in +// space (where n = 2 * radius) and 2nd-order accurate in time. #include "StencilBase.hpp" @@ -37,6 +37,11 @@ class Iso3dfdStencil : public StencilRadiusBase { public: + // For this stencil, the 'radius' is half the FD accuracy in space. For + // example, radius=8 implements a 16th-order accurate FD stencil. To + // obtain the correct result, the 'coeff' array should be initialized + // with the corresponding central FD coefficients. The accuracy in time + // is fixed at 2nd order. Iso3dfdStencil(StencilList& stencils, int radius=8) : StencilRadiusBase("iso3dfd", stencils, radius) { @@ -62,37 +67,39 @@ class Iso3dfdStencil : public StencilRadiusBase { GET_OFFSET(y); GET_OFFSET(z); - // start with center value multiplied by coeff 0. - GridValue v = pressure(t, x, y, z) * coeff(0); + // Start with center value multiplied by coeff 0. + GridValue next_p = pressure(t, x, y, z) * coeff(0); - // add values from x, y, and z axes multiplied by the + // Add values from x, y, and z axes multiplied by the // coeff for the given radius. for (int r = 1; r <= _radius; r++) { // Add values from axes at radius r. - v += ( - // x-axis. - pressure(t, x-r, y, z) + - pressure(t, x+r, y, z) + + next_p += ( + // x-axis. + pressure(t, x-r, y, z) + + pressure(t, x+r, y, z) + - // y-axis. - pressure(t, x, y-r, z) + - pressure(t, x, y+r, z) + + // y-axis. + pressure(t, x, y-r, z) + + pressure(t, x, y+r, z) + - // z-axis. - pressure(t, x, y, z-r) + - pressure(t, x, y, z+r) + // z-axis. + pressure(t, x, y, z-r) + + pressure(t, x, y, z+r) - ) * coeff(r); + ) * coeff(r); } - // finish equation, including t-1 and velocity components. - v = (2.0 * pressure(t, x, y, z)) + // Finish equation, including t-1 and velocity components. + next_p = (2.0 * pressure(t, x, y, z)) - pressure(t-1, x, y, z) // subtract pressure from t-1. - + (v * vel(x, y, z)); // add v * velocity. + + (next_p * vel(x, y, z)); // add next_p * velocity. - // define the value at t+1 to be equivalent to v. - pressure(t+1, x, y, z) IS_EQUIV_TO v; + // Define the value at t+1 to be equal to next_p. + // Since this implements the finite-difference method, this + // is actually an approximation. + pressure(t+1, x, y, z) EQUALS next_p; } }; diff --git a/src/foldBuilder/stencils/StreamStencil.hpp b/src/foldBuilder/stencils/StreamStencil.hpp index 0ed2cc09..fab36aa1 100644 --- a/src/foldBuilder/stencils/StreamStencil.hpp +++ b/src/foldBuilder/stencils/StreamStencil.hpp @@ -58,7 +58,7 @@ class StreamStencil : public StencilRadiusBase { } // define the value at t+1 to be equivalent to v. - grid(t+1, x, y, z) IS_EQUIV_TO v; + grid(t+1, x, y, z) EQUALS v; } }; diff --git a/src/idiv.hpp b/src/idiv.hpp index a182f3fe..0a5afd57 100644 --- a/src/idiv.hpp +++ b/src/idiv.hpp @@ -47,12 +47,14 @@ namespace yask { // +6 / +3 = +2, +6 % +3 = +0, idiv_flr(+6, +3) = +2, imod_flr(+6, +3) = +0 // +7 / +3 = +2, +7 % +3 = +1, idiv_flr(+7, +3) = +2, imod_flr(+7, +3) = +1 // +8 / +3 = +2, +8 % +3 = +2, idiv_flr(+8, +3) = +2, imod_flr(+8, +3) = +2 + template inline T idiv_flr(T a, T b) { //return (a<0 ? a-(b-1) : a) / b; //return (a - (a<0 ? b-1 : 0)) / b; return (a + (a>>(sizeof(a)*8-1)) * (b-1)) / b; } + template inline T imod_flr(T a, T b) { //return ((a % b) + b) % b; @@ -60,6 +62,9 @@ namespace yask { //T c = a % b; return (c < 0) ? c + b : c; T c = a % b; return c - ((c>>(sizeof(c)*8-1)) * b); } + + // NB: (a>>(sizeof(a)*8-1) is equiv to (a >= 0) ? 0 : 1; + // thus, (a>>(sizeof(a)*8-1) * b is equiv to (a >= 0) ? 0 : b; } #endif From f21a807647372b29d7709e7d61de3bf23144030d Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Wed, 8 Mar 2017 13:49:04 -0700 Subject: [PATCH 04/20] Change -g* knobs and vars to -bg* (groups to block-groups). In anticipation of adding other types of groups. Also, Makefile cleanup. --- Makefile | 6 +++-- src/stencil_calc.cpp | 52 ++++++++++++++++++++++---------------------- src/stencil_calc.hpp | 2 +- stencil-tuner.pl | 26 +++++++++++----------- 4 files changed, 44 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index 0ce1a848..197a3f62 100644 --- a/Makefile +++ b/Makefile @@ -320,8 +320,10 @@ ifneq ($(findstring ic,$(notdir $(CXX))),) # Intel compiler CODE_STATS = code_stats CXXFLAGS += $(ISA) -debug extended -Fa -restrict -ansi-alias -fno-alias -CXXFLAGS += -fimf-precision=low -fast-transcendentals -no-prec-sqrt -no-prec-div -fp-model fast=2 -fno-protect-parens -rcd -ftz -fma -fimf-domain-exclusion=none -qopt-assume-safe-padding -qoverride-limits -vec-threshold0 -CXXFLAGS += -qopt-report=5 -qopt-report-phase=VEC,PAR,OPENMP,IPO,LOOP +CXXFLAGS += -fimf-precision=low -fast-transcendentals -no-prec-sqrt -no-prec-div -fp-model fast=2 -fno-protect-parens -rcd -ftz -fma -fimf-domain-exclusion=none -qopt-assume-safe-padding +#CXXFLAGS += -qoverride-limits -vec-threshold0 +CXXFLAGS += -qopt-report=5 +#CXXFLAGS += -qopt-report-phase=VEC,PAR,OPENMP,IPO,LOOP CXXFLAGS += -no-diag-message-catalog CXX_VER_CMD = $(CXX) -V diff --git a/src/stencil_calc.cpp b/src/stencil_calc.cpp index 6d267221..a43d015a 100644 --- a/src/stencil_calc.cpp +++ b/src/stencil_calc.cpp @@ -299,11 +299,11 @@ namespace yask { const idx_t step_ry = _opts->by; const idx_t step_rz = _opts->bz; - // Groups in region loops are based on group sizes. - const idx_t group_size_rw = _opts->gw; - const idx_t group_size_rx = _opts->gx; - const idx_t group_size_ry = _opts->gy; - const idx_t group_size_rz = _opts->gz; + // Groups in region loops are based on block-group sizes. + const idx_t group_size_rw = _opts->bgw; + const idx_t group_size_rx = _opts->bgx; + const idx_t group_size_ry = _opts->bgy; + const idx_t group_size_rz = _opts->bgz; // Not yet supporting temporal blocking. if (step_rt != 1) { @@ -787,7 +787,7 @@ namespace yask { " block-size: " << _opts->bt << '*' << _opts->bw << '*' << _opts->bx << '*' << _opts->by << '*' << _opts->bz << endl << " block-group-size: 1*" << - _opts->gw << '*' << _opts->gx << '*' << _opts->gy << '*' << _opts->gz << endl << + _opts->bgw << '*' << _opts->bgx << '*' << _opts->bgy << '*' << _opts->bgz << endl << " region-size: " << _opts->rt << '*' << _opts->rw << '*' << _opts->rx << '*' << _opts->ry << '*' << _opts->rz << endl << " rank-domain-size: " << @@ -1445,7 +1445,7 @@ namespace yask { ADD_T_DIM_OPTION("d", "Domain size for this rank", d); ADD_T_DIM_OPTION("r", "Region size", r); ADD_DIM_OPTION("b", "Block size", b); - ADD_DIM_OPTION("g", "Block-group size", g); + ADD_DIM_OPTION("bg", "Block-group size", bg); ADD_DIM_OPTION("p", "Extra memory-padding size", p); #ifdef USE_MPI ADD_DIM_OPTION("nr", "Num ranks", nr); @@ -1542,13 +1542,13 @@ namespace yask { // Determine num regions. // Also fix up region sizes as needed. os << "\nRegions:" << endl; - idx_t nrgt = findNumRegions(os, rt, dt, CPTS_T, "t"); - idx_t nrgw = findNumRegions(os, rw, dw, CPTS_W, "w"); - idx_t nrgx = findNumRegions(os, rx, dx, CPTS_X, "x"); - idx_t nrgy = findNumRegions(os, ry, dy, CPTS_Y, "y"); - idx_t nrgz = findNumRegions(os, rz, dz, CPTS_Z, "z"); - idx_t nrg = nrgt * nrgw * nrgx * nrgy * nrgz; - os << " num-regions-per-rank: " << nrg << endl; + idx_t nrt = findNumRegions(os, rt, dt, CPTS_T, "t"); + idx_t nrw = findNumRegions(os, rw, dw, CPTS_W, "w"); + idx_t nrx = findNumRegions(os, rx, dx, CPTS_X, "x"); + idx_t nry = findNumRegions(os, ry, dy, CPTS_Y, "y"); + idx_t nrz = findNumRegions(os, rz, dz, CPTS_Z, "z"); + idx_t nr = nrt * nrw * nrx * nry * nrz; + os << " num-regions-per-rank: " << nr << endl; // Determine num blocks. // Also fix up block sizes as needed. @@ -1562,20 +1562,20 @@ namespace yask { os << " num-blocks-per-region: " << nb << endl; // Adjust defaults for block-groups. - if (!gw) gw = bw; - if (!gx) gx = bx; - if (!gy) gy = by; - if (!gz) gz = bz; + if (!bgw) bgw = bw; + if (!bgx) bgx = bx; + if (!bgy) bgy = by; + if (!bgz) bgz = bz; - // Determine num groups. - // Also fix up group sizes as needed. + // Determine num block-groups. + // Also fix up block-group sizes as needed. os << "\nBlock-groups:" << endl; - idx_t ngw = findNumGroups(os, gw, rw, bw, "w"); - idx_t ngx = findNumGroups(os, gx, rx, bx, "x"); - idx_t ngy = findNumGroups(os, gy, ry, by, "y"); - idx_t ngz = findNumGroups(os, gz, rz, bz, "z"); - idx_t ng = ngw * ngx * ngy * ngz; - os << " num-block-groups-per-region: " << ng << endl; + idx_t nbgw = findNumGroups(os, bgw, rw, bw, "w"); + idx_t nbgx = findNumGroups(os, bgx, rx, bx, "x"); + idx_t nbgy = findNumGroups(os, bgy, ry, by, "y"); + idx_t nbgz = findNumGroups(os, bgz, rz, bz, "z"); + idx_t nbg = nbgw * nbgx * nbgy * nbgz; + os << " num-block-groups-per-region: " << nbg << endl; } diff --git a/src/stencil_calc.hpp b/src/stencil_calc.hpp index eb0588cc..ed06bebd 100644 --- a/src/stencil_calc.hpp +++ b/src/stencil_calc.hpp @@ -112,7 +112,7 @@ namespace yask { idx_t dt=1, dw=0, dx=0, dy=0, dz=0; // rank size (without halos). idx_t rt=1, rw=0, rx=0, ry=0, rz=0; // region size (used for wave-front tiling). idx_t bt=1, bw=0, bx=0, by=0, bz=0; // block size (used for cache locality). - idx_t gw=0, gx=0, gy=0, gz=0; // group-of-blocks size (only used for 'grouped' loop paths). + idx_t bgw=0, bgx=0, bgy=0, bgz=0; // group-of-blocks size (only used for 'grouped' loop paths). idx_t pw=0, px=0, py=0, pz=0; // spatial padding (in addition to halos, to avoid aliasing). // MPI settings. diff --git a/stencil-tuner.pl b/stencil-tuner.pl index 92bdd32e..dd49eb46 100755 --- a/stencil-tuner.pl +++ b/stencil-tuner.pl @@ -337,7 +337,7 @@ sub usage { 'cluster-size', 'vector-size', 'num-regions', - 'num-groups-per-region', + 'num-blocks-per-region', 'num-block-groups-per-region', 'padding', 'max-halos', @@ -509,10 +509,10 @@ sub usage { [ 0, $maxDim, 1, 'ry' ], [ 0, $maxDim, 1, 'rz' ], - # group size. - [ 0, $maxDim, 1, 'gx' ], - [ 0, $maxDim, 1, 'gy' ], - [ 0, $maxDim, 1, 'gz' ], + # block-group size. + [ 0, $maxDim, 1, 'bgx' ], + [ 0, $maxDim, 1, 'bgy' ], + [ 0, $maxDim, 1, 'bgz' ], # block size. [ 0, $maxDim, 1, 'bx' ], @@ -1222,7 +1222,7 @@ sub fitness { my $h = makeHash($values); my @ds = readHashes($h, 'd', 0); my @rs = readHashes($h, 'r', 0); - my @gs = readHashes($h, 'g', 0); + my @bgs = readHashes($h, 'bg', 0); my @bs = readHashes($h, 'b', 0); my @cvs = readHashes($h, 'c', 1); # in vectors, not in points! my @ps = readHashes($h, 'p', 0); @@ -1277,12 +1277,12 @@ sub fitness { # adjust inner sizes. adjSizes(\@rs, \@ds); adjSizes(\@bs, \@rs); - adjSizes(\@gs, \@rs); + adjSizes(\@bgs, \@rs); # 3d sizes in points. my $dPts = mult(@ds); my $rPts = mult(@rs); - my $gPts = mult(@gs); + my $bgPts = mult(@bgs); my $bPts = mult(@bs); my $cPts = mult(@cs); my $fPts = mult(@fs); @@ -1296,8 +1296,8 @@ sub fitness { my $rBlks = mult(@rbs); # Groups per region. - my @rgs = map { ceil($rs[$_] / $gs[$_]) } 0..$#dirs; - my $rGrps = mult(@rgs); + my @rbgs = map { ceil($rs[$_] / $bgs[$_]) } 0..$#dirs; + my $rBlkGrps = mult(@rbgs); # Regions per rank. my @drs = map { ceil($ds[$_] / $rs[$_]) } 0..$#dirs; @@ -1310,7 +1310,7 @@ sub fitness { print "Sizes:\n"; print " rank size = $dPts\n"; print " region size = $rPts\n"; - print " group size = $gPts\n"; + print " block-group size = $bgPts\n"; print " block size = $bPts\n"; print " cluster size = $cPts\n"; print " fold size = $fPts\n"; @@ -1363,7 +1363,7 @@ sub fitness { addStat($ok, 'mem estimate', $overallSize); addStat($ok, 'rank size', $dPts); addStat($ok, 'region size', $rPts); - addStat($ok, 'group size', $gPts); + addStat($ok, 'block-group size', $bgPts); addStat($ok, 'block size', $bPts); addStat($ok, 'cluster size', $cPts); addStat($ok, 'regions per rank', $dRegs); @@ -1433,7 +1433,7 @@ sub fitness { $args .= " -dx $ds[0] -dy $ds[1] -dz $ds[2]"; $args .= " -rx $rs[0] -ry $rs[1] -rz $rs[2]"; $args .= " -bx $bs[0] -by $bs[1] -bz $bs[2]"; - $args .= " -gx $gs[0] -gy $gs[1] -gz $gs[2]"; + $args .= " -bgx $bgs[0] -bgy $bgs[1] -bgz $bgs[2]"; $args .= " -px $ps[0] -py $ps[1] -pz $ps[2]"; # num of iterations and trials. From 9673aa4ca9ef2bab1d1742812bc117280231e17f Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Fri, 10 Mar 2017 08:08:23 -0700 Subject: [PATCH 05/20] Fix DOT output. --- src/foldBuilder/Print.cpp | 54 ++++++++++++++++++++++----------------- src/foldBuilder/Print.hpp | 17 +++++++----- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/foldBuilder/Print.cpp b/src/foldBuilder/Print.cpp index 6de46dc0..5eb05e56 100644 --- a/src/foldBuilder/Print.cpp +++ b/src/foldBuilder/Print.cpp @@ -450,43 +450,47 @@ void DOTPrintVisitor::visit(CodeExpr* ce) { // Generic numeric unary operators. void DOTPrintVisitor::visit(UnaryNumExpr* ue) { string label = getLabel(ue); - if (label.size()) + if (label.size()) { _os << label << " [ label = \"" << ue->getOpStr() << "\" ];" << endl; - _os << ue->makeQuotedStr() << " -> " << ue->getRhs()->makeQuotedStr() << ";" << endl; - ue->getRhs()->accept(this); + _os << getLabel(ue, false) << " -> " << getLabel(ue->getRhs(), false) << ";" << endl; + ue->getRhs()->accept(this); + } } // Generic numeric binary operators. void DOTPrintVisitor::visit(BinaryNumExpr* be) { string label = getLabel(be); - if (label.size()) + if (label.size()) { _os << label << " [ label = \"" << be->getOpStr() << "\" ];" << endl; - _os << be->makeQuotedStr() << " -> " << be->getLhs()->makeQuotedStr() << ";" << endl << - be->makeQuotedStr() << " -> " << be->getRhs()->makeQuotedStr() << ";" << endl; - be->getLhs()->accept(this); - be->getRhs()->accept(this); + _os << getLabel(be, false) << " -> " << getLabel(be->getLhs(), false) << ";" << endl << + getLabel(be, false) << " -> " << getLabel(be->getRhs(), false) << ";" << endl; + be->getLhs()->accept(this); + be->getRhs()->accept(this); + } } // A commutative operator. void DOTPrintVisitor::visit(CommutativeExpr* ce) { string label = getLabel(ce); - if (label.size()) + if (label.size()) { _os << label << " [ label = \"" << ce->getOpStr() << "\" ];" << endl; - for (auto ep : ce->getOps()) { - _os << ce->makeQuotedStr() << " -> " << ep->makeQuotedStr() << ";" << endl; - ep->accept(this); + for (auto ep : ce->getOps()) { + _os << getLabel(ce, false) << " -> " << getLabel(ep, false) << ";" << endl; + ep->accept(this); + } } } // An equals operator. void DOTPrintVisitor::visit(EqualsExpr* ee) { string label = getLabel(ee); - if (label.size()) - _os << label << " [ label = \"==\" ];" << endl; - _os << ee->makeQuotedStr() << " -> " << ee->getLhs()->makeQuotedStr() << ";" << endl << - ee->makeQuotedStr() << " -> " << ee->getRhs()->makeQuotedStr() << ";" << endl; - ee->getLhs()->accept(this); - ee->getRhs()->accept(this); + if (label.size()) { + _os << label << " [ label = \"EQUALS\" ];" << endl; + _os << getLabel(ee, false) << " -> " << getLabel(ee->getLhs(), false) << ";" << endl << + getLabel(ee, false) << " -> " << getLabel(ee->getRhs(), false) << ";" << endl; + ee->getLhs()->accept(this); + ee->getRhs()->accept(this); + } } // A grid or parameter access. @@ -494,9 +498,10 @@ void SimpleDOTPrintVisitor::visit(GridPoint* gp) { if (gp->isParam()) return; string label = getLabel(gp); - if (label.size()) + if (label.size()) { _os << label << " [ shape = box ];" << endl; - _gridsSeen.insert(gp->makeQuotedStr()); + _gridsSeen.insert(label); + } } // Generic numeric unary operators. @@ -521,7 +526,7 @@ void SimpleDOTPrintVisitor::visit(EqualsExpr* ee) { // LHS is source. ee->getLhs()->accept(this); - string label = ee->makeQuotedStr(); + string label = getLabel(ee, false); for (auto g : _gridsSeen) label = g; // really should only be one. _gridsSeen.clear(); @@ -580,13 +585,14 @@ void DOTPrinter::print(ostream& os) { new SimpleDOTPrintVisitor(os) : new DOTPrintVisitor(os); - os << "digraph \"Stencil " << _stencil.getName() << "\" {" << endl; + os << "digraph \"Stencil " << _stencil.getName() << "\" {\n" + "rankdir=LR; ranksep=1.5;\n"; // Loop through all eqGroups. for (auto& eq : _eqGroups) { - //os << "subgraph \"Equation-group " << eq.getName() << "\" {" << endl; + os << "subgraph \"Equation-group " << eq.getName() << "\" {" << endl; eq.visitEqs(pv); - //os << "}" << endl; + os << "}" << endl; } os << "}" << endl; delete pv; diff --git a/src/foldBuilder/Print.hpp b/src/foldBuilder/Print.hpp index 5933f316..d06301c9 100644 --- a/src/foldBuilder/Print.hpp +++ b/src/foldBuilder/Print.hpp @@ -323,14 +323,19 @@ class DOTPrintVisitor : public ExprVisitor { set _done; // Get label to use. - // Return empty string if already done. - virtual string getLabel(Expr* ep) { - string key = ep->makeQuotedStr(); - if (_done.count(key)) - return ""; - _done.insert(key); + // Return empty string if already done if once == true. + virtual string getLabel(Expr* ep, bool once = true) { + string key = ep->makeQuotedStr("\""); + if (once) { + if (_done.count(key)) + return ""; + _done.insert(key); + } return key; } + virtual string getLabel(ExprPtr ep, bool once = true) { + return getLabel(ep.get(), once); + } public: DOTPrintVisitor(ostream& os) : _os(os) { } From 231a75281090b5712d36e2a7f7bf4e74bd6bb02d Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Fri, 10 Mar 2017 08:09:28 -0700 Subject: [PATCH 06/20] Use g++ for building foldBuilder. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 197a3f62..95c158c1 100644 --- a/Makefile +++ b/Makefile @@ -245,7 +245,7 @@ MAKE = make CXXFLAGS += -g -O3 -std=c++11 -Wall OMPFLAGS += -fopenmp LFLAGS += -lrt -FB_CXX = $(CXX) +FB_CXX = g++ # faster than icpc for the foldBuilder. FB_CXXFLAGS += -g -O0 -std=c++11 -Wall # low opt to reduce compile time. EXTRA_FB_CXXFLAGS = FB_FLAGS += -st $(stencil) -cluster $(cluster) -fold $(fold) From 5c7f953244cf58df6504d26894152bfb2c09d306 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Fri, 10 Mar 2017 08:09:43 -0700 Subject: [PATCH 07/20] Add comment to example. --- src/foldBuilder/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/foldBuilder/main.cpp b/src/foldBuilder/main.cpp index 5a58f7c6..00e86f1d 100644 --- a/src/foldBuilder/main.cpp +++ b/src/foldBuilder/main.cpp @@ -166,7 +166,7 @@ void usage(const string& cmd) { //" -pp Print POV-Ray code.\n" "\n" "Examples:\n" - " " << cmd << " -st 3axis -or 2 -fold x=4,y=4 -ph -\n" + " " << cmd << " -st 3axis -or 2 -fold x=4,y=4 -ph - # '-' for stdout\n" " " << cmd << " -st awp -fold y=4,y=2 -p256 stencil_code.hpp\n" " " << cmd << " -st iso3dfd -or 8 -fold x=4,y=4 -cluster y=2 -p512 stencil_code.hpp\n"; exit(1); From ae3ba5b0b5c1823fe8223f4d49739c22aa6b2aec Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Fri, 10 Mar 2017 14:10:04 -0700 Subject: [PATCH 08/20] Add reporting of number of grid reads, blocks-per-group, and blocks-per-region. --- src/foldBuilder/Expr.hpp | 8 +-- src/foldBuilder/ExprUtils.hpp | 1 + src/foldBuilder/Print.cpp | 15 +++--- src/foldBuilder/main.cpp | 41 ++++++++++----- src/stencil_calc.cpp | 93 ++++++++++++++++++++++++----------- src/stencil_calc.hpp | 24 +++++---- src/stencil_main.cpp | 6 +-- src/utils.cpp | 32 ++++++------ src/utils.hpp | 16 ++++-- stencil-tuner.pl | 5 +- 10 files changed, 158 insertions(+), 83 deletions(-) diff --git a/src/foldBuilder/Expr.hpp b/src/foldBuilder/Expr.hpp index 00558559..8194163e 100644 --- a/src/foldBuilder/Expr.hpp +++ b/src/foldBuilder/Expr.hpp @@ -879,7 +879,7 @@ class EqDeps { // Check whether eq a depends on b. virtual bool is_dep_on(EqualsExprPtr a, EqualsExprPtr b) const { - assert(_done); + assert(_done || _deps.size() == 0); return _full_deps.count(a) && _full_deps.at(a).count(b) > 0; } @@ -1325,9 +1325,11 @@ class EqGroups : public vector { EqDepMap& eq_deps); void findEqGroups(Grids& grids, const string& targets, - IntTuple& pts) { + IntTuple& pts, + bool find_deps) { EqDepMap eq_deps; - grids.findDeps(pts, _dims->_stepDim, &eq_deps); + if (find_deps) + grids.findDeps(pts, _dims->_stepDim, &eq_deps); findEqGroups(grids, targets, pts, eq_deps); } diff --git a/src/foldBuilder/ExprUtils.hpp b/src/foldBuilder/ExprUtils.hpp index 7ecc62f8..9c24f75b 100644 --- a/src/foldBuilder/ExprUtils.hpp +++ b/src/foldBuilder/ExprUtils.hpp @@ -136,6 +136,7 @@ class TrackingVisitor : public ExprVisitor { map _counts; int _visits; + // Visits are considered unique by address, not semantic equivalence. virtual bool alreadyVisited(Expr* ep) { #if DEBUG_TRACKING >= 1 cout << "- tracking '" << ep->makeStr() << "'@" << ep << endl; diff --git a/src/foldBuilder/Print.cpp b/src/foldBuilder/Print.cpp index 5eb05e56..030dffae 100644 --- a/src/foldBuilder/Print.cpp +++ b/src/foldBuilder/Print.cpp @@ -793,15 +793,16 @@ void YASKCppPrinter::printCode(ostream& os) { "\n struct " << egsName << " {\n" << " std::string name = \"" << eqName << "\";\n"; - // Ops for this eqGroup. - CounterVisitor fpops; - eq.visitEqs(&fpops); + // Stats for this eqGroup. + CounterVisitor stats; + eq.visitEqs(&stats); // Example computation. - os << endl << " // " << fpops.getNumOps() << " FP operation(s) per point:" << endl; + os << endl << " // " << stats.getNumOps() << " FP operation(s) per point:" << endl; addComment(os, eq); - os << " const int scalar_fp_ops = " << fpops.getNumOps() << ";" << endl << - " const int scalar_points_updated = " << eq.getNumEqs() << ";" << endl; + os << " const int scalar_fp_ops = " << stats.getNumOps() << ";\n" << + " const int scalar_points_read = " << stats.getNumReads() << ";\n" << + " const int scalar_points_written = " << stats.getNumWrites() << ";\n"; // Eq-group ctor. { @@ -901,7 +902,7 @@ void YASKCppPrinter::printCode(ostream& os) { os << " // SIMD calculations use " << vv.getNumPoints() << " vector block(s) created from " << vv.getNumAlignedVecs() << " aligned vector-block(s)." << endl; - os << " // There are approximately " << (fpops.getNumOps() * numResults) << + os << " // There are approximately " << (stats.getNumOps() * numResults) << " FP operation(s) per invocation." << endl; os << " void calc_cluster(" << _context_base << "& context, " << _dims._allDims.makeDimStr(", ", "idx_t ", "v") << ") {" << endl; diff --git a/src/foldBuilder/main.cpp b/src/foldBuilder/main.cpp index 00e86f1d..273746eb 100644 --- a/src/foldBuilder/main.cpp +++ b/src/foldBuilder/main.cpp @@ -70,6 +70,7 @@ bool doCse = true; string stepDim = "t"; int haloSize = 0; // 0 means auto. int stepAlloc = 0; // 0 means auto. +bool find_deps = true; // find dependencies between equations. string eq_group_basename_default = "stencil"; ostream* open_file(const string& name) { @@ -129,11 +130,11 @@ void usage(const string& cmd) { " -halo \n" " Specify the sizes of the halos.\n" " By default, halos are calculated automatically for each grid.\n" - " -lus\n" - " Make last dimension of fold unit stride (instead of first).\n" + " [-no]-lus\n" + " Make last dimension of fold unit stride (default=" << (!firstInner) << ").\n" " This controls the intra-vector memory layout.\n" - " -aul\n" - " Allow simple unaligned loads.\n" + " [-no]-aul\n" + " Allow simple unaligned loads (default=" << allowUnalignedLoads << ").\n" " To use this correctly, only 1D folds are allowed, and\n" " the memory layout used by YASK must have that same dimension in unit stride.\n" " [-no]-comb\n" @@ -144,6 +145,8 @@ void usage(const string& cmd) { " Set heuristic for max single expression-size (default=" << maxExprSize << ").\n" " -min-es \n" " Set heuristic for min expression-size for reuse (default=" << minExprSize << ").\n" + " [-no]-find-deps\n" + " Automatically find dependencies between equations (default=" << find_deps << ").\n" "\n" //" -ps Print stats for all folding options for given vector length.\n" " -pm \n" @@ -190,8 +193,16 @@ void parseOpts(int argc, const char* argv[]) else if (opt == "-lus") firstInner = false; + else if (opt == "-no-lus") + firstInner = true; else if (opt == "-aul") allowUnalignedLoads = true; + else if (opt == "-no-aul") + allowUnalignedLoads = false; + else if (opt == "-find-deps") + find_deps = true; + else if (opt == "-no-find-deps") + find_deps = false; else if (opt == "-comb") doComb = true; else if (opt == "-no-comb") @@ -399,21 +410,25 @@ int main(int argc, const char* argv[]) { stencilFunc->define(dims._allDims); // Check for illegal dependencies within equations for scalar size. - cout << "Checking equation(s) with scalar operations...\n" - " If this fails, review stencil equation(s) for illegal dependencies.\n"; - grids.checkDeps(dims._scalar, dims._stepDim); + if (find_deps) { + cout << "Checking equation(s) with scalar operations...\n" + " If this fails, review stencil equation(s) for illegal dependencies.\n"; + grids.checkDeps(dims._scalar, dims._stepDim); + } // Check for illegal dependencies within equations for vector size. - cout << "Checking equation(s) with folded-vector operations...\n" - " If this fails, the fold dimensions are not compatible with all equations.\n"; - grids.checkDeps(dims._fold, dims._stepDim); - - // Check for illegal dependencies within equations for cluster sizes and + if (find_deps) { + cout << "Checking equation(s) with folded-vector operations...\n" + " If this fails, the fold dimensions are not compatible with all equations.\n"; + grids.checkDeps(dims._fold, dims._stepDim); + } + + // Check for illegal dependencies within equations for cluster size and // also create equation groups based on legal dependencies. cout << "Checking equation(s) with clusters of vectors...\n" " If this fails, the cluster dimensions are not compatible with all equations.\n"; EqGroups eqGroups(eq_group_basename_default, dims); - eqGroups.findEqGroups(grids, eqGroupTargets, dims._clusterPts); + eqGroups.findEqGroups(grids, eqGroupTargets, dims._clusterPts, find_deps); optimizeEqGroups(eqGroups, "scalar & vector", false, cout); // Make copies of all the equations at each cluster offset. diff --git a/src/stencil_calc.cpp b/src/stencil_calc.cpp index a43d015a..1972f28b 100644 --- a/src/stencil_calc.cpp +++ b/src/stencil_calc.cpp @@ -809,13 +809,19 @@ namespace yask { " manual-L2-prefetch-distance: " << PFDL2 << endl << endl; - rank_numpts_1t = 0; rank_numFpOps_1t = 0; // sums across eqs for this rank. + // sums across eqs for this rank. + rank_numpts_1t = 0; + rank_reads_1t = 0; + rank_numFpOps_1t = 0; for (auto eg : eqGroups) { - idx_t updates1 = eg->get_scalar_points_updated(); + idx_t updates1 = eg->get_scalar_points_written(); idx_t updates_domain = updates1 * eg->bb_num_points; + rank_numpts_1t += updates_domain; + idx_t reads1 = eg->get_scalar_points_read(); + idx_t reads_domain = reads1 * eg->bb_num_points; + rank_reads_1t += reads_domain; idx_t fpops1 = eg->get_scalar_fp_ops(); idx_t fpops_domain = fpops1 * eg->bb_num_points; - rank_numpts_1t += updates_domain; rank_numFpOps_1t += fpops_domain; os << "Stats for equation-group '" << eg->get_name() << "':\n" << " sub-domain size: " << @@ -823,6 +829,8 @@ namespace yask { " valid points in sub domain: " << printWithPow10Multiplier(eg->bb_num_points) << endl << " grid-updates per point: " << updates1 << endl << " grid-updates in sub-domain: " << printWithPow10Multiplier(updates_domain) << endl << + " grid-reads per point: " << reads1 << endl << + " grid-reads in sub-domain: " << printWithPow10Multiplier(reads_domain) << endl << " est FP-ops per point: " << fpops1 << endl << " est FP-ops in sub-domain: " << printWithPow10Multiplier(fpops_domain) << endl; } @@ -840,6 +848,10 @@ namespace yask { tot_numpts_1t = sumOverRanks(rank_numpts_1t, comm); tot_numpts_dt = tot_numpts_1t * dt; + rank_reads_dt = rank_reads_1t * dt; + tot_reads_1t = sumOverRanks(rank_reads_1t, comm); + tot_reads_dt = tot_reads_1t * dt; + rank_numFpOps_dt = rank_numFpOps_1t * dt; tot_numFpOps_1t = sumOverRanks(rank_numFpOps_1t, comm); tot_numFpOps_dt = tot_numFpOps_1t * dt; @@ -861,15 +873,24 @@ namespace yask { " problem-size in all ranks, for all time-steps: " << printWithPow10Multiplier(tot_domain_dt) << endl << endl << - " grid-points-updated in this rank, for one time-step: " << + " grid-point-updates in this rank, for one time-step: " << printWithPow10Multiplier(rank_numpts_1t) << endl << - " grid-points-updated in all ranks, for one time-step: " << + " grid-point-updates in all ranks, for one time-step: " << printWithPow10Multiplier(tot_numpts_1t) << endl << - " grid-points-updated in this rank, for all time-steps: " << + " grid-point-updates in this rank, for all time-steps: " << printWithPow10Multiplier(rank_numpts_dt) << endl << - " grid-points-updated in all ranks, for all time-steps: " << + " grid-point-updates in all ranks, for all time-steps: " << printWithPow10Multiplier(tot_numpts_dt) << endl << endl << + " grid-point-reads in this rank, for one time-step: " << + printWithPow10Multiplier(rank_reads_1t) << endl << + " grid-point-reads in all ranks, for one time-step: " << + printWithPow10Multiplier(tot_reads_1t) << endl << + " grid-point-reads in this rank, for all time-steps: " << + printWithPow10Multiplier(rank_reads_dt) << endl << + " grid-point-reads in all ranks, for all time-steps: " << + printWithPow10Multiplier(tot_reads_dt) << endl << + endl << " est-FP-ops in this rank, for one time-step: " << printWithPow10Multiplier(rank_numFpOps_1t) << endl << " est-FP-ops in all ranks, for one time-step: " << @@ -881,8 +902,9 @@ namespace yask { endl << "Notes:\n" << " problem-size is based on rank-domain sizes specified in command-line (dw * dx * dy * dz).\n" << - " grid-points-updated is based sum of grid-updates-in-sub-domain across equation-group(s).\n" << - " est-FP-ops is based on sum of est-FP-ops-in-sub-domain across equation-group(s).\n" << + " grid-point-updates is based on sum of grid-updates in sub-domain across equation-group(s).\n" << + " grid-point-reads is based on sum of grid-reads in sub-domain across equation-group(s).\n" << + " est-FP-ops is based on sum of est-FP-ops in sub-domain across equation-group(s).\n" << endl; } @@ -1542,26 +1564,36 @@ namespace yask { // Determine num regions. // Also fix up region sizes as needed. os << "\nRegions:" << endl; - idx_t nrt = findNumRegions(os, rt, dt, CPTS_T, "t"); - idx_t nrw = findNumRegions(os, rw, dw, CPTS_W, "w"); - idx_t nrx = findNumRegions(os, rx, dx, CPTS_X, "x"); - idx_t nry = findNumRegions(os, ry, dy, CPTS_Y, "y"); - idx_t nrz = findNumRegions(os, rz, dz, CPTS_Z, "z"); + idx_t nrt = findNumRegionsInDomain(os, rt, dt, CPTS_T, "t"); + idx_t nrw = findNumRegionsInDomain(os, rw, dw, CPTS_W, "w"); + idx_t nrx = findNumRegionsInDomain(os, rx, dx, CPTS_X, "x"); + idx_t nry = findNumRegionsInDomain(os, ry, dy, CPTS_Y, "y"); + idx_t nrz = findNumRegionsInDomain(os, rz, dz, CPTS_Z, "z"); idx_t nr = nrt * nrw * nrx * nry * nrz; - os << " num-regions-per-rank: " << nr << endl; + os << " num-regions-per-rank-domain: " << nr << endl; + os << " Since the temporal region size is " << rt << + ", temporal wave-front tiling is "; + if (rt <= 1) os << "NOT "; + os << "enabled.\n"; // Determine num blocks. // Also fix up block sizes as needed. os << "\nBlocks:" << endl; - idx_t nbt = findNumBlocks(os, bt, rt, CPTS_T, "t"); - idx_t nbw = findNumBlocks(os, bw, rw, CPTS_W, "w"); - idx_t nbx = findNumBlocks(os, bx, rx, CPTS_X, "x"); - idx_t nby = findNumBlocks(os, by, ry, CPTS_Y, "y"); - idx_t nbz = findNumBlocks(os, bz, rz, CPTS_Z, "z"); + idx_t nbt = findNumBlocksInRegion(os, bt, rt, CPTS_T, "t"); + idx_t nbw = findNumBlocksInRegion(os, bw, rw, CPTS_W, "w"); + idx_t nbx = findNumBlocksInRegion(os, bx, rx, CPTS_X, "x"); + idx_t nby = findNumBlocksInRegion(os, by, ry, CPTS_Y, "y"); + idx_t nbz = findNumBlocksInRegion(os, bz, rz, CPTS_Z, "z"); idx_t nb = nbt * nbw * nbx * nby * nbz; os << " num-blocks-per-region: " << nb << endl; - - // Adjust defaults for block-groups. + nbw = findNumBlocksInDomain(os, bw, dw, CPTS_W, "w"); + nbx = findNumBlocksInDomain(os, bx, dx, CPTS_X, "x"); + nby = findNumBlocksInDomain(os, by, dy, CPTS_Y, "y"); + nbz = findNumBlocksInDomain(os, bz, dz, CPTS_Z, "z"); + nb = nbw * nbx * nby * nbz; + os << " num-blocks-per-rank-domain: " << nb << endl; + + // Adjust defaults for block-groups to be size of block. if (!bgw) bgw = bw; if (!bgx) bgx = bx; if (!bgy) bgy = by; @@ -1569,15 +1601,20 @@ namespace yask { // Determine num block-groups. // Also fix up block-group sizes as needed. + // TODO: only print this if block-grouping is enabled. os << "\nBlock-groups:" << endl; - idx_t nbgw = findNumGroups(os, bgw, rw, bw, "w"); - idx_t nbgx = findNumGroups(os, bgx, rx, bx, "x"); - idx_t nbgy = findNumGroups(os, bgy, ry, by, "y"); - idx_t nbgz = findNumGroups(os, bgz, rz, bz, "z"); + idx_t nbgw = findNumGroupsInRegion(os, bgw, rw, bw, "w"); + idx_t nbgx = findNumGroupsInRegion(os, bgx, rx, bx, "x"); + idx_t nbgy = findNumGroupsInRegion(os, bgy, ry, by, "y"); + idx_t nbgz = findNumGroupsInRegion(os, bgz, rz, bz, "z"); idx_t nbg = nbgw * nbgx * nbgy * nbgz; os << " num-block-groups-per-region: " << nbg << endl; + nbw = findNumBlocksInGroup(os, bw, bgw, CPTS_W, "w"); + nbx = findNumBlocksInGroup(os, bx, bgx, CPTS_X, "x"); + nby = findNumBlocksInGroup(os, by, bgy, CPTS_Y, "y"); + nbz = findNumBlocksInGroup(os, bz, bgz, CPTS_Z, "z"); + nb = nbw * nbx * nby * nbz; + os << " num-blocks-per-block-group: " << nb << endl; } - - } // namespace yask. diff --git a/src/stencil_calc.hpp b/src/stencil_calc.hpp index ed06bebd..18957bca 100644 --- a/src/stencil_calc.hpp +++ b/src/stencil_calc.hpp @@ -260,16 +260,18 @@ namespace yask { // Various metrics calculated in allocAll(). // 'rank_' prefix indicates for this rank. // 'tot_' prefix indicates over all ranks. - // 'numpts' indicates points actually calculated in sub-domains. // 'domain' indicates points in domain-size specified on cmd-line. + // 'numpts' indicates points actually calculated in sub-domains. + // 'reads' indicates points actually read by eq-groups. // 'numFpOps' indicates est. number of FP ops. // 'nbytes' indicates number of bytes allocated. // '_1t' suffix indicates work for one time-step. // '_dt' suffix indicates work for all time-steps. - idx_t rank_numpts_1t, rank_numpts_dt, tot_numpts_1t, tot_numpts_dt; - idx_t rank_domain_1t, rank_domain_dt, tot_domain_1t, tot_domain_dt; - idx_t rank_numFpOps_1t, rank_numFpOps_dt, tot_numFpOps_1t, tot_numFpOps_dt; - idx_t rank_nbytes, tot_nbytes; + idx_t rank_domain_1t=0, rank_domain_dt=0, tot_domain_1t=0, tot_domain_dt=0; + idx_t rank_numpts_1t=0, rank_numpts_dt=0, tot_numpts_1t=0, tot_numpts_dt=0; + idx_t rank_reads_1t=0, rank_reads_dt=0, tot_reads_1t=0, tot_reads_dt=0; + idx_t rank_numFpOps_1t=0, rank_numFpOps_dt=0, tot_numFpOps_1t=0, tot_numFpOps_dt=0; + idx_t rank_nbytes=0, tot_nbytes=0; // MPI environment. MPI_Comm comm=0; @@ -473,8 +475,9 @@ namespace yask { // Get estimated number of FP ops done for one scalar eval. virtual int get_scalar_fp_ops() const =0; - // Get number of points updated for one scalar eval. - virtual int get_scalar_points_updated() const =0; + // Get number of points read and written for one scalar eval. + virtual int get_scalar_points_read() const =0; + virtual int get_scalar_points_written() const =0; // Set the bounding-box vars for this eq group in this rank. virtual void find_bounding_box(); @@ -553,8 +556,11 @@ namespace yask { virtual int get_scalar_fp_ops() const { return _eqGroup.scalar_fp_ops; } - virtual int get_scalar_points_updated() const { - return _eqGroup.scalar_points_updated; + virtual int get_scalar_points_read() const { + return _eqGroup.scalar_points_read; + } + virtual int get_scalar_points_written() const { + return _eqGroup.scalar_points_written; } // Add dependency. diff --git a/src/stencil_main.cpp b/src/stencil_main.cpp index 2f0024e5..4cbc6a25 100644 --- a/src/stencil_main.cpp +++ b/src/stencil_main.cpp @@ -258,7 +258,7 @@ int main(int argc, char** argv) os << "time (sec): " << printWithPow10Multiplier(elapsed_time) << endl << "throughput (prob-size-points/sec): " << printWithPow10Multiplier(dpps) << endl << - "throughput (points-updated/sec): " << printWithPow10Multiplier(apps) << endl << + "throughput (point-updates/sec): " << printWithPow10Multiplier(apps) << endl << "throughput (est-FLOPS): " << printWithPow10Multiplier(flops) << endl; #ifdef USE_MPI os << @@ -276,12 +276,12 @@ int main(int argc, char** argv) os << divLine << "best-time (sec): " << printWithPow10Multiplier(best_elapsed_time) << endl << "best-throughput (prob-size-points/sec): " << printWithPow10Multiplier(best_dpps) << endl << - "best-throughput (points-updated/sec): " << printWithPow10Multiplier(best_apps) << endl << + "best-throughput (point-updates/sec): " << printWithPow10Multiplier(best_apps) << endl << "best-throughput (est-FLOPS): " << printWithPow10Multiplier(best_flops) << endl << divLine << "Notes:\n" << " prob-size-points/sec is based on problem-size as described above.\n" << - " points-updated/sec is based on grid-points-updated as described above.\n" << + " point-updates/sec is based on grid-point-updates as described above.\n" << " est-FLOPS is based on est-FP-ops as described above.\n" << endl; diff --git a/src/utils.cpp b/src/utils.cpp index e0292fb2..57bca7db 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -109,25 +109,29 @@ namespace yask { return res; } - // Fix bsize, if needed, to fit into rsize and be a multiple of mult. + // Alter 'inner_size', if needed, to fit into 'outer_size' and be a multiple of 'mult'. + // Output info to 'os' using '*_name' and 'dim'. // Return number of blocks. idx_t findNumSubsets(ostream& os, - idx_t& bsize, const string& bname, - idx_t rsize, const string& rname, + idx_t& inner_size, const string& inner_name, + idx_t outer_size, const string& outer_name, idx_t mult, const string& dim) { - if (bsize < 1) bsize = rsize; // 0 => use full size. - bsize = ROUND_UP(bsize, mult); - if (bsize > rsize) bsize = rsize; - idx_t nblks = (rsize + bsize - 1) / bsize; - idx_t rem = rsize % bsize; - idx_t nfull_blks = rem ? (nblks - 1) : nblks; - - os << " In '" << dim << "' dimension, " << rname << " of size " << - rsize << " is divided into " << nfull_blks << " " << bname << "(s) of size " << bsize; + if (inner_size < 1) + inner_size = outer_size; // 0 => use full size. + inner_size = ROUND_UP(inner_size, mult); + if (inner_size > outer_size) + inner_size = outer_size; + idx_t ninner = (outer_size + inner_size - 1) / inner_size; // full or partial. + idx_t rem = outer_size % inner_size; // size of remainder. + idx_t nfull = rem ? (ninner - 1) : ninner; // full only. + + os << " In '" << dim << "' dimension, " << outer_name << " of size " << + outer_size << " contains " << nfull << " " << + inner_name << "(s) of size " << inner_size; if (rem) - os << " plus 1 remainder " << bname << " of size " << rem; + os << " plus 1 remainder " << inner_name << " of size " << rem; os << "." << endl; - return nblks; + return ninner; } // Find sum of rank_vals over all ranks. diff --git a/src/utils.hpp b/src/utils.hpp index 3361d3f1..13a2877c 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -135,15 +135,23 @@ namespace yask { idx_t& inner_size, const std::string& inner_name, idx_t outer_size, const std::string& outer_name, idx_t mult, const std::string& dim); - inline idx_t findNumBlocks(std::ostream& os, idx_t& bsize, idx_t rsize, + inline idx_t findNumBlocksInDomain(std::ostream& os, idx_t& bsize, idx_t dsize, + idx_t mult, const std::string& dim) { + return findNumSubsets(os, bsize, "block", dsize, "rank-domain", mult, dim); + } + inline idx_t findNumBlocksInRegion(std::ostream& os, idx_t& bsize, idx_t rsize, idx_t mult, const std::string& dim) { return findNumSubsets(os, bsize, "block", rsize, "region", mult, dim); } - inline idx_t findNumGroups(std::ostream& os, idx_t& gsize, idx_t rsize, + inline idx_t findNumBlocksInGroup(std::ostream& os, idx_t& bsize, idx_t gsize, + idx_t mult, const std::string& dim) { + return findNumSubsets(os, bsize, "block", gsize, "block-group", mult, dim); + } + inline idx_t findNumGroupsInRegion(std::ostream& os, idx_t& gsize, idx_t rsize, idx_t mult, const std::string& dim) { - return findNumSubsets(os, gsize, "group", rsize, "region", mult, dim); + return findNumSubsets(os, gsize, "block-group", rsize, "region", mult, dim); } - inline idx_t findNumRegions(std::ostream& os, idx_t& rsize, idx_t dsize, + inline idx_t findNumRegionsInDomain(std::ostream& os, idx_t& rsize, idx_t dsize, idx_t mult, const std::string& dim) { return findNumSubsets(os, rsize, "region", dsize, "rank-domain", mult, dim); } diff --git a/stencil-tuner.pl b/stencil-tuner.pl index dd49eb46..76231e27 100755 --- a/stencil-tuner.pl +++ b/stencil-tuner.pl @@ -322,7 +322,7 @@ sub usage { open OUTFILE, ">$outFile" or die "error: cannot write to '$outFile'\n"; # things to get from the run. -my $fitnessMetric = 'best-throughput (points-updated/sec)'; +my $fitnessMetric = 'best-throughput (point-updates/sec)'; my $timeMetric = 'best-time (sec)'; my $dimsMetric = 'rank-domain-size'; my @metrics = ( $fitnessMetric, @@ -344,7 +344,8 @@ sub usage { 'manual-L1-prefetch-distance', 'manual-L2-prefetch-distance', 'problem-size in all ranks, for all time-steps', - 'grid-points-updated in all ranks, for all time-steps', + 'grid-point-updates in all ranks, for all time-steps', + 'grid-point-reads in all ranks, for all time-steps', 'Total overall allocation', ); From d110e0fb03c52c8a3aa75088a68f80ea3bd919a8 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Sun, 12 Mar 2017 16:22:50 -0700 Subject: [PATCH 09/20] Add sub-blocks. This is another level of tiling between blocks and vector-clusters. This allows threads in a team to work on arbitrarily-sized tiles. Previously, they could only work on slabs or pencils within a block. Grouping for sub-blocks is supported with cmd-line options to control group size. Also: remove some g++ warnings. --- Makefile | 86 ++++++++++------- src/foldBuilder/Expr.cpp | 6 +- src/foldBuilder/main.cpp | 6 +- src/stencil_calc.cpp | 185 ++++++++++++++++++++++++++++-------- src/stencil_calc.hpp | 127 +++++++++++++------------ src/utils.hpp | 16 +++- stencil-tuner.pl | 198 +++++++++++++++++++++------------------ 7 files changed, 390 insertions(+), 234 deletions(-) diff --git a/Makefile b/Makefile index 95c158c1..6e8ef78c 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ # # fold: How to fold vectors (x*y*z). # Vectorization in dimensions perpendicular to the inner loop -# (defined by BLOCK_LOOP_CODE below) often works well. +# (defined by SUB_BLOCK_LOOP_CODE below) often works well. # # cluster: How many folded vectors to evaluate simultaneously. # @@ -127,8 +127,6 @@ else ifeq ($(stencil),fsg) eqs ?= v_br=v_br,v_bl=v_bl,v_tr=v_tr,v_tl=v_tl,s_br=s_br,s_bl=s_bl,s_tr=s_tr,s_tl=s_tl time_alloc ?= 1 ifeq ($(arch),knl) -REGION_LOOP_CODE ?= omp serpentine loop(rn,ry,rx,rz) { calc(block(rt)); } -BLOCK_LOOP_CODE ?= serpentine loop(bnv,byv,bxv) { prefetch(L2) loop(bzv) { calc(cluster(bt)); } } omp_schedule ?= guided def_block_size ?= 16 def_thread_divisor ?= 4 @@ -144,8 +142,8 @@ ifeq ($(arch),knc) ISA ?= -mmic MACROS += USE_INTRIN512 FB_TARGET ?= knc -def_block_threads ?= 4 -BLOCK_LOOP_INNER_MODS ?= prefetch(L1,L2) +def_block_threads ?= 4 +SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L1,L2) else ifeq ($(arch),knl) @@ -156,7 +154,7 @@ FB_TARGET ?= 512 def_block_size ?= 96 def_block_threads ?= 8 streaming_stores ?= 0 -BLOCK_LOOP_INNER_MODS ?= prefetch(L1) +SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L1) else ifeq ($(arch),skx) @@ -255,8 +253,9 @@ FB_STENCIL_LIST := src/foldBuilder/stencils.hpp GEN_HEADERS = $(addprefix src/, \ stencil_rank_loops.hpp \ stencil_region_loops.hpp \ - stencil_halo_loops.hpp \ stencil_block_loops.hpp \ + stencil_sub_block_loops.hpp \ + stencil_halo_loops.hpp \ layout_macros.hpp layouts.hpp \ $(ST_MACRO_FILE) $(ST_CODE_FILE) ) ifneq ($(eqs),) @@ -341,39 +340,47 @@ endif # gen-loops.pl args: -# Rank loops break up the whole rank into smaller regions. -# In order for temporal wavefronts to operate properly, the -# order of spatial dimensions may be changed, but traversal -# paths that do not have strictly incrementing indices (e.g., -# grouped, serpentine, square-wave) may not be used here when -# using temporal wavefronts. The time loop may be found -# in StencilEquations::calc_rank(). +# Rank loops break up the whole rank into smaller regions. In order for +# temporal wavefronts to operate properly, the order of spatial dimensions +# may be changed, but the scanning paths must have strictly incrementing +# indices. Those that do not (e.g., grouped, serpentine, square-wave) may +# *not* be used here when using temporal wavefronts. The time loop may be +# found in StencilEquations::calc_rank(). RANK_LOOP_OPTS = -dims 'dw,dx,dy,dz' RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop(dw,dx,dy,dz) \ { $(RANK_LOOP_INNER_MODS) calc(region(start_dt, stop_dt, eqGroup_ptr)); } -# Region loops break up a region using OpenMP threading into blocks. -# The region time loops are not coded here to allow for proper -# spatial skewing for temporal wavefronts. The time loop may be found -# in StencilEquations::calc_region(). +# Region loops break up a region using OpenMP threading into blocks. The +# 'omp' modifier creates an outer OpenMP loop so that each block is assigned +# to a top-level OpenMP thread. The region time loops are not coded here to +# allow for proper spatial skewing for temporal wavefronts. The time loop +# may be found in StencilEquations::calc_region(). REGION_LOOP_OPTS = -dims 'rw,rx,ry,rz' \ -ompConstruct '$(omp_par_for) schedule($(omp_schedule)) proc_bind(spread)' \ -calcPrefix 'eg->calc_' REGION_LOOP_OUTER_MODS ?= grouped omp -REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) \ - { $(REGION_LOOP_INNER_MODS) calc(block(rt)); } - -# Block loops break up a block into vector clusters. -# The indices at this level are by vector instead of element; -# this is indicated by the 'v' suffix. -# The 'omp' modifier creates a nested OpenMP loop. -# There is no time loop here because threaded temporal blocking is not yet supported. -BLOCK_LOOP_OPTS = -dims 'bwv,bxv,byv,bzv' \ +REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) { \ + $(REGION_LOOP_INNER_MODS) calc(block(rt)); } + +# Block loops break up a block into sub-blocks. The 'omp' modifier creates +# a nested OpenMP loop so that each sub-block is assigned to a nested OpenMP +# thread. There is no time loop here because threaded temporal blocking is +# not yet supported. +BLOCK_LOOP_OPTS = -dims 'bw,bx,by,bz' \ -ompConstruct '$(omp_par_for) schedule($(omp_block_schedule)) proc_bind(close)' -BLOCK_LOOP_INNER_MODS ?= prefetch(L2) -BLOCK_LOOP_OUTER_MODS ?= omp -BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bwv,bxv) { loop(byv) \ - { $(BLOCK_LOOP_INNER_MODS) loop(bzv) { calc(cluster(bt)); } } } +BLOCK_LOOP_OUTER_MODS ?= grouped omp +BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bw,bx,by,bz) { \ + $(BLOCK_LOOP_INNER_MODS) calc(sub_block(bt)); } + +# Sub-block loops break up a sub-block into vector clusters. The indices at +# this level are by vector instead of element; this is indicated by the 'v' +# suffix. The innermost loop here is the final innermost loop. There is +# no time loop here because threaded temporal blocking is not yet supported. +SUB_BLOCK_LOOP_OPTS = -dims 'sbwv,sbxv,sbyv,sbzv' +SUB_BLOCK_LOOP_OUTER_MODS ?= square_wave serpentine +SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L2) +SUB_BLOCK_LOOP_CODE ?= $(SUB_BLOCK_LOOP_OUTER_MODS) loop(sbwv,sbxv,sbyv) { \ + $(SUB_BLOCK_LOOP_INNER_MODS) loop(sbzv) { calc(cluster(sbt)); } } # Halo pack/unpack loops break up a region face, edge, or corner into vectors. # The indices at this level are by vector instead of element; @@ -453,6 +460,10 @@ echo-settings: @echo BLOCK_LOOP_OUTER_MODS="\"$(BLOCK_LOOP_OUTER_MODS)\"" @echo BLOCK_LOOP_INNER_MODS="\"$(BLOCK_LOOP_INNER_MODS)\"" @echo BLOCK_LOOP_CODE="\"$(BLOCK_LOOP_CODE)\"" + @echo SUB_BLOCK_LOOP_OPTS="\"$(SUB_BLOCK_LOOP_OPTS)\"" + @echo SUB_BLOCK_LOOP_OUTER_MODS="\"$(SUB_BLOCK_LOOP_OUTER_MODS)\"" + @echo SUB_BLOCK_LOOP_INNER_MODS="\"$(SUB_BLOCK_LOOP_INNER_MODS)\"" + @echo SUB_BLOCK_LOOP_CODE="\"$(SUB_BLOCK_LOOP_CODE)\"" @echo HALO_LOOP_OPTS="\"$(HALO_LOOP_OPTS)\"" @echo HALO_LOOP_OUTER_MODS="\"$(RANK_LOOP_OUTER_MODS)\"" @echo HALO_LOOP_INNER_MODS="\"$(RANK_LOOP_INNER_MODS)\"" @@ -463,7 +474,7 @@ echo-settings: code_stats: @echo @echo "Code stats for stencil computation:" - ./get-loop-stats.pl -t='block_loops' *.s + ./get-loop-stats.pl -t='sub_block_loops' *.s $(STENCIL_EXEC_NAME): $(STENCIL_OBJS) $(LD) -o $@ $(STENCIL_OBJS) $(CXXFLAGS) $(LFLAGS) @@ -476,12 +487,15 @@ src/stencil_rank_loops.hpp: gen-loops.pl Makefile src/stencil_region_loops.hpp: gen-loops.pl Makefile ./$< -output $@ $(REGION_LOOP_OPTS) $(EXTRA_LOOP_OPTS) $(EXTRA_REGION_LOOP_OPTS) "$(REGION_LOOP_CODE)" -src/stencil_halo_loops.hpp: gen-loops.pl Makefile - ./$< -output $@ $(HALO_LOOP_OPTS) $(EXTRA_LOOP_OPTS) $(EXTRA_HALO_LOOP_OPTS) "$(HALO_LOOP_CODE)" - src/stencil_block_loops.hpp: gen-loops.pl Makefile ./$< -output $@ $(BLOCK_LOOP_OPTS) $(EXTRA_LOOP_OPTS) $(EXTRA_BLOCK_LOOP_OPTS) "$(BLOCK_LOOP_CODE)" +src/stencil_sub_block_loops.hpp: gen-loops.pl Makefile + ./$< -output $@ $(SUB_BLOCK_LOOP_OPTS) $(EXTRA_LOOP_OPTS) $(EXTRA_SUB_BLOCK_LOOP_OPTS) "$(SUB_BLOCK_LOOP_CODE)" + +src/stencil_halo_loops.hpp: gen-loops.pl Makefile + ./$< -output $@ $(HALO_LOOP_OPTS) $(EXTRA_LOOP_OPTS) $(EXTRA_HALO_LOOP_OPTS) "$(HALO_LOOP_CODE)" + src/layout_macros.hpp: gen-layouts.pl ./$< -m > $@ @@ -537,7 +551,7 @@ help: @echo "make clean; make arch=knl stencil=iso3dfd" @echo "make clean; make arch=knl stencil=awp mpi=1" @echo "make clean; make arch=skx stencil=ave fold='x=1,y=2,z=4' cluster='x=2'" - @echo "make clean; make arch=knc stencil=3axis radius=4 BLOCK_LOOP_INNER_MODS='prefetch(L1,L2)' EXTRA_MACROS='PFDL1=2 PFDL2=4'" + @echo "make clean; make arch=knc stencil=3axis radius=4 SUB_BLOCK_LOOP_INNER_MODS='prefetch(L1,L2)' EXTRA_MACROS='PFDL1=2 PFDL2=4'" @echo " " @echo "Example debug usage:" @echo "make arch=knl stencil=iso3dfd OMPFLAGS='-qopenmp-stubs' EXTRA_CXXFLAGS='-O0' EXTRA_MACROS='DEBUG'" diff --git a/src/foldBuilder/Expr.cpp b/src/foldBuilder/Expr.cpp index d94c4864..282bf3fc 100644 --- a/src/foldBuilder/Expr.cpp +++ b/src/foldBuilder/Expr.cpp @@ -684,7 +684,7 @@ void Grids::findDeps(IntTuple& pts, assert(outGrids.count(eq1p)); assert(inGrids.count(eq1p)); auto& og1 = outGrids.at(eq1p); - auto& ig1 = inGrids.at(eq1p); + //auto& ig1 = inGrids.at(eq1p); auto& op1 = outPts.at(eq1p); auto& ip1 = inPts.at(eq1p); auto cond1 = g1->getCond(eq1p); @@ -744,7 +744,7 @@ void Grids::findDeps(IntTuple& pts, // All eqs in grid g2. for (auto eq2 : g2->getEqs()) { auto* eq2p = eq2.get(); - auto& og2 = outGrids.at(eq2p); + //auto& og2 = outGrids.at(eq2p); auto& ig2 = inGrids.at(eq2p); auto& op2 = outPts.at(eq2p); auto& ip2 = inPts.at(eq2p); @@ -1168,7 +1168,7 @@ void EqGroups::findEqGroups(Grids& allGrids, IntTuple& pts, EqDepMap& eq_deps) { - auto& stepDim = _dims->_stepDim; + //auto& stepDim = _dims->_stepDim; // Map to track indices per eq-group name. map indices; diff --git a/src/foldBuilder/main.cpp b/src/foldBuilder/main.cpp index 273746eb..590908f1 100644 --- a/src/foldBuilder/main.cpp +++ b/src/foldBuilder/main.cpp @@ -131,10 +131,10 @@ void usage(const string& cmd) { " Specify the sizes of the halos.\n" " By default, halos are calculated automatically for each grid.\n" " [-no]-lus\n" - " Make last dimension of fold unit stride (default=" << (!firstInner) << ").\n" + " Make last [first] dimension of fold unit stride (default=" << (!firstInner) << ").\n" " This controls the intra-vector memory layout.\n" " [-no]-aul\n" - " Allow simple unaligned loads (default=" << allowUnalignedLoads << ").\n" + " Do [not] allow simple unaligned loads (default=" << allowUnalignedLoads << ").\n" " To use this correctly, only 1D folds are allowed, and\n" " the memory layout used by YASK must have that same dimension in unit stride.\n" " [-no]-comb\n" @@ -396,7 +396,7 @@ int main(int argc, const char* argv[]) { // Reference to the grids and params in the stencil. Grids& grids = stencilFunc->getGrids(); - Params& params = stencilFunc->getParams(); + //Params& params = stencilFunc->getParams(); // Find all the stencil dimensions from the grids. // Create the final folds and clusters from the cmd-line options. diff --git a/src/stencil_calc.cpp b/src/stencil_calc.cpp index 1972f28b..9da0616d 100644 --- a/src/stencil_calc.cpp +++ b/src/stencil_calc.cpp @@ -280,13 +280,15 @@ namespace yask { // Each region is typically computed in a separate OpenMP 'for' region. // In it, we loop over the time steps and the stencil // equations and evaluate the blocks in the region. + // Boundaries are named start_d* and stop_d* because region loops are nested + // inside the rank-domain loops. void StencilContext::calc_region(idx_t start_dt, idx_t stop_dt, EqGroupSet* eqGroup_set, idx_t start_dw, idx_t start_dx, idx_t start_dy, idx_t start_dz, idx_t stop_dw, idx_t stop_dx, idx_t stop_dy, idx_t stop_dz) { TRACE_MSG("calc_region(t=" << start_dt << ".." << (stop_dt-1) << - ", n=" << start_dw << ".." << (stop_dw-1) << + ", w=" << start_dw << ".." << (stop_dw-1) << ", x=" << start_dx << ".." << (stop_dx-1) << ", y=" << start_dy << ".." << (stop_dy-1) << ", z=" << start_dz << ".." << (stop_dz-1) << @@ -332,7 +334,7 @@ namespace yask { if (!eqGroup_set || eqGroup_set->count(eg)) { TRACE_MSG("calc_region: eq-group '" << eg->get_name() << "'"); - // For wavefnot adjustments, see conceptual diagram in + // For wavefront adjustments, see conceptual diagram in // calc_rank_opt(). In this function, 1 of the 4 // parallelogram-shaped regions is being evaluated. At // each time-step, the parallelogram may be trimmed @@ -362,10 +364,11 @@ namespace yask { // Set number of threads for a region. set_region_threads(); - // Include automatically-generated loop code that calls - // calc_block() for each block in this region. Loops - // through n from begin_rn to end_rn-1; similar for x, y, - // and z. This code typically contains OpenMP loop(s). + // Include automatically-generated loop code that + // calls calc_block() for each block in this region. + // Loops through w from begin_rw to end_rw-1; + // similar for x, y, and z. This code typically + // contains the outer OpenMP loop(s). #include "stencil_region_loops.hpp" // Reset threads back to max. @@ -391,6 +394,42 @@ namespace yask { } // time. } + // Calculate results within a block. + void EqGroupBase::calc_block(idx_t bt, + idx_t begin_bw, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, + idx_t end_bw, idx_t end_bx, idx_t end_by, idx_t end_bz) + { + TRACE_MSG2(get_name() << ".calc_block(t=" << bt << + ", w=" << begin_bw << ".." << (end_bw-1) << + ", x=" << begin_bx << ".." << (end_bx-1) << + ", y=" << begin_by << ".." << (end_by-1) << + ", z=" << begin_bz << ".." << (end_bz-1) << + ")."); + StencilSettings& opts = _generic_context->get_settings(); + + // Steps within a block are based on sub-block sizes. + const idx_t step_bw = opts.sbw; + const idx_t step_bx = opts.sbx; + const idx_t step_by = opts.sby; + const idx_t step_bz = opts.sbz; + + // Groups in block loops are based on sub-block-group sizes. + const idx_t group_size_bw = opts.sbgw; + const idx_t group_size_bx = opts.sbgx; + const idx_t group_size_by = opts.sbgy; + const idx_t group_size_bz = opts.sbgz; + + // Set number of threads for a block. + _generic_context->set_block_threads(); + + // Include automatically-generated loop code that calls + // calc_sub_block() for each sub-block in this block. Loops through + // w from begin_bw to end_bw-1; similar for x, y, and z. This + // code typically contains the nested OpenMP loop(s). +#include "stencil_block_loops.hpp" + + } + // Init MPI-related vars and other vars related to my rank's place in // the global problem: rank index, offset, etc. Need to call this even // if not using MPI to properly init these vars. Called from @@ -780,19 +819,23 @@ namespace yask { // Report some stats. idx_t dt = _opts->dt; os << "\nSizes in points per grid (t*w*x*y*z):\n" - " vector-size: " << + " vector-size: " << VLEN_T << '*' << VLEN_W << '*' << VLEN_X << '*' << VLEN_Y << '*' << VLEN_Z << endl << - " cluster-size: " << + " cluster-size: " << CPTS_T << '*' << CPTS_W << '*' << CPTS_X << '*' << CPTS_Y << '*' << CPTS_Z << endl << - " block-size: " << + " sub-block-size: " << + _opts->sbt << '*' << _opts->sbw << '*' << _opts->sbx << '*' << _opts->sby << '*' << _opts->sbz << endl << + " sub-block-group-size: 1*" << + _opts->sbgw << '*' << _opts->sbgx << '*' << _opts->sbgy << '*' << _opts->sbgz << endl << + " block-size: " << _opts->bt << '*' << _opts->bw << '*' << _opts->bx << '*' << _opts->by << '*' << _opts->bz << endl << - " block-group-size: 1*" << + " block-group-size: 1*" << _opts->bgw << '*' << _opts->bgx << '*' << _opts->bgy << '*' << _opts->bgz << endl << - " region-size: " << + " region-size: " << _opts->rt << '*' << _opts->rw << '*' << _opts->rx << '*' << _opts->ry << '*' << _opts->rz << endl << - " rank-domain-size: " << + " rank-domain-size: " << dt << '*' << _opts->dw << '*' << _opts->dx << '*' << _opts->dy << '*' << _opts->dz << endl << - " problem-size: " << + " overall-problem-size: " << dt << '*' << tot_w << '*' << tot_x << '*' << tot_y << '*' << tot_z << endl << endl << "Other settings:\n" @@ -901,10 +944,10 @@ namespace yask { printWithPow10Multiplier(tot_numFpOps_dt) << endl << endl << "Notes:\n" << - " problem-size is based on rank-domain sizes specified in command-line (dw * dx * dy * dz).\n" << - " grid-point-updates is based on sum of grid-updates in sub-domain across equation-group(s).\n" << - " grid-point-reads is based on sum of grid-reads in sub-domain across equation-group(s).\n" << - " est-FP-ops is based on sum of est-FP-ops in sub-domain across equation-group(s).\n" << + " problem-sizes are based on rank-domain sizes specified in command-line (dw * dx * dy * dz).\n" << + " grid-point-updates are based on sum of grid-updates in sub-domain across equation-group(s).\n" << + " grid-point-reads are based on sum of grid-reads in sub-domain across equation-group(s).\n" << + " est-FP-ops are based on sum of est-FP-ops in sub-domain across equation-group(s).\n" << endl; } @@ -1464,10 +1507,12 @@ namespace yask { // Add these settigns to a cmd-line parser. void StencilSettings::add_options(CommandLineParser& parser) { - ADD_T_DIM_OPTION("d", "Domain size for this rank", d); + ADD_T_DIM_OPTION("d", "Rank-domain size", d); ADD_T_DIM_OPTION("r", "Region size", r); ADD_DIM_OPTION("b", "Block size", b); ADD_DIM_OPTION("bg", "Block-group size", bg); + ADD_DIM_OPTION("sb", "Sub-block size", sb); + ADD_DIM_OPTION("sbg", "Sub-block-group size", sbg); ADD_DIM_OPTION("p", "Extra memory-padding size", p); #ifdef USE_MPI ADD_DIM_OPTION("nr", "Num ranks", nr); @@ -1501,18 +1546,47 @@ namespace yask { os << "Usage: " << pgmName << " [options]\n" "Options:\n"; parser.print_help(os); - os << "Guidelines:\n" - " Set block sizes to specify the amount of work done in each block.\n" + os << + "Terms:\n" + " A vector is composed of FP elements.\n" + " A 'folded vector' contains elements in more than one dimension.\n" + " The size of a vector is typically that of a SIMD register.\n" + " A vector-cluster is composed of vectors.\n" + " The size of a vector-cluster is set at compile-time.\n" + " A sub-block is composed of vector-clusters.\n" + " This is the unit of work for one OpenMP thread.\n" + " A block is composed of sub-blocks.\n" + " This is the unit of work for one OpenMP thread team.\n" + " A region is composed of blocks.\n" + " Regions are used to implement temporal wave-front tiling.\n" + " A rank-domain is composed of regions.\n" + " This is the unit of work for one MPI rank.\n" + " The overall problem domain is composed of rank-domains.\n" + " This is the unit of work for all MPI ranks.\n" + "Guidelines:\n" + " Set sub-block sizes to specify a unit of work done by each thread.\n" + " A sub-block size of 0 in dimensions 'w' or 'x' =>\n" + " sub-block size is set to vector-cluster size in that dimension.\n" + " A sub-block size of 0 in dimensions 'y' or 'z' =>\n" + " sub-block size is set to block size in that dimension.\n" + " Thus, the default sub-block is a 'y-z' slab.\n" + " Temporal tiling in sub-blocks is not yet supported, so effectively, sbt = 1.\n" + " Set sub-block-group sizes to control in what order sub-blocks are evaluated within a block.\n" + " All sub-blocks that fit within a sub-block-group are evaluated before sub-blocks\n" + " in the next sub-block-group.\n" + " A sub-block-group size of 0 in a given dimension =>\n" + " sub-block-group size is set to sub-block size in that dimension.\n" + " Set block sizes to specify a unit of work done by each thread team.\n" " A block size of 0 in a given dimension =>\n" " block size is set to region size in that dimension.\n" - " Temporal cache-blocking is not yet supported, so effectively, bt = 1.\n" - " Set block-group sizes to control in what order blocks are evaluated.\n" + " Temporal tiling in blocks is not yet supported, so effectively, bt = 1.\n" + " Set block-group sizes to control in what order blocks are evaluated within a region.\n" " All blocks that fit within a block-group are evaluated before blocks\n" " in the next block-group.\n" " A block-group size of 0 in a given dimension =>\n" " block-group size is set to block size in that dimension.\n" " Set region sizes to control temporal wave-front tile sizes.\n" - " The tempral region size should be larger than one, and\n" + " The temopral region size should be larger than one, and\n" " the spatial region sizes should be less than the rank-domain sizes\n" " in at least one dimension to enable temporal wave-front tiling.\n" " The spatial region sizes should be greater than block sizes\n" @@ -1586,12 +1660,7 @@ namespace yask { idx_t nbz = findNumBlocksInRegion(os, bz, rz, CPTS_Z, "z"); idx_t nb = nbt * nbw * nbx * nby * nbz; os << " num-blocks-per-region: " << nb << endl; - nbw = findNumBlocksInDomain(os, bw, dw, CPTS_W, "w"); - nbx = findNumBlocksInDomain(os, bx, dx, CPTS_X, "x"); - nby = findNumBlocksInDomain(os, by, dy, CPTS_Y, "y"); - nbz = findNumBlocksInDomain(os, bz, dz, CPTS_Z, "z"); - nb = nbw * nbx * nby * nbz; - os << " num-blocks-per-rank-domain: " << nb << endl; + os << " num-blocks-per-rank-domain: " << (nb * nr) << endl; // Adjust defaults for block-groups to be size of block. if (!bgw) bgw = bw; @@ -1603,18 +1672,58 @@ namespace yask { // Also fix up block-group sizes as needed. // TODO: only print this if block-grouping is enabled. os << "\nBlock-groups:" << endl; - idx_t nbgw = findNumGroupsInRegion(os, bgw, rw, bw, "w"); - idx_t nbgx = findNumGroupsInRegion(os, bgx, rx, bx, "x"); - idx_t nbgy = findNumGroupsInRegion(os, bgy, ry, by, "y"); - idx_t nbgz = findNumGroupsInRegion(os, bgz, rz, bz, "z"); - idx_t nbg = nbgw * nbgx * nbgy * nbgz; + idx_t nbgw = findNumBlockGroupsInRegion(os, bgw, rw, bw, "w"); + idx_t nbgx = findNumBlockGroupsInRegion(os, bgx, rx, bx, "x"); + idx_t nbgy = findNumBlockGroupsInRegion(os, bgy, ry, by, "y"); + idx_t nbgz = findNumBlockGroupsInRegion(os, bgz, rz, bz, "z"); + idx_t nbg = nbt * nbgw * nbgx * nbgy * nbgz; os << " num-block-groups-per-region: " << nbg << endl; - nbw = findNumBlocksInGroup(os, bw, bgw, CPTS_W, "w"); - nbx = findNumBlocksInGroup(os, bx, bgx, CPTS_X, "x"); - nby = findNumBlocksInGroup(os, by, bgy, CPTS_Y, "y"); - nbz = findNumBlocksInGroup(os, bz, bgz, CPTS_Z, "z"); + nbw = findNumBlocksInBlockGroup(os, bw, bgw, CPTS_W, "w"); + nbx = findNumBlocksInBlockGroup(os, bx, bgx, CPTS_X, "x"); + nby = findNumBlocksInBlockGroup(os, by, bgy, CPTS_Y, "y"); + nbz = findNumBlocksInBlockGroup(os, bz, bgz, CPTS_Z, "z"); nb = nbw * nbx * nby * nbz; os << " num-blocks-per-block-group: " << nb << endl; + + // Adjust defaults for sub-blocks to be y-z slab. + if (!sbw) sbw = CPTS_W; + if (!sbx) sbx = CPTS_X; + if (!sby) sby = by; + if (!sbz) sbz = bz; + + // Determine num sub-blocks. + // Also fix up sub-block sizes as needed. + os << "\nSub-blocks:" << endl; + idx_t nsbt = findNumSubBlocksInBlock(os, sbt, bt, CPTS_T, "t"); + idx_t nsbw = findNumSubBlocksInBlock(os, sbw, bw, CPTS_W, "w"); + idx_t nsbx = findNumSubBlocksInBlock(os, sbx, bx, CPTS_X, "x"); + idx_t nsby = findNumSubBlocksInBlock(os, sby, by, CPTS_Y, "y"); + idx_t nsbz = findNumSubBlocksInBlock(os, sbz, bz, CPTS_Z, "z"); + idx_t nsb = nsbt * nsbw * nsbx * nsby * nsbz; + os << " num-sub-blocks-per-block: " << nsb << endl; + + // Adjust defaults for sub-block-groups to be size of sub-block. + if (!sbgw) sbgw = sbw; + if (!sbgx) sbgx = sbx; + if (!sbgy) sbgy = sby; + if (!sbgz) sbgz = sbz; + + // Determine num sub-block-groups. + // Also fix up sub-block-group sizes as needed. + // TODO: only print this if sub-block-grouping is enabled. + os << "\nSub-block-groups:" << endl; + idx_t nsbgw = findNumSubBlockGroupsInBlock(os, sbgw, bw, sbw, "w"); + idx_t nsbgx = findNumSubBlockGroupsInBlock(os, sbgx, bx, sbx, "x"); + idx_t nsbgy = findNumSubBlockGroupsInBlock(os, sbgy, by, sby, "y"); + idx_t nsbgz = findNumSubBlockGroupsInBlock(os, sbgz, bz, sbz, "z"); + idx_t nsbg = nsbgw * nsbgx * nsbgy * nsbgz; + os << " num-sub-block-groups-per-block: " << nsbg << endl; + nsbw = findNumSubBlocksInSubBlockGroup(os, sbw, sbgw, CPTS_W, "w"); + nsbx = findNumSubBlocksInSubBlockGroup(os, sbx, sbgx, CPTS_X, "x"); + nsby = findNumSubBlocksInSubBlockGroup(os, sby, sbgy, CPTS_Y, "y"); + nsbz = findNumSubBlocksInSubBlockGroup(os, sbz, sbgz, CPTS_Z, "z"); + nsb = nsbw * nsbx * nsby * nsbz; + os << " num-sub-blocks-per-sub-block-group: " << nsb << endl; } } // namespace yask. diff --git a/src/stencil_calc.hpp b/src/stencil_calc.hpp index 18957bca..73925e3e 100644 --- a/src/stencil_calc.hpp +++ b/src/stencil_calc.hpp @@ -111,8 +111,10 @@ namespace yask { // Sizes are the same for all grids. TODO: relax this restriction. idx_t dt=1, dw=0, dx=0, dy=0, dz=0; // rank size (without halos). idx_t rt=1, rw=0, rx=0, ry=0, rz=0; // region size (used for wave-front tiling). - idx_t bt=1, bw=0, bx=0, by=0, bz=0; // block size (used for cache locality). - idx_t bgw=0, bgx=0, bgy=0, bgz=0; // group-of-blocks size (only used for 'grouped' loop paths). + idx_t bgw=0, bgx=0, bgy=0, bgz=0; // block-group size (only used for 'grouped' region loops). + idx_t bt=1, bw=0, bx=0, by=0, bz=0; // block size (used for each outer thread). + idx_t sbgw=0, sbgx=0, sbgy=0, sbgz=0; // sub-block-group size (only used for 'grouped' block loops). + idx_t sbt=1, sbw=0, sbx=0, sby=0, sbz=0; // sub-block size (used for each nested thread). idx_t pw=0, px=0, py=0, pz=0; // spatial padding (in addition to halos, to avoid aliasing). // MPI settings. @@ -402,6 +404,7 @@ namespace yask { } // Set number of threads for a block. + // Return that number. virtual int set_block_threads() { // This should be a nested OMP region. @@ -423,8 +426,11 @@ namespace yask { // Vectorized and blocked stencil calculations. virtual void calc_rank_opt(); - // Calculate results within a region. - // TODO: create a public interface w/a more logical index ordering. + // Calculate results within a region. Boundaries are named start_d* + // and stop_d* because region loops are nested inside the + // rank-domain loops; the actual begin_r* and end_r* values for the + // region are derived from these. TODO: create a public interface + // w/a more logical index ordering. virtual void calc_region(idx_t start_dt, idx_t stop_dt, EqGroupSet* eqGroup_set, idx_t start_dw, idx_t start_dx, idx_t start_dy, idx_t start_dz, @@ -488,10 +494,15 @@ namespace yask { // Calculate one scalar result at time t. virtual void calc_scalar(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) =0; - // Calculate one block of results from begin to end-1 on each dimension. + // Calculate results within a block. virtual void calc_block(idx_t bt, idx_t begin_bw, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, - idx_t end_bw, idx_t end_bx, idx_t end_by, idx_t end_bz) =0; + idx_t end_bw, idx_t end_bx, idx_t end_by, idx_t end_bz); + + // Calculate one sub-block of results from begin to end-1 on each dimension. + virtual void calc_sub_block(idx_t sbt, + idx_t begin_sbw, idx_t begin_sbx, idx_t begin_sby, idx_t begin_sbz, + idx_t end_sbw, idx_t end_sbx, idx_t end_sby, idx_t end_sbz) =0; }; // Define a method named cfn to prefetch a cluster by calling vfn. @@ -574,14 +585,14 @@ namespace yask { } // Determine whether indices are in [sub-]domain for this eq group. - virtual bool is_in_valid_domain(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) { + virtual bool is_in_valid_domain(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) final { return _eqGroup.is_in_valid_domain(*_context, t, ARG_W(w) x, y, z); } // Calculate one scalar result. // This function implements the interface in the base class. - virtual void calc_scalar(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) { + virtual void calc_scalar(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) final { TRACE_MSG2(get_name() << ".calc_scalar(t=" << t << ", w=" << w << ", x=" << x << ", y=" << y << ", z=" << z << ")"); @@ -589,7 +600,7 @@ namespace yask { } // Calculate results within a cluster of vectors. - // Called from calc_block(). + // Called from calc_sub_block(). // The begin/end_c* vars are the start/stop_b* vars from the block loops. ALWAYS_INLINE void calc_cluster(idx_t ct, @@ -597,13 +608,13 @@ namespace yask { idx_t end_cwv, idx_t end_cxv, idx_t end_cyv, idx_t end_czv) { TRACE_MSG2("calc_cluster(t=" << ct << - ", wv=" << begin_cwv << ".." << (end_cwv-1) << - ", xv=" << begin_cxv << ".." << (end_cxv-1) << - ", yv=" << begin_cyv << ".." << (end_cyv-1) << - ", zv=" << begin_czv << ".." << (end_czv-1) << + ", w=" << (begin_cwv*CLEN_W) << ".." << (end_cwv*CLEN_W-1) << + ", x=" << (begin_cxv*CLEN_X) << ".." << (end_cxv*CLEN_X-1) << + ", y=" << (begin_cyv*CLEN_Y) << ".." << (end_cyv*CLEN_Y-1) << + ", z=" << (begin_czv*CLEN_Z) << ".." << (end_czv*CLEN_Z-1) << ")"); - // The step vars are hard-coded in calc_block below, and there should + // The step vars are hard-coded in calc_sub_block below, and there should // never be a partial step at this level. So, we can assume one var and // exactly CLEN_d steps in each given direction d are calculated in this // function. Thus, we can ignore the end_* vars in the calc function. @@ -621,26 +632,26 @@ namespace yask { // TODO: handle pre-fetching correctly for non-simple BBs. PREFETCH_CLUSTER_METHOD(prefetch_cluster, prefetch_cluster) #if USING_DIM_W - PREFETCH_CLUSTER_METHOD(prefetch_cluster_bwv, prefetch_cluster_w) + PREFETCH_CLUSTER_METHOD(prefetch_cluster_sbwv, prefetch_cluster_w) #endif - PREFETCH_CLUSTER_METHOD(prefetch_cluster_bxv, prefetch_cluster_x) - PREFETCH_CLUSTER_METHOD(prefetch_cluster_byv, prefetch_cluster_y) - PREFETCH_CLUSTER_METHOD(prefetch_cluster_bzv, prefetch_cluster_z) + PREFETCH_CLUSTER_METHOD(prefetch_cluster_sbxv, prefetch_cluster_x) + PREFETCH_CLUSTER_METHOD(prefetch_cluster_sbyv, prefetch_cluster_y) + PREFETCH_CLUSTER_METHOD(prefetch_cluster_sbzv, prefetch_cluster_z) - // Calculate results within a cache block. + // Calculate results for one sub-block. // This function implements the interface in the base class. - // Each block is typically computed in a separate OpenMP task. - // The begin/end_b* vars are the start/stop_r* vars from the region loops. + // Each block is typically computed in a separate OpenMP thread. + // The begin/end_sb* vars are the start/stop_b* vars from the block loops. virtual void - calc_block(idx_t bt, - idx_t begin_bw, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, - idx_t end_bw, idx_t end_bx, idx_t end_by, idx_t end_bz) + calc_sub_block(idx_t sbt, + idx_t begin_sbw, idx_t begin_sbx, idx_t begin_sby, idx_t begin_sbz, + idx_t end_sbw, idx_t end_sbx, idx_t end_sby, idx_t end_sbz) final { - TRACE_MSG2(get_name() << ".calc_block(t=" << bt << - ", w=" << begin_bw << ".." << (end_bw-1) << - ", x=" << begin_bx << ".." << (end_bx-1) << - ", y=" << begin_by << ".." << (end_by-1) << - ", z=" << begin_bz << ".." << (end_bz-1) << + TRACE_MSG2(get_name() << ".calc_sub_block(t=" << sbt << + ", w=" << begin_sbw << ".." << (end_sbw-1) << + ", x=" << begin_sbx << ".." << (end_sbx-1) << + ", y=" << begin_sby << ".." << (end_sby-1) << + ", z=" << begin_sbz << ".." << (end_sbz-1) << ")."); // If not a 'simple' domain, must use scalar code. TODO: this @@ -648,24 +659,24 @@ namespace yask { if (!bb_simple) { TRACE_MSG2("...using scalar code."); - for (idx_t w = begin_bw; w < end_bw; w++) - for (idx_t x = begin_bx; x < end_bx; x++) - for (idx_t y = begin_by; y < end_by; y++) { + for (idx_t w = begin_sbw; w < end_sbw; w++) + for (idx_t x = begin_sbx; x < end_sbx; x++) + for (idx_t y = begin_sby; y < end_sby; y++) { // Are there holes in the BB? if (bb_num_points != bb_size) { - for (idx_t z = begin_bz; z < end_bz; z++) { + for (idx_t z = begin_sbz; z < end_sbz; z++) { // Update only if point is in sub-domain for this eq group. - if (is_in_valid_domain(bt, w, x, y, z)) - calc_scalar(bt, w, x, y, z); + if (is_in_valid_domain(sbt, w, x, y, z)) + calc_scalar(sbt, w, x, y, z); } } // If no holes, don't need to check domain. else { - for (idx_t z = begin_bz; z < end_bz; z++) { - calc_scalar(bt, w, x, y, z); + for (idx_t z = begin_sbz; z < end_sbz; z++) { + calc_scalar(sbt, w, x, y, z); } } } @@ -675,38 +686,36 @@ namespace yask { // Divide indices by vector lengths. Use idiv_flr() instead of '/' // because begin/end vars may be negative (if in halo). - const idx_t begin_bwv = idiv_flr(begin_bw, VLEN_W); - const idx_t begin_bxv = idiv_flr(begin_bx, VLEN_X); - const idx_t begin_byv = idiv_flr(begin_by, VLEN_Y); - const idx_t begin_bzv = idiv_flr(begin_bz, VLEN_Z); - const idx_t end_bwv = idiv_flr(end_bw, VLEN_W); - const idx_t end_bxv = idiv_flr(end_bx, VLEN_X); - const idx_t end_byv = idiv_flr(end_by, VLEN_Y); - const idx_t end_bzv = idiv_flr(end_bz, VLEN_Z); + const idx_t begin_sbwv = idiv_flr(begin_sbw, VLEN_W); + const idx_t begin_sbxv = idiv_flr(begin_sbx, VLEN_X); + const idx_t begin_sbyv = idiv_flr(begin_sby, VLEN_Y); + const idx_t begin_sbzv = idiv_flr(begin_sbz, VLEN_Z); + const idx_t end_sbwv = idiv_flr(end_sbw, VLEN_W); + const idx_t end_sbxv = idiv_flr(end_sbx, VLEN_X); + const idx_t end_sbyv = idiv_flr(end_sby, VLEN_Y); + const idx_t end_sbzv = idiv_flr(end_sbz, VLEN_Z); // Vector-size steps are based on cluster lengths. // Using CLEN_* instead of CPTS_* because we want multiples of vector lengths. - const idx_t step_bwv = CLEN_W; - const idx_t step_bxv = CLEN_X; - const idx_t step_byv = CLEN_Y; - const idx_t step_bzv = CLEN_Z; - - // Groups in block loops are set to smallest size. - const idx_t group_size_bwv = 1; - const idx_t group_size_bxv = 1; - const idx_t group_size_byv = 1; - const idx_t group_size_bzv = 1; + const idx_t step_sbwv = CLEN_W; + const idx_t step_sbxv = CLEN_X; + const idx_t step_sbyv = CLEN_Y; + const idx_t step_sbzv = CLEN_Z; + + // Groups in sub-block loops are set to cluster size. + // TODO: specify cluster-group sizes. + const idx_t group_size_bwv = CLEN_W; + const idx_t group_size_bxv = CLEN_X; + const idx_t group_size_byv = CLEN_Y; + const idx_t group_size_bzv = CLEN_Z; #if !defined(DEBUG) && defined(__INTEL_COMPILER) #pragma forceinline recursive #endif { - // Set threads for a block. - _context->set_block_threads(); - // Include automatically-generated loop code that calls calc_cluster() // and optionally, the prefetch functions(). -#include "stencil_block_loops.hpp" +#include "stencil_sub_block_loops.hpp" } } }; diff --git a/src/utils.hpp b/src/utils.hpp index 13a2877c..f2d915e1 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -135,6 +135,18 @@ namespace yask { idx_t& inner_size, const std::string& inner_name, idx_t outer_size, const std::string& outer_name, idx_t mult, const std::string& dim); + inline idx_t findNumSubBlocksInBlock(std::ostream& os, idx_t& sbsize, idx_t bsize, + idx_t mult, const std::string& dim) { + return findNumSubsets(os, sbsize, "sub-block", bsize, "block", mult, dim); + } + inline idx_t findNumSubBlocksInSubBlockGroup(std::ostream& os, idx_t& sbsize, idx_t sbgsize, + idx_t mult, const std::string& dim) { + return findNumSubsets(os, sbsize, "sub-block", sbgsize, "sub-block-group", mult, dim); + } + inline idx_t findNumSubBlockGroupsInBlock(std::ostream& os, idx_t& sbgsize, idx_t bsize, + idx_t mult, const std::string& dim) { + return findNumSubsets(os, sbgsize, "sub-block-group", bsize, "block", mult, dim); + } inline idx_t findNumBlocksInDomain(std::ostream& os, idx_t& bsize, idx_t dsize, idx_t mult, const std::string& dim) { return findNumSubsets(os, bsize, "block", dsize, "rank-domain", mult, dim); @@ -143,11 +155,11 @@ namespace yask { idx_t mult, const std::string& dim) { return findNumSubsets(os, bsize, "block", rsize, "region", mult, dim); } - inline idx_t findNumBlocksInGroup(std::ostream& os, idx_t& bsize, idx_t gsize, + inline idx_t findNumBlocksInBlockGroup(std::ostream& os, idx_t& bsize, idx_t gsize, idx_t mult, const std::string& dim) { return findNumSubsets(os, bsize, "block", gsize, "block-group", mult, dim); } - inline idx_t findNumGroupsInRegion(std::ostream& os, idx_t& gsize, idx_t rsize, + inline idx_t findNumBlockGroupsInRegion(std::ostream& os, idx_t& gsize, idx_t rsize, idx_t mult, const std::string& dim) { return findNumSubsets(os, gsize, "block-group", rsize, "region", mult, dim); } diff --git a/stencil-tuner.pl b/stencil-tuner.pl index 76231e27..5122d26c 100755 --- a/stencil-tuner.pl +++ b/stencil-tuner.pl @@ -75,7 +75,7 @@ my $doBuild = 1; # do compiles. my $doVal = 0; # do validation runs. my $maxVecsInCluster = 4; # max vectors in a cluster. -my @folds = (); # folding variations to explore +my @folds = (); # folding variations to explore. sub usage { my $msg = shift; # error message or undef. @@ -334,6 +334,8 @@ sub usage { 'region-size', 'block-group-size', 'block-size', + 'sub-block-group-size', + 'sub-block-size', 'cluster-size', 'vector-size', 'num-regions', @@ -390,82 +392,64 @@ sub usage { # only allow z (dim 4) in last mem dimension if requested. @layouts = grep /4$/, @layouts if $zLayout; -# list of possible loop orders. -# start with w on outer loop only. +# List of possible loop orders. +# Start with w on outer loop only. my @loopOrders = ('wxyz', 'wxzy', 'wyxz', 'wyzx', 'wzxy', 'wzyx'); -# add more options if there are >1 var, i.e., 'w' -# is meaningful. +# Add more options if there are >1 var, i.e., 'w' is meaningful. push @loopOrders, ('xwyz', 'xwzy', 'xywz', 'xyzw', 'xzwy', 'xzyw', 'ywxz', 'ywzx', 'yxwz', 'yxzw', 'yzwx', 'yzxw', 'zwxy', 'zwyx', 'zxwy', 'zxyw', 'zywx', 'zyxw', ) if $dw > 1; -# only allow z in inner loop if requested. +# Only allow z in inner loop if requested. @loopOrders = grep /z$/, @loopOrders if $zLoop; -# types of space-filling curves. -# 'grouped' must be last. +# Possible space-filling curve modifiers. my @pathNames = ('', 'serpentine', 'square_wave serpentine', 'grouped'); -# list of possible block-loop templates. +# Possible loops for various levels. # D0..D3 will get replaced by bv..bz, but not necessarily in that order. -# modifiers 'pipeline' & 'prefetch' will be removed if not enabled. -# modifier placeholder 'PATH' will be removed or changed as selected. -# this is the loop taken by each OpenMP task. -my @blockLoops = - ( - # nested omp: - "loop(D0) { omp loop(D1) { pipeline prefetch loop(D2) { loop(D3) { calc(cluster(bt)); } } } }", - "loop(D0) { omp loop(D1) { pipeline prefetch PATH1 loop(D2,D3) { calc(cluster(bt)); } } }", - - "loop(D0) { loop(D1) { omp loop(D2) { pipeline prefetch loop(D3) { calc(cluster(bt)); } } } }", - "PATH0 loop(D0,D1) { omp loop(D2) { pipeline prefetch loop(D3) { calc(cluster(bt)); } } }", - - "loop(D0) { omp PATH0 loop(D1,D2) { pipeline prefetch loop(D3) { calc(cluster(bt)); } } }", - - # no nested omp: - "PATH0 loop(D0,D1,D2) { pipeline prefetch loop(D3) { calc(cluster(bt)); } }", - "PATH0 loop(D0,D1) { loop(D2) { pipeline prefetch loop(D3) { calc(cluster(bt)); } } }", +# Modifier placeholders 'PATH*' and 'SBMOD' will be removed or changed +# based on relevant genes. - "loop(D0) { loop(D1) { PATH1 pipeline prefetch loop(D2,D3) { calc(cluster(bt)); } } }", - "PATH0 loop(D0,D1) { PATH1 pipeline prefetch loop(D2,D3) { calc(cluster(bt)); } }", - - "loop(D0) { PATH1 pipeline prefetch loop(D1,D2,D3) { calc(cluster(bt)); } }", - - # omp on inner loop: - #"loop(D0) { loop(D1) { loop(D2) { omp pipeline prefetch loop(D3) { calc(cluster(bt)); } } } }", - #"loop(D0) { PATH0 loop(D1,D2) { omp pipeline prefetch loop(D3) { calc(cluster(bt)); } } }", - #"PATH0 loop(D0,D1,D2) { omp pipeline prefetch loop(D3) { calc(cluster(bt)); } }", - #"PATH0 loop(D0,D1) { loop(D2) { omp pipeline prefetch loop(D3) { calc(cluster(bt)); } } }", - - #"loop(D0) { loop(D1) { PATH1 omp pipeline prefetch loop(D2,D3) { calc(cluster(bt)); } } }", - #"PATH0 loop(D0,D1) { PATH1 omp pipeline prefetch loop(D2,D3) { calc(cluster(bt)); } }", +# List of possible block-loop templates. +# This is the loop taken by each nested OpenMP task. +my @subBlockLoops = + ( + "PATH0 loop(D0,D1,D2) { SBMOD loop(D3) { calc(cluster(bt)); } }", + "PATH0 loop(D0,D1) { loop(D2) { SBMOD loop(D3) { calc(cluster(bt)); } } }", + ); - #"loop(D0) { PATH1 omp pipeline prefetch loop(D1,D2,D3) { calc(cluster(bt)); } }", +# List of possible block-loop templates. +# This is the loop that creates nested OpenMP tasks. +# TODO: add other options. +my @blockLoops = + ( + "omp PATH1 loop(D0,D1,D2,D3) { calc(sub_block(rt)); }", ); -# list of possible region loop templates. -# this is the loop that creates OpenMP tasks. +# List of possible region loop templates. +# This is the loop that creates outer OpenMP tasks. # TODO: add other options. my @regionLoops = ( "omp PATH2 loop(D0,D1,D2,D3) { calc(block(rt)); }", ); -# list of possible rank loop templates. -# this is the loop that creates OpenMP regions. +# List of possible rank loop templates. +# This is the loop that creates OpenMP regions. # TODO: add other options. my @rankLoops = ( "PATH3 loop(D0,D1,D2,D3) { calc(region(start_dt, stop_dt, eqGroup_ptr)); }", ); -# list of folds. -# start with inline in z only. +# List of folds. +# Start with inline in z only. if ( !@folds ) { @folds = "1 1 $velems"; @@ -494,7 +478,7 @@ sub usage { # Data structure to describe each gene in the genome. # 2-D array. Each outer array element contains the following elements: -# 0. min allowed value. +# 0. min allowed value; '0' is a special case handled by YASK. # 1. max allowed value. # 2. step size between values (usually 1). # 3. name. @@ -520,6 +504,16 @@ sub usage { [ 0, $maxDim, 1, 'by' ], [ 0, $maxDim, 1, 'bz' ], + # sub-block-group size. + [ 0, $maxDim, 1, 'sbgx' ], + [ 0, $maxDim, 1, 'sbgy' ], + [ 0, $maxDim, 1, 'sbgz' ], + + # sub-block size. + [ 0, $maxDim, 1, 'sbx' ], + [ 0, $maxDim, 1, 'sby' ], + [ 0, $maxDim, 1, 'sbz' ], + # padding. [ 0, $maxPad, 1, 'px' ], [ 0, $maxPad, 1, 'py' ], @@ -535,18 +529,25 @@ sub usage { push @rangesAll, ( - # loops, from the list above. + # Loops, from the list above. + # Each loop consists of the loop structure, index order, and path mods. + [ 0, $#subBlockLoops, 1, 'subBlockLoop' ], + [ 0, $#loopOrders, 1, 'subBlockLoopOrder' ], + [ 0, $#pathNames, 1, 'path0' ], [ 0, $#blockLoops, 1, 'blockLoop' ], [ 0, $#loopOrders, 1, 'blockLoopOrder' ], + [ 0, $#pathNames, 1, 'path1' ], [ 0, $#regionLoops, 1, 'regionLoop' ], [ 0, $#loopOrders, 1, 'regionLoopOrder' ], + [ 0, $#pathNames, 1, 'path2' ], [ 0, $#rankLoops, 1, 'rankLoop' ], [ 0, $#loopOrders, 1, 'rankLoopOrder' ], + [ 0, 0, 1, 'path3' ], # allow only incrementing-index path for rank. # how to shape vectors, from the list above. [ 0, $#folds, 1, 'fold' ], - # cluster sizes. + # vector-cluster sizes. [ 1, $maxCluster, 1, 'cx' ], [ 1, $maxCluster, 1, 'cy' ], [ 1, $maxCluster, 1, 'cz' ], @@ -557,13 +558,6 @@ sub usage { # whether or not to allow pipelining. #[ 0, 1, 1, 'pipe' ], - # types of curves. - # (use '-1' to avoid grouping.) - [ 0, $#pathNames-1, 1, 'path0' ], - [ 0, $#pathNames-1, 1, 'path1' ], - [ 0, $#pathNames, 1, 'path2' ], # grouping ok here. - [ 0, $#pathNames-1, 1, 'path3' ], - # prefetch distances for l1 and l2. # all non-pos numbers => no prefetching, so ~50% chance of being enabled. [ -$maxPfdl1, $maxPfdl1, 1, 'pfdl1' ], @@ -647,7 +641,7 @@ () $bestGen = 0; } -# convert a number so that values in [a0..a1] are mapped to [b0..b1], +# convert a number n so that values in [a0..a1] are mapped to [b0..b1], # values < a0 => b0. # values > a1 => b1. sub adjRange($$$$$) { @@ -821,28 +815,32 @@ ($$$) my $numGrids = 0; my $numUpdatedGrids = 0; my @cmdOut; - print "Running '$cmd' to determine number of grids...\n"; - open CMD, "$cmd 2>&1 |" or die "error: cannot run '$cmd'\n"; - while () { - push @cmdOut, $_; - - # E.g., - # 4D (t=1 * x=8 * y=1 * z=1) 'vel_x' data is at 0x7fce08200000: 1.176K element(s) of 4 byte(s) each, 147 vector(s), 4.59375KiB. - # 3D (x=8 * y=1 * z=1) 'lambda' data is at 0x7fce0820f880: 600 element(s) of 4 byte(s) each, 75 vector(s), 2.34375KiB. - if (/^\s*5D.*t=(\d+).*w=(\d+)/) { - $numSpatialGrids += $1 * $2; # twxyz - } - elsif (/^\s*4D.*w=(\d+)/) { - $numSpatialGrids += $1; # wxyz - } - elsif (/^\s*4D.*t=(\d+)/) { - $numSpatialGrids += $1; # txyz. - } - elsif (/^\s*3D.*x=/) { - $numSpatialGrids += 1; # xyz. + if ($testing) { + $numSpatialGrids = 1; + } else { + print "Running '$cmd' to determine number of grids...\n"; + open CMD, "$cmd 2>&1 |" or die "error: cannot run '$cmd'\n"; + while () { + push @cmdOut, $_; + + # E.g., + # 4D (t=1 * x=8 * y=1 * z=1) 'vel_x' data is at 0x7fce08200000: 1.176K element(s) of 4 byte(s) each, 147 vector(s), 4.59375KiB. + # 3D (x=8 * y=1 * z=1) 'lambda' data is at 0x7fce0820f880: 600 element(s) of 4 byte(s) each, 75 vector(s), 2.34375KiB. + if (/^\s*5D.*t=(\d+).*w=(\d+)/) { + $numSpatialGrids += $1 * $2; # twxyz + } + elsif (/^\s*4D.*w=(\d+)/) { + $numSpatialGrids += $1; # wxyz + } + elsif (/^\s*4D.*t=(\d+)/) { + $numSpatialGrids += $1; # txyz. + } + elsif (/^\s*3D.*x=/) { + $numSpatialGrids += 1; # xyz. + } } + close CMD; } - close CMD; if (!$numSpatialGrids) { map { print ">> $_"; } @cmdOut; die "error: no grids defined in '$cmd'.\n"; @@ -1225,13 +1223,15 @@ sub fitness { my @rs = readHashes($h, 'r', 0); my @bgs = readHashes($h, 'bg', 0); my @bs = readHashes($h, 'b', 0); + my @sbgs = readHashes($h, 'sbg', 0); + my @sbs = readHashes($h, 'sb', 0); my @cvs = readHashes($h, 'c', 1); # in vectors, not in points! my @ps = readHashes($h, 'p', 0); my $fold = readHash($h, 'fold', 1); my $exprSize = readHash($h, 'exprSize', 1); my $thread_divisor_exp = readHash($h, 'thread_divisor_exp', 0); my $bthreads_exp = readHash($h, 'bthreads_exp', 0); - my $pipe = 0; # readHash($h, 'pipe', 1); + my $pipe = 0; # readHash($h, 'pipe', 0); my @paths = ( readHash($h, 'path0', 1), readHash($h, 'path1', 1), readHash($h, 'path2', 1), @@ -1245,18 +1245,21 @@ sub fitness { my $foldNums = $folds[$fold]; my @fs = split ' ', $foldNums; - # block loops. - my $blockCode = makeLoopCode($h, 'block', 'b', 'v', \@blockLoops); - $blockCode =~ s/\bpipeline\b//g if !$pipe; + # sub-block loops. + my $subBlockCode = makeLoopCode($h, 'subBlock', 'sb', 'v', \@subBlockLoops); + my $subBlockMods = ''; + $subBlockMods .= 'pipeline ' if $pipe; if ($pfdl1 > 0 && $pfdl2 > 0) { - $blockCode =~ s/\bprefetch\b/prefetch(L1,L2)/g; + $subBlockMods .= 'prefetch(L1,L2) '; } elsif ($pfdl1 > 0) { - $blockCode =~ s/\bprefetch\b/prefetch(L1)/g; + $subBlockMods .= 'prefetch(L1) '; } elsif ($pfdl2 > 0) { - $blockCode =~ s/\bprefetch\b/prefetch(L2)/g; - } else { - $blockCode =~ s/\bprefetch\b//g; + $subBlockMods .= 'prefetch(L2) '; } + $subBlockCode =~ s/SBMOD/$subBlockMods/g; + + # block loops. + my $blockCode = makeLoopCode($h, 'block', 'b', '', \@blockLoops); # region loops. my $regionCode = makeLoopCode($h, 'region', 'r', '', \@regionLoops); @@ -1275,16 +1278,20 @@ sub fitness { # cluster sizes in points. my @cs = map { $fs[$_] * $cvs[$_] } 0..$#dirs; - # adjust inner sizes. + # adjust inner sizes to fit in their enclosing sizes. adjSizes(\@rs, \@ds); - adjSizes(\@bs, \@rs); adjSizes(\@bgs, \@rs); + adjSizes(\@bs, \@rs); # use region because groups are rounded up. + adjSizes(\@sbgs, \@bs); + adjSizes(\@sbs, \@bs); # use block because groups are rounded up. # 3d sizes in points. my $dPts = mult(@ds); my $rPts = mult(@rs); my $bgPts = mult(@bgs); my $bPts = mult(@bs); + my $sbgPts = mult(@sbgs); + my $sbPts = mult(@sbs); my $cPts = mult(@cs); my $fPts = mult(@fs); @@ -1296,10 +1303,6 @@ sub fitness { my @rbs = map { ceil($rs[$_] / $bs[$_]) } 0..$#dirs; my $rBlks = mult(@rbs); - # Groups per region. - my @rbgs = map { ceil($rs[$_] / $bgs[$_]) } 0..$#dirs; - my $rBlkGrps = mult(@rbgs); - # Regions per rank. my @drs = map { ceil($ds[$_] / $rs[$_]) } 0..$#dirs; my $dRegs = mult(@drs); @@ -1313,6 +1316,8 @@ sub fitness { print " region size = $rPts\n"; print " block-group size = $bgPts\n"; print " block size = $bPts\n"; + print " sub-block-group size = $sbgPts\n"; + print " sub-block size = $sbPts\n"; print " cluster size = $cPts\n"; print " fold size = $fPts\n"; print " regions per rank = $dRegs\n"; @@ -1366,6 +1371,8 @@ sub fitness { addStat($ok, 'region size', $rPts); addStat($ok, 'block-group size', $bgPts); addStat($ok, 'block size', $bPts); + addStat($ok, 'sub-block-group size', $sbgPts); + addStat($ok, 'sub-block size', $sbPts); addStat($ok, 'cluster size', $cPts); addStat($ok, 'regions per rank', $dRegs); addStat($ok, 'blocks per region', $rBlks); @@ -1409,7 +1416,10 @@ sub fitness { # gen-loops vars. $mvars .= " RANK_LOOP_CODE='$rankCode'". " REGION_LOOP_CODE='$regionCode'". - " BLOCK_LOOP_CODE='$blockCode'"; + " BLOCK_LOOP_CODE='$blockCode'". + " SUB_BLOCK_LOOP_CODE='$subBlockCode'"; + + # substitute PATH* placeholders with actual path strings. for my $pi (0..$#paths) { my $pathName = $pathNames[$paths[$pi]]; $mvars =~ s/\bPATH$pi\b/$pathName/g; @@ -1435,6 +1445,8 @@ sub fitness { $args .= " -rx $rs[0] -ry $rs[1] -rz $rs[2]"; $args .= " -bx $bs[0] -by $bs[1] -bz $bs[2]"; $args .= " -bgx $bgs[0] -bgy $bgs[1] -bgz $bgs[2]"; + $args .= " -sbx $sbs[0] -sby $sbs[1] -sbz $sbs[2]"; + $args .= " -sbgx $sbgs[0] -sbgy $sbgs[1] -sbgz $sbgs[2]"; $args .= " -px $ps[0] -py $ps[1] -pz $ps[2]"; # num of iterations and trials. From 81019c9d86d3f2c9368213b7bb1982d35e17538e Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Mon, 13 Mar 2017 16:23:12 -0700 Subject: [PATCH 10/20] Remove equation-group template. Now generated equation-group classes inherit from EqGroupBase. Also: Add knob to control loop splitting for L2 prefetches. Add CXXOPT var in Makefile. --- Makefile | 44 ++++--- gen-loops.pl | 122 ++++++++--------- src/foldBuilder/Cpp.hpp | 21 ++- src/foldBuilder/Expr.hpp | 6 +- src/foldBuilder/Print.cpp | 134 ++++++++++++++----- src/stencil_calc.cpp | 69 +++++++++- src/stencil_calc.hpp | 270 +++++++------------------------------- stencil-tuner.pl | 6 +- 8 files changed, 325 insertions(+), 347 deletions(-) diff --git a/Makefile b/Makefile index 6e8ef78c..c8e09361 100644 --- a/Makefile +++ b/Makefile @@ -127,11 +127,12 @@ else ifeq ($(stencil),fsg) eqs ?= v_br=v_br,v_bl=v_bl,v_tr=v_tr,v_tl=v_tl,s_br=s_br,s_bl=s_bl,s_tr=s_tr,s_tl=s_tl time_alloc ?= 1 ifeq ($(arch),knl) -omp_schedule ?= guided -def_block_size ?= 16 -def_thread_divisor ?= 4 -def_block_threads ?= 1 -def_pad ?= 2 +omp_schedule ?= guided +def_block_size ?= 16 +def_thread_divisor ?= 4 +def_block_threads ?= 1 +def_pad ?= 2 +SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L2) endif endif # stencil-specific. @@ -234,23 +235,24 @@ endif # not 512 bits. # More build flags. ifeq ($(mpi),1) -CXX = mpiicpc +CXX := mpiicpc else -CXX = icpc +CXX := icpc endif -LD = $(CXX) -MAKE = make -CXXFLAGS += -g -O3 -std=c++11 -Wall +LD := $(CXX) +MAKE := make +CXXOPT := -O3 +CXXFLAGS += -g -std=c++11 -Wall $(CXXOPT) OMPFLAGS += -fopenmp LFLAGS += -lrt -FB_CXX = g++ # faster than icpc for the foldBuilder. +FB_CXX := g++ # faster than icpc for the foldBuilder. FB_CXXFLAGS += -g -O0 -std=c++11 -Wall # low opt to reduce compile time. EXTRA_FB_CXXFLAGS = FB_FLAGS += -st $(stencil) -cluster $(cluster) -fold $(fold) ST_MACRO_FILE := stencil_macros.hpp ST_CODE_FILE := stencil_code.hpp FB_STENCIL_LIST := src/foldBuilder/stencils.hpp -GEN_HEADERS = $(addprefix src/, \ +GEN_HEADERS := $(addprefix src/, \ stencil_rank_loops.hpp \ stencil_region_loops.hpp \ stencil_block_loops.hpp \ @@ -320,7 +322,8 @@ ifneq ($(findstring ic,$(notdir $(CXX))),) # Intel compiler CODE_STATS = code_stats CXXFLAGS += $(ISA) -debug extended -Fa -restrict -ansi-alias -fno-alias CXXFLAGS += -fimf-precision=low -fast-transcendentals -no-prec-sqrt -no-prec-div -fp-model fast=2 -fno-protect-parens -rcd -ftz -fma -fimf-domain-exclusion=none -qopt-assume-safe-padding -#CXXFLAGS += -qoverride-limits -vec-threshold0 +CXXFLAGS += -qoverride-limits +#CXXFLAGS += -vec-threshold0 CXXFLAGS += -qopt-report=5 #CXXFLAGS += -qopt-report-phase=VEC,PAR,OPENMP,IPO,LOOP CXXFLAGS += -no-diag-message-catalog @@ -358,7 +361,7 @@ RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop(dw,dx,dy,dz) \ REGION_LOOP_OPTS = -dims 'rw,rx,ry,rz' \ -ompConstruct '$(omp_par_for) schedule($(omp_schedule)) proc_bind(spread)' \ -calcPrefix 'eg->calc_' -REGION_LOOP_OUTER_MODS ?= grouped omp +REGION_LOOP_OUTER_MODS ?= omp grouped REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) { \ $(REGION_LOOP_INNER_MODS) calc(block(rt)); } @@ -368,7 +371,7 @@ REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) { \ # not yet supported. BLOCK_LOOP_OPTS = -dims 'bw,bx,by,bz' \ -ompConstruct '$(omp_par_for) schedule($(omp_block_schedule)) proc_bind(close)' -BLOCK_LOOP_OUTER_MODS ?= grouped omp +BLOCK_LOOP_OUTER_MODS ?= omp grouped BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bw,bx,by,bz) { \ $(BLOCK_LOOP_INNER_MODS) calc(sub_block(bt)); } @@ -377,10 +380,13 @@ BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bw,bx,by,bz) { \ # suffix. The innermost loop here is the final innermost loop. There is # no time loop here because threaded temporal blocking is not yet supported. SUB_BLOCK_LOOP_OPTS = -dims 'sbwv,sbxv,sbyv,sbzv' +ifeq ($(split_L2),1) +SUB_BLOCK_LOOP_OPTS += -splitL2 +endif SUB_BLOCK_LOOP_OUTER_MODS ?= square_wave serpentine SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L2) SUB_BLOCK_LOOP_CODE ?= $(SUB_BLOCK_LOOP_OUTER_MODS) loop(sbwv,sbxv,sbyv) { \ - $(SUB_BLOCK_LOOP_INNER_MODS) loop(sbzv) { calc(cluster(sbt)); } } + $(SUB_BLOCK_LOOP_INNER_MODS) loop(sbzv) { calc(cluster(begin_sbtv)); } } # Halo pack/unpack loops break up a region face, edge, or corner into vectors. # The indices at this level are by vector instead of element; @@ -554,6 +560,6 @@ help: @echo "make clean; make arch=knc stencil=3axis radius=4 SUB_BLOCK_LOOP_INNER_MODS='prefetch(L1,L2)' EXTRA_MACROS='PFDL1=2 PFDL2=4'" @echo " " @echo "Example debug usage:" - @echo "make arch=knl stencil=iso3dfd OMPFLAGS='-qopenmp-stubs' EXTRA_CXXFLAGS='-O0' EXTRA_MACROS='DEBUG'" - @echo "make arch=intel64 stencil=ave OMPFLAGS='-qopenmp-stubs' EXTRA_CXXFLAGS='-O0' EXTRA_MACROS='DEBUG' model_cache=2" - @echo "make arch=intel64 stencil=3axis radius=0 fold='x=1,y=1,z=1' OMPFLAGS='-qopenmp-stubs' EXTRA_MACROS='DEBUG DEBUG_TOLERANCE NO_INTRINSICS TRACE TRACE_MEM TRACE_INTRINSICS' EXTRA_CXXFLAGS='-O0'" + @echo "make arch=knl stencil=iso3dfd OMPFLAGS='-qopenmp-stubs' CXXOPT='-O0' EXTRA_MACROS='DEBUG'" + @echo "make arch=intel64 stencil=ave OMPFLAGS='-qopenmp-stubs' CXXOPT='-O0' EXTRA_MACROS='DEBUG' model_cache=2" + @echo "make arch=intel64 stencil=3axis radius=0 fold='x=1,y=1,z=1' OMPFLAGS='-qopenmp-stubs' EXTRA_MACROS='DEBUG DEBUG_TOLERANCE NO_INTRINSICS TRACE TRACE_MEM TRACE_INTRINSICS' CXXOPT='-O0'" diff --git a/gen-loops.pl b/gen-loops.pl index 21942f00..bd392c0a 100755 --- a/gen-loops.pl +++ b/gen-loops.pl @@ -51,17 +51,16 @@ BEGIN # Globals. my %OPT; # cmd-line options. my @dims; # names of dimensions. -my @results; # names of result buffers. my $genericCache = "L#"; # a string placeholder for L1 or L2. # loop-feature bit fields. my $bSerp = 0x1; # serpentine path my $bSquare = 0x2; # square_wave path -my $bGroup = 0x4; # group path +my $bGroup = 0x4; # group path my $bSimd = 0x8; # simd prefix my $bPrefetchL1 = 0x10; # prefetch L1 my $bPrefetchL2 = 0x20; # prefetch L2 -my $bPipe = 0x40; # pipeline +my $bPipe = 0x100; # pipeline ########## # Function to make names of variables based on dimension string(s). @@ -851,6 +850,7 @@ ($) my $ucDir = uc($innerDim); my $pfd = "PFD$genericCache"; my $nVar = numItersVar(@loopDims); + my $doSplitL2 = ($features & $bPrefetchL2) && $OPT{splitL2}; # declare pipeline vars. push @code, " // Pipeline accumulators.", " MAKE_PIPE_$ucDir;" @@ -902,7 +902,7 @@ ($) } # midpoint calculation for L2 prefetch only. - if ($features & $bPrefetchL2) { + if ($doSplitL2) { my $ofs = ($features & $bPrefetchL1) ? "(PFDL2-PFDL1)" : "PFDL2"; push @code, " // Point where L2-prefetch policy changes."; push @code, " // This covers all L1 fetches, even unneeded one(s) beyond end." @@ -917,7 +917,7 @@ ($) # loop 1: w/o L2 prefetch from midpoint to end. # if no L2 prefetch: # loop 0: no L2 prefetch from start to end. - my $lastLoop = ($features & $bPrefetchL2) ? 1 : 0; + my $lastLoop = $doSplitL2 ? 1 : 0; for my $loop (0 .. $lastLoop) { my $name = "Computation"; @@ -929,6 +929,7 @@ ($) my $comment = " // $name loop."; $comment .= " Same as previous loop, except no L2 prefetch." if $loop==1; push @code, $comment; + push @code, $OPT{innerMod}; beginLoop(\@code, \@loopDims, \@loopPrefix, $beginVal, $endVal, $features, \@loopStack); @@ -1041,64 +1042,63 @@ ($) # Parse arguments and emit code. sub main() { - my(@KNOBS) = - ( # knob, description, optional default - [ "dims=s", "Comma-separated names of dimensions (in order passed via calls).", 'v,x,y,z'], - [ "comArgs=s", "Common arguments to all calls (after L1/L2 for prefetch).", ''], - [ "resultBlks=s", "Comma-separated name of block-sized buffers that hold inter-loop values and/or final result at 'save' command.", 'result'], - [ "calcPrefix=s", "Prefix for calculation call.", 'calc_'], - [ "primePrefix=s", "Prefix for pipeline-priming call.", 'prime_'], - [ "pipePrefix=s", "Additional prefix for pipeline call.", 'pipe_'], - [ "pfPrefix=s", "Prefix for prefetch call.", 'prefetch_'], - [ "ompConstruct=s", "Pragma to use before 'omp' loop(s).", "omp parallel for"], - [ "output=s", "Name of output file.", 'loops.h'], - ); - my($command_line) = process_command_line(\%OPT, \@KNOBS); - print "$command_line\n" if $OPT{verbose}; - - my $script = basename($0); - if (!$command_line || $OPT{help} || @ARGV < 1) { - print "Outputs C++ code for a loop block.\n", - "Usage: $script [options] \n", - "Examples:\n", - " $script -dims x,y 'loop(x,y) { calc(f); }'\n", - " $script -dims x,y,z 'omp loop(x,y) { loop(z) { calc(f); } }'\n", - " $script -dims x,y,z 'omp loop(x,y) { prefetch loop(z) { calc(f); } }'\n", - #" $script -dims x,y,z 'omp loop(x,y) { pipeline loop(z) { calc(f); } }'\n", - " $script -dims x,y,z 'grouped omp loop(x,y,z) { calc(f); }'\n", - " $script -dims x,y,z 'omp loop(x) { serpentine loop(y,z) { calc(f); } }'\n", - " $script -dims x,y,z 'omp loop(x) { crew loop(y) { loop(z) { calc(f); } } }'\n", - "Inner loops should contain calc statements that generate calls to calculation functions.\n", - "A loop statement with more than one argument will generate a single collapsed loop.\n", - "Optional loop modifiers:\n", - " omp: generate an OpenMP for loop (distribute work across SW threads).\n", - " crew: generate an Intel crew loop (distribute work across HW threads).\n", - " prefetch: generate calls to SW L1 & L2 prefetch functions in addition to calc functions.\n", - " prefetch(L1,L2): generate calls to SW L1 & L2 prefetch functions in addition to calc functions.\n", - " prefetch(L1): generate calls to SW L1 prefetch functions in addition to calc functions.\n", - " prefetch(L2): generate calls to SW L2 prefetch functions in addition to calc functions.\n", - " grouped: generate grouped path within a collapsed loop.\n", - " serpentine: generate reverse path when enclosing loop dimension is odd.\n", - " square_wave: generate 2D square-wave path for two innermost dimensions of a collapsed loop.\n", - #" pipeline: generate calls to pipeline versions of calculation functions (deprecated).\n", - "For each dim D in dims, loops are generated from begin_D to end_D-1 by step_D;\n", - " if grouping is used, groups are of size group_size_D;\n", - " these vars must be defined *outside* of the generated code.\n", - "Each iteration will cover values from start_D to stop_D-1;\n", - " these vars will be defined in the generated code.\n", - "Options:\n"; - print_options_help(\@KNOBS); - exit 1; - } - - @dims = split(/\s*,\s*/, $OPT{dims}); - @results = split(/\s*,\s*/, $OPT{resultBlks}); + my(@KNOBS) = ( + # knob, description, optional default + [ "dims=s", "Comma-separated names of dimensions (in order passed via calls).", 'v,x,y,z'], + [ "comArgs=s", "Common arguments to all calls (after L1/L2 for prefetch).", ''], + [ "calcPrefix=s", "Prefix for calculation call.", 'calc_'], + [ "primePrefix=s", "Prefix for pipeline-priming call.", 'prime_'], + [ "pipePrefix=s", "Additional prefix for pipeline call.", 'pipe_'], + [ "pfPrefix=s", "Prefix for prefetch call.", 'prefetch_'], + [ "ompConstruct=s", "Pragma to use before 'omp' loop(s).", "omp parallel for"], + [ "innerMod=s", "Code to insert before inner computation loops.", + '_Pragma("nounroll_and_jam") _Pragma("nofusion")'], + [ "splitL2!", "Split inner loops with L2 prefetching.", 0], + [ "output=s", "Name of output file.", 'loops.h'], + ); + my($command_line) = process_command_line(\%OPT, \@KNOBS); + print "$command_line\n" if $OPT{verbose}; + + my $script = basename($0); + if (!$command_line || $OPT{help} || @ARGV < 1) { + print "Outputs C++ code for a loop block.\n", + "Usage: $script [options] \n", + "Examples:\n", + " $script -dims x,y 'loop(x,y) { calc(f); }'\n", + " $script -dims x,y,z 'omp loop(x,y) { loop(z) { calc(f); } }'\n", + " $script -dims x,y,z 'omp loop(x,y) { prefetch loop(z) { calc(f); } }'\n", + #" $script -dims x,y,z 'omp loop(x,y) { pipeline loop(z) { calc(f); } }'\n", + " $script -dims x,y,z 'grouped omp loop(x,y,z) { calc(f); }'\n", + " $script -dims x,y,z 'omp loop(x) { serpentine loop(y,z) { calc(f); } }'\n", + " $script -dims x,y,z 'omp loop(x) { crew loop(y) { loop(z) { calc(f); } } }'\n", + "Inner loops should contain calc statements that generate calls to calculation functions.\n", + "A loop statement with more than one argument will generate a single collapsed loop.\n", + "Optional loop modifiers:\n", + " omp: generate an OpenMP for loop (distribute work across SW threads).\n", + " crew: generate an Intel crew loop (distribute work across HW threads).\n", + " prefetch: generate calls to SW L1 & L2 prefetch functions in addition to calc functions.\n", + " prefetch(L1,L2): generate calls to SW L1 & L2 prefetch functions in addition to calc functions.\n", + " prefetch(L1): generate calls to SW L1 prefetch functions in addition to calc functions.\n", + " prefetch(L2): generate calls to SW L2 prefetch functions in addition to calc functions.\n", + " grouped: generate grouped path within a collapsed loop.\n", + " serpentine: generate reverse path when enclosing loop dimension is odd.\n", + " square_wave: generate 2D square-wave path for two innermost dimensions of a collapsed loop.\n", + #" pipeline: generate calls to pipeline versions of calculation functions (deprecated).\n", + "For each dim D in dims, loops are generated from begin_D to end_D-1 by step_D;\n", + " if grouping is used, groups are of size group_size_D;\n", + " these vars must be defined *outside* of the generated code.\n", + "Each iteration will cover values from start_D to stop_D-1;\n", + " these vars will be defined in the generated code.\n", + "Options:\n"; + print_options_help(\@KNOBS); + exit 1; + } - warn "info: generating ".scalar(@dims)."-D loop code with ". - scalar(@results)." output(s).\n"; + @dims = split(/\s*,\s*/, $OPT{dims}); + warn "info: generating ".scalar(@dims)."-D loop code...\n"; - my $codeString = join(' ', @ARGV); - processCode($codeString); + my $codeString = join(' ', @ARGV); + processCode($codeString); } main(); diff --git a/src/foldBuilder/Cpp.hpp b/src/foldBuilder/Cpp.hpp index e5f67bd6..d936074c 100644 --- a/src/foldBuilder/Cpp.hpp +++ b/src/foldBuilder/Cpp.hpp @@ -64,7 +64,7 @@ class CppPrintHelper : public PrintHelper { // Return a parameter reference. virtual string readFromParam(ostream& os, const GridPoint& pp) { - string str = "(*context." + pp.getName() + ")(" + pp.makeValStr() + ")"; + string str = "(*_context->" + pp.getName() + ")(" + pp.makeValStr() + ")"; return str; } @@ -73,7 +73,7 @@ class CppPrintHelper : public PrintHelper { virtual string makePointCall(const GridPoint& gp, const string& fname, string optArg = "") const { ostringstream oss; - oss << "context." << gp.getName() << "->" << fname << "("; + oss << "_context->" << gp.getName() << "->" << fname << "("; if (optArg.length()) oss << optArg << ", "; oss << gp.makeDimValOffsetStr() << ", __LINE__)"; return oss.str(); @@ -120,7 +120,7 @@ class CppVecPrintHelper : public VecPrintHelper { // Return a parameter reference. virtual string readFromParam(ostream& os, const GridPoint& pp) { - string str = "(*context." + pp.getName() + ")(" + pp.makeValStr() + ")"; + string str = "(*_context->" + pp.getName() + ")(" + pp.makeValStr() + ")"; return str; } @@ -140,7 +140,7 @@ class CppVecPrintHelper : public VecPrintHelper { const string& firstArg, const string& lastArg, bool isNorm) const { - os << " context." << gp.getName() << "->" << funcName << "("; + os << " _context->" << gp.getName() << "->" << funcName << "("; if (firstArg.length()) os << firstArg << ", "; if (isNorm) @@ -300,6 +300,8 @@ class YASKCppPrinter : public PrinterBase { Dimensions& _dims; YASKCppSettings& _settings; string _context, _context_base; + IntTuple _yask_dims; // spatial dims in yask. + string _yask_step; // step dim in yask. // Print an expression as a one-line C++ comment. void addComment(ostream& os, EqGroup& eq) { @@ -333,12 +335,23 @@ class YASKCppPrinter : public PrinterBase { // name of C++ struct. _context = "StencilContext_" + _stencil.getName(); _context_base = _context + "_data"; + + // YASK dims are hard-coded. + // TODO: fix YASK. + _yask_step = "t"; + _yask_dims.addDimBack("w", 1); + _yask_dims.addDimBack("x", 1); + _yask_dims.addDimBack("y", 1); + _yask_dims.addDimBack("z", 1); } virtual ~YASKCppPrinter() { } virtual void printMacros(ostream& os); virtual void printGrids(ostream& os); virtual void printCode(ostream& os); + virtual void printShim(ostream& os, const string& fname, + bool use_template = false, + const string& dim = ""); }; #endif diff --git a/src/foldBuilder/Expr.hpp b/src/foldBuilder/Expr.hpp index 8194163e..aaa4ca74 100644 --- a/src/foldBuilder/Expr.hpp +++ b/src/foldBuilder/Expr.hpp @@ -1432,10 +1432,10 @@ typedef NumExprPtr GridValue; // of the local var must be evaluated and inserted in the expr. // Example code: // GridValue v; -// SET_VALUE_FROM_EXPR(v =, "context.temp * " << 0.2); -// SET_VALUE_FROM_EXPR(v +=, "context.coeff[" << r << "]"); +// SET_VALUE_FROM_EXPR(v =, "_context->temp * " << 0.2); +// SET_VALUE_FROM_EXPR(v +=, "_context->coeff[" << r << "]"); // This example would generate the following partial expression (when r=9): -// (context.temp * 2.00000000000000000e-01) + (context.coeff[9]) +// (_context->temp * 2.00000000000000000e-01) + (_context->coeff[9]) #define SET_VALUE_FROM_EXPR(lhs, rhs) do { \ ostringstream oss; \ oss << setprecision(17) << scientific; \ diff --git a/src/foldBuilder/Print.cpp b/src/foldBuilder/Print.cpp index 030dffae..8c2ffa40 100644 --- a/src/foldBuilder/Print.cpp +++ b/src/foldBuilder/Print.cpp @@ -550,8 +550,8 @@ void PseudoPrinter::print(ostream& os) { // Loop through all eqGroups. for (auto& eq : _eqGroups) { - string eqName = eq.getName(); - os << endl << " ////// Equation group '" << eqName << + string egName = eq.getName(); + os << endl << " ////// Equation group '" << egName << "' //////" << endl; CounterVisitor cv; @@ -623,6 +623,36 @@ void POVRayPrinter::print(ostream& os) { ///// YASK. +// Print a shim function to map hard-coded YASK vars to actual dims. +void YASKCppPrinter::printShim(ostream& os, const string& fname, + bool use_template, + const string& dim) { + + os << "\n // Simple shim function to map sub-block start vars to simple vars (ignoring stop vars).\n"; + if (use_template) + os << " template "; + os << " inline void " << fname; + if (dim.size()) + os << "_sb" << dim << "v"; + os << "(idx_t start_sbtv, " << + _yask_dims.makeDimStr(", ", "idx_t start_sb", "v") << ", " << + _yask_dims.makeDimStr(", ", "idx_t stop_sb", "v") << ") {\n"; + for (auto dim : _dims._allDims.getDims()) { + if (*dim != _yask_step && !_yask_dims.lookup(*dim)) { + cerr << "Error: YASK does not support '" << *dim << "' dimension.\n"; + exit(1); + } + os << " idx_t " << *dim << "v = start_sb" << *dim << "v;\n"; + } + os << " " << fname; + if (dim.size()) + os << "_" << dim; + if (use_template) + os << ""; + os << "(" << _dims._allDims.makeDimStr(", ", "", "v") << ");\n" + "} // " << fname << " shim.\n"; +} + // Print YASK code in new stencil context class. // TODO: split this into smaller methods. void YASKCppPrinter::printCode(ostream& os) { @@ -780,18 +810,20 @@ void YASKCppPrinter::printCode(ostream& os) { os << "}; // " << _context_base << endl; } - // A struct for each eqGroup. + // A struct for each equation group. for (size_t ei = 0; ei < _eqGroups.size(); ei++) { // Scalar eqGroup. auto& eq = _eqGroups.at(ei); - string eqName = eq.getName(); - string eqDesc = eq.getDescription(); - string egsName = "EqGroup_" + eqName; + string egName = eq.getName(); + string egDesc = eq.getDescription(); + string egsName = "EqGroup_" + egName; - os << endl << " ////// Stencil " << eqDesc << " //////\n" << - "\n struct " << egsName << " {\n" << - " std::string name = \"" << eqName << "\";\n"; + os << endl << " ////// Stencil " << egDesc << " //////\n" << + "\n class " << egsName << " : public EqGroupBase {\n" + " protected:\n" + " " << _context_base << "* _context = 0;\n" + " public:\n"; // Stats for this eqGroup. CounterVisitor stats; @@ -800,35 +832,37 @@ void YASKCppPrinter::printCode(ostream& os) { // Example computation. os << endl << " // " << stats.getNumOps() << " FP operation(s) per point:" << endl; addComment(os, eq); - os << " const int scalar_fp_ops = " << stats.getNumOps() << ";\n" << - " const int scalar_points_read = " << stats.getNumReads() << ";\n" << - " const int scalar_points_written = " << stats.getNumWrites() << ";\n"; // Eq-group ctor. { - os << " " << egsName << "(" << _context_base << "& context, " - "GridPtrs& outputGridPtrs, GridPtrs& inputGridPtrs) {" << endl; + os << " " << egsName << "(" << _context_base << "* context) :\n" + " EqGroupBase(context),\n" + " _context(context) {\n" + " _name = \"" << egName << "\";\n" + " _scalar_fp_ops = " << stats.getNumOps() << ";\n" + " _scalar_points_read = " << stats.getNumReads() << ";\n" + " _scalar_points_written = " << stats.getNumWrites() << ";\n"; // I/O grids. if (eq.getOutputGrids().size()) { os << "\n // The following grids are written by " << egsName << endl; for (auto gp : eq.getOutputGrids()) - os << " outputGridPtrs.push_back(context." << gp->getName() << ");" << endl; + os << " outputGridPtrs.push_back(_context->" << gp->getName() << ");" << endl; } if (eq.getInputGrids().size()) { os << "\n // The following grids are read by " << egsName << endl; for (auto gp : eq.getInputGrids()) if (!gp->isParam()) - os << " inputGridPtrs.push_back(context." << gp->getName() << ");" << endl; + os << " inputGridPtrs.push_back(_context->" << gp->getName() << ");" << endl; } os << " } // Ctor." << endl; } // Condition. { - os << endl << " // Determine whether " << egsName << " is valid at the given indices. " << - "Return true if indices are within the valid sub-domain or false otherwise." << endl << - " bool is_in_valid_domain(" << _context_base << "& context, " << + os << endl << " // Determine whether " << egsName << " is valid at the given indices." + " Return true if indices are within the valid sub-domain or false otherwise.\n" + " virtual bool is_in_valid_domain(" << _dims._allDims.makeDimStr(", ", "idx_t ") << ") {" << endl; if (eq.cond.get()) os << " return " << eq.cond->makeStr() << ";" << endl; @@ -847,8 +881,8 @@ void YASKCppPrinter::printCode(ostream& os) { // Stencil-calculation code. // Function header. os << endl << " // Calculate one scalar result relative to indices " << - _dims._allDims.makeDimStr(", ") << "." << endl; - os << " void calc_scalar(" << _context_base << "& context, " << + _dims._allDims.makeDimStr(", ") << ".\n" + " virtual void calc_scalar(" << _dims._allDims.makeDimStr(", ", "idx_t ") << ") {" << endl; // C++ code generator. @@ -872,7 +906,7 @@ void YASKCppPrinter::printCode(ostream& os) { // This should be the same eq-group because it was copied from the // scalar one. auto& ceq = _clusterEqGroups.at(ei); - assert(eqDesc == ceq.getDescription()); + assert(egDesc == ceq.getDescription()); // Create vector info for this eqGroup. // The visitor is accepted at all nodes in the cluster AST; @@ -904,7 +938,7 @@ void YASKCppPrinter::printCode(ostream& os) { " aligned vector-block(s)." << endl; os << " // There are approximately " << (stats.getNumOps() * numResults) << " FP operation(s) per invocation." << endl; - os << " void calc_cluster(" << _context_base << "& context, " << + os << " inline void calc_cluster(" << _dims._allDims.makeDimStr(", ", "idx_t ", "v") << ") {" << endl; // Element indices. @@ -929,6 +963,9 @@ void YASKCppPrinter::printCode(ostream& os) { // End of function. os << "} // calc_cluster." << endl; + // Insert shim function. + printShim(os, "calc_cluster"); + // Generate prefetch code for no specific direction and then each // orthogonal direction. for (int diri = -1; diri < _dims._allDims.size(); diri++) { @@ -964,25 +1001,60 @@ void YASKCppPrinter::printCode(ostream& os) { _dims._fold.makeDimValStr(" * ") << "' vector(s)." << endl; os << " // Indices must be normalized, i.e., already divided by VLEN_*." << endl; - string fname = "prefetch_cluster"; + string fname1 = "prefetch_cluster"; + string fname2 = fname1; if (dir.size()) - fname += "_" + *dir.getDirName(); - os << " template void " << fname << - "(" << _context_base << "& context, " << + fname2 += "_" + *dir.getDirName(); + os << " template inline void " << fname2 << "(" << _dims._allDims.makeDimStr(", ", "idx_t ", "v") << ") {" << endl; // C++ prefetch code. vp->printPrefetches(os, dir); // End of function. - os << "} // " << fname << "." << endl; + os << "} // " << fname2 << "." << endl; + + // Insert shim function. + printShim(os, fname1, true, dir.size() ? *dir.getDirName() : ""); } // direction. delete vp; + + // Sub-block. + os << endl << + " // Calculate one sub-block of whole clusters.\n" + " virtual void calc_sub_block_of_clusters(" << + _dims._allDims.makeDimStr(", ", "idx_t begin_sb", "v") << ", " << + _dims._allDims.makeDimStr(", ", "idx_t end_sb", "v") << + ") {\n" + "\n" + " // Steps and group sizes are based on cluster lengths.\n"; + for (auto dim : _dims._allDims.getDims()) { + string ucDim = allCaps(*dim); + os << " const idx_t step_sb" << *dim << "v = CLEN_" << ucDim << ";\n" + " const idx_t group_size_sb" << *dim << "v = CLEN_" << ucDim << ";\n"; + } + for (auto dim : _yask_dims.getDims()) { + string ucDim = allCaps(*dim); + if (!_dims._allDims.lookup(dim)) + os << " const idx_t begin_sb" << *dim << "v = 0; // not used in this stencil.\n" + " const idx_t end_sb" << *dim << "v = 1;\n" + " const idx_t step_sb" << *dim << "v = CLEN_" << ucDim << ";\n" + " const idx_t group_size_sb" << *dim << "v = CLEN_" << ucDim << ";\n"; + } + os << " #if !defined(DEBUG) && defined(__INTEL_COMPILER)\n" + " #pragma forceinline recursive\n" + " #endif\n" + " {\n" + " // Include automatically-generated loop code that calls calc_cluster()" + " and optionally, the prefetch function(s).\n" + " #include \"stencil_sub_block_loops.hpp\"\n" + " }\n" + "} // calc_sub_block_of_clusters\n"; } - os << "};" << endl; // end of class. + os << "}; // " << egsName << ".\n"; // end of class. } // stencil eqGroups. @@ -995,8 +1067,8 @@ void YASKCppPrinter::printCode(ostream& os) { os << endl << " // Stencil equation-groups." << endl; for (auto& eg : _eqGroups) { string egName = eg.getName(); - os << " EqGroupTemplate eqGroup_" << egName << ";" << endl; + string egsName = "EqGroup_" + egName; + os << " " << egsName << " eqGroup_" << egName << ";" << endl; } // Ctor. diff --git a/src/stencil_calc.cpp b/src/stencil_calc.cpp index 9da0616d..e823979d 100644 --- a/src/stencil_calc.cpp +++ b/src/stencil_calc.cpp @@ -122,10 +122,10 @@ namespace yask { for (idx_t z = eg->begin_bbz; z < eg->end_bbz; z++) { // Update only if point is in sub-domain for this eq group. - if (eg->is_in_valid_domain(t, w, x, y, z)) { + if (eg->is_in_valid_domain(t, ARG_W(w) x, y, z)) { // Evaluate the reference scalar code. - eg->calc_scalar(t, w, x, y, z); + eg->calc_scalar(t, ARG_W(w) x, y, z); } } @@ -430,6 +430,69 @@ namespace yask { } + // Calculate results for one sub-block. + // Each block is typically computed in a separate OpenMP thread. + // The begin/end_sb* vars are the start/stop_b* vars from the block loops. + void EqGroupBase::calc_sub_block(idx_t sbt, + idx_t begin_sbw, idx_t begin_sbx, idx_t begin_sby, idx_t begin_sbz, + idx_t end_sbw, idx_t end_sbx, idx_t end_sby, idx_t end_sbz) + { + TRACE_MSG2(get_name() << ".calc_sub_block(t=" << sbt << + ", w=" << begin_sbw << ".." << (end_sbw-1) << + ", x=" << begin_sbx << ".." << (end_sbx-1) << + ", y=" << begin_sby << ".." << (end_sby-1) << + ", z=" << begin_sbz << ".." << (end_sbz-1) << + ")."); + + // If not a 'simple' domain, must use scalar code. TODO: this + // is very inefficient--need to vectorize as much as possible. + if (!bb_simple) { + + TRACE_MSG2("...using scalar code."); + for (idx_t w = begin_sbw; w < end_sbw; w++) + for (idx_t x = begin_sbx; x < end_sbx; x++) + for (idx_t y = begin_sby; y < end_sby; y++) { + + // Are there holes in the BB? + if (bb_num_points != bb_size) { + for (idx_t z = begin_sbz; z < end_sbz; z++) { + + // Update only if point is in sub-domain for this eq group. + if (is_in_valid_domain(sbt, ARG_W(w) x, y, z)) + calc_scalar(sbt, ARG_W(w) x, y, z); + } + } + + // If no holes, don't need to check domain. + else { + for (idx_t z = begin_sbz; z < end_sbz; z++) { + calc_scalar(sbt, ARG_W(w) x, y, z); + } + } + } + + return; + } + + // Divide indices by vector lengths. Use idiv_flr() instead of '/' + // because begin/end vars may be negative (if in halo). + const idx_t begin_sbtv = sbt; + const idx_t begin_sbwv = idiv_flr(begin_sbw, VLEN_W); + const idx_t begin_sbxv = idiv_flr(begin_sbx, VLEN_X); + const idx_t begin_sbyv = idiv_flr(begin_sby, VLEN_Y); + const idx_t begin_sbzv = idiv_flr(begin_sbz, VLEN_Z); + const idx_t end_sbtv = sbt + CLEN_T; + const idx_t end_sbwv = idiv_flr(end_sbw, VLEN_W); + const idx_t end_sbxv = idiv_flr(end_sbx, VLEN_X); + const idx_t end_sbyv = idiv_flr(end_sby, VLEN_Y); + const idx_t end_sbzv = idiv_flr(end_sbz, VLEN_Z); + + // Evaluate sub-block of clusters. + calc_sub_block_of_clusters(begin_sbtv, ARG_W(begin_sbwv) + begin_sbxv, begin_sbyv, begin_sbzv, + end_sbtv, ARG_W(end_sbwv) end_sbxv, end_sbyv, end_sbzv); + } + // Init MPI-related vars and other vars related to my rank's place in // the global problem: rank index, offset, etc. Need to call this even // if not using MPI to properly init these vars. Called from @@ -1088,7 +1151,7 @@ namespace yask { for(idx_t z = context.ofs_z; z < context.ofs_z + opts.dz; z++) { // Update only if point in domain for this eq group. - if (is_in_valid_domain(t, w, x, y, z)) { + if (is_in_valid_domain(t, ARG_W(w) x, y, z)) { minw = min(minw, w); maxw = max(maxw, w); minx = min(minx, x); diff --git a/src/stencil_calc.hpp b/src/stencil_calc.hpp index 73925e3e..3de0e122 100644 --- a/src/stencil_calc.hpp +++ b/src/stencil_calc.hpp @@ -460,7 +460,14 @@ namespace yask { // A pure-virtual class base for a stencil equation-set. class EqGroupBase : public BoundingBox { protected: - StencilContext* _generic_context; + StencilContext* _generic_context = 0; + std::string _name; + int _scalar_fp_ops = 0; + int _scalar_points_read = 0; + int _scalar_points_written = 0; + + // Eq-groups that this one depends on. + std::map _depends_on; public: @@ -472,107 +479,24 @@ namespace yask { // ctor, dtor. EqGroupBase(StencilContext* context) : - _generic_context(context) { } - virtual ~EqGroupBase() { } - - // Get name of this equation set. - virtual const std::string& get_name() const =0; - - // Get estimated number of FP ops done for one scalar eval. - virtual int get_scalar_fp_ops() const =0; - - // Get number of points read and written for one scalar eval. - virtual int get_scalar_points_read() const =0; - virtual int get_scalar_points_written() const =0; - - // Set the bounding-box vars for this eq group in this rank. - virtual void find_bounding_box(); - - // Determine whether indices are in [sub-]domain. - virtual bool is_in_valid_domain(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) =0; - - // Calculate one scalar result at time t. - virtual void calc_scalar(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) =0; - - // Calculate results within a block. - virtual void calc_block(idx_t bt, - idx_t begin_bw, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, - idx_t end_bw, idx_t end_bx, idx_t end_by, idx_t end_bz); - - // Calculate one sub-block of results from begin to end-1 on each dimension. - virtual void calc_sub_block(idx_t sbt, - idx_t begin_sbw, idx_t begin_sbx, idx_t begin_sby, idx_t begin_sbz, - idx_t end_sbw, idx_t end_sbx, idx_t end_sby, idx_t end_sbz) =0; - }; - - // Define a method named cfn to prefetch a cluster by calling vfn. -#define PREFETCH_CLUSTER_METHOD(cfn, vfn) \ - template \ - ALWAYS_INLINE void \ - cfn (idx_t ct, \ - idx_t begin_cwv, idx_t begin_cxv, idx_t begin_cyv, idx_t begin_czv, \ - idx_t end_cwv, idx_t end_cxv, idx_t end_cyv, idx_t end_czv) { \ - TRACE_MSG2(get_name() << "." #cfn "<" << level << ">(" \ - "t=" << ct << \ - ", wv=" << begin_cwv << \ - ", xv=" << begin_cxv << \ - ", yv=" << begin_cyv << \ - ", zv=" << begin_czv << ")"); \ - _eqGroup.vfn(*_context, ct, \ - ARG_W(begin_cwv) begin_cxv, begin_cyv, begin_czv); \ - } - - // A template that provides wrappers around a stencil-equation class - // created by the foldBuilder. A template is used instead of inheritance - // for performance. By using templates, the compiler can inline stencil - // code into loops and avoid indirect calls. - template - class EqGroupTemplate : public EqGroupBase { - - protected: - - // Pointer to a more specific context. - ContextClass* _context; - - // EqGroupClass must implement calc_scalar(), calc_cluster(), - // etc., that are used below. - // This class is generated by the foldBuilder. - EqGroupClass _eqGroup; - - // Eq-groups that this one depends on. - std::map _depends_on; - - public: - - // Ctor. - EqGroupTemplate(ContextClass* context) : - EqGroupBase(context), - _context(context), - _eqGroup(*_context, outputGridPtrs, inputGridPtrs) - { - assert(_generic_context); - assert(_context); + _generic_context(context) { // Make sure map entries exist. for (DepType dt = certain_dep; dt < num_deps; dt = DepType(dt+1)) { _depends_on[dt]; } } - virtual ~EqGroupTemplate() {} + virtual ~EqGroupBase() { } - // Get values from _eqGroup. - virtual const std::string& get_name() const { - return _eqGroup.name; - } - virtual int get_scalar_fp_ops() const { - return _eqGroup.scalar_fp_ops; - } - virtual int get_scalar_points_read() const { - return _eqGroup.scalar_points_read; - } - virtual int get_scalar_points_written() const { - return _eqGroup.scalar_points_written; - } + // Get name of this equation set. + virtual const std::string& get_name() { return _name; } + + // Get estimated number of FP ops done for one scalar eval. + virtual int get_scalar_fp_ops() { return _scalar_fp_ops; } + + // Get number of points read and written for one scalar eval. + virtual int get_scalar_points_read() const { return _scalar_points_read; } + virtual int get_scalar_points_written() const { return _scalar_points_written; } // Add dependency. virtual void add_dep(DepType dt, EqGroupBase* eg) { @@ -584,143 +508,43 @@ namespace yask { return _depends_on.at(dt); } - // Determine whether indices are in [sub-]domain for this eq group. - virtual bool is_in_valid_domain(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) final { - return _eqGroup.is_in_valid_domain(*_context, t, - ARG_W(w) x, y, z); - } + // Set the bounding-box vars for this eq group in this rank. + virtual void find_bounding_box(); - // Calculate one scalar result. - // This function implements the interface in the base class. - virtual void calc_scalar(idx_t t, idx_t w, idx_t x, idx_t y, idx_t z) final { - TRACE_MSG2(get_name() << ".calc_scalar(t=" << t << - ", w=" << w << ", x=" << x << - ", y=" << y << ", z=" << z << ")"); - _eqGroup.calc_scalar(*_context, t, ARG_W(w) x, y, z); - } + // Determine whether indices are in [sub-]domain. + virtual bool + is_in_valid_domain(idx_t t, ARG_W(idx_t w) idx_t x, idx_t y, idx_t z) =0; - // Calculate results within a cluster of vectors. - // Called from calc_sub_block(). - // The begin/end_c* vars are the start/stop_b* vars from the block loops. - ALWAYS_INLINE void - calc_cluster(idx_t ct, - idx_t begin_cwv, idx_t begin_cxv, idx_t begin_cyv, idx_t begin_czv, - idx_t end_cwv, idx_t end_cxv, idx_t end_cyv, idx_t end_czv) - { - TRACE_MSG2("calc_cluster(t=" << ct << - ", w=" << (begin_cwv*CLEN_W) << ".." << (end_cwv*CLEN_W-1) << - ", x=" << (begin_cxv*CLEN_X) << ".." << (end_cxv*CLEN_X-1) << - ", y=" << (begin_cyv*CLEN_Y) << ".." << (end_cyv*CLEN_Y-1) << - ", z=" << (begin_czv*CLEN_Z) << ".." << (end_czv*CLEN_Z-1) << - ")"); - - // The step vars are hard-coded in calc_sub_block below, and there should - // never be a partial step at this level. So, we can assume one var and - // exactly CLEN_d steps in each given direction d are calculated in this - // function. Thus, we can ignore the end_* vars in the calc function. - assert(end_cwv == begin_cwv + CLEN_W); - assert(end_cxv == begin_cxv + CLEN_X); - assert(end_cyv == begin_cyv + CLEN_Y); - assert(end_czv == begin_czv + CLEN_Z); - - // Calculate results. - _eqGroup.calc_cluster(*_context, ct, ARG_W(begin_cwv) begin_cxv, begin_cyv, begin_czv); - } + // Calculate one scalar result at time t. + virtual void + calc_scalar(idx_t t, ARG_W(idx_t w) idx_t x, idx_t y, idx_t z) =0; - // Prefetch a cluster. - // Separate methods for full cluster and each direction. - // TODO: handle pre-fetching correctly for non-simple BBs. - PREFETCH_CLUSTER_METHOD(prefetch_cluster, prefetch_cluster) -#if USING_DIM_W - PREFETCH_CLUSTER_METHOD(prefetch_cluster_sbwv, prefetch_cluster_w) -#endif - PREFETCH_CLUSTER_METHOD(prefetch_cluster_sbxv, prefetch_cluster_x) - PREFETCH_CLUSTER_METHOD(prefetch_cluster_sbyv, prefetch_cluster_y) - PREFETCH_CLUSTER_METHOD(prefetch_cluster_sbzv, prefetch_cluster_z) - - // Calculate results for one sub-block. - // This function implements the interface in the base class. - // Each block is typically computed in a separate OpenMP thread. - // The begin/end_sb* vars are the start/stop_b* vars from the block loops. + // Calculate results within a block. + virtual void + calc_block(idx_t bt, + idx_t begin_bw, idx_t begin_bx, idx_t begin_by, idx_t begin_bz, + idx_t end_bw, idx_t end_bx, idx_t end_by, idx_t end_bz); + + // Calculate one sub-block of results from begin to end-1 on each dimension. + // In the 't' dimension, evaluation is at 'sbt' only. virtual void calc_sub_block(idx_t sbt, idx_t begin_sbw, idx_t begin_sbx, idx_t begin_sby, idx_t begin_sbz, - idx_t end_sbw, idx_t end_sbx, idx_t end_sby, idx_t end_sbz) final - { - TRACE_MSG2(get_name() << ".calc_sub_block(t=" << sbt << - ", w=" << begin_sbw << ".." << (end_sbw-1) << - ", x=" << begin_sbx << ".." << (end_sbx-1) << - ", y=" << begin_sby << ".." << (end_sby-1) << - ", z=" << begin_sbz << ".." << (end_sbz-1) << - ")."); - - // If not a 'simple' domain, must use scalar code. TODO: this - // is very inefficient--need to vectorize as much as possible. - if (!bb_simple) { - - TRACE_MSG2("...using scalar code."); - for (idx_t w = begin_sbw; w < end_sbw; w++) - for (idx_t x = begin_sbx; x < end_sbx; x++) - for (idx_t y = begin_sby; y < end_sby; y++) { - - // Are there holes in the BB? - if (bb_num_points != bb_size) { - for (idx_t z = begin_sbz; z < end_sbz; z++) { - - // Update only if point is in sub-domain for this eq group. - if (is_in_valid_domain(sbt, w, x, y, z)) - calc_scalar(sbt, w, x, y, z); - } - } - - // If no holes, don't need to check domain. - else { - for (idx_t z = begin_sbz; z < end_sbz; z++) { - calc_scalar(sbt, w, x, y, z); - } - } - } - - return; - } + idx_t end_sbw, idx_t end_sbx, idx_t end_sby, idx_t end_sbz); - // Divide indices by vector lengths. Use idiv_flr() instead of '/' - // because begin/end vars may be negative (if in halo). - const idx_t begin_sbwv = idiv_flr(begin_sbw, VLEN_W); - const idx_t begin_sbxv = idiv_flr(begin_sbx, VLEN_X); - const idx_t begin_sbyv = idiv_flr(begin_sby, VLEN_Y); - const idx_t begin_sbzv = idiv_flr(begin_sbz, VLEN_Z); - const idx_t end_sbwv = idiv_flr(end_sbw, VLEN_W); - const idx_t end_sbxv = idiv_flr(end_sbx, VLEN_X); - const idx_t end_sbyv = idiv_flr(end_sby, VLEN_Y); - const idx_t end_sbzv = idiv_flr(end_sbz, VLEN_Z); - - // Vector-size steps are based on cluster lengths. - // Using CLEN_* instead of CPTS_* because we want multiples of vector lengths. - const idx_t step_sbwv = CLEN_W; - const idx_t step_sbxv = CLEN_X; - const idx_t step_sbyv = CLEN_Y; - const idx_t step_sbzv = CLEN_Z; - - // Groups in sub-block loops are set to cluster size. - // TODO: specify cluster-group sizes. - const idx_t group_size_bwv = CLEN_W; - const idx_t group_size_bxv = CLEN_X; - const idx_t group_size_byv = CLEN_Y; - const idx_t group_size_bzv = CLEN_Z; - -#if !defined(DEBUG) && defined(__INTEL_COMPILER) -#pragma forceinline recursive -#endif - { - // Include automatically-generated loop code that calls calc_cluster() - // and optionally, the prefetch functions(). -#include "stencil_sub_block_loops.hpp" - } - } + // Calculate one sub-block of results in whole clusters from + // 'begin_sb*v' to 'end_sb*v'-1 on each spatial dimension. In the + // time dimension, evaluation is at 'begin_sbtv' only. All indices + // are in vectors (hence, the 'v' suffix), i.e., element indices + // dividied by 'VLEN_*'. + virtual void + calc_sub_block_of_clusters(idx_t begin_sbtv, ARG_W(idx_t begin_sbwv) + idx_t begin_sbxv, idx_t begin_sbyv, idx_t begin_sbzv, + idx_t end_sbtv, ARG_W(idx_t end_sbwv) + idx_t end_sbxv, idx_t end_sbyv, idx_t end_sbzv) =0; }; -} +} // yask namespace. // Include auto-generated stencil code. #include "stencil_code.hpp" diff --git a/stencil-tuner.pl b/stencil-tuner.pl index 5122d26c..823181be 100755 --- a/stencil-tuner.pl +++ b/stencil-tuner.pl @@ -420,8 +420,8 @@ sub usage { # This is the loop taken by each nested OpenMP task. my @subBlockLoops = ( - "PATH0 loop(D0,D1,D2) { SBMOD loop(D3) { calc(cluster(bt)); } }", - "PATH0 loop(D0,D1) { loop(D2) { SBMOD loop(D3) { calc(cluster(bt)); } } }", + "PATH0 loop(D0,D1,D2) { SBMOD loop(D3) { calc(cluster(begin_sbtv)); } }", + "PATH0 loop(D0,D1) { loop(D2) { SBMOD loop(D3) { calc(cluster(begin_sbtv)); } } }", ); # List of possible block-loop templates. @@ -429,7 +429,7 @@ sub usage { # TODO: add other options. my @blockLoops = ( - "omp PATH1 loop(D0,D1,D2,D3) { calc(sub_block(rt)); }", + "omp PATH1 loop(D0,D1,D2,D3) { calc(sub_block(bt)); }", ); # List of possible region loop templates. From b94e9af0b3ce69010c689e4729954218eb529018 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Tue, 14 Mar 2017 10:48:45 -0700 Subject: [PATCH 11/20] Improve help message and intro doc regarding tiling levels. --- docs/YASK-intro.pdf | Bin 634985 -> 635980 bytes src/stencil_calc.cpp | 313 +++++++++++++++++++++++-------------------- src/stencil_main.cpp | 10 +- 3 files changed, 170 insertions(+), 153 deletions(-) diff --git a/docs/YASK-intro.pdf b/docs/YASK-intro.pdf index 5a8e5562f9dacc4e02a44cd25c8a8e85beccab0d..c4a30d4df2da78e49284c5ca23d357bac935006b 100755 GIT binary patch delta 70890 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 delta 70099 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

FE;^euihXfS!=2dQu@S*h=KkHk%v@ zo5_6E!O`r9f|r&WD%PbB@&dEA09@0^rxW@K&3%s&W%+dzkY!ywrs&uW^bn zuH$nS3v0*Jq|~noto5-2Na{zIc4n_tR~o3OQ+u98cm1(dWd;?+Dr+(Q} z2@NN=#E5+OROq`3|3V7JEjJISI)a*PqrC^V+b%>VixdnSOkXK-U)N*jhY04!zY`Xc zpCC?*|3WjOL=exE$?}Z9DbCgf(2K^IaM6HNTg89V%?Kz+Grhar0k|-xkg~dc<=G$~ z0@zJ1j@zjE(Cc0Ty(LenZ8;+7PW#l9(+~`2zgh;d@NIEUrY+%9lGn@_=1qBf z!Z#^D^Df!;sjJ@-kB2|R(y|nUbAV9*RxERq83Zj%dl7cKrqme)+wCXw1vkB{V@{Rm zcuj`}0;%pYOIcwyx4N70L>$H+?ZBr;LdpC1cSQVe3~q%l&RT>5S8egH=$~*I zxbcwMpGXJ~>evY!n-X^cfBAOqdm-cJjIi#fIZY7C(~i4feF^lsY1!Nn-@n>24pEa{ z{CUv@xPkYLaB75!UuY7PL zWIa>ny?mO0x~j}&=D>l}h$kvl(Ix(cV$(?enGWACLkoFPxakZ=rqsIIi*@hG1t~u4 zwn)(j#br@?HSjzwmDdjA?z1|gn?PY{sH~cObW{xFTe5dISPI|G+q^~(vdUsb+!6Yl znq{%EIcnpg2gjZyq$C%Rdte;7M&BL26@))y3A<)BY_x)VuX$HgcKCNL`ER~h1YDOe z=4}8a+;U?`Qz+nOR8E9&b^ws`c2de7Ap4Ubc)(T{u#)7kVVzREo;yd?QvU+$+m@y7 zr*6v{IU(hTvGbb_FOY^M;m-?w`zP}hT73VbMl*Td95SJv^&=tBg2HfDgmliZSobWE z`+NK9Vj-}QpS0OsBV7J=$eeO2cA{eFjnX%u_Gf@#4&OlMA^O?}q70OJ9af-ajv>U& zF>sOs_oSJj*me3U#P=C@h0NJ*%7QQp`_^xv+1_Wg*#!RV01(;0i6al3X2A4fZ% z2^$1bSpwQGK0AS+O7Q14b{Vm%@n+5uhD@nD)U7|q?Si#^OWkb_n-M?3U8VD+_lFV< zaxu(%!-c1nOjI%Alei!m_YM6aARR62;kL%o7jbDgL8UU}?R?+x5r?goIo6B(d4(Q~d)1ik+Lf96)k8Ki6?ntimds6Upo44Erm}Qu$h^9315?I9m+Pnah;kT@PHf z3)mY|3%_?9b%`sP4N-qx$w)d!{?c3e9bssxbiBk=Tpm{Drt5W&kY|f@{`MUIm-O{e^gdTojk)XU@t+N^MpjNAtF1eUl>dvXw~UHp3$}HE!rk57 z-QC^Yt?|a)3wL*ScXt|hcW7uF8gFRaAA9ff?mO@PTO)H-)mW=CM@B}>_~!B%6cp6Y z3;?*}uUo5n)2%=5$Q|r76}q0SQj4^@d@yW@-^&BGa?J4;+LG%fdx33uugDp>6ZTu7 z&P{cpit)bP{G%)>u-s^OXrSpv_9?ZrD?s{m)EQH^rcJKAx^g`CPr0= zJXSNH$@@hIZqwZap`P9o9-U6b?Y2U~=fC6flx|2o{0#5pVE;(|;P~`kVRD<#X;b>R z3-Bdn7>|K!F)Y`xv9@Hf^gN}4f<-jbf4b9yT-M&TYaRC&rg60OrA1Yn-#R!#$iKSw zmlQwXuU%S9QMZmw=P8gAyKHRd_&7$74}pus|wSceD0Zv{|}6h}c@LMoorK zwh6J+*~q`ZKwTfu8%$A@aNsnToj!>MG?LswnC z2?>yCK%)-xT#kJ(#@O8^u3(MfzWoK+H&0l$W(FC}A)kyy-(y%zZo`JRX3*K|kkm_8 z5yNw=NuJ%oj>=m%4g?(tvRD2mSRwnz>5}^&xh!{jUk!j8>i^fG^Z!~~qBa8l6?NbO zIC(hJ%WDDis#ANTZiM4@3)o7DTelpbeDaevYMOb-+W=I*VFkjchIaBE{tn7l18Iv7flYy0?3Z&{7{15=_erK)i^#kGkG$D$ zQyU@|rI}Hsu0`G<>_snD2kq*T_`%`6BJcehWrQ;yG`sd+vfry!W1OtD_LJ-<3^U3% z4p-kXBoic+rvJ&BJDZ!>rTaDj;J=VxUv|evDF14viCMn%)?Y>Ouf1bVzi9@171Ue4 zl-XZi$y|W1Q~%8T*BJRN7M9*qS_KhO`SeZgKBzGVSGHlI(mf-<{a6=MUVaLAo z_2)gbEW~B*s*tEdD2-zy^!2XU`uL4Mht}L*GOVAySd@rBt|X|saEYci%cLp;1EW!7 z>9d8f+HBHlwremgzg~K3vhS}x5^vd_-@w}m*WeZh92gM3=mCX5?1LMmihbAoMq8Yp zboTtLHK%N_=KY_5sVQqJ?ug3Y&DrRiV4c9k74%{9x4;2r$@1Pnm5&j@P{w*Fcrz*R zzQCuAcuuBH$V^)2?6^D6a@RWG5TvKtdx%WIhM`?s^_1JMhR~)Jhn0Ldm@RK89#dCc z?!^^Qx^b!#UQS#{1Sl0`bhd=@H)``b&J@KR&S*A`-ghuSutG&XM-tpK`1^Kkdxc4V z4N>*+WlGmbe=AWfUMi?H`byx=S59=Mqdzyu)Yc2Sc-p=1`zps5x+@vDM@;OiVYH~T z_9w!vbLgaFBtDx%&q~Xn4y((X?^JSr7!+0h&~!`kstw`gP@}neEUjsv*&}JFWFZPg zvMB-k=K35VWbTx8-#N_{wz)DfZ7hOESMrPToHE73%huAK(rE2T>CBQL;1pX(epN2L zuZn%uU*>u2y=>CQXh;W)T2u7c9~e|;mQC1-Te|v)({C(G=I~;CHwSvrIc^Yrp%jnJJIg)dWCPkUz)HrRegOMxj9-xf zd2_&tw{FZDiuoEbRjsqdmWW4~MwgnK8oHRVs23fw)LB@>1TzT;u-CT;D>;#d$3=q1 z3gU(+3!)qcF$`u7LaDyM>44nrYYCAQ3aCoZ>ViX3tKvcCD9Z`pgYKF}?LFk2@^omuV{`LZ zP{STVJsOI|l?4GsRcL8Xl*gVn9s<5ODt{@HjZoYi_9zfFv${h*>#hZu`-&6h*`9eogP1It8RssO{n zUwcKa3u&YGFh_MBtvil4NAT#9KZVw8N|?isFQtBhW*3j3mmtw&W3dVgLrEO-R|(jH zrjSq1Dgc{TR???|g*A}4O)j0xS9C(8%Lh+D#0|!-Mi7-dwT26Y1*M?^)0~qRF`R&J z08Ohl2kO)oI}vG{nD5o{LblY>v`$3aA193dtkp2Zj)|P6mKBd(_sz#u{Q`SFLbJ$e z-&+}xTZsdu*a?;7pSA~Nuq0KmshRf$HHiL{EBy7_tbt_z#r3C&FM4=gD6`k4`V%Yd zqeYXT#>gZqnq^END#>$(tqS$RK8t}INhKE;2ZY#jdAisxH&4AG=?P0$TxAK^CO`*> zO&Lx(dygmdreq8nW;kPT$^`wuF{w}ge(^ht;#aQnqTS^~%H%HMJ^3RTP~%{E+IX>- zDoy>bs|GPNRt3($36vwIsI@|liz|;F2(Zj#XC0eya}PCWo+2#77}solkXnrwQk_5t z8;F=75b2119!eURF@joLu2M!pS=fWlX{rP7U$dz|nyQ+g?7G4*V4b62J%?PpsY^(r zzIq!dS|;mqz%I>RnWNf$XnVopD1*lg;oi#1CT%4RpCx(>XLpA8@2}`WmKgtr8{)|n zS8m@@B~Q4ZK~krUiAc;}BNYcx&_V^K(y&(lVTm$lXs6WSB%k6nb|1J4>H%%x1s{(wWyo~_%KcZMjiLg0P&krG#^t0eLLWL3>g>iNwo z3CAy$Rm`L~cTIKIoVeu|vWgq6Y>RoWhv#nd)x71e!nr(~@l>w{5|2AGaW6igfd#p) zNEa9}bSfSk%a5T_N~BVXs!=}hXa+3{WdD>10IL|_8k}q5J4O`w-l4cpvWZj~NfCnK zc4_K0Vt_fzUFX+i)!=dSE?yKbvsJ zjIOQkf4*t&B_bv@7?k9kYS@rq2>vyPbOWL``&wWF%z6C~t~OK&193&~hx_$rGANg9 zl$kV&3d2L}h!)D`y?pb-oKvj6u`|SZBv+M6qerd^7^L;)t~Vt&tqlVEH6fvc5gC;S zX(8yDy9i1^RV-o&C;Q5?ck~4*BCjzWgi4dvk9Ut6M_xb0e*TU&&CnH0yW`xj36A`h zWH@F5g;}{QaAO3*413DboX74Wj1a{uYnW1;E=?VjmXJ9*iN9*9N&HkCo)xXZ%F=1DkSo&R4EiS`e#m}SUiIVyz%V=+EkG3y_$7WdGi#;WaScBhI z=o3;skM-uibRWtj;QwnFFiMA+0tB~D4*+ODfkv)e{7JFEN?YIKA>pl-&`~b=P*5;Y z>I#xl-zRvmeDh8JDJAmSaMwOUnphEd<_V3z&iKvMK?#EhQUOnYn>P8f>XTz?7u^Q& zhuSAdCQQeELnxoLpsqj%$#Jnt@h%%8;Ke~6Ll~(*=MOS95^-$;d}VVp_ihMH9V5)-IC`-? zT7jeWZxd`Wl#V8QFjjeNjzZ*vVr1V?T7GK5Fw@P|$`^*?a*^*~AP(|Wu#HR@?Xl|N zSIT@}ZhQA0VXL0+AoZrhRZeu>E6Oec1*SN($uyTgHP9Zx{8h{PE#VB(SRZlhgHF!M zziV}6Ijv_j)f4J;U-8XPQj!=a&uG_o1gGa%wFme5Vc;dv9l!#4nfp6*z!#rRohVXs zDPg?yw>68_bp@siQQzP*d|zvr)x_G@2X- zLf>LbI%i8mncr~`=BVMO?c_Uo8`GV%&cu(J5+?mir39=`9QiWhW*R^2jOe+?Xl^CR ztmi~g5k<#8ifU(r+Ru^KvyfMZ0 z!$pZM64Khv>zv7h^nQ*(b;sG0hyq`pS9+Et3G_!Q0I^pe4A}QyD;Eyb(bHf6I9S)V zH08~#Redr$bb~;gd%faMI*7ljMGzDH{BYbU7ETGTHA`ccOn+_0VJf>)Rh^~5 zU6RqNwqj^GY~4yz1rakiahkB(VOWDX|C}wVUeB!8XQ7SBGqsc&!2G#kMgHc%LlJ>& z-q<^XQ2OpUy7fwM9FAu-CZ99d0H*7HO45D`U&4^EKYxMIGB2)c8w^Z#UvHB`J|^N_PZ*t=wE{4DTxuq{YeYond=i~Qg{R4 zq*s|bVV5-N4ni66^FDCB2xp=N(-e~QR^au`0i9YP5u!-_fJwVrP|F7y3u_MQxms+k z2PU6~5atu+M4M-L)TNhk!=ueHO-M_;+|2zJ!GK)VM{n)-elQvOr)Hc~--cWO&T90$ z&HgkBNql}4#km>J)JA=ex6>Y|mIpzx=c$#s@g=q1=1m{(VHOZ>tiNazyc?De71X@C zzT2Hq5etNHyX(f|ejKjnaYTp%o^=9Agu;PjGB*T#iVJ(IWwFs;Tuaj6J>*B6M^=@h z#z`fqxPhdqp^q^=?WTK)NaF@f+~j$v<(vsbq_$Nme|&(l&4)*2YXBARwOR$ zZsnPwg22VT4Gs{2fo)L{QB;rKW;}Pe7onltT>7;#r~G7!>w%6MieV1CWq;quAJR@1 zyzcpzTy5~S7dz?)s4sFF5|P~-$d#AW0xd7MW7h5xKFO0qYy0e9@LbyMv{){Hlr7Wz z%NhzEN^$U}fC>B?QTd%0yA83kNPDD^Aa0Iw{Rz;e)y1jSv=7=(D@EgFLN zm1ydHpZ4D0q~!ad9`T+h`5WPFK7wk%)#VE&(;1)V?f!|PtU1%WeMWZ_OG2@yDG^_- z-u9w1wJ3LaG0TzOp7)tFx9kDZmL@kZv!gGJTyB{{#=lu6|tte%R{b`rz&ZZy3lf07X^$b6mfgg z$%O=*Q^blT8fRl}{havVF?Am(1r^EI@Xug{Mg-u|V3jbug*1Ob{MO`xGt_s83AHHp zPj@>u_E8NBSjWRZHWHC)d7H~;RjXL*pMP*;ncn}-(Yo=8jjNb16n=uD8J>Ve3GE^Z zi3UHg;35D)6!$Nq_lU=t`lE3XetoDdQqHWig{8t^ws69{1gvOT1+)l)C|MNCz~}V4 zH2y)C&{_D)(!=g+@CamrV!t(FSk)c)@0mHZ3iy}j$&1KE%<(U3ef+!!n-0D`oE>t!c6BE=##ri5mQ2$(vwrKG z)z-MWdV3C1@bEy~@gLtf^7W8vm}+yf5HI`tM>pQ^;tJTa^UK7+-)ClhGJE(XI|tQt zD7yg?s6OxmICG6_oMVW@D7Uvh5o6~atM7&IiCWil;!ii9 z!>lyo9yidH(X)^X%mnIM9$_XtAC;mHxR)iq+=i+mFCyl93X5x=;g0!*&c?^8#YkDD z)ie;SdCo1lKel)DW24c@qB=O8aZaoXnZY`8<(9-Ty?1n1aqnN-9Bh||PMF1g*>ch= z4&MgN-BsEvK(D$Q^ltq#<_P8qX7Y8;r80ehWf20ulN&8!SHeDKpJ?ymt4bE0V%j0#};mK1;Yp3Dft80fv zlQR#!Cd&}~pJNje-YMCu~9Gh5SUY&2zgIIPlW6JLw>6M0n###=gP!{PEG0!^q( zi-3E=M9P?D&5L$4ZlN5KK&zQx-5zE+9Op;7&g?96yV!0e~ht0@zlVs4uZ}Jvy z+dOo(1Pz`s=(5+OwqXP_JyQXa#8**T``!%L8?**vOBr$ALs5>I8tjKcWLV(nObe(_ zUBwn3FI%WzMlMxi^ha_%P~U-%4~_;Yiwe1lE-4j>$UJMz#H0s|@;E)G@1>l*R!|O! z_pQkr>x%kV>e5x;U$pf!)nQ0Cj29w|9Tz$Zro7bFDmhd{rDCc3;ZjWCiKWc{SfUP! zwdz3iy7}&n&8E6Sw3Dd418#v>YK1wwocJ5%hs3nlYf|$0Ndp8rH_t$|b!-Z9HKZDb z4(KR?l^?8jI`nvQ>Z-^dUlLQ6F+H1s)XQM*;{Z83uLU@T6?(D)eFLcrH~79S-i({^ zYrHXq45=-iw}tlnpn*Ev!tP#L$|n8W9;hbDj9vklqL3&XRFG6RBTJZ=S3lna!l(y;~p%W^5o3GRJA!OHM!hZ_>RMz{I1dMhHlM@H-r z6M|xf$WT5lNPQBj|L^-hNoUmV<&FzwsMBrhV=-`1QLRKTF##L4MngWLX>RcaW5ebg zxNpY#W<064WiI)kd|j0&6gg^aWK&Kq&Fv%coqM`^Z^~wKrZ>Q$0o!jk#UW;I{ZXO^ zVV^E~zJF?jaoolTpot~&zszDj5MzKy?A2MS0usFk;(GjWImdH{%k^K#$>ClF1c*M` zYX_%X-`xO2sD}`FWE`p=hfq7xh9PE2wXn$0cy4r4c#p-TmsK}z<#=9 zCz;@KWs{|-cK1{`g#TT%L>c9R9}tM)F{y;FdI>!`tGjw$bZTHNUL}~>Ochzn)~wkD(6;Zjl`a<2AnIlq3?1TF_JJtWnUc^ zv;x=Z=}t>{WFTSL4u}y)^B%_qUcs4vUtnBskYTgRh*PE7cr8gsIE80{62;R8`P7gU z#T!yUVpj*6jJu%b9y2Kh%+Uc=u9;6SSr%(IF`}{5W@11%;K19@!ne~@)aEAO5Yjfh zn#!zkHTyFN(o{YFP*f6rm%W7hGpcM#K3k1tmq$L{D??68lV}wtD+81Em`fj(iDgc( zX0l!X2k%*%;;P?As+t_;iJan$;plxKjnBLZUUw51?Dq?eoN1CJ*W{LI89KV&b?&KK z%2R5n&t$a>;5tcX!J5+81AWVrb$>sJE_DUv=C8zFqARq!XkjZ$uY&CWnxkG-L0%G4!t)2l^)*C zFqj;upsoFWQ6=vJ6m%1xU{}kBi=<*Sv4b=OC(wk5%?nd?Cumj6SYv42)4@JF%Hd=m z_tf8g7-g1K8OSQC7gyi{g9Z>DgY%K;=g2-PV>?Oz9`9s9FrF6k~ z6sL4MLuSJ_F^ayk+&|UdXLKJ1_rjuQfSMk7Ic>(uUjN-`C5Z3+`g!J6NKvQ^iuKty zjW)9<8j^jEt;L!9af-QME!c$PTy>H%=rK6_ciOB6Y;}h-yl@5nHTMZ*y0Xv>Mwv0D zt#jHpi0z~yQ;4j9awrU~A1LhY4N()Y-tg1_Y%#Td+V!U~YJGL#P)MHVZpmyA2ZCte zPhrqVDDM`PLa9T=NfMn{P=@;q@{t~K3p70yiQhsj2tY;A=K?NTQ_x}+W(2eXL{a|~ z^Lq8PqciOR3T67GEA4~Q0VAXHpPz^8h5%hRU*kR9Avea|dI3%;ZG`s#ywXz2Jpw;r zU*gbjy69UF5+N1IfmW+4^W0`K+_(sTP;3b<-(az(1=aY0VY(;zNkxpfIIUOMuAxB!WtHm2kkcU)YYc%Q%$|O!umFlOU>aCsw*&P+Y*FNyRC!8y4g; zB0%Xp$9FH8+?N{{jr62%K6Ihx>{~Nognr_9s#PBW*}S2jWl~S$OJS*VEQ(rfDd~`T zrTLhaO+?x=q?)OQqIVcu+5-O=pgN0Bfellg8_2uPtK=7rNJx0ljPQ8M$S+3QIsX2Q z^~r4EvCI;C5wqfYPP_2)6T}`+zAf=z!2Gra_&-K@8tJFM03q$H>i}X<(3Etw4L};O zi_Nu9i^0LmHhj1Cw%@nSLE6Iy6Fi zJR0#(%FJK$l%z(V4eNcaoXv`DG~bgfC#Z!2nCw9qtI5Lbk~y z4$kySH}P^qs9e=ddug%4=&MnbwMS%N6&`h0(LpQG>uNo1@7^zsp8@;P2Gp0_GwB9$ zuO#&P(%IE&U!9Nx$K6>?>6PZZw>tr04y$um*H{pF_OxAVMDCUR!%nad+`7cm9ZK?n ztTS<$!nq582eX`A&qfUElha)kgv!o*$I?m*XB1-S-Pk96( zmWxW=!6tuJQ?1LyAD=i3jIxid%P?&Lh}F(d1+lgEr8hy8&-d__*i|^KDD?R=YdZRw zkV%%;B>9>3FY5tl56j%(n7CTNJc)z#z(q#l(?oC>bdHOFbTNLoBXiLGn|@) z-fm#QXGMfm=|otN>h3{T89vZC#O>-%BOd(Iu!rk>0WmGj#a!|mpNeeqT-k0$BkM?2 zGQXBvtMiO+{5dSGe4^J5#rH}f6Ube(5VJBPyL+uFEyg8xQ@y-7#yAOJSbO~Rb~g*_ zfEd2&$R|On0DjbGtLb8BY$W=t3h9e}}MFjrhY77QOI$^)4rzqWa`;Zlv&Pvt27y! zd`nFMuVcJwMsL1lB>wFDkiOT2pMojM8y#Z#Dx8PrF!(t*AD|=v4yFHjv(!HNU)fm0 zbiU)Sx-SR1n1At!7&o0$Oom1~4+8!}Z8_&U z96_9Ru19_P^FE6Up)2guWO=cP!5A`5+8BH(BG_$I9m=eAJb?-4MCHltYl>v=C^R$g z%9mo!!sGD3V|8Z!5Ao8U1`Wk{URCzwiOk4YegTq@;yS@!RL`p~s%PD(v})~xk07s| z0fm>&uf#l99_V1bD>x_K6f1e;AkYZHmxzOsxqgc0i8B6l>u!a45^d2QqvkDcs3 z1p8zGAKf0R)KyGUg*I}BRDs(gI6SAiA*z+H6u%gT?@`P#3cO7f`f+r7@*s`jgSj1@ z?|h7C{JjiHHu|6gfkj= zAr}fZ1ata zn2~x!lwTSkOtbENZP=VpN9Tx0k>xFO`{MB@q7`Yh=>Zud^V&=MHC$Oy?LbONI!hdx za+=<456t#H9acS)6XvJiX%Q4W0r7I*h|$tG!X%n2!i+*^MW4eXNhR~K?~d0SN3 zJDlaD%n8&+7cJ%^id%Ords?CzMmaT{;R~}qG}I>#chP#s8HZm1qiOh)`D(Ksf z+hwUHz=%!zAY#svD@{ssV}c>D{24c>KGU_9BL{}ry ztXyIv1IkqN00b&fxW3e=Kl%{E2>SfERbTay0-J5`(@L!lqcI}6H1zTyFo;}=bK=o| zI)D49EfGQ)LBa@)Sr1(Vw!2#&v(0{#!zyDxooK%tltiN^?RGIhf1|BoA-lBz{ne3< ztj#h1xWSUw4mlc+gDpq!B#V>?ay!g$OgWKD5cv}<+z;9}cEGrmzM7xbG{!9M=J~L_ zF23YmpX|;uY4!0-x>=ii`HF+c*&RXBB^leT-mc#at*>NOAJcO0B7&g=N2J()yY4`@$ z2pu+%ZZSRyb|Zh;-F)-)gFYJ0GD}M}N(jzbVECr@fl+6#>>wDgGXvc*H-p?!ys8;A z-|h>Yb$E^&-$oa;VwXeAuF!=3*Ix??`*vPfHafgaNH+#*67oHfur&k|;k z7p(lw;ZV1_QHt~o2*SoYo%mniXng*^14o66ufVbI_@8D5uk^j&fPPSf_M#I&2^cs+ zbI#{~pYXp0ME~Du*@Pm1db-KoS7mel;-AW*CV8^n8+0-b~>VI6!2HM zCp0CgjzcnO;_^}J1FX$Z0KNFtM5e22iO$=@oR|&o20B~C`eDp92{pv*W8pOzTZ8K; z!BwF=YwqfIGbEtO55X$OYBh$+xr)Km_14k<2(iGLWMhCj@qD*{z|7%fWNN(-i;xDN=JFZ|^ z!45|bQ3XaSFU++gCSih;2Sh?4pwv62@U0d%laN^Ww(UYYE|+T8W899UY-u5@pLd5) z=>v^&*Q4a#=o2w}tH_>R`BQ=!lf1Kpxib8w#e@1Oc(eLgzn($gq6ONPw2du%SCx?^ zYQghQoH|hEH^58U8hkU1V*(8I+XBwnI{Yu+A%+|vPQN-)FKBZVg+o6&usXna`vgMd zH7L7niUMsptX8yuHI{a%fEBI0>C0(gagZcvk)3+Rvvh;Iv=&gaf*FUrTqa@%xon|q zhr4Xi^Rq@RJ4n&+pRuqVZoViYi@4q*=L*9 zjd#x7B4M~BUkpPhzXG)=Doq(PMMH)zOHXepvktziQ&bX@Hmid(;=4qMNBCUtv(M z8Re{~2D6?gcsALGRjDTrLljR=Y(U0{E1IH2+b?YV7?QuCEWqKb(TMu!d2H21diq$R zV7)k0JTiQx+$v$vK=ypVJ!BCfdsL$VDh`SfiM5tw_wNp#PzZbFDSm>^Cx13DWQ(y3 zXhj)=>_U(pB>4r5Gts`f7nPgVZ}gKbk`IrJWo->}I5UHqIe+$8f-z|Puk z6kAZtw}fE=QQ({9ydFBo(adBOD9fbr#=KR=6D+DJ;+zXErUMeH^Wff=m|;`XrI(h} zvwCPT<_Cs1_prB!DsG$1MO@gIY#zX$pojLdH*Lbt3^so@6~CQChF*>HZ5>4k3xK@N?36rdvBk2?YQ~ANe{l?s5 za?Qach|C^U<9yPIY14L&K2U3exo1W=eRE+bs)rwdY$|k;Ay5J)H`uX=ABIxr9?I?l zSINshE&Avhs$WRnDk9&`LS z$ZfSBwrxA8KVdDY$Uuf{{J^ZYPjF|9(#NJNEBpeN1W8p*!;O*uQLIUpvUaK~<1o$m z<^b#9i7uK0ySzag<*jUlM=%X5-BD< zg7au8@N%|Z-GBv@0pW+8TH%t8XKIs{jZ6#bCW4D)`37C z3&J2LqH%U60BBYhr z7J{7FjE9Q2DFYZ*XgCJ0S^f zBn)p(D1)Op%*cq>@~HA7!Q#iRCS;6KvlI0GBYw1__2{YrG#UMtW;f_1p;r85Q6fRD z${-}BAbL&4vpzs2&cFKr2;&>afM;#(;B@)!A+|)V{y?RSsL%%ANr|H9IWTG%xq_^U z_y)}HGjh!8pcZ&hPh@szk}{e>#juWi=#ApSUW&+Suz^vb!eY|+PZZF0WMJ`THlvIr zVMWyt@z=0~iYc^Pkg|x&gpg){q*M&m_G@ewb*?goFi9YsW(hXRF{QY}p2R*r6ed6s z`8K@KaTg1wh1WAKWE_+kt{grcqp)jF9`z<2B0Y)tTQrPxnchGK`7O@6tNsN49WDX_ zcpXM`)t*)2*1(T6=#g&$mi!kfvzVuvh*!vfS`d2-h^zf;s;GJFK%5AxRBUp@RSHU7 z{HHK5lT_e!ZiWdB^EyRsn7a*g1b&SiE3-5dlr+UGh!(9TOt9SHh)6UHXB6mQ#F#mh zl$>B9xIF}}pgw6NMKuhd3A6+zC$&En)sb78p;Y`g2+ZWzz+Y8LJm#aGJbY+C#LfWJ zjr2_k1(uSi)g&V;>VBFfb%>v_?j1w0CnPUo(hM+^7sR<_*paV1bq+)+D3^Dg-*qq? z4YEN53;SL3g&QA07KM*0Y*jM9o6$flJ2yO~SY5mzm13;^k5%+@(-2D#3BRf{1w4U2KnTWPjyt}iXD>-`>?GXpheCfH6$_?9;ss zM4~ihZtRZGQ}y>Ug-Yy4q(U?^$^ntdVk!u^Sx61Mn>^2w1gQyE&rX9;@#|rvQjp)Z z$W!jdWJes;Z7^r`BY1e?BA8V6?VkPraTEnYXbUj1Z22pUb~iN z1Jt)XtH&X5^scqSK)OH)4DXVD(&SV=^GSP%RHCPE2$rYSh3J1IG;8MZU3`ssPPEY> zp9F0@PdLALn9CWexFBQS>2EU$z^WC-B1F9kec9+`BPkjxFicSUBo=0*x%1xm1k zkN(Vkhx?Y>0E8F;Cp-6YC!`~R&IZ&=aMI4>*@2Q>Q?>Xkk5_D+1`qb=@~W8B99*a< z_j`m2ly!)br5qz`@cHSU@Q4~=oXMa3GDezPmnl%hN3#5Ghr)Lm39U9<7aePiRBk?vBcU?Q2*Iqann!)&YK(sRXWb5TAJI@{89VTsvb!mKF&jY+D9bWtM( ziM~6H5+K)ue(AU9o>9%{&KS;M6g{?|x)$ejckk1ZQrO|FbUcfr(Eh|RZka67*g9-S zPFoir@w+X?xZYMqFdO zw&>Mlr?Vjg{TiF5v-l^@Yvb$`#fD4>rU_U_x5{yANplIcz*P706xFHT;=Zwy$wZUF?}9=01bg3&($lw z@23ZwKz;Y|kL&#C=lY`& zN$&YIxsJ`70fQ*SUbAnCkYnbrOAgA%bj@j)M1j*)jd{E&l>75(j9a);Jy0ZAG}tZB=Sj8Rm;2K zw#D0%P9EbbxYYDvT+;ZnbA4|<14>tyrskoo`*rw%ds}q0Vohro>%{dUx>`uG;ddwA z#o09Tb8HckontiFP#p0IGbH)GcE9HPYlvPUHb^8m^mR^F>b>UhvXRn4McU|E|9yYx zGE;JsJ+dR-LyU4&=V>IP!kM6P0~r!lR8djHMVWz$JQh^xi;HMu2GVKN15cJVNmdk% zaa7%4HH1ojM#_s-i4^j-P%OcGjj~vBe|!HTFX1&%VE;jhYlIeCuaTZE7#XO>D?!<% zmZdljKXhWw%feREMIMa$BM49R5`2b4gQEPaap`BIfml_P>{mtV68y*4G4aEkt$TXJad(Ybw{;TiLUQ(4FFPC_lX`soJk4O_g=oSz1BNuxia$ zbGoZb>o}cP-?hRL8TmFiQm%TjYjmZ|#&t&AJedVc{p4RuIKV=PaI`Cg?M#Spf_ZsM zdc5o|apZDLe8j|9NfN&1-39)*p{^1BVhT?q>wz+8%!Mr0-UC^0;8ez%c0tC_2 zsmV`qsXN?Q~+ZL{`oZ)VkgftS962iRnUjpiF${Q0G#IPTZ3 ztvL1mf|34OD$CLTjIua#@4t4<#vsAwmonCAH4i%kM`EjtD7=VbI&!Il@+byvi-3578{_hSe2hj_0*1yz{c@7Eu#{ z#J+oYE7uHa89z1NRh!@;vJpTHcrFnvchBHe1BP3zM(RrN$HN;z8DRK_OQyubOwdZ2 zjh#b&hgw9cm~^6{F&U`H^=@q`8=_nuy<~Avrewvkr)8oFXZS;v$lX0R(V+w zE>w>2jZWR`#P_9;(d)}`Tyl(vP^Fs={vJ`7TMOfmoh&y$Egz%MMFX!qGF6Kuul#ta z4k&uy9$=MR^!d8Cw>0hU;L{`gq$|MCLdc|ZCUz9!V`bASK-yWQh$jq2n4O6ieE za0~eM%=nAK<#GSLI-KhhN8+3QOMdgHVZpA0VWv@N)s;Hany8P>)HAsMdQ{ikXr8pFl%Nr2v+iUQ9 zX8`fMz32KK_eWt%3YVv6TwTv~*Yy#>51oc= zDF?eTJHLCWKHk|>*esn%+odF$_ql#R-%Up_V$C$MW7wd)qgq*@4Ngdc^Ht5DtVH<7 z)KHsQ#tj>S>Kc}6VZU)aS4fj0u5}w2Y*gyI&!0r= z+jq-12NPIKu7l!}*Iq8FRJgV^~PrTfH|PJ8zdm z-Qh$r&y(x*&L1zA-CViO=RohjS(OauH*i#~X(?wqZb~KI3TQ6RSHccI{RuFcthK%i ze%*Ad@>tUm7FcZnvoXxw_H0x>aE6}q)?<>UP9;s?(SS!lSa(Mupu(a2<IKaqk1D2}J7XnPK9){)Pf4 zL@>xiLhmcL%~*IKJ{0LAAz{;Lkh*Z_GA`WqDigl7qfV&UrZ3p){Bju{8u|2R4RcNG zJ@sXCp3ZkuWialw6$0qLC(IXty@p7kAi8<<=IKItcYj#E^TWk)A{_7XVa@$!B4$o_ zZrYJ*3$KT*`<<^K5{Q7>_ltC$B;)6$kR&IqnWg(HQ(#ZF#@hW#iZ}Z)?s4HM*R*qJ zu3_fI`C#E=6!~bq_LKMcj*mpVq0s!-USRO8Z>*$bM_Pnd5E`HVKec^lI9=P;u--{T zv>;lP=(}$O5kU|odau!Y@3xX4qJ-!{1kr2s5=0jSQA0%UohVVi<(zx(d(ZYh&-?51 zV_S3VG3Fd&%+c1KYt3nFv1n`fnBn$P?)k^{hw}&$Cw!&tgLAi!UiiYkN;78p8A_@8 zW8>m$+%}?|aOQalR6;5enZXst+ckhK&p|?#$E861n*~1nLXS?UNkp;fUGwpo8AnFs z)5o!)6vMF8HJ(1dcZGM#L+FJ#!HGRjYVqQ@h6nskM?or9!*59%8+DZ-o;^jj+6+?> zRVzam&9kfn5V<$Me>eC?Sy`KuV0X8vCr>xVmSzf7N}|FGQ7T>EYzDXe&8k=2{ezUs zraWq1$hVeqc80P`a$C|+&WmRxD-1hhs-4I)MRW#u3Fpo|>v_W98Bx6^?0<6q(fgQ; ztl6H`B4kb6n%nC6EjQ!S4-|&Ms+2F;4={}5P!uvIdv9!Q5I%P0q(iTwDYk`NT69N` zDt**9Jmzp;D4-I7oGH!i$LajIl7baqwKYE%y3SQ}il=rjqFZ9LZ0bHTKGsU|Hn-wI z>RYRsMedI|2GeX%mAEI|HTe?5V~kdHwnG~$4i&!*48$hY*=FT<&)+-P?|kDq-}Yyx znr(4gMTHnztzOdn_{kw$`MU`_S?^>TbFE;B0S zrEq?V2ZM#zDhfZh#6M*7`z4v1Ti%>mhCZ{9jl0iP~ey$FQ+N%7G zzjAtT?UWPpw%b~~#KDI>*OeiAF?#uhB&BNlM^jF@vvs%w@S;dOsZQFV3;z=!2=Dg} zmB*pJ^wFC);C!M8A!#|J3}G0_n-A~ol6}9?uy7u%;W`3+5HfPcrs=Dja%1_UB}|pk zXk?QAi`7J_(0+H5oUG7*;J3TUhOW=WOhiZUD|5XyrIy`4>c=XUS?~KszO$JI*F3(d z3K6PS4Cy-Dv|!&D9m{>>8&x1xLZ$e1Y+tbn1tS@Q^{>$^6937W)&~DMX|V5qKM`E2 zM>i#7UcqdxATw)uCMqFvG*rg1K-ab=s4*Fod`x0C-q>B*IWSy=5;?qSq(#}1p`EiW zPTEs35Hvh}L@>F(?>%Fb$}Zh)^)-6-mezwOaI1V;#u!EEZf)DDH657RfjqT5GGa0s z73b=3(n^)=-SSQpuMwZ2klA+3_o#WOKCODFk7#Nat7%oTgnTN?(ygJx-s_9?lQBz| z&In_EkBRX9e6W>yB0@&>Knp*$Hsq@{Z9tGNB_Eh}djPGXQh+mdl;$`W?n*tJb?!7%Qj`Q&@RUTYQeA=5#5>D=d*jbpJJPeh)4 z)*>(Iw(xNhV`M8xYCC=>9`$2Ss!rTlvt;W+gAJMog?l^KFz^4exG%nc{tC5!_Bj-B z{4)|~a3yj~)C<%8i3TyIk*?MO*00cFi<7wBI!S(Avek)sxOP;lhAVUz>gJ6cIS9D3 zpKK2svfD>eC4Yu@){Xvw-T(?jmdb89Bn-qF5!#2Wrb>C}0@=9XUegH1l^jXQ-2chj@dZ0>DzkrfmV2hlHl-y91(Y;F}@+UTo&6c=*$ za_EN_)h^c_G~#DrXLBz|z?-C3k6d?qL@jT94B(C5l|g3>e`m4V>rfK%`_mtX!tm;s zS!sJE`+S!@6)hcIRnX=Bd7&a|7b%x=*(1CN+0NX4MvBI^yJLfN+jJYdG@3$q@izi~ z%z0koC(CuHT`gTxVj~3L3Bz?bk5TA*tQ6^K74X)&`rTL%8#dXCDe%NF3H`zxiEa1O zkNAs6jiu0*3!)D;eknR#Et@aGz`LrN8a(OEu&Et1%9EGx1~-G~zb5aU%@K4gZH}16+WJpDA<}zhZwqF^td-fk z2f9CCV3ZgiQ?A6~@Z88Ap(Gz1#rm4~77~E7zEn61!+jWF_~v^p2GucsDiQMGP6)4ixt_78j17+jPH&ZTM7$9;X`Rx5(a1O!PEZB+Tza-ln7QkAn6F3LCy`_Ssj>TXF4a)6LF~l0W zMc{Av(U9<`^&HHT7!R-8kb=ZKdX37EB)l0rWfrADg>n4+sj!DW{p!664ID2{`d2!k zVE6EQNV#AWoZVZRLT{YEHFG7(eyhoL+G-|&IcM1o4(Ka6LMf?R%?7{Mu-OgL?r`B^ zL=ZEErA_!#JXV~iN)LHhh+z*qCK`&29t!wmoZ&dz3mVK67g3g>Wxh$w^A_}0)8Z#l zp{yiIt(px)(ZWW{`OxQQ%TL_lx4iT+I!*&p0eLYJ8@Fo|_5#MnNgohnJl1Spa4OOX z8|D&FDOBYTY6rRrea6#D9>x>;SrwFuMX3Ce0QXsf{2YPAO)z!-xS3yjY4mI2)OV~d zb{;C?KBnYBY1D@OEFJSaD~x4MFYZbt<%K>)fpWD@Bprt<=V7XjOHV$WG^dJl2YPa0 z!FdU|pZc>tCo!dZOdpEX(=$f@>a5ecSrJEqB>s)e%`wT&rgWuo3|6K@tP6bU`{C~b zL%HKxQ*2)r5iJx4H{2u~AyRuTXpU6IWWply$z^GNID#?Lu0+cIg?xbvIVY7<2y()4 zLB%XS!Fc%6qyjv2yTaUiU!M>nt&Bex4nuH4g`o6IEHq z`mGOLv{KnJG!!RHFG4v96s%d2SUHPiR8`sI8VUk$n#apTsdFX$T=~NG)~P*(m{W@= zs7#jNJ5`rS(M!aR_QT_BrbdCsm8>2ZgZZdXfd!kd8i}8=ALKVXVh+xODFk0*`L?j! z50ajxc`cKd)r+Ic1)_2P7=y2-#pS80U4EN zDb3aJ%^BDMji8_NMW;5kkuW>*alVZ58;n7P;4xDn+~4*Ltx2>5N^>-9xdbn*p40}S zD1H*cx|_d^d=mPi?|+MVJRVYd*BF!Q1yNHUkNLoJ)0f!I_F8jTdQw*O;2#}A7zEmd zrNRwVe0sc{n(X;(d_(H4u@6&XEBjSsYbsP_YwlxxV{vsZycx*AEoV|1PBbc&`*KY3 z(W|ilI;(nDpRK5>$5=xRpJX$@5m~pDctI zbR7mN2J}~ z9MAOZDEjyKS0=9?>;jtDB_?Er0l!yBVQ=WL=)fjCgJWEQRHg~e*zylk=u9Z{Qp);K z?AT_ww+Tj=Y$N0YMB9$#$wifDKivNwveVDbWIv3}WEE4EJkF%rVP(u^Qj7hV80>Y6 z_bVsHV?B_1`G;Z})7?eDE5c^=7Dr5#94ST<$SmiZ_e%zQ`^ zC-^kR&s3KsiGoEcJuBLzP@<4|Wt@gCxHyzkA?6XLS$71FK-nFO=W#JiWr;{ygePLpYpnQ?l;U|(&zPU<=aG`sxqSPJV)XB z_rgdhmx~7abPbTT80r``)UFQu`evAH*taPjVZV`Dp~{d<@?oFSGuQp{=bpt&o?Dsk zUs3l_zvZsKx%|Tn>t5OCmz>X;U0a&FG1bZOGw@^DaB+$(7=C=|*2YL@=DZ-rp?0HV zZNLm0Wrj`+mL4%J}YtJxYs2rk=)XXqi$m=qFQC|W_;CF zgYmc(hn7W{?_t*D1ZNnl79(>&W%>{PE}W11WxCATR4_Sy@l_TRkJ`7!tPDvKm8j@v zeB9^1!U80TSoq8Fl8tC_t8WK|$JoVKg4MFx@=cQ%OG)~__mV$P*U1i3eSy0#F>xwt z7IAO!ot%sR_c)N)^A75F5B-NzOub132=COGx{1X{@IJJ}0SKu$?v&$EjjJU~7l*a; z^xn|imStTf{6sd;N`Imy&RQLd6M_0!GhsF)l}>j3z4?b zn}i>TsecEbl4B4*7KW8WCsbB&85ywu>nx1EGw(9sT_27g=t!J;W^Lo!h}G=HsQHyu}OQU5^Pt zIWS=0@axE6@E++RO(yVww@}`Z{Bjz?-F-r2!%|Dn{3Jx&7Mqh)fa|Wf*2m_eH$AHY zoE8|gsu&_)xH1g`xR_nOw7g=kb;fYWA*Oor-tqKDzo;*pj*HppW=@G1@KYgb`f_n- z`{H7LSgtDpcH@WBtC-H~5hD&!6eZoRY>5342lRFV7e+ zXJ4I-iCxT|Qtu(c6`Q2kK1=lI3j=3#Z%0J#yCLyIo+uTZZ#^c6u4s%=i7(8&rz?+ zhG@KPTZ~@7lN>LeGAe5$YHuI+E^nhNH2+Q@JtawKmn^v{jGd$}{<3 zwXf^5E$+qtJ*BnsqSG?({_k;mjmO z^L@h{2WX_PBywHIu^jR1creOtAEE&cE49=X9U>S~826Hw4(YX@w+fawVs{cP$jdED zjc)+Aq{KF;LaS7oayfpO_3`hy%z747Y%dl_ZMK25U2S?2&T4!-v*XmX%LbqD=2d-E zev6{|iJhE$e@t0U4s{NSx?dFgW~$x?JjFSe z_Eok8T5F@!KD|j{*aMZ7GQ@pma~kSQ0Y0lf64pY?*HqGpZ^?@8$&de8n%1gHcKiwj zExolVu)6a($EH3^{=VMVq$K=`7E9{;*o8txsSms+J*Y&93f52@6R*RDVSUGw&s63A zm_?MR!1Q$1O7h>YkLF)gRBkY3yk0f#-!F|Il`lT&TE4i@s$?4us)lXr zOPl%1xa0hV#5i>nGh(~H=)gjFzUb`WRphXkp6!-S%8jBH(siqXQi>+>D!GC_bDr{n zq_`05x6Ci$%pdhU$ZJDpC-tOi?m6p5d)n1`a1>93f0X+k9~BLsE=?KC;?>AO?YtCIGi%aTd8qI!>#O(upSl-Jp696zYlJZjFDGz#QTj<{lWzPMK@F>o%HC6{ zdu+7{KP^*8H=&1AOJ3AM`Ub4}L<}X1Y$ZY=bd>pQ9XIfa6>n2?4@*FBuy^s|Is;&<#=@1A3SzguXEN@&Uk9xYHj|c4|8!g^1NJcnoRI(D|2Ug z@FV_36KRjB7pNK|y)B-!JE`e*>viPudZ2yZ_Ybm42eck!9`;qpm^;cY)v@gVba798 z>8Jdr_{ph*vV-_U{Wlgo`3WbP36DO;wS!j^^>2g@pC3@Pn%hxS$dL~n+V!;l=!jeW z1taW??#hxuvL3a5=%V!8Zeh=R)vX`)F6()A9M&+|V>t`dOZJLKH?=V>>W4pWDp%iT zx-qr+p0s2$EMa`dxYgSGsLyPVX6hnOQYqPuX7g@DNK1^URd*dQG2i6mM$HQ=zKcDe zuWZ=S6J`?WL|TL=kUcRdjlc24K;XcnX|I&>GTf)0(zUAoJTQ;(P5d0JswX66+l&rY zHFW;NDc*Jz_27}nfi-+Zc2zmjA}`0UFQTpia)V)jle@;=%bme+|t}{-sp3QM%BJSDl#moB>S%#`xQc;qbDP8O~=}g9N!tqoWOjnB1i3f+v+6Sb&{P= zt%slDMv1npZUKfZyDg+PR=wcow7MM`2D@ko199133%&p(ak!&6(_F46UZglbM z`?R)a2{-Ltm@ciA+)(svP+GA{Q@v0LFYnOQyY&JyJ~e&@*)sfE+1Dd-2G`RoB~2>v z1k<@v#za^|q2K%mRm5h+XD*H(qCa&m!#A>BOVUi&D}LmgyZ8N`qS6!8n;@5mwIleq zocghzKa@btm*9^T`Fd0}H{6Qa8`~UR3Lvb|omqCuIWv-zN$>!s3sccR(?O;C=k zx#nxASNv;HhW=ouC!+Lc;K{kf%mbAHch_^a4mBeb{c!oPKudD5z#M^!ea(97=S%*s zp^Er#pGLwd;qjUFlbu`BmO3zz0x-aq~doUf@E=y~%99Wr3T%|gpqVvA1%XnyW zGCL#$1->a}b?V09$C?sfW&gfxuH{ah=mWgn7I)jzL;Cr2kp4uY?-%V~z(-0}SjaL> zTJ&;1D68BCHostL+-v^BD!Y3qLqjU@?I_$F(0p8p`49Cz@&mq_&4(i)QxoQQOwV6^ zKhj<^3i;jYJG^cp9rbiPv4HM{sPLzt2E}@0|z7(X?Y+OJtllAHemXTTDF47V9EXnK3Pi?Mt%Tj#4z`j4ai8?Ub z1&nTVbCpcUWYi0RNPL-L@2R+$%A$|tce7vC#E)~;hCi~fa2ni9imA#%y*v3$s*Hbd z9IuoaBD+AE_FKRcwcPoXe=2j;eK7G{cRq3EqD4ubBu-y6M7_9HaxJEw%)^w3xI5iV zk7fvS5_38rH*a;kol$4~fKp-)rLNYeovK6csB-YzL1FKyNin4-h0Tz?I%m>+mnrh- zIGJ>Npg6nr*oNeM7aQ>i*vzoW|<3U{{M$C-b=E=;R#dwpx{M$EBj zr)TBQMeAGMN8+~@Y7a`zZvhRVx)(#ll?72RukJim!AKA4&>UKZpSDHzsU*uoFdBI9LvqS&CAwaBL)v@Wbqe*YqjDc{USx>3YLEDFa4^B z$6lFQFMDeM6DcgjRL)8D67LZaSxCEk*Us3pY{ZfEyyk|VuPkbrY}1J`B@I{GOJSMP zc2>dKno)o7x2(zLv()m2@~_^jmZ|O;?amgf^xA+yfBP`vY*Ed=p6X!|qUU|k;=SAP zD|0*P>vku?Ww=1-T(K-``^^4?*?Zm8PqO-2eiZv z6Drq!+@YVW7Mbi(M_8X;`z55CIsfSE3r8nSiU!@=55hn7 zwisug8~M^d*8UW0T@I(ZHuXcIsZZy^N+7ON^<7G6iPut^*q3R63Z;K z@k~E}BIWg0E#L9R^@W>`Pt5+1;%*}*=Eb`qCua;wuAxGUg>^k=Fs17D`BSX|Baurj zeBoZH>aiygngwK#OG}_-p)#rl112Xin9%myL|)-?lrIUzM2f2trRb}HMuTx4=atnr zCpIeyznoF=`mbK6wXq9#`-YT#EM&E&cTS;-Ava?1E{b)1_Gu}Q+g7ouz4=g>So>To ze;1VbpSDl7}GPpH4=`?Mw`_?TY7@XzY6Bf8DmgNG$4 z35+|wlBrJWQ>GVwxk8UqY5P8J@?G9wYeUsvEgsNtUTs5WZw#F9`QQP+s;7e~zoM~~ zii<730uyi;H@}pnld}tO1p*HE1m2%OLBMddecU&-<)Y|2NgL^QP`M^f-NQi#SR~jA zHeUjb#oyp87~-_Z-jO3NaeH|{RCeL3S-4GCnU$KRMkmWCth?0H+UgyCLa;d zH=-Q7^P>`4(@?`Fu~)P8S)W)>7dR>yLgcRW6 z3{f?Oig%f7k5CAq(LTxhlj2(neD^8>#h!$c}NkZlph^E?@vogc(tY zJ*YMlqF&teJd2N!`^BRYY`Qo8nlH(?6jmqKO*azzsQ^}U&unTfE?+`c?Y<``Oa6q) zu7pPZxN2`{-3Qg6^X!;{)ZF|AGa)t1ypi2^6Auoc_X<#9D)tgiuNBUeyluK=b|jAE zW5M0|La*HRvN`Wes^tsGWP7XC-z$#~Q{E>dQ%ilqNH4;<75u??3>L;n?s}cG!x(PU>~X2Wx1#&KRtYCZu>!z8+S$kz?9sI;8zQ!u(kfSDfxQW)fLUgJ3r` z@h~VXF#iMUIDkn_QAQ%7?%dexd%K(pL)(Df>Q@kklEfV%o_R%pV`;oUwls#90yNEa)ifV7oMIu=l`_LDer{Ry3$f8!$ zX7o5PqhS2Xgun2)gW7e&$lm$h=WN7_03KJ=XYtRfm!=~zcmYDX(+9cpZu~#k`-cC~QJ#%JjbM(#NbN#RF#`>B+J?A~|&fCikii_M$lu&@)D^s0U{D&dIm zdzxkw@>IAZUNL*uFBX=nY@k2sAe!oWe)w^H?fd4!cWKt|s~H6D zII4xsrcLf=XhID+zay*uoK!g z?)UwOsPD(>k)6z#S?g*&cd1sSM2Ri#&n;t5W*iGv&n=6U*r(|=d0jlUua(VFwbm|8DP(n>1@R~mKEWSi}} z%=sJPlh(ZW1evWnCL~Ns)#NBoK;lsyCOL{^swJ5FZi07|2Q;|%YYoFmmQWmNFS#2s z|NQyfH!%uhN;fg^h2PTaKTciQ7k@I7)mVB?>7UeW9^&xOk)_$u?~$R!WqMc0+USkL z?Ss68gvv%xV8#V-lbb6CZ&Hipb>ph*^Vfiz~@=xB;8#b zFN)%fvDsJ)B4WdB;Ht?x)nzf8za|YWg@PjD!^7daU~ojz*u8c&45eLzRpIsy1u1p9 z2edzKw>f;WQ^{S`fQNv=29pm2GHXQ~lY`q#|W*-x)JWTmg;HP?(BI zL--B^Q<;o8G4;cnj2mP+K9dH8JXKljz=ctiZ^Za$*9;U*ht?< zdo|R!<0&dr-jTiP!{AG|=&=0?a?%&4H(>&UNiBjSugIyA=lMdIqfF!&=Wm2)y~y0& z|B~E4;!)`4lW#s9X)FAjks)oD;T`3MTDn?5AM*nJ3G=@66CRvfsAn(Y$Vs%wO79qU zn0uKkn{%0CnNOH~G7A&Ew^T;BM3?mr6xwOt&f!2!pp#CqrqHI_=9(+`<*l+Y`xn;? z;l+0L^i!NEjup{Cjr7xdzwvcLwp7fLtx$e`7ldrsL4@Q?pt06T6(iMv@6RIHhe-&F z-_Pt3mdg3-J*rJq(m~xbd--hwvoi1oBb?1Y6~hf{oY0WN!84$TIItMQ?-uR1h5r7_ zbB=I4#)KMzdx1Z>99)bZ$vg#@=00SZ%WckK!@e7u8I$zFfnhNq-6i%D0aM^jE`krh zNAo8pZsJm&Tw@%|r~a1WC$}Du5DM@Td~69Pe7ejAtfZy{ERvvj%nb#xduTPkH9O(O zJ3rs$Iez?%fm%O>T+z9uFEocX&MUYMGMtKglqr`=t_*o(MYZ#Vp4O7Ll|G5rMu)zt zyHs8(rJI14_5<6#BL*|uyd$rMZip5btP`vS?u{{u56@?5<)Jm}7LeN_RyAvT7mh!$ zt3GrVetg?!PN*04?9C4%uugy=5z(u_UiCRF2V7i>FF^{JC&U8={ufw~+le1A&T-fH z9-_phxIGNH1{?x{iJ`h*Tj+7g8%kS(>xhT-(zgPi=%+7X!Kl+4nPRAOtJD2Cm7IYt zSiA$Wi5t>C6^CG}TGX|WbJ2eQKGp{BP%Ra3KIa}b_<*_~D#_gm3v0=EG*5Y_#KAud zKP>D{2G+3}Rl70nS8gT!XHQfy8XlcfT9x?dOLnV&SS6Als}qoGACh}H!M#=$hNJzg zNiDq|Yl?EMLrXj>j$r-gt# zJ$sR#8d|d%iibt$nD_57m~99cg!~a|$Z54<5JwfZ>M}SMvkx_E`a7-+JFx_YQc3r@^R zGKd!(Et-vI{r>ef^xnAmUP#)lZNd#DoL}_uQPpWBQGW*F#iaM7KS>sUIqH2tcPEX% z_7CcD@nb%>JiruEh`beE`p$iMvC$&${9yBCLAk-)AFeR!Pv)PT<2NDI(f328-R)hyHIb>_faclNjv! zJF3jhTNv-W3Q1Tb2*;pS&4|(TupxLXJ+uA-72rV-K))f2XUy{0iJYD}CY6&g+KC|H zVaup98{nybCguSB=r* zw`4DHY&x+7kkvKqxZePYz6SM_?>4G{L8p;0O!+FKs2a5mb0!f#Fw793{}2;*ln(iV~7zfe{Jj zqJ*4}AP6KJ3P&KsA0x!YVNeJXDu{pz2t0;A5|9K-Lg1ng{x6J#K~X{`6bx~7LYcG6 z)oEL#L>?);6Yghv`wX4C)2HsNuYfn0L))mzq$6l#3YiWGtSHqoD>d5lGuZNPy<&*H z)t0OMjF9Eff(@#jAF7t=CTH>y@qT$%IxTA7d0XVP;oDK#>b=es`n=h`I=v03@?(d`Aa6*6fsaV}v6<*ouEtbNpKAHtb3tdBD7mn-Q)*&9 zO`c&=>bX(lSq2BKL2K1PZ?Cepp7t14hew0vm>XI=u!a?Hm$HhetuYFY`G}0mDvx?i zH^Z{;2krh_?a4s`^)yW$jt8Rcq*2s}t?DbL_8O_8SsWDnFet?e4R01r~(q zbl$FhcPg?>eN-H%5q6r!&#nhe_!cC}M^@}R?D-^&)tW3-`p^8}+t()WFA?o!BG2fu z_G|RpcR8qXekz62<2WM^g64i#zIGnH|E(zEr>8yxNopo=L>_-#4b2o?&TOi2Z{$w! z>!{|}?V7~MQLIS1K$P@rxlw5b=F|kf*Z8k{$oTlC^J_BPJeFNW!q!755luQzbsK96 z%4<#&jrU~QV>fp_ci*_8deyythKWE>yBmJ(F6#m&vb)BYb8$IK-|zUCg0hx=-O;8i zsQlw^ye_1n-6};T?0P%XT1(qxZNnm>zM!VU8gx-OwHul_`CRWlL{_~~f2^-c{)EmKiW%zELWi$b1_)z?>`W9ppW>uXt78@aE zeqybdeC z#chwnI}d8e#%8R}$j0W5YH$(rQa}CQ31C8ivo~zWA|`k7e+G!nDLi;xzr&P0 zs^-SgjQ-OO>|=_%Ef9}8D!T%A+o2BvJ9lZ)THbnKq4?(n8~nvggf0S?LD;DF5RdK- zD}|({S<^Fy%VzFljFLI|-WKa)j$h5|9>m-8A`L;+yZjBW6?g23$5SR4=QCO4Wke5R zmL6Y{7|b}IQD5#hvWplbTUjCTr|0N=UY6{N^>!Q`f6EEA%;Hm~21T?;J!P$Uwv=me z8~*){JSu`f+Kn|^3j-YeYjmoTZ;w_!B20=UGh;BN%ltNckM<=%fXK{<9(`gxEZRbC z8=h$9X<-G0CEt;cu#-Aw35)LUGJgSoME4~;TPo{QqpQ12l(G~H>-*@gE^`;2Jz7|} zvQ$py&|u#a>jlV3!sdsJXM?#Gs!(lu-bmpnR84f~QdV~kkEAF-0(7+isiXY}kchG- z4h}zgu8YB{?D#p7LPQ}48WI8F0N$0FRKa%2U9^eUX32~g?6Yumf!wBkdpFk4_l6(ggktij}T5;8XEO?LhPq>|wcILamzEB4g6b78j z6xr~+qG+LtrH=M<#6U-pL>shvR+&?OSb*z}Wd!_6VdO&uwk{AX2H<-si!0Vv%v^wu zKLAZ$rw~f(!}>6Q<@e<6UFKOF6EuIqeWVbqiP2rTDivH@cPJw`rD9mSuXJ$5&XuZ2 zuQ~~}M72S`(wjymnr)GaFISi~#8BG=E*U0F)r^HEN+I@Rj$qVlP4BcZ`WE#iGfSU+ z4Yhm$4@??sD9L--BBjFmYj7^q0SbFWCl5@MN@gkfER^!RnznMFV$DQlf`<27j?(xO z>qd*H``e5T& zp$s-6K9}C`3q9YZpbuo6H>CFu+F_*+-FK|16#1^|%n4urwNbNl$qru@75=HJPe|9Fc$e;sD$|j?!;pus z1F0J(=RnKXsaAufVf>)@%Sa8?*sMD$F?d>M%?$siKVc0yVxIj$ppL_ht&XC^(0T`9 zyH-XA{EJXwNB-8JYoIUQMzK}faeC>gWYF)R`1~wPjIma%UdGA44zMFku{VlF~hwN8J(+(f%BIIa? zvat={gHe7&w2j&#|Bzg?o#UAP{cO{*VstixfzHn*l1selS)JZ5N542Y2Rl||T|VNy zw8be0J{}{ltl8(v^sbEi=kwvjPZ9YeOcnBqp_ZgdlRQ|K5fxH^<&d{u`P>z<{|M)& z&w9G+EQwl}w`fr^kv^j{#Sw;5m8^;6Ln~%&P%xuAAuQOoyy{1qp{xLFwqroaGpAEF zzNTkiEljT5a|&=zI>6R@IZb21w}60QH)>HDp&;F0O*7uX`)~pBw}(k)Mnv}$n8ku+ zX0~G&94JO6h54>EN)1l`8Z*;A1-@xy2FL zfLA2K9@}J=UWKfmLXVB+oeWrK58>AFPZ62#$cL^%%%|Iw5k68+Gl5Wa)kGx&)PJ?3 z`$H`&nxukkujHfzIF!tM7u`i*RsoWwwY?HMGZt`t+sB}*AO#1+347McX?hQQN29g* zaMaeV?4)B;d{{J4EAtCnn{vt~SR?u)5D{QjG?Wo7@Sv71;IR8WTD8~8K*zB_95i~4 zFrFYm(XbBm`GdaSnJSDn_J+OdIpk64n>}a0id7#A0}M{%h%-2npZ?tuFx`K5^a=0f zrlRGitXRuNUt1^k3K-nx5|ntF;&dX-&?nKwXYqlq&e*T_^;Y5yky1RWjwiocSS%WE z>yf-RcIAn@SP5Y(!-;>y$P>|s(!>LtnmYD%l2!ai*#+AsG(v!Y&V<4ytWSVOLg4m06JncJMWwLd5488~UT+G}_d(hj zN?F}nw#F%QjH(Zf_O+*lyz~pEn_QCgnif~~D-wzd!W~_Goo7;m5+Zo#qUv4O$Z2}FLb7%0$?~3F#o{ce~b_T11t$05r#mb&oKf4zmA7Mg8r@6e`ysY0D}sm zF%7_wp#NwK^|#xB1Q3Yp#0Vt15dwHfBpQPt0i?jysZ;+g2mk|tuQ7v!AkdjX2_Rwr z3o%0QKOFv>K_p<-*9H-UUxNvPAlC*4yaa8EK>~tM7=ZVe{{O9h0EmR385{@%LR@1I z1Oi4uV+I5kKo4BNR)W`T27tl-OEajyq8{Mv5J5CI0bnpebY@_H!=p_qZ-~s~Zfd&K@0Q7*S1t7E{(2NzpgI{AB4hDm+guBxJ zf7A~LL*VEV!@+<{pbrAL`?WzJz|#cicu>LXOao!zzwrK=e-Ir0zYqg)8QqZJ2qe1O zf#3*17`j!%fmDL#?;to5gdRl!7z&KIjtB)`?+^yNCNUB)5Oj%=2>5k8 zU4U~U(W4nq_6Wey1_of)rUZb&plC)2R4Fj@@D0EO1yE?xfdQXD55ZSK6g>k1FeG}t z0EPfR_@WOCfn5t+K$L+a{w2tN>Ob&Ribe}GUIkYEsK^&fzU zxHiX7*tGxx)X!Jd%T>z%j{yU)YuOGA#8B`xW&{PUQtw~1|E~YQFdzb>&mB+%L(#bg zOzYZ|05v`g-GE_0c0y+m$g=3A847$=4P8S7`~+S7u&eb0G>L(%h#p#jaD=?3emGEn zpnDFG?0~xBul(~rDue?HLuUrafarAq5Ix}Nkr04gOXUFUx?v)b|CK#S{w}W|z-&T6 z)i|1j^qL7b%Lpj}zR+d^0@OU{5fpfA56JibR0;qCj28`tK)_&VW&kX|T&ws280_zH z|0^#6Ktc3y4g~=vHQFEm45;P*IWXV}0_dgzz#!<}3b+ySAJzTUyj++D6-zHuCLoF<`IYVMfk!fdRXb&V89S7jJrhv){@ntO0{p)g%l_}> zG*LaKzgNKe_t< n!4$uelc^ii-@Nc^Dw~=CPpJaUD~-{kGYo+u1pxN`V)*|7OmBKe diff --git a/src/stencil_calc.cpp b/src/stencil_calc.cpp index e823979d..365caeba 100644 --- a/src/stencil_calc.cpp +++ b/src/stencil_calc.cpp @@ -153,13 +153,13 @@ namespace yask { os << "Modeling cache...\n"; #endif - // Problem begin points. + // Domain begin points. idx_t begin_dw = begin_bbw; idx_t begin_dx = begin_bbx; idx_t begin_dy = begin_bby; idx_t begin_dz = begin_bbz; - // Problem end-points. + // Domain end-points. idx_t end_dw = end_bbw; idx_t end_dx = end_bbx; idx_t end_dy = end_bby; @@ -970,13 +970,13 @@ namespace yask { // Print some more stats. os << endl << "Amount-of-work stats:\n" << - " problem-size in this rank, for one time-step: " << + " domain-size in this rank, for one time-step: " << printWithPow10Multiplier(rank_domain_1t) << endl << - " problem-size in all ranks, for one time-step: " << + " overall-problem-size in all ranks, for one time-step: " << printWithPow10Multiplier(tot_domain_1t) << endl << - " problem-size in this rank, for all time-steps: " << + " domain-size in this rank, for all time-steps: " << printWithPow10Multiplier(rank_domain_dt) << endl << - " problem-size in all ranks, for all time-steps: " << + " overall-problem-size in all ranks, for all time-steps: " << printWithPow10Multiplier(tot_domain_dt) << endl << endl << " grid-point-updates in this rank, for one time-step: " << @@ -1006,13 +1006,13 @@ namespace yask { " est-FP-ops in all ranks, for all time-steps: " << printWithPow10Multiplier(tot_numFpOps_dt) << endl << endl << - "Notes:\n" << - " problem-sizes are based on rank-domain sizes specified in command-line (dw * dx * dy * dz).\n" << - " grid-point-updates are based on sum of grid-updates in sub-domain across equation-group(s).\n" << - " grid-point-reads are based on sum of grid-reads in sub-domain across equation-group(s).\n" << - " est-FP-ops are based on sum of est-FP-ops in sub-domain across equation-group(s).\n" << - endl; - + "Notes:\n" + " Domain-sizes and overall-problem-sizes are based on rank-domain sizes (dw * dx * dy * dz)\n" + " and number of ranks (nrw * nrx * nry * nrz) regardless of number of grids or sub-domains.\n" + " Grid-point-updates are based on sum of grid-updates in sub-domain across equation-group(s).\n" + " Grid-point-reads are based on sum of grid-reads in sub-domain across equation-group(s).\n" + " Est-FP-ops are based on sum of est-FP-ops in sub-domain across equation-group(s).\n" + "\n"; } // Init all grids & params by calling initFn. @@ -1609,24 +1609,30 @@ namespace yask { os << "Usage: " << pgmName << " [options]\n" "Options:\n"; parser.print_help(os); - os << - "Terms:\n" - " A vector is composed of FP elements.\n" - " A 'folded vector' contains elements in more than one dimension.\n" + os << "Terms for the various levels of tiling:\n" + " A 'point' is a single floating-point (FP) element.\n" + " This binary uses " << REAL_BYTES << "-byte FP elements.\n" + " A 'vector' is composed of points.\n" + " A 'folded vector' contains points in more than one dimension.\n" " The size of a vector is typically that of a SIMD register.\n" - " A vector-cluster is composed of vectors.\n" - " The size of a vector-cluster is set at compile-time.\n" - " A sub-block is composed of vector-clusters.\n" + " A 'vector-cluster' is composed of vectors.\n" + " This is the unit of work done in each inner-most loop iteration.\n" + " A 'sub-block' is composed of vector-clusters.\n" " This is the unit of work for one OpenMP thread.\n" - " A block is composed of sub-blocks.\n" + " A 'block' is composed of sub-blocks.\n" " This is the unit of work for one OpenMP thread team.\n" - " A region is composed of blocks.\n" + " A 'region' is composed of blocks.\n" " Regions are used to implement temporal wave-front tiling.\n" - " A rank-domain is composed of regions.\n" + " A 'rank-domain' is composed of regions.\n" " This is the unit of work for one MPI rank.\n" - " The overall problem domain is composed of rank-domains.\n" + " The 'overall-problem' is composed of rank-domains.\n" " This is the unit of work for all MPI ranks.\n" - "Guidelines:\n" +#ifndef USE_MPI + " This binary has NOT been compiled with MPI support.\n" +#endif + "Guidelines for setting tiling sizes:\n" + " The vector and vector-cluster sizes are set at compile-time, so\n" + " there are no run-time options to set them.\n" " Set sub-block sizes to specify a unit of work done by each thread.\n" " A sub-block size of 0 in dimensions 'w' or 'x' =>\n" " sub-block size is set to vector-cluster size in that dimension.\n" @@ -1634,17 +1640,17 @@ namespace yask { " sub-block size is set to block size in that dimension.\n" " Thus, the default sub-block is a 'y-z' slab.\n" " Temporal tiling in sub-blocks is not yet supported, so effectively, sbt = 1.\n" - " Set sub-block-group sizes to control in what order sub-blocks are evaluated within a block.\n" - " All sub-blocks that fit within a sub-block-group are evaluated before sub-blocks\n" - " in the next sub-block-group.\n" + " Set sub-block-group sizes to control the ordering of sub-blocks within a block.\n" + " All sub-blocks that intersect a given sub-block-group are evaluated\n" + " before sub-blocks in the next sub-block-group.\n" " A sub-block-group size of 0 in a given dimension =>\n" " sub-block-group size is set to sub-block size in that dimension.\n" " Set block sizes to specify a unit of work done by each thread team.\n" " A block size of 0 in a given dimension =>\n" " block size is set to region size in that dimension.\n" " Temporal tiling in blocks is not yet supported, so effectively, bt = 1.\n" - " Set block-group sizes to control in what order blocks are evaluated within a region.\n" - " All blocks that fit within a block-group are evaluated before blocks\n" + " Set block-group sizes to control the ordering of blocks within a region.\n" + " All blocks that intersect a given block-group are evaluated before blocks\n" " in the next block-group.\n" " A block-group size of 0 in a given dimension =>\n" " block-group size is set to block size in that dimension.\n" @@ -1652,29 +1658,33 @@ namespace yask { " The temopral region size should be larger than one, and\n" " the spatial region sizes should be less than the rank-domain sizes\n" " in at least one dimension to enable temporal wave-front tiling.\n" - " The spatial region sizes should be greater than block sizes\n" + " The spatial region sizes should be greater than corresponding block sizes\n" " to enable threading withing each wave-front tile.\n" " Control the time-steps in each temporal wave-front with -rt.\n" " Special cases:\n" " Using '-rt 1' disables wave-front tiling.\n" - " Using '-rt 0' => all time-steps in one wave-front.\n" + " Using '-rt 0' => all time-steps done in one wave-front.\n" " A region size of 0 in a given dimension =>\n" " region size is set to rank-domain size in that dimension.\n" - " Set rank-domain sizes to specify the problem size done on this rank.\n" - " To 'weak-scale' this to a larger overall problem size, use multiple MPI ranks.\n" -#ifndef USE_MPI - " This binary has NOT been built with MPI support.\n" + " Set rank-domain sizes to specify the work done on this rank.\n" + " This and the number of grids affect the amount of memory used.\n" + "Controlling OpenMP threading:\n" + " Using '-max_threads 0' =>\n" + " max_threads is set to OpenMP's default number of threads.\n" + " The -thread_divisor option is a convenience to control the number of\n" + " hyper-threads used without having to know the number of cores,\n" + " e.g., using '-thread_divisor 2' will halve the number of OpenMP threads.\n" + " For stencil evaluation, threads are allocated using nested OpenMP:\n" + " Num blocks evaluated in parallel = max_threads / thread_divisor / block_threads.\n" + " Num threads per block = block_threads.\n" + " Num threads per sub-block = 1.\n" << +#ifdef USE_MPI + "Controlling MPI scaling:\n" + " To 'weak-scale' to a larger overall-problem size, use multiple MPI ranks\n" + " and keep the rank-domain sizes constant.\n" + " To 'strong-scale' a given overall-problem size, use multiple MPI ranks\n" + " and reduce the size of each rank-domain appropriately.\n" << #endif - " So, rank-domain size >= region size >= block-group size >= block size.\n" - " Controlling OpenMP threading:\n" - " Using '-max_threads 0' =>\n" - " max_threads is set to OpenMP's default number of threads.\n" - " The -thread_divisor option is a convenience to control the number of\n" - " hyper-threads used without having to know the number of cores,\n" - " e.g., using '-thread_divisor 2' will halve the number of OpenMP threads.\n" - " For stencil evaluation, threads are allocated using nested OpenMP:\n" - " Num blocks evaluated in parallel = max_threads / thread_divisor / block_threads.\n" - " Num threads per block = block_threads.\n" << appNotes << "Examples:\n" << " " << pgmName << " -d 768 -dt 25\n" << @@ -1686,107 +1696,114 @@ namespace yask { os << flush; } - // Make sure all user-provided settings are valid and finish setting up some - // other vars before allocating memory. - // Called from allocAll(), so it doesn't normally need to be called from user code. - void StencilSettings::finalizeSettings(std::ostream& os) { - - // Round up domain size as needed. - dt = roundUp(os, dt, CPTS_T, "rank domain size in t (time steps)"); - dw = roundUp(os, dw, CPTS_W, "rank domain size in w"); - dx = roundUp(os, dx, CPTS_X, "rank domain size in x"); - dy = roundUp(os, dy, CPTS_Y, "rank domain size in y"); - dz = roundUp(os, dz, CPTS_Z, "rank domain size in z"); - - // Determine num regions. - // Also fix up region sizes as needed. - os << "\nRegions:" << endl; - idx_t nrt = findNumRegionsInDomain(os, rt, dt, CPTS_T, "t"); - idx_t nrw = findNumRegionsInDomain(os, rw, dw, CPTS_W, "w"); - idx_t nrx = findNumRegionsInDomain(os, rx, dx, CPTS_X, "x"); - idx_t nry = findNumRegionsInDomain(os, ry, dy, CPTS_Y, "y"); - idx_t nrz = findNumRegionsInDomain(os, rz, dz, CPTS_Z, "z"); - idx_t nr = nrt * nrw * nrx * nry * nrz; - os << " num-regions-per-rank-domain: " << nr << endl; - os << " Since the temporal region size is " << rt << - ", temporal wave-front tiling is "; - if (rt <= 1) os << "NOT "; - os << "enabled.\n"; - - // Determine num blocks. - // Also fix up block sizes as needed. - os << "\nBlocks:" << endl; - idx_t nbt = findNumBlocksInRegion(os, bt, rt, CPTS_T, "t"); - idx_t nbw = findNumBlocksInRegion(os, bw, rw, CPTS_W, "w"); - idx_t nbx = findNumBlocksInRegion(os, bx, rx, CPTS_X, "x"); - idx_t nby = findNumBlocksInRegion(os, by, ry, CPTS_Y, "y"); - idx_t nbz = findNumBlocksInRegion(os, bz, rz, CPTS_Z, "z"); - idx_t nb = nbt * nbw * nbx * nby * nbz; - os << " num-blocks-per-region: " << nb << endl; - os << " num-blocks-per-rank-domain: " << (nb * nr) << endl; - - // Adjust defaults for block-groups to be size of block. - if (!bgw) bgw = bw; - if (!bgx) bgx = bx; - if (!bgy) bgy = by; - if (!bgz) bgz = bz; - - // Determine num block-groups. - // Also fix up block-group sizes as needed. - // TODO: only print this if block-grouping is enabled. - os << "\nBlock-groups:" << endl; - idx_t nbgw = findNumBlockGroupsInRegion(os, bgw, rw, bw, "w"); - idx_t nbgx = findNumBlockGroupsInRegion(os, bgx, rx, bx, "x"); - idx_t nbgy = findNumBlockGroupsInRegion(os, bgy, ry, by, "y"); - idx_t nbgz = findNumBlockGroupsInRegion(os, bgz, rz, bz, "z"); - idx_t nbg = nbt * nbgw * nbgx * nbgy * nbgz; - os << " num-block-groups-per-region: " << nbg << endl; - nbw = findNumBlocksInBlockGroup(os, bw, bgw, CPTS_W, "w"); - nbx = findNumBlocksInBlockGroup(os, bx, bgx, CPTS_X, "x"); - nby = findNumBlocksInBlockGroup(os, by, bgy, CPTS_Y, "y"); - nbz = findNumBlocksInBlockGroup(os, bz, bgz, CPTS_Z, "z"); - nb = nbw * nbx * nby * nbz; - os << " num-blocks-per-block-group: " << nb << endl; - - // Adjust defaults for sub-blocks to be y-z slab. - if (!sbw) sbw = CPTS_W; - if (!sbx) sbx = CPTS_X; - if (!sby) sby = by; - if (!sbz) sbz = bz; - - // Determine num sub-blocks. - // Also fix up sub-block sizes as needed. - os << "\nSub-blocks:" << endl; - idx_t nsbt = findNumSubBlocksInBlock(os, sbt, bt, CPTS_T, "t"); - idx_t nsbw = findNumSubBlocksInBlock(os, sbw, bw, CPTS_W, "w"); - idx_t nsbx = findNumSubBlocksInBlock(os, sbx, bx, CPTS_X, "x"); - idx_t nsby = findNumSubBlocksInBlock(os, sby, by, CPTS_Y, "y"); - idx_t nsbz = findNumSubBlocksInBlock(os, sbz, bz, CPTS_Z, "z"); - idx_t nsb = nsbt * nsbw * nsbx * nsby * nsbz; - os << " num-sub-blocks-per-block: " << nsb << endl; - - // Adjust defaults for sub-block-groups to be size of sub-block. - if (!sbgw) sbgw = sbw; - if (!sbgx) sbgx = sbx; - if (!sbgy) sbgy = sby; - if (!sbgz) sbgz = sbz; - - // Determine num sub-block-groups. - // Also fix up sub-block-group sizes as needed. - // TODO: only print this if sub-block-grouping is enabled. - os << "\nSub-block-groups:" << endl; - idx_t nsbgw = findNumSubBlockGroupsInBlock(os, sbgw, bw, sbw, "w"); - idx_t nsbgx = findNumSubBlockGroupsInBlock(os, sbgx, bx, sbx, "x"); - idx_t nsbgy = findNumSubBlockGroupsInBlock(os, sbgy, by, sby, "y"); - idx_t nsbgz = findNumSubBlockGroupsInBlock(os, sbgz, bz, sbz, "z"); - idx_t nsbg = nsbgw * nsbgx * nsbgy * nsbgz; - os << " num-sub-block-groups-per-block: " << nsbg << endl; - nsbw = findNumSubBlocksInSubBlockGroup(os, sbw, sbgw, CPTS_W, "w"); - nsbx = findNumSubBlocksInSubBlockGroup(os, sbx, sbgx, CPTS_X, "x"); - nsby = findNumSubBlocksInSubBlockGroup(os, sby, sbgy, CPTS_Y, "y"); - nsbz = findNumSubBlocksInSubBlockGroup(os, sbz, sbgz, CPTS_Z, "z"); - nsb = nsbw * nsbx * nsby * nsbz; - os << " num-sub-blocks-per-sub-block-group: " << nsb << endl; - } + // Make sure all user-provided settings are valid and finish setting up some + // other vars before allocating memory. + // Called from allocAll(), so it doesn't normally need to be called from user code. + void StencilSettings::finalizeSettings(std::ostream& os) { + + // Round up domain size as needed. + dt = roundUp(os, dt, CPTS_T, "rank domain size in t (time steps)"); + dw = roundUp(os, dw, CPTS_W, "rank domain size in w"); + dx = roundUp(os, dx, CPTS_X, "rank domain size in x"); + dy = roundUp(os, dy, CPTS_Y, "rank domain size in y"); + dz = roundUp(os, dz, CPTS_Z, "rank domain size in z"); + + // Determine num regions. + // Also fix up region sizes as needed. + // Default region size (if 0) will be size of rank-domain. + os << "\nRegions:" << endl; + idx_t nrt = findNumRegionsInDomain(os, rt, dt, CPTS_T, "t"); + idx_t nrw = findNumRegionsInDomain(os, rw, dw, CPTS_W, "w"); + idx_t nrx = findNumRegionsInDomain(os, rx, dx, CPTS_X, "x"); + idx_t nry = findNumRegionsInDomain(os, ry, dy, CPTS_Y, "y"); + idx_t nrz = findNumRegionsInDomain(os, rz, dz, CPTS_Z, "z"); + idx_t nr = nrt * nrw * nrx * nry * nrz; + os << " num-regions-per-rank-domain: " << nr << endl; + os << " Since the temporal region size is " << rt << + ", temporal wave-front tiling is "; + if (rt <= 1) os << "NOT "; + os << "enabled.\n"; + + // Determine num blocks. + // Also fix up block sizes as needed. + // Default block size (if 0) will be size of region. + os << "\nBlocks:" << endl; + idx_t nbt = findNumBlocksInRegion(os, bt, rt, CPTS_T, "t"); + idx_t nbw = findNumBlocksInRegion(os, bw, rw, CPTS_W, "w"); + idx_t nbx = findNumBlocksInRegion(os, bx, rx, CPTS_X, "x"); + idx_t nby = findNumBlocksInRegion(os, by, ry, CPTS_Y, "y"); + idx_t nbz = findNumBlocksInRegion(os, bz, rz, CPTS_Z, "z"); + idx_t nb = nbt * nbw * nbx * nby * nbz; + os << " num-blocks-per-region: " << nb << endl; + os << " num-blocks-per-rank-domain: " << (nb * nr) << endl; + + // Adjust defaults for sub-blocks to be y-z slab. + // Otherwise, findNumSubBlocksInBlock() would set default + // to entire block. + if (!sbw) sbw = CPTS_W; // min size in 'w'. + if (!sbx) sbx = CPTS_X; // min size in 'x'. + if (!sby) sby = by; // max size in 'y'. + if (!sbz) sbz = bz; // max size in 'z'. + + // Determine num sub-blocks. + // Also fix up sub-block sizes as needed. + os << "\nSub-blocks:" << endl; + idx_t nsbt = findNumSubBlocksInBlock(os, sbt, bt, CPTS_T, "t"); + idx_t nsbw = findNumSubBlocksInBlock(os, sbw, bw, CPTS_W, "w"); + idx_t nsbx = findNumSubBlocksInBlock(os, sbx, bx, CPTS_X, "x"); + idx_t nsby = findNumSubBlocksInBlock(os, sby, by, CPTS_Y, "y"); + idx_t nsbz = findNumSubBlocksInBlock(os, sbz, bz, CPTS_Z, "z"); + idx_t nsb = nsbt * nsbw * nsbx * nsby * nsbz; + os << " num-sub-blocks-per-block: " << nsb << endl; + + // Now, we adjust groups. These are done after all the above sizes + // because group sizes are more like 'guidelines' and don't have + // their own loops. + os << "\nGroups:" << endl; + + // Adjust defaults for block-groups to be size of block. + if (!bgw) bgw = bw; + if (!bgx) bgx = bx; + if (!bgy) bgy = by; + if (!bgz) bgz = bz; + + // Determine num block-groups. + // Also fix up block-group sizes as needed. + // TODO: only print this if block-grouping is enabled. + idx_t nbgw = findNumBlockGroupsInRegion(os, bgw, rw, bw, "w"); + idx_t nbgx = findNumBlockGroupsInRegion(os, bgx, rx, bx, "x"); + idx_t nbgy = findNumBlockGroupsInRegion(os, bgy, ry, by, "y"); + idx_t nbgz = findNumBlockGroupsInRegion(os, bgz, rz, bz, "z"); + idx_t nbg = nbt * nbgw * nbgx * nbgy * nbgz; + os << " num-block-groups-per-region: " << nbg << endl; + nbw = findNumBlocksInBlockGroup(os, bw, bgw, CPTS_W, "w"); + nbx = findNumBlocksInBlockGroup(os, bx, bgx, CPTS_X, "x"); + nby = findNumBlocksInBlockGroup(os, by, bgy, CPTS_Y, "y"); + nbz = findNumBlocksInBlockGroup(os, bz, bgz, CPTS_Z, "z"); + nb = nbw * nbx * nby * nbz; + os << " num-blocks-per-block-group: " << nb << endl; + + // Adjust defaults for sub-block-groups to be size of sub-block. + if (!sbgw) sbgw = sbw; + if (!sbgx) sbgx = sbx; + if (!sbgy) sbgy = sby; + if (!sbgz) sbgz = sbz; + + // Determine num sub-block-groups. + // Also fix up sub-block-group sizes as needed. + // TODO: only print this if sub-block-grouping is enabled. + idx_t nsbgw = findNumSubBlockGroupsInBlock(os, sbgw, bw, sbw, "w"); + idx_t nsbgx = findNumSubBlockGroupsInBlock(os, sbgx, bx, sbx, "x"); + idx_t nsbgy = findNumSubBlockGroupsInBlock(os, sbgy, by, sby, "y"); + idx_t nsbgz = findNumSubBlockGroupsInBlock(os, sbgz, bz, sbz, "z"); + idx_t nsbg = nsbgw * nsbgx * nsbgy * nsbgz; + os << " num-sub-block-groups-per-block: " << nsbg << endl; + nsbw = findNumSubBlocksInSubBlockGroup(os, sbw, sbgw, CPTS_W, "w"); + nsbx = findNumSubBlocksInSubBlockGroup(os, sbx, sbgx, CPTS_X, "x"); + nsby = findNumSubBlocksInSubBlockGroup(os, sby, sbgy, CPTS_Y, "y"); + nsbz = findNumSubBlocksInSubBlockGroup(os, sbz, sbgz, CPTS_Z, "z"); + nsb = nsbw * nsbx * nsby * nsbz; + os << " num-sub-blocks-per-sub-block-group: " << nsb << endl; + } } // namespace yask. diff --git a/src/stencil_main.cpp b/src/stencil_main.cpp index 4cbc6a25..baf525a1 100644 --- a/src/stencil_main.cpp +++ b/src/stencil_main.cpp @@ -119,17 +119,17 @@ struct AppSettings : public StencilSettings { if (help) { string appNotes = - " Validation is very slow and uses 2x memory,\n" - " so run with very small sizes and number of time-steps.\n" - " If validation fails, it may be due to rounding error;\n" - " try building with 8-byte reals.\n"; + "Validation is very slow and uses 2x memory,\n" + " so run with very small sizes and number of time-steps.\n" + " If validation fails, it may be due to rounding error;\n" + " try building with 8-byte reals.\n"; vector appExamples; appExamples.push_back("-t 2"); appExamples.push_back("-v"); print_usage(cout, parser, argv[0], appNotes, appExamples); exit_yask(1); } - + if (args.size()) { cerr << "Error: extraneous parameter(s):"; for (auto arg : args) From 42429f357d8b58e36a7b2fedb457f5a2c222e1e1 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Thu, 16 Mar 2017 10:57:55 -0700 Subject: [PATCH 12/20] Improve MPI perf by keeping region threading for buffer packing. Before this change, the max number of threads was used for buffer packing. This caused OpenMP to switch threading schemes on every time-step. Now, the MPI packing just uses the region threads w/o changing them. --- src/stencil_calc.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/stencil_calc.cpp b/src/stencil_calc.cpp index 365caeba..8faaab55 100644 --- a/src/stencil_calc.cpp +++ b/src/stencil_calc.cpp @@ -211,6 +211,9 @@ namespace yask { ", z=" << begin_dz << ".." << (end_dz-1) << ")"); + // Set number of threads for a region. + set_region_threads(); + // Number of iterations to get from begin_dt to (but not including) end_dt, // stepping by step_dt. const idx_t num_dt = ((end_dt - begin_dt) + (step_dt - 1)) / step_dt; @@ -264,6 +267,9 @@ namespace yask { } + // Reset threads back to max. + set_all_threads(); + #ifdef MODEL_CACHE // Print cache stats, then disable. // Thus, cache is only modeled for first call. @@ -361,9 +367,6 @@ namespace yask { end_ry > begin_ry && end_rz > begin_rz) { - // Set number of threads for a region. - set_region_threads(); - // Include automatically-generated loop code that // calls calc_block() for each block in this region. // Loops through w from begin_rw to end_rw-1; @@ -371,8 +374,6 @@ namespace yask { // contains the outer OpenMP loop(s). #include "stencil_region_loops.hpp" - // Reset threads back to max. - set_all_threads(); } // Shift spatial region boundaries for next iteration to @@ -420,6 +421,7 @@ namespace yask { const idx_t group_size_bz = opts.sbgz; // Set number of threads for a block. + // This should be nested within a top-level OpenMP task. _generic_context->set_block_threads(); // Include automatically-generated loop code that calls @@ -1412,6 +1414,7 @@ namespace yask { // Define calc_halo to copy data between main grid and MPI buffer. // Add a short loop in z-dim to increase work done in halo loop. + // Use 'index_*' vars to access buffers because they are always 0-based. #define calc_halo(t, \ start_wv, start_xv, start_yv, start_zv, \ stop_wv, stop_xv, stop_yv, stop_zv) do { \ @@ -1421,8 +1424,9 @@ namespace yask { idx_t izv = index_zv * step_zv; \ if (hi == halo_isend) { \ for (idx_t zv = start_zv; zv < stop_zv; zv++) { \ - real_vec_t hval = gp->readVecNorm_TWXYZ(t, wv, xv, yv, zv, \ - __LINE__); \ + real_vec_t hval = \ + gp->readVecNorm_TWXYZ(t, wv, xv, yv, zv, \ + __LINE__); \ sendBuf->writeVecNorm(hval, index_wv, index_xv, index_yv, izv++, \ __LINE__); \ } \ @@ -1675,9 +1679,10 @@ namespace yask { " hyper-threads used without having to know the number of cores,\n" " e.g., using '-thread_divisor 2' will halve the number of OpenMP threads.\n" " For stencil evaluation, threads are allocated using nested OpenMP:\n" - " Num blocks evaluated in parallel = max_threads / thread_divisor / block_threads.\n" + " Num threads per region = max_threads / thread_divisor / block_threads.\n" " Num threads per block = block_threads.\n" - " Num threads per sub-block = 1.\n" << + " Num threads per sub-block = 1.\n" + " Num threads used for halo exchange is same as num per region.\n" #ifdef USE_MPI "Controlling MPI scaling:\n" " To 'weak-scale' to a larger overall-problem size, use multiple MPI ranks\n" From 2caf4141017acb1975549764b6a5e85ed8443d85 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Thu, 16 Mar 2017 10:58:21 -0700 Subject: [PATCH 13/20] Control OMP block scheduling from tuner. --- stencil-tuner.pl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/stencil-tuner.pl b/stencil-tuner.pl index 823181be..b3f475e9 100755 --- a/stencil-tuner.pl +++ b/stencil-tuner.pl @@ -474,7 +474,7 @@ sub usage { # OMP. my @schedules = - ( 'static', 'dynamic', 'guided' ); + ( 'static,1', 'dynamic,1', 'guided,1' ); # Data structure to describe each gene in the genome. # 2-D array. Each outer array element contains the following elements: @@ -565,7 +565,8 @@ sub usage { # other build options. [ 0, 100, 1, 'exprSize' ], # expression-size threshold. - [ 0, $#schedules, 1, 'ompSchedule' ], # OMP for schedule. + [ 0, $#schedules, 1, 'ompSchedule' ], # OMP schedule for region loop. + [ 0, $#schedules, 1, 'ompBlockSchedule' ], # OMP schedule for block loop. ); } @@ -1240,6 +1241,7 @@ sub fitness { my $pfdl1 = readHash($h, 'pfdl1', 1); my $pfdl2 = readHash($h, 'pfdl2', 1); my $ompSchedule = readHash($h, 'ompSchedule', 1); + my $ompBlockSchedule = readHash($h, 'ompBlockSchedule', 1); # fold numbers. my $foldNums = $folds[$fold]; @@ -1384,6 +1386,7 @@ sub fitness { # OMP settings. my $scheduleStr = $schedules[$ompSchedule]; + my $blockScheduleStr = $schedules[$ompBlockSchedule]; # compile-time settings. my $macros = ''; @@ -1426,7 +1429,7 @@ sub fitness { } # other vars. - $mvars .= " omp_schedule=$scheduleStr expr_size=$exprSize"; + $mvars .= " omp_schedule=$scheduleStr omp_block_schedule=$blockScheduleStr expr_size=$exprSize"; $mvars .= " mpi=1" if $nranks > 1; # how to make. From d2112995231aafa862468cbb5bfd4cb358f17d2b Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Thu, 16 Mar 2017 11:14:04 -0700 Subject: [PATCH 14/20] Fix non-MPI build. --- src/stencil_calc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stencil_calc.cpp b/src/stencil_calc.cpp index 8faaab55..b1135d0a 100644 --- a/src/stencil_calc.cpp +++ b/src/stencil_calc.cpp @@ -1682,7 +1682,7 @@ namespace yask { " Num threads per region = max_threads / thread_divisor / block_threads.\n" " Num threads per block = block_threads.\n" " Num threads per sub-block = 1.\n" - " Num threads used for halo exchange is same as num per region.\n" + " Num threads used for halo exchange is same as num per region.\n" << #ifdef USE_MPI "Controlling MPI scaling:\n" " To 'weak-scale' to a larger overall-problem size, use multiple MPI ranks\n" From a7985ca3f61199072efa8f1a5621288ee463cb70 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Sun, 26 Mar 2017 14:08:51 -0700 Subject: [PATCH 15/20] Cleanup of some elastic stencil examples. --- .../ElasticStencil/ElasticStencil.hpp | 44 +- .../stencils/FSGElasticStencil.hpp | 1012 +++++++++-------- .../stencils/SSGElasticStencil.hpp | 36 +- 3 files changed, 552 insertions(+), 540 deletions(-) diff --git a/src/foldBuilder/stencils/ElasticStencil/ElasticStencil.hpp b/src/foldBuilder/stencils/ElasticStencil/ElasticStencil.hpp index 3d9ae2aa..07e8e798 100644 --- a/src/foldBuilder/stencils/ElasticStencil/ElasticStencil.hpp +++ b/src/foldBuilder/stencils/ElasticStencil/ElasticStencil.hpp @@ -61,18 +61,18 @@ class ElasticStencilBase : public StencilBase { Grid rho; // Spatial FD coefficients. - const float c0_8 = 1.2f; - const float c1_8 = 1.4f; - const float c2_8 = 1.6f; - const float c3_8 = 1.8f; + const double c0_8 = 1.2; + const double c1_8 = 1.4; + const double c2_8 = 1.6; + const double c3_8 = 1.8; // Physical dimensions in time and space. - const float delta_t = 0.002452f; + const double delta_t = 0.002452; // Inverse of discretization. - const float dxi = 36.057693f; - const float dyi = 36.057693f; - const float dzi = 36.057693f; + const double dxi = 36.057693; + const double dyi = 36.057693; + const double dzi = 36.057693; bool hasBoundaryCondition; ElasticBoundaryCondition *bc; @@ -90,32 +90,32 @@ class ElasticStencilBase : public StencilBase { GridValue interp_rho( GridIndex x, GridIndex y, GridIndex z, const TL ) { - return ( 2.0f/ (rho(x , y , z ) + - rho(x+1, y , z )) ); + return ( 2.0/ (rho(x , y , z ) + + rho(x+1, y , z )) ); } GridValue interp_rho( GridIndex x, GridIndex y, GridIndex z, const TR ) { - return ( 2.0f/ (rho(x , y , z ) + - rho(x , y+1, z )) ); + return ( 2.0/ (rho(x , y , z ) + + rho(x , y+1, z )) ); } GridValue interp_rho( GridIndex x, GridIndex y, GridIndex z, const BL ) { - return ( 2.0f/ (rho(x , y , z ) + - rho(x , y , z+1)) ); + return ( 2.0/ (rho(x , y , z ) + + rho(x , y , z+1)) ); } GridValue interp_rho( GridIndex x, GridIndex y, GridIndex z, const BR ) { - return ( 8.0f/ (rho(x , y , z ) + - rho(x , y , z+1) + - rho(x , y+1, z ) + - rho(x+1, y , z ) + - rho(x+1, y+1, z ) + - rho(x , y+1, z+1) + - rho(x+1, y , z+1) + - rho(x+1, y+1, z+1)) ); + return ( 8.0/ (rho(x , y , z ) + + rho(x , y , z+1) + + rho(x , y+1, z ) + + rho(x+1, y , z ) + + rho(x+1, y+1, z ) + + rho(x , y+1, z+1) + + rho(x+1, y , z+1) + + rho(x+1, y+1, z+1)) ); } template diff --git a/src/foldBuilder/stencils/FSGElasticStencil.hpp b/src/foldBuilder/stencils/FSGElasticStencil.hpp index 2bc670e7..1f6a50c3 100644 --- a/src/foldBuilder/stencils/FSGElasticStencil.hpp +++ b/src/foldBuilder/stencils/FSGElasticStencil.hpp @@ -31,529 +31,543 @@ IN THE SOFTWARE. namespace fsg { -class FSG_ABC; - -class FSGBoundaryCondition : public ElasticBoundaryCondition -{ -public: - virtual void velocity (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) {} - virtual void stress (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) {} -}; - -class FSGElasticStencilBase : public ElasticStencilBase { - friend FSG_ABC; - -protected: - - //struct V_Node { - //Grid u, v, w; - //}; - // Time-varying 3D-spatial velocity grids. - //V_Node v_bl, v_br, v_tl, v_tr; - - Grid v_bl_u, v_bl_v, v_bl_w; - Grid v_br_u, v_br_v, v_br_w; - Grid v_tl_u, v_tl_v, v_tl_w; - Grid v_tr_u, v_tr_v, v_tr_w; - - //struct S_Node { - //Grid xx, yy, zz, xy, xz, yz; - //}; - // Time-varying 3D-spatial Stress grids. - //S_Node s_bl, s_br, s_tl, s_tr; - - Grid s_bl_xx, s_bl_yy, s_bl_zz, s_bl_xy, s_bl_xz, s_bl_yz; - Grid s_br_xx, s_br_yy, s_br_zz, s_br_xy, s_br_xz, s_br_yz; - Grid s_tl_xx, s_tl_yy, s_tl_zz, s_tl_xy, s_tl_xz, s_tl_yz; - Grid s_tr_xx, s_tr_yy, s_tr_zz, s_tr_xy, s_tr_xz, s_tr_yz; - - // 3D-spatial coefficients. - Grid c11,c12,c13,c14,c15,c16; - Grid c22,c23,c24,c25,c26; - Grid c33,c34,c35,c36; - Grid c44,c45,c46; - Grid c55,c56; - Grid c66; - -public: - - FSGElasticStencilBase( const string &name, StencilList& stencils ) : - ElasticStencilBase ( name, stencils ) + class FSG_ABC; + + class FSGBoundaryCondition : public ElasticBoundaryCondition { + public: + virtual void velocity (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) {} + virtual void stress (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) {} + }; + + class FSGElasticStencilBase : public ElasticStencilBase { + friend FSG_ABC; + + protected: + + //struct V_Node { + //Grid u, v, w; + //}; + // Time-varying 3D-spatial velocity grids. + //V_Node v_bl, v_br, v_tl, v_tr; + + Grid v_bl_u, v_bl_v, v_bl_w; + Grid v_br_u, v_br_v, v_br_w; + Grid v_tl_u, v_tl_v, v_tl_w; + Grid v_tr_u, v_tr_v, v_tr_w; + + //struct S_Node { + //Grid xx, yy, zz, xy, xz, yz; + //}; + // Time-varying 3D-spatial Stress grids. + //S_Node s_bl, s_br, s_tl, s_tr; + + Grid s_bl_xx, s_bl_yy, s_bl_zz, s_bl_xy, s_bl_xz, s_bl_yz; + Grid s_br_xx, s_br_yy, s_br_zz, s_br_xy, s_br_xz, s_br_yz; + Grid s_tl_xx, s_tl_yy, s_tl_zz, s_tl_xy, s_tl_xz, s_tl_yz; + Grid s_tr_xx, s_tr_yy, s_tr_zz, s_tr_xy, s_tr_xz, s_tr_yz; + + // 3D-spatial coefficients. + Grid c11,c12,c13,c14,c15,c16; + Grid c22,c23,c24,c25,c26; + Grid c33,c34,c35,c36; + Grid c44,c45,c46; + Grid c55,c56; + Grid c66; + + public: + + FSGElasticStencilBase( const string &name, StencilList& stencils ) : + ElasticStencilBase ( name, stencils ) + { initGrids(); - } + } - FSGElasticStencilBase( const string &name, FSGBoundaryCondition *bc, StencilList& stencils ) : - ElasticStencilBase ( name, bc, stencils ) - { + FSGElasticStencilBase( const string &name, FSGBoundaryCondition *bc, StencilList& stencils ) : + ElasticStencilBase ( name, bc, stencils ) + { initGrids(); - } + } - void initGrids () - { - // Specify the dimensions of each grid. - // (This names the dimensions; it does not specify their sizes.) - INIT_GRID_4D(v_bl_u, t, x, y, z); - INIT_GRID_4D(v_bl_v, t, x, y, z); - INIT_GRID_4D(v_bl_w, t, x, y, z); - INIT_GRID_4D(v_br_u, t, x, y, z); - INIT_GRID_4D(v_br_v, t, x, y, z); - INIT_GRID_4D(v_br_w, t, x, y, z); - INIT_GRID_4D(v_tl_u, t, x, y, z); - INIT_GRID_4D(v_tl_v, t, x, y, z); - INIT_GRID_4D(v_tl_w, t, x, y, z); - INIT_GRID_4D(v_tr_u, t, x, y, z); - INIT_GRID_4D(v_tr_v, t, x, y, z); - INIT_GRID_4D(v_tr_w, t, x, y, z); - INIT_GRID_4D(s_bl_xx, t, x, y, z); - INIT_GRID_4D(s_bl_yy, t, x, y, z); - INIT_GRID_4D(s_bl_zz, t, x, y, z); - INIT_GRID_4D(s_bl_yz, t, x, y, z); - INIT_GRID_4D(s_bl_xz, t, x, y, z); - INIT_GRID_4D(s_bl_xy, t, x, y, z); - INIT_GRID_4D(s_br_xx, t, x, y, z); - INIT_GRID_4D(s_br_yy, t, x, y, z); - INIT_GRID_4D(s_br_zz, t, x, y, z); - INIT_GRID_4D(s_br_yz, t, x, y, z); - INIT_GRID_4D(s_br_xz, t, x, y, z); - INIT_GRID_4D(s_br_xy, t, x, y, z); - INIT_GRID_4D(s_tl_xx, t, x, y, z); - INIT_GRID_4D(s_tl_yy, t, x, y, z); - INIT_GRID_4D(s_tl_zz, t, x, y, z); - INIT_GRID_4D(s_tl_yz, t, x, y, z); - INIT_GRID_4D(s_tl_xz, t, x, y, z); - INIT_GRID_4D(s_tl_xy, t, x, y, z); - INIT_GRID_4D(s_tr_xx, t, x, y, z); - INIT_GRID_4D(s_tr_yy, t, x, y, z); - INIT_GRID_4D(s_tr_zz, t, x, y, z); - INIT_GRID_4D(s_tr_yz, t, x, y, z); - INIT_GRID_4D(s_tr_xz, t, x, y, z); - INIT_GRID_4D(s_tr_xy, t, x, y, z); - INIT_GRID_3D(rho, x, y, z); - INIT_GRID_3D(c11, x, y, z); - INIT_GRID_3D(c12, x, y, z); - INIT_GRID_3D(c13, x, y, z); - INIT_GRID_3D(c14, x, y, z); - INIT_GRID_3D(c15, x, y, z); - INIT_GRID_3D(c16, x, y, z); - INIT_GRID_3D(c22, x, y, z); - INIT_GRID_3D(c23, x, y, z); - INIT_GRID_3D(c24, x, y, z); - INIT_GRID_3D(c25, x, y, z); - INIT_GRID_3D(c26, x, y, z); - INIT_GRID_3D(c33, x, y, z); - INIT_GRID_3D(c34, x, y, z); - INIT_GRID_3D(c35, x, y, z); - INIT_GRID_3D(c36, x, y, z); - INIT_GRID_3D(c44, x, y, z); - INIT_GRID_3D(c45, x, y, z); - INIT_GRID_3D(c46, x, y, z); - INIT_GRID_3D(c55, x, y, z); - INIT_GRID_3D(c56, x, y, z); - INIT_GRID_3D(c66, x, y, z); - - // StencilContex specific code - REGISTER_STENCIL_CONTEXT_EXTENSION( - void initData() - { - initDiff(); - } - ); - } + void initGrids () + { + // Specify the dimensions of each grid. + // (This names the dimensions; it does not specify their sizes.) + INIT_GRID_4D(v_bl_u, t, x, y, z); + INIT_GRID_4D(v_bl_v, t, x, y, z); + INIT_GRID_4D(v_bl_w, t, x, y, z); + INIT_GRID_4D(v_br_u, t, x, y, z); + INIT_GRID_4D(v_br_v, t, x, y, z); + INIT_GRID_4D(v_br_w, t, x, y, z); + INIT_GRID_4D(v_tl_u, t, x, y, z); + INIT_GRID_4D(v_tl_v, t, x, y, z); + INIT_GRID_4D(v_tl_w, t, x, y, z); + INIT_GRID_4D(v_tr_u, t, x, y, z); + INIT_GRID_4D(v_tr_v, t, x, y, z); + INIT_GRID_4D(v_tr_w, t, x, y, z); + INIT_GRID_4D(s_bl_xx, t, x, y, z); + INIT_GRID_4D(s_bl_yy, t, x, y, z); + INIT_GRID_4D(s_bl_zz, t, x, y, z); + INIT_GRID_4D(s_bl_yz, t, x, y, z); + INIT_GRID_4D(s_bl_xz, t, x, y, z); + INIT_GRID_4D(s_bl_xy, t, x, y, z); + INIT_GRID_4D(s_br_xx, t, x, y, z); + INIT_GRID_4D(s_br_yy, t, x, y, z); + INIT_GRID_4D(s_br_zz, t, x, y, z); + INIT_GRID_4D(s_br_yz, t, x, y, z); + INIT_GRID_4D(s_br_xz, t, x, y, z); + INIT_GRID_4D(s_br_xy, t, x, y, z); + INIT_GRID_4D(s_tl_xx, t, x, y, z); + INIT_GRID_4D(s_tl_yy, t, x, y, z); + INIT_GRID_4D(s_tl_zz, t, x, y, z); + INIT_GRID_4D(s_tl_yz, t, x, y, z); + INIT_GRID_4D(s_tl_xz, t, x, y, z); + INIT_GRID_4D(s_tl_xy, t, x, y, z); + INIT_GRID_4D(s_tr_xx, t, x, y, z); + INIT_GRID_4D(s_tr_yy, t, x, y, z); + INIT_GRID_4D(s_tr_zz, t, x, y, z); + INIT_GRID_4D(s_tr_yz, t, x, y, z); + INIT_GRID_4D(s_tr_xz, t, x, y, z); + INIT_GRID_4D(s_tr_xy, t, x, y, z); + INIT_GRID_3D(rho, x, y, z); + INIT_GRID_3D(c11, x, y, z); + INIT_GRID_3D(c12, x, y, z); + INIT_GRID_3D(c13, x, y, z); + INIT_GRID_3D(c14, x, y, z); + INIT_GRID_3D(c15, x, y, z); + INIT_GRID_3D(c16, x, y, z); + INIT_GRID_3D(c22, x, y, z); + INIT_GRID_3D(c23, x, y, z); + INIT_GRID_3D(c24, x, y, z); + INIT_GRID_3D(c25, x, y, z); + INIT_GRID_3D(c26, x, y, z); + INIT_GRID_3D(c33, x, y, z); + INIT_GRID_3D(c34, x, y, z); + INIT_GRID_3D(c35, x, y, z); + INIT_GRID_3D(c36, x, y, z); + INIT_GRID_3D(c44, x, y, z); + INIT_GRID_3D(c45, x, y, z); + INIT_GRID_3D(c46, x, y, z); + INIT_GRID_3D(c55, x, y, z); + INIT_GRID_3D(c56, x, y, z); + INIT_GRID_3D(c66, x, y, z); + + // StencilContex specific code + REGISTER_STENCIL_CONTEXT_EXTENSION + ( + virtual void initData() { + initDiff(); + } ); + } - GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const BR ) - { - return 1.0f / (0.25f*(c(x , y , z ) + - c(x , y+1, z ) + - c(x , y , z+1) + - c(x , y+1, z+1))); - } - GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const BL ) - { - return 1.0f / (0.25f*(c(x , y , z ) + - c(x+1, y , z ) + - c(x , y , z+1) + - c(x+1, y , z+1))); - } - GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const TR ) - { - return 1.0f / (0.25f*(c(x , y , z ) + - c(x , y+1, z ) + - c(x+1, y , z ) + - c(x+1, y+1, z ))); - } - GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const TL ) - { - return 1.0f / c(x , y , z ); - } - template - GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c ) - { - return cell_coeff( x, y, z, c, N() ); - } + GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const BR ) + { + return 1.0 / (0.25*(c(x , y , z ) + + c(x , y+1, z ) + + c(x , y , z+1) + + c(x , y+1, z+1))); + } + GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const BL ) + { + return 1.0 / (0.25*(c(x , y , z ) + + c(x+1, y , z ) + + c(x , y , z+1) + + c(x+1, y , z+1))); + } + GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const TR ) + { + return 1.0 / (0.25*(c(x , y , z ) + + c(x , y+1, z ) + + c(x+1, y , z ) + + c(x+1, y+1, z ))); + } + GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const TL ) + { + return 1.0 / c(x , y , z ); + } + template + GridValue cell_coeff( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c ) + { + return cell_coeff( x, y, z, c, N() ); + } - GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const BR ) - { - return 0.25f *( 1.0f / c(x , y , z ) + - 1.0f / c(x , y+1, z ) + - 1.0f / c(x , y , z+1) + - 1.0f / c(x , y+1, z+1) ); - } - GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const BL ) - { - return 0.25f *( 1.0f / c(x , y , z ) + - 1.0f / c(x+1, y , z ) + - 1.0f / c(x , y , z+1) + - 1.0f / c(x+1, y , z+1) ); - } - GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const TR ) - { - return 0.25f *( 1.0f / c(x , y , z ) + - 1.0f / c(x , y+1, z ) + - 1.0f / c(x+1, y , z ) + - 1.0f / c(x+1, y+1, z ) ); - } - GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const TL ) - { - return 1.0f / c(x , y , z ); - } - template - GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c ) - { - return cell_coeff_artm( x, y, z, c, N() ); - } - - GridValue stress_update( GridValue c1, GridValue c2, GridValue c3, - GridValue c4, GridValue c5, GridValue c6, - GridValue u_z, GridValue u_y, GridValue u_x, - GridValue v_z, GridValue v_y, GridValue v_x, - GridValue w_z, GridValue w_y, GridValue w_x ) - { - return delta_t * c1 * u_x - + delta_t * c2 * v_y - + delta_t * c3 * w_z - + delta_t * c4 * (w_y + v_z) - + delta_t * c5 * (w_x + u_z) - + delta_t * c6 * (v_x + u_y); - } - - // - // Stress-grid define functions. For each D in xx, yy, zz, xy, xz, yz, - // define stress_D at t+1 based on stress_D at t and vel grids at t+1. - // This implies that the velocity-grid define functions must be called - // before these for a given value of t. Note that the t, x, y, z - // parameters are integer grid indices, not actual offsets in time or - // space, so half-steps due to staggered grids are adjusted - // appropriately. - - template - void define_str(GridIndex t, GridIndex x, GridIndex y, GridIndex z, - Grid &sxx, Grid &syy, Grid &szz, Grid &sxy, Grid &sxz, Grid &syz, - Grid &vxu, Grid &vxv, Grid &vxw, Grid &vyu, Grid &vyv, Grid &vyw, Grid &vzu, Grid &vzv, Grid &vzw ) { - - // Interpolate coeffs. - GridValue ic11 = cell_coeff (x, y, z, c11); - GridValue ic12 = cell_coeff (x, y, z, c12); - GridValue ic13 = cell_coeff (x, y, z, c13); - GridValue ic14 = cell_coeff_artm(x, y, z, c14); - GridValue ic15 = cell_coeff_artm(x, y, z, c15); - GridValue ic16 = cell_coeff_artm(x, y, z, c16); - GridValue ic22 = cell_coeff (x, y, z, c22); - GridValue ic23 = cell_coeff (x, y, z, c23); - GridValue ic24 = cell_coeff_artm(x, y, z, c24); - GridValue ic25 = cell_coeff_artm(x, y, z, c25); - GridValue ic26 = cell_coeff_artm(x, y, z, c26); - GridValue ic33 = cell_coeff (x, y, z, c33); - GridValue ic34 = cell_coeff_artm(x, y, z, c34); - GridValue ic35 = cell_coeff_artm(x, y, z, c35); - GridValue ic36 = cell_coeff_artm(x, y, z, c36); - GridValue ic44 = cell_coeff (x, y, z, c44); - GridValue ic45 = cell_coeff_artm(x, y, z, c45); - GridValue ic46 = cell_coeff_artm(x, y, z, c46); - GridValue ic55 = cell_coeff (x, y, z, c55); - GridValue ic56 = cell_coeff_artm(x, y, z, c56); - GridValue ic66 = cell_coeff (x, y, z, c66); - - // Compute stencils. Note that we are using the velocity values at t+1. - GridValue u_z = stencil_O8( t+1, x, y, z, vzu ); - GridValue v_z = stencil_O8( t+1, x, y, z, vzv ); - GridValue w_z = stencil_O8( t+1, x, y, z, vzw ); - - GridValue u_x = stencil_O8( t+1, x, y, z, vxu ); - GridValue v_x = stencil_O8( t+1, x, y, z, vxv ); - GridValue w_x = stencil_O8( t+1, x, y, z, vxw ); - - GridValue u_y = stencil_O8( t+1, x, y, z, vyu ); - GridValue v_y = stencil_O8( t+1, x, y, z, vyv ); - GridValue w_y = stencil_O8( t+1, x, y, z, vyw ); - - // Compute next stress value - GridValue next_sxx = sxx(t, x, y, z) + - stress_update(ic11,ic12,ic13,ic14,ic15,ic16,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); - GridValue next_syy = syy(t, x, y, z) + - stress_update(ic12,ic22,ic23,ic24,ic25,ic26,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); - GridValue next_szz = szz(t, x, y, z) + - stress_update(ic13,ic23,ic33,ic34,ic35,ic36,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); - GridValue next_syz = syz(t, x, y, z) + - stress_update(ic14,ic24,ic34,ic44,ic45,ic46,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); - GridValue next_sxz = sxz(t, x, y, z) + - stress_update(ic15,ic25,ic35,ic45,ic55,ic56,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); - GridValue next_sxy = sxy(t, x, y, z) + - stress_update(ic16,ic26,ic36,ic46,ic56,ic66,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); - - // define the value at t+1. - if ( hasBoundaryCondition ) { - Condition not_at_bc = bc->is_not_at_boundary(t,x,y,z); - sxx(t+1, x, y, z) EQUALS next_sxx IF not_at_bc; - syy(t+1, x, y, z) EQUALS next_syy IF not_at_bc; - szz(t+1, x, y, z) EQUALS next_szz IF not_at_bc; - syz(t+1, x, y, z) EQUALS next_syz IF not_at_bc; - sxz(t+1, x, y, z) EQUALS next_sxz IF not_at_bc; - sxy(t+1, x, y, z) EQUALS next_sxy IF not_at_bc; - } else { - sxx(t+1, x, y, z) EQUALS next_sxx; - syy(t+1, x, y, z) EQUALS next_syy; - szz(t+1, x, y, z) EQUALS next_szz; - syz(t+1, x, y, z) EQUALS next_syz; - sxz(t+1, x, y, z) EQUALS next_sxz; - sxy(t+1, x, y, z) EQUALS next_sxy; + GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const BR ) + { + return 0.25 *( 1.0 / c(x , y , z ) + + 1.0 / c(x , y+1, z ) + + 1.0 / c(x , y , z+1) + + 1.0 / c(x , y+1, z+1) ); + } + GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const BL ) + { + return 0.25 *( 1.0 / c(x , y , z ) + + 1.0 / c(x+1, y , z ) + + 1.0 / c(x , y , z+1) + + 1.0 / c(x+1, y , z+1) ); + } + GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const TR ) + { + return 0.25 *( 1.0 / c(x , y , z ) + + 1.0 / c(x , y+1, z ) + + 1.0 / c(x+1, y , z ) + + 1.0 / c(x+1, y+1, z ) ); + } + GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c, const TL ) + { + return 1.0 / c(x , y , z ); + } + template + GridValue cell_coeff_artm( const GridIndex x, const GridIndex y, const GridIndex z, Grid &c ) + { + return cell_coeff_artm( x, y, z, c, N() ); } - } + GridValue stress_update( GridValue c1, GridValue c2, GridValue c3, + GridValue c4, GridValue c5, GridValue c6, + GridValue u_z, GridValue u_y, GridValue u_x, + GridValue v_z, GridValue v_y, GridValue v_x, + GridValue w_z, GridValue w_y, GridValue w_x ) + { + return delta_t * c1 * u_x + + delta_t * c2 * v_y + + delta_t * c3 * w_z + + delta_t * c4 * (w_y + v_z) + + delta_t * c5 * (w_x + u_z) + + delta_t * c6 * (v_x + u_y); + } - // Call all the define_* functions. - virtual void define(const IntTuple& offsets) { - GET_OFFSET(t); - GET_OFFSET(x); - GET_OFFSET(y); - GET_OFFSET(z); + // + // Stress-grid define functions. For each D in xx, yy, zz, xy, xz, yz, + // define stress_D at t+1 based on stress_D at t and vel grids at t+1. + // This implies that the velocity-grid define functions must be called + // before these for a given value of t. Note that the t, x, y, z + // parameters are integer grid indices, not actual offsets in time or + // space, so half-steps due to staggered grids are adjusted + // appropriately. + + template + void define_str(GridIndex t, GridIndex x, GridIndex y, GridIndex z, + Grid &sxx, Grid &syy, Grid &szz, Grid &sxy, Grid &sxz, Grid &syz, + Grid &vxu, Grid &vxv, Grid &vxw, Grid &vyu, Grid &vyv, Grid &vyw, Grid &vzu, Grid &vzv, Grid &vzw ) { + + // Interpolate coeffs. + GridValue ic11 = cell_coeff (x, y, z, c11); + GridValue ic12 = cell_coeff (x, y, z, c12); + GridValue ic13 = cell_coeff (x, y, z, c13); + GridValue ic14 = cell_coeff_artm(x, y, z, c14); + GridValue ic15 = cell_coeff_artm(x, y, z, c15); + GridValue ic16 = cell_coeff_artm(x, y, z, c16); + GridValue ic22 = cell_coeff (x, y, z, c22); + GridValue ic23 = cell_coeff (x, y, z, c23); + GridValue ic24 = cell_coeff_artm(x, y, z, c24); + GridValue ic25 = cell_coeff_artm(x, y, z, c25); + GridValue ic26 = cell_coeff_artm(x, y, z, c26); + GridValue ic33 = cell_coeff (x, y, z, c33); + GridValue ic34 = cell_coeff_artm(x, y, z, c34); + GridValue ic35 = cell_coeff_artm(x, y, z, c35); + GridValue ic36 = cell_coeff_artm(x, y, z, c36); + GridValue ic44 = cell_coeff (x, y, z, c44); + GridValue ic45 = cell_coeff_artm(x, y, z, c45); + GridValue ic46 = cell_coeff_artm(x, y, z, c46); + GridValue ic55 = cell_coeff (x, y, z, c55); + GridValue ic56 = cell_coeff_artm(x, y, z, c56); + GridValue ic66 = cell_coeff (x, y, z, c66); + + // Compute stencils. Note that we are using the velocity values at t+1. + GridValue u_z = stencil_O8( t+1, x, y, z, vzu ); + GridValue v_z = stencil_O8( t+1, x, y, z, vzv ); + GridValue w_z = stencil_O8( t+1, x, y, z, vzw ); + + GridValue u_x = stencil_O8( t+1, x, y, z, vxu ); + GridValue v_x = stencil_O8( t+1, x, y, z, vxv ); + GridValue w_x = stencil_O8( t+1, x, y, z, vxw ); + + GridValue u_y = stencil_O8( t+1, x, y, z, vyu ); + GridValue v_y = stencil_O8( t+1, x, y, z, vyv ); + GridValue w_y = stencil_O8( t+1, x, y, z, vyw ); + + // Compute next stress value + GridValue next_sxx = sxx(t, x, y, z) + + stress_update(ic11,ic12,ic13,ic14,ic15,ic16,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); + GridValue next_syy = syy(t, x, y, z) + + stress_update(ic12,ic22,ic23,ic24,ic25,ic26,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); + GridValue next_szz = szz(t, x, y, z) + + stress_update(ic13,ic23,ic33,ic34,ic35,ic36,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); + GridValue next_syz = syz(t, x, y, z) + + stress_update(ic14,ic24,ic34,ic44,ic45,ic46,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); + GridValue next_sxz = sxz(t, x, y, z) + + stress_update(ic15,ic25,ic35,ic45,ic55,ic56,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); + GridValue next_sxy = sxy(t, x, y, z) + + stress_update(ic16,ic26,ic36,ic46,ic56,ic66,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y); + + // define the value at t+1. + if ( hasBoundaryCondition ) { + Condition not_at_bc = bc->is_not_at_boundary(t,x,y,z); + sxx(t+1, x, y, z) EQUALS next_sxx IF not_at_bc; + syy(t+1, x, y, z) EQUALS next_syy IF not_at_bc; + szz(t+1, x, y, z) EQUALS next_szz IF not_at_bc; + syz(t+1, x, y, z) EQUALS next_syz IF not_at_bc; + sxz(t+1, x, y, z) EQUALS next_sxz IF not_at_bc; + sxy(t+1, x, y, z) EQUALS next_sxy IF not_at_bc; + } else { + sxx(t+1, x, y, z) EQUALS next_sxx; + syy(t+1, x, y, z) EQUALS next_syy; + szz(t+1, x, y, z) EQUALS next_szz; + syz(t+1, x, y, z) EQUALS next_syz; + sxz(t+1, x, y, z) EQUALS next_sxz; + sxy(t+1, x, y, z) EQUALS next_sxy; + } + } + + + // Call all the define_* functions. + virtual void define(const IntTuple& offsets) { + GET_OFFSET(t); + GET_OFFSET(x); + GET_OFFSET(y); + GET_OFFSET(z); - FSGBoundaryCondition &fsg_bc = *static_cast(bc); - - // Define velocity components. - define_vel(t, x, y, z, v_tl_w, s_tl_yz, s_tr_xz, s_bl_zz); - define_vel(t, x, y, z, v_tr_w, s_tr_yz, s_tl_xz, s_br_zz); - define_vel(t, x, y, z, v_bl_w, s_bl_yz, s_br_xz, s_tl_zz); - define_vel(t, x, y, z, v_br_w, s_br_yz, s_bl_xz, s_tr_zz); - define_vel(t, x, y, z, v_tl_u, s_tl_xy, s_tr_xx, s_bl_xz); - define_vel(t, x, y, z, v_tr_u, s_tr_xy, s_tl_xx, s_br_xz); - define_vel(t, x, y, z, v_bl_u, s_bl_xy, s_br_xx, s_tl_xz); - define_vel(t, x, y, z, v_br_u, s_br_xy, s_bl_xx, s_tr_xz); - define_vel(t, x, y, z, v_tl_v, s_tl_yy, s_tr_xy, s_bl_yz); - define_vel(t, x, y, z, v_tr_v, s_tr_yy, s_tl_xy, s_br_yz); - define_vel(t, x, y, z, v_bl_v, s_bl_yy, s_br_xy, s_tl_yz); - define_vel(t, x, y, z, v_br_v, s_br_yy, s_bl_xy, s_tr_yz); + FSGBoundaryCondition &fsg_bc = *static_cast(bc); + + // Define velocity components. + define_vel(t, x, y, z, v_tl_w, s_tl_yz, s_tr_xz, s_bl_zz); + define_vel(t, x, y, z, v_tr_w, s_tr_yz, s_tl_xz, s_br_zz); + define_vel(t, x, y, z, v_bl_w, s_bl_yz, s_br_xz, s_tl_zz); + define_vel(t, x, y, z, v_br_w, s_br_yz, s_bl_xz, s_tr_zz); + define_vel(t, x, y, z, v_tl_u, s_tl_xy, s_tr_xx, s_bl_xz); + define_vel(t, x, y, z, v_tr_u, s_tr_xy, s_tl_xx, s_br_xz); + define_vel(t, x, y, z, v_bl_u, s_bl_xy, s_br_xx, s_tl_xz); + define_vel(t, x, y, z, v_br_u, s_br_xy, s_bl_xx, s_tr_xz); + define_vel(t, x, y, z, v_tl_v, s_tl_yy, s_tr_xy, s_bl_yz); + define_vel(t, x, y, z, v_tr_v, s_tr_yy, s_tl_xy, s_br_yz); + define_vel(t, x, y, z, v_bl_v, s_bl_yy, s_br_xy, s_tl_yz); + define_vel(t, x, y, z, v_br_v, s_br_yy, s_bl_xy, s_tr_yz); - if ( hasBoundaryCondition ) - fsg_bc.velocity(t,x,y,z); - - //// Define stresses components. - define_str(t, x, y, z, s_br_xx, s_br_yy, s_br_zz, s_br_xy, s_br_xz, s_br_yz, - v_br_u, v_br_v, v_br_w, v_bl_u, v_bl_v, v_bl_w, v_tr_u, v_tr_v, v_tr_w); - define_str(t, x, y, z, s_bl_xx, s_bl_yy, s_bl_zz, s_bl_xy, s_bl_xz, s_bl_yz, - v_bl_u, v_bl_v, v_bl_w, v_br_u, v_br_v, v_br_w, v_tl_u, v_tl_v, v_tl_w); - define_str(t, x, y, z, s_tr_xx, s_tr_yy, s_tr_zz, s_tr_xy, s_tr_xz, s_tr_yz, - v_tr_u, v_tr_v, v_tr_w, v_tl_u, v_tl_v, v_tl_w, v_br_u, v_br_v, v_br_w); - define_str(t, x, y, z, s_tl_xx, s_tl_yy, s_tl_zz, s_tl_xy, s_tl_xz, s_tl_yz, - v_tl_u, v_tl_v, v_tl_w, v_tr_u, v_tr_v, v_tr_w, v_bl_u, v_bl_v, v_bl_w); + if ( hasBoundaryCondition ) + fsg_bc.velocity(t,x,y,z); + + //// Define stresses components. + define_str(t, x, y, z, s_br_xx, s_br_yy, s_br_zz, s_br_xy, s_br_xz, s_br_yz, + v_br_u, v_br_v, v_br_w, v_bl_u, v_bl_v, v_bl_w, v_tr_u, v_tr_v, v_tr_w); + define_str(t, x, y, z, s_bl_xx, s_bl_yy, s_bl_zz, s_bl_xy, s_bl_xz, s_bl_yz, + v_bl_u, v_bl_v, v_bl_w, v_br_u, v_br_v, v_br_w, v_tl_u, v_tl_v, v_tl_w); + define_str(t, x, y, z, s_tr_xx, s_tr_yy, s_tr_zz, s_tr_xy, s_tr_xz, s_tr_yz, + v_tr_u, v_tr_v, v_tr_w, v_tl_u, v_tl_v, v_tl_w, v_br_u, v_br_v, v_br_w); + define_str(t, x, y, z, s_tl_xx, s_tl_yy, s_tl_zz, s_tl_xy, s_tl_xz, s_tl_yz, + v_tl_u, v_tl_v, v_tl_w, v_tr_u, v_tr_v, v_tr_w, v_bl_u, v_bl_v, v_bl_w); - if ( hasBoundaryCondition ) - fsg_bc.stress(t,x,y,z); - } -}; - -class FSG_ABC : public FSGBoundaryCondition -{ - const int abc_width = 20; + if ( hasBoundaryCondition ) + fsg_bc.stress(t,x,y,z); + } + }; + + class FSG_ABC : public FSGBoundaryCondition + { + const int abc_width = 20; - // Sponge coefficients. - Grid sponge_lx; - Grid sponge_rx; - Grid sponge_bz; - Grid sponge_tz; - Grid sponge_fy; - Grid sponge_by; - Grid sponge_sq_lx; - Grid sponge_sq_rx; - Grid sponge_sq_bz; - Grid sponge_sq_tz; - Grid sponge_sq_fy; - Grid sponge_sq_by; + // Sponge coefficients. + Grid sponge_lx; + Grid sponge_rx; + Grid sponge_bz; + Grid sponge_tz; + Grid sponge_fy; + Grid sponge_by; + Grid sponge_sq_lx; + Grid sponge_sq_rx; + Grid sponge_sq_bz; + Grid sponge_sq_tz; + Grid sponge_sq_fy; + Grid sponge_sq_by; - FSGElasticStencilBase &fsg; + FSGElasticStencilBase &fsg; + + public: + + FSG_ABC (FSGElasticStencilBase &_fsg) : fsg(_fsg) + { + // fsg.INIT_GRID_3D is a hack on how the macro works. It can break at anytime + fsg.INIT_GRID_3D(sponge_lx, x, y, z); + fsg.INIT_GRID_3D(sponge_rx, x, y, z); + fsg.INIT_GRID_3D(sponge_bz, x, y, z); + fsg.INIT_GRID_3D(sponge_tz, x, y, z); + fsg.INIT_GRID_3D(sponge_fy, x, y, z); + fsg.INIT_GRID_3D(sponge_by, x, y, z); + fsg.INIT_GRID_3D(sponge_sq_lx, x, y, z); + fsg.INIT_GRID_3D(sponge_sq_rx, x, y, z); + fsg.INIT_GRID_3D(sponge_sq_bz, x, y, z); + fsg.INIT_GRID_3D(sponge_sq_tz, x, y, z); + fsg.INIT_GRID_3D(sponge_sq_fy, x, y, z); + fsg.INIT_GRID_3D(sponge_sq_by, x, y, z); + } -public: + Condition is_at_boundary( GridIndex t, GridIndex x, GridIndex y, GridIndex z ) + { + Condition bc = ( z < first_index(z)+abc_width || z > last_index(z)-abc_width ) || + ( y < first_index(y)+abc_width || y > last_index(y)-abc_width ) || + ( x < first_index(x)+abc_width || x > last_index(x)-abc_width ); + return bc; + } + Condition is_not_at_boundary( GridIndex t, GridIndex x, GridIndex y, GridIndex z ) + { + return !is_at_boundary(t,x,y,z); + } - FSG_ABC (FSGElasticStencilBase &_fsg) : fsg(_fsg) - { - // fsg.INIT_GRID_3D is a hack on how the macro works. It can break at anytime - fsg.INIT_GRID_3D(sponge_lx, x, y, z); - fsg.INIT_GRID_3D(sponge_rx, x, y, z); - fsg.INIT_GRID_3D(sponge_bz, x, y, z); - fsg.INIT_GRID_3D(sponge_tz, x, y, z); - fsg.INIT_GRID_3D(sponge_fy, x, y, z); - fsg.INIT_GRID_3D(sponge_by, x, y, z); - fsg.INIT_GRID_3D(sponge_sq_lx, x, y, z); - fsg.INIT_GRID_3D(sponge_sq_rx, x, y, z); - fsg.INIT_GRID_3D(sponge_sq_bz, x, y, z); - fsg.INIT_GRID_3D(sponge_sq_tz, x, y, z); - fsg.INIT_GRID_3D(sponge_sq_fy, x, y, z); - fsg.INIT_GRID_3D(sponge_sq_by, x, y, z); - } - - Condition is_at_boundary( GridIndex t, GridIndex x, GridIndex y, GridIndex z ) - { - Condition bc = ( z < first_index(z)+abc_width || z > last_index(z)-abc_width ) || - ( y < first_index(y)+abc_width || y > last_index(y)-abc_width ) || - ( x < first_index(x)+abc_width || x > last_index(x)-abc_width ); - return bc; - } - Condition is_not_at_boundary( GridIndex t, GridIndex x, GridIndex y, GridIndex z ) - { - return !is_at_boundary(t,x,y,z); - } - - template - void define_vel_abc(GridIndex t, GridIndex x, GridIndex y, GridIndex z, - Grid &v, Grid &sx, Grid &sy, Grid &sz, - Grid &abc_x, Grid &abc_y, Grid &abc_z, Grid &abc_sq_x, Grid &abc_sq_y, Grid &abc_sq_z) { - - Condition at_abc = is_at_boundary(t,x,y,z); - - GridValue next_v = v(t, x, y, z) * abc_x(x,y,z) * abc_y(x,y,z) * abc_z(x,y,z); - - GridValue lrho = fsg.interp_rho( x, y, z ); - - GridValue stx = fsg.stencil_O2_X( t, x, y, z, sx ); - GridValue sty = fsg.stencil_O2_Y( t, x, y, z, sy ); - GridValue stz = fsg.stencil_O2_Z( t, x, y, z, sz ); - - next_v += ((stx + sty + stz) * fsg.delta_t * lrho); - next_v *= abc_sq_x(x,y,z) * abc_sq_y(x,y,z) * abc_sq_z(x,y,z); - - // define the value at t+1. - v(t+1, x, y, z) EQUALS next_v IF at_abc; - } + template + void define_vel_abc(GridIndex t, GridIndex x, GridIndex y, GridIndex z, + Grid &v, Grid &sx, Grid &sy, Grid &sz, + Grid &abc_x, Grid &abc_y, Grid &abc_z, Grid &abc_sq_x, Grid &abc_sq_y, Grid &abc_sq_z) { + + Condition at_abc = is_at_boundary(t,x,y,z); + + GridValue next_v = v(t, x, y, z) * abc_x(x,y,z) * abc_y(x,y,z) * abc_z(x,y,z); + + GridValue lrho = fsg.interp_rho( x, y, z ); + + GridValue stx = fsg.stencil_O2_X( t, x, y, z, sx ); + GridValue sty = fsg.stencil_O2_Y( t, x, y, z, sy ); + GridValue stz = fsg.stencil_O2_Z( t, x, y, z, sz ); + + next_v += ((stx + sty + stz) * fsg.delta_t * lrho); + next_v *= abc_sq_x(x,y,z) * abc_sq_y(x,y,z) * abc_sq_z(x,y,z); + + // define the value at t+1. + v(t+1, x, y, z) EQUALS next_v IF at_abc; + } - void velocity (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) - { - define_vel_abc(t, x, y, z, fsg.v_tl_w, fsg.s_tl_yz, fsg.s_tr_xz, fsg.s_bl_zz, sponge_lx, sponge_by, sponge_tz, sponge_sq_lx, sponge_sq_by, sponge_sq_tz); - define_vel_abc(t, x, y, z, fsg.v_tr_w, fsg.s_tr_yz, fsg.s_tl_xz, fsg.s_br_zz, sponge_rx, sponge_fy, sponge_tz, sponge_sq_rx, sponge_sq_fy, sponge_sq_tz); - define_vel_abc(t, x, y, z, fsg.v_bl_w, fsg.s_bl_yz, fsg.s_br_xz, fsg.s_tl_zz, sponge_lx, sponge_fy, sponge_bz, sponge_sq_lx, sponge_sq_fy, sponge_sq_bz); - define_vel_abc(t, x, y, z, fsg.v_br_w, fsg.s_br_yz, fsg.s_bl_xz, fsg.s_tr_zz, sponge_rx, sponge_by, sponge_bz, sponge_sq_rx, sponge_sq_by, sponge_sq_bz); - define_vel_abc(t, x, y, z, fsg.v_tl_u, fsg.s_tl_xy, fsg.s_tr_xx, fsg.s_bl_xz, sponge_lx, sponge_by, sponge_tz, sponge_sq_lx, sponge_sq_by, sponge_sq_tz); - define_vel_abc(t, x, y, z, fsg.v_tr_u, fsg.s_tr_xy, fsg.s_tl_xx, fsg.s_br_xz, sponge_rx, sponge_fy, sponge_tz, sponge_sq_rx, sponge_sq_fy, sponge_sq_tz); - define_vel_abc(t, x, y, z, fsg.v_bl_u, fsg.s_bl_xy, fsg.s_br_xx, fsg.s_tl_xz, sponge_lx, sponge_fy, sponge_bz, sponge_sq_lx, sponge_sq_fy, sponge_sq_bz); - define_vel_abc(t, x, y, z, fsg.v_br_u, fsg.s_br_xy, fsg.s_bl_xx, fsg.s_tr_xz, sponge_rx, sponge_by, sponge_bz, sponge_sq_rx, sponge_sq_by, sponge_sq_bz); - define_vel_abc(t, x, y, z, fsg.v_tl_v, fsg.s_tl_yy, fsg.s_tr_xy, fsg.s_bl_yz, sponge_lx, sponge_by, sponge_tz, sponge_sq_lx, sponge_sq_by, sponge_sq_tz); - define_vel_abc(t, x, y, z, fsg.v_tr_v, fsg.s_tr_yy, fsg.s_tl_xy, fsg.s_br_yz, sponge_rx, sponge_fy, sponge_tz, sponge_sq_rx, sponge_sq_fy, sponge_sq_tz); - define_vel_abc(t, x, y, z, fsg.v_bl_v, fsg.s_bl_yy, fsg.s_br_xy, fsg.s_tl_yz, sponge_lx, sponge_fy, sponge_bz, sponge_sq_lx, sponge_sq_fy, sponge_sq_bz); - define_vel_abc(t, x, y, z, fsg.v_br_v, fsg.s_br_yy, fsg.s_bl_xy, fsg.s_tr_yz, sponge_rx, sponge_by, sponge_bz, sponge_sq_rx, sponge_sq_by, sponge_sq_bz); - } - - template - void define_str_abc(GridIndex t, GridIndex x, GridIndex y, GridIndex z, - Grid &sxx, Grid &syy, Grid &szz, Grid &sxy, Grid &sxz, Grid &syz, - Grid &vxu, Grid &vxv, Grid &vxw, Grid &vyu, Grid &vyv, Grid &vyw, Grid &vzu, Grid &vzv, Grid &vzw, - Grid &abc_x, Grid &abc_y, Grid &abc_z, Grid &abc_sq_x, Grid &abc_sq_y, Grid &abc_sq_z) { - - GridValue abc = abc_x(x,y,z) * abc_y(x,y,z) * abc_z(x,y,z); - GridValue next_sxx = sxx(t, x, y, z) * abc; - GridValue next_syy = syy(t, x, y, z) * abc; - GridValue next_szz = szz(t, x, y, z) * abc; - GridValue next_syz = syz(t, x, y, z) * abc; - GridValue next_sxz = sxz(t, x, y, z) * abc; - GridValue next_sxy = sxy(t, x, y, z) * abc; - - // Interpolate coeffs. - GridValue ic11 = fsg.cell_coeff (x, y, z, fsg.c11); - GridValue ic12 = fsg.cell_coeff (x, y, z, fsg.c12); - GridValue ic13 = fsg.cell_coeff (x, y, z, fsg.c13); - GridValue ic14 = fsg.cell_coeff_artm(x, y, z, fsg.c14); - GridValue ic15 = fsg.cell_coeff_artm(x, y, z, fsg.c15); - GridValue ic16 = fsg.cell_coeff_artm(x, y, z, fsg.c16); - GridValue ic22 = fsg.cell_coeff (x, y, z, fsg.c22); - GridValue ic23 = fsg.cell_coeff (x, y, z, fsg.c23); - GridValue ic24 = fsg.cell_coeff_artm(x, y, z, fsg.c24); - GridValue ic25 = fsg.cell_coeff_artm(x, y, z, fsg.c25); - GridValue ic26 = fsg.cell_coeff_artm(x, y, z, fsg.c26); - GridValue ic33 = fsg.cell_coeff (x, y, z, fsg.c33); - GridValue ic34 = fsg.cell_coeff_artm(x, y, z, fsg.c34); - GridValue ic35 = fsg.cell_coeff_artm(x, y, z, fsg.c35); - GridValue ic36 = fsg.cell_coeff_artm(x, y, z, fsg.c36); - GridValue ic44 = fsg.cell_coeff (x, y, z, fsg.c44); - GridValue ic45 = fsg.cell_coeff_artm(x, y, z, fsg.c45); - GridValue ic46 = fsg.cell_coeff_artm(x, y, z, fsg.c46); - GridValue ic55 = fsg.cell_coeff (x, y, z, fsg.c55); - GridValue ic56 = fsg.cell_coeff_artm(x, y, z, fsg.c56); - GridValue ic66 = fsg.cell_coeff (x, y, z, fsg.c66); - - // Compute stencils. Note that we are using the velocity values at t+1. - GridValue u_z = fsg.stencil_O2_Z( t+1, x, y, z, vzu ); - GridValue v_z = fsg.stencil_O2_Z( t+1, x, y, z, vzv ); - GridValue w_z = fsg.stencil_O2_Z( t+1, x, y, z, vzw ); - - GridValue u_x = fsg.stencil_O2_X( t+1, x, y, z, vxu ); - GridValue v_x = fsg.stencil_O2_X( t+1, x, y, z, vxv ); - GridValue w_x = fsg.stencil_O2_X( t+1, x, y, z, vxw ); - - GridValue u_y = fsg.stencil_O2_Y( t+1, x, y, z, vyu ); - GridValue v_y = fsg.stencil_O2_Y( t+1, x, y, z, vyv ); - GridValue w_y = fsg.stencil_O2_Y( t+1, x, y, z, vyw ); - - // Compute next stress value - GridValue abc_sq = abc_sq_x(x,y,z) * abc_sq_y(x,y,z) * abc_sq_z(x,y,z); - next_sxx += fsg.stress_update(ic11,ic12,ic13,ic14,ic15,ic16,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; - next_syy += fsg.stress_update(ic12,ic22,ic23,ic24,ic25,ic26,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; - next_szz += fsg.stress_update(ic13,ic23,ic33,ic34,ic35,ic36,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; - next_syz += fsg.stress_update(ic14,ic24,ic34,ic44,ic45,ic46,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; - next_sxz += fsg.stress_update(ic15,ic25,ic35,ic45,ic55,ic56,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; - next_sxy += fsg.stress_update(ic16,ic26,ic36,ic46,ic56,ic66,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; - - // define the value at t+1. - Condition at_abc = is_at_boundary(t,x,y,z); - sxx(t+1, x, y, z) EQUALS next_sxx IF at_abc; - syy(t+1, x, y, z) EQUALS next_syy IF at_abc; - szz(t+1, x, y, z) EQUALS next_szz IF at_abc; - syz(t+1, x, y, z) EQUALS next_syz IF at_abc; - sxz(t+1, x, y, z) EQUALS next_sxz IF at_abc; - sxy(t+1, x, y, z) EQUALS next_sxy IF at_abc; - } + void velocity (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) + { + define_vel_abc(t, x, y, z, fsg.v_tl_w, fsg.s_tl_yz, fsg.s_tr_xz, fsg.s_bl_zz, + sponge_lx, sponge_by, sponge_tz, sponge_sq_lx, sponge_sq_by, sponge_sq_tz); + define_vel_abc(t, x, y, z, fsg.v_tr_w, fsg.s_tr_yz, fsg.s_tl_xz, fsg.s_br_zz, + sponge_rx, sponge_fy, sponge_tz, sponge_sq_rx, sponge_sq_fy, sponge_sq_tz); + define_vel_abc(t, x, y, z, fsg.v_bl_w, fsg.s_bl_yz, fsg.s_br_xz, fsg.s_tl_zz, + sponge_lx, sponge_fy, sponge_bz, sponge_sq_lx, sponge_sq_fy, sponge_sq_bz); + define_vel_abc(t, x, y, z, fsg.v_br_w, fsg.s_br_yz, fsg.s_bl_xz, fsg.s_tr_zz, + sponge_rx, sponge_by, sponge_bz, sponge_sq_rx, sponge_sq_by, sponge_sq_bz); + define_vel_abc(t, x, y, z, fsg.v_tl_u, fsg.s_tl_xy, fsg.s_tr_xx, fsg.s_bl_xz, + sponge_lx, sponge_by, sponge_tz, sponge_sq_lx, sponge_sq_by, sponge_sq_tz); + define_vel_abc(t, x, y, z, fsg.v_tr_u, fsg.s_tr_xy, fsg.s_tl_xx, fsg.s_br_xz, + sponge_rx, sponge_fy, sponge_tz, sponge_sq_rx, sponge_sq_fy, sponge_sq_tz); + define_vel_abc(t, x, y, z, fsg.v_bl_u, fsg.s_bl_xy, fsg.s_br_xx, fsg.s_tl_xz, + sponge_lx, sponge_fy, sponge_bz, sponge_sq_lx, sponge_sq_fy, sponge_sq_bz); + define_vel_abc(t, x, y, z, fsg.v_br_u, fsg.s_br_xy, fsg.s_bl_xx, fsg.s_tr_xz, + sponge_rx, sponge_by, sponge_bz, sponge_sq_rx, sponge_sq_by, sponge_sq_bz); + define_vel_abc(t, x, y, z, fsg.v_tl_v, fsg.s_tl_yy, fsg.s_tr_xy, fsg.s_bl_yz, + sponge_lx, sponge_by, sponge_tz, sponge_sq_lx, sponge_sq_by, sponge_sq_tz); + define_vel_abc(t, x, y, z, fsg.v_tr_v, fsg.s_tr_yy, fsg.s_tl_xy, fsg.s_br_yz, + sponge_rx, sponge_fy, sponge_tz, sponge_sq_rx, sponge_sq_fy, sponge_sq_tz); + define_vel_abc(t, x, y, z, fsg.v_bl_v, fsg.s_bl_yy, fsg.s_br_xy, fsg.s_tl_yz, + sponge_lx, sponge_fy, sponge_bz, sponge_sq_lx, sponge_sq_fy, sponge_sq_bz); + define_vel_abc(t, x, y, z, fsg.v_br_v, fsg.s_br_yy, fsg.s_bl_xy, fsg.s_tr_yz, + sponge_rx, sponge_by, sponge_bz, sponge_sq_rx, sponge_sq_by, sponge_sq_bz); + } + + template + void define_str_abc(GridIndex t, GridIndex x, GridIndex y, GridIndex z, + Grid &sxx, Grid &syy, Grid &szz, Grid &sxy, Grid &sxz, Grid &syz, + Grid &vxu, Grid &vxv, Grid &vxw, Grid &vyu, Grid &vyv, Grid &vyw, Grid &vzu, Grid &vzv, Grid &vzw, + Grid &abc_x, Grid &abc_y, Grid &abc_z, Grid &abc_sq_x, Grid &abc_sq_y, Grid &abc_sq_z) { + + GridValue abc = abc_x(x,y,z) * abc_y(x,y,z) * abc_z(x,y,z); + GridValue next_sxx = sxx(t, x, y, z) * abc; + GridValue next_syy = syy(t, x, y, z) * abc; + GridValue next_szz = szz(t, x, y, z) * abc; + GridValue next_syz = syz(t, x, y, z) * abc; + GridValue next_sxz = sxz(t, x, y, z) * abc; + GridValue next_sxy = sxy(t, x, y, z) * abc; + + // Interpolate coeffs. + GridValue ic11 = fsg.cell_coeff (x, y, z, fsg.c11); + GridValue ic12 = fsg.cell_coeff (x, y, z, fsg.c12); + GridValue ic13 = fsg.cell_coeff (x, y, z, fsg.c13); + GridValue ic14 = fsg.cell_coeff_artm(x, y, z, fsg.c14); + GridValue ic15 = fsg.cell_coeff_artm(x, y, z, fsg.c15); + GridValue ic16 = fsg.cell_coeff_artm(x, y, z, fsg.c16); + GridValue ic22 = fsg.cell_coeff (x, y, z, fsg.c22); + GridValue ic23 = fsg.cell_coeff (x, y, z, fsg.c23); + GridValue ic24 = fsg.cell_coeff_artm(x, y, z, fsg.c24); + GridValue ic25 = fsg.cell_coeff_artm(x, y, z, fsg.c25); + GridValue ic26 = fsg.cell_coeff_artm(x, y, z, fsg.c26); + GridValue ic33 = fsg.cell_coeff (x, y, z, fsg.c33); + GridValue ic34 = fsg.cell_coeff_artm(x, y, z, fsg.c34); + GridValue ic35 = fsg.cell_coeff_artm(x, y, z, fsg.c35); + GridValue ic36 = fsg.cell_coeff_artm(x, y, z, fsg.c36); + GridValue ic44 = fsg.cell_coeff (x, y, z, fsg.c44); + GridValue ic45 = fsg.cell_coeff_artm(x, y, z, fsg.c45); + GridValue ic46 = fsg.cell_coeff_artm(x, y, z, fsg.c46); + GridValue ic55 = fsg.cell_coeff (x, y, z, fsg.c55); + GridValue ic56 = fsg.cell_coeff_artm(x, y, z, fsg.c56); + GridValue ic66 = fsg.cell_coeff (x, y, z, fsg.c66); + + // Compute stencils. Note that we are using the velocity values at t+1. + GridValue u_z = fsg.stencil_O2_Z( t+1, x, y, z, vzu ); + GridValue v_z = fsg.stencil_O2_Z( t+1, x, y, z, vzv ); + GridValue w_z = fsg.stencil_O2_Z( t+1, x, y, z, vzw ); + + GridValue u_x = fsg.stencil_O2_X( t+1, x, y, z, vxu ); + GridValue v_x = fsg.stencil_O2_X( t+1, x, y, z, vxv ); + GridValue w_x = fsg.stencil_O2_X( t+1, x, y, z, vxw ); + + GridValue u_y = fsg.stencil_O2_Y( t+1, x, y, z, vyu ); + GridValue v_y = fsg.stencil_O2_Y( t+1, x, y, z, vyv ); + GridValue w_y = fsg.stencil_O2_Y( t+1, x, y, z, vyw ); + + // Compute next stress value + GridValue abc_sq = abc_sq_x(x,y,z) * abc_sq_y(x,y,z) * abc_sq_z(x,y,z); + next_sxx += fsg.stress_update(ic11,ic12,ic13,ic14,ic15,ic16,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; + next_syy += fsg.stress_update(ic12,ic22,ic23,ic24,ic25,ic26,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; + next_szz += fsg.stress_update(ic13,ic23,ic33,ic34,ic35,ic36,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; + next_syz += fsg.stress_update(ic14,ic24,ic34,ic44,ic45,ic46,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; + next_sxz += fsg.stress_update(ic15,ic25,ic35,ic45,ic55,ic56,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; + next_sxy += fsg.stress_update(ic16,ic26,ic36,ic46,ic56,ic66,u_z,u_x,u_y,v_z,v_x,v_y,w_z,w_x,w_y) * abc_sq; + + // define the value at t+1. + Condition at_abc = is_at_boundary(t,x,y,z); + sxx(t+1, x, y, z) EQUALS next_sxx IF at_abc; + syy(t+1, x, y, z) EQUALS next_syy IF at_abc; + szz(t+1, x, y, z) EQUALS next_szz IF at_abc; + syz(t+1, x, y, z) EQUALS next_syz IF at_abc; + sxz(t+1, x, y, z) EQUALS next_sxz IF at_abc; + sxy(t+1, x, y, z) EQUALS next_sxy IF at_abc; + } - void stress (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) - { - define_str_abc(t, x, y, z, fsg.s_br_xx, fsg.s_br_yy, fsg.s_br_zz, fsg.s_br_xy, fsg.s_br_xz, - fsg.s_br_yz, fsg.v_br_u, fsg.v_br_v, fsg.v_br_w, fsg.v_bl_u, - fsg.v_bl_v, fsg.v_bl_w, fsg.v_tr_u, fsg.v_tr_v, fsg.v_tr_w, - sponge_rx, sponge_by, sponge_bz, sponge_sq_rx, sponge_sq_by, sponge_sq_bz); - define_str_abc(t, x, y, z, fsg.s_bl_xx, fsg.s_bl_yy, fsg.s_bl_zz, fsg.s_bl_xy, fsg.s_bl_xz, - fsg.s_bl_yz, fsg.v_bl_u, fsg.v_bl_v, fsg.v_bl_w, fsg.v_br_u, - fsg.v_br_v, fsg.v_br_w, fsg.v_tl_u, fsg.v_tl_v, fsg.v_tl_w, - sponge_lx, sponge_fy, sponge_bz, sponge_sq_lx, sponge_sq_fy, sponge_sq_bz); - define_str_abc(t, x, y, z, fsg.s_tr_xx, fsg.s_tr_yy, fsg.s_tr_zz, fsg.s_tr_xy, fsg.s_tr_xz, - fsg.s_tr_yz, fsg.v_tr_u, fsg.v_tr_v, fsg.v_tr_w, fsg.v_tl_u, - fsg.v_tl_v, fsg.v_tl_w, fsg.v_br_u, fsg.v_br_v, fsg.v_br_w, - sponge_rx, sponge_fy, sponge_tz, sponge_sq_rx, sponge_sq_fy, sponge_sq_tz); - define_str_abc(t, x, y, z, fsg.s_tl_xx, fsg.s_tl_yy, fsg.s_tl_zz, fsg.s_tl_xy, fsg.s_tl_xz, - fsg.s_tl_yz, fsg.v_tl_u, fsg.v_tl_v, fsg.v_tl_w, fsg.v_tr_u, - fsg.v_tr_v, fsg.v_tr_w, fsg.v_bl_u, fsg.v_bl_v, fsg.v_bl_w, - sponge_lx, sponge_by, sponge_tz, sponge_sq_lx, sponge_sq_by, sponge_sq_tz); - } + void stress (GridIndex t, GridIndex x, GridIndex y, GridIndex z ) + { + define_str_abc(t, x, y, z, fsg.s_br_xx, fsg.s_br_yy, fsg.s_br_zz, fsg.s_br_xy, fsg.s_br_xz, + fsg.s_br_yz, fsg.v_br_u, fsg.v_br_v, fsg.v_br_w, fsg.v_bl_u, + fsg.v_bl_v, fsg.v_bl_w, fsg.v_tr_u, fsg.v_tr_v, fsg.v_tr_w, + sponge_rx, sponge_by, sponge_bz, sponge_sq_rx, sponge_sq_by, sponge_sq_bz); + define_str_abc(t, x, y, z, fsg.s_bl_xx, fsg.s_bl_yy, fsg.s_bl_zz, fsg.s_bl_xy, fsg.s_bl_xz, + fsg.s_bl_yz, fsg.v_bl_u, fsg.v_bl_v, fsg.v_bl_w, fsg.v_br_u, + fsg.v_br_v, fsg.v_br_w, fsg.v_tl_u, fsg.v_tl_v, fsg.v_tl_w, + sponge_lx, sponge_fy, sponge_bz, sponge_sq_lx, sponge_sq_fy, sponge_sq_bz); + define_str_abc(t, x, y, z, fsg.s_tr_xx, fsg.s_tr_yy, fsg.s_tr_zz, fsg.s_tr_xy, fsg.s_tr_xz, + fsg.s_tr_yz, fsg.v_tr_u, fsg.v_tr_v, fsg.v_tr_w, fsg.v_tl_u, + fsg.v_tl_v, fsg.v_tl_w, fsg.v_br_u, fsg.v_br_v, fsg.v_br_w, + sponge_rx, sponge_fy, sponge_tz, sponge_sq_rx, sponge_sq_fy, sponge_sq_tz); + define_str_abc(t, x, y, z, fsg.s_tl_xx, fsg.s_tl_yy, fsg.s_tl_zz, fsg.s_tl_xy, fsg.s_tl_xz, + fsg.s_tl_yz, fsg.v_tl_u, fsg.v_tl_v, fsg.v_tl_w, fsg.v_tr_u, + fsg.v_tr_v, fsg.v_tr_w, fsg.v_bl_u, fsg.v_bl_v, fsg.v_bl_w, + sponge_lx, sponge_by, sponge_tz, sponge_sq_lx, sponge_sq_by, sponge_sq_tz); + } -}; + }; -struct FSGElasticStencil : public FSGElasticStencilBase { - FSGElasticStencil(StencilList& stencils) : FSGElasticStencilBase("fsg", stencils) {} -}; + struct FSGElasticStencil : public FSGElasticStencilBase { + FSGElasticStencil(StencilList& stencils) : + FSGElasticStencilBase("fsg", stencils) { } + }; -struct FSGABCElasticStencil : public FSGElasticStencilBase { - FSG_ABC abc; // Absorbing Boundary Condition + struct FSGABCElasticStencil : public FSGElasticStencilBase { + FSG_ABC abc; // Absorbing Boundary Condition - FSGABCElasticStencil(StencilList& stencils) : abc(*this), FSGElasticStencilBase("fsg_abc", &abc, stencils) {} -}; + FSGABCElasticStencil(StencilList& stencils) : + FSGElasticStencilBase("fsg_abc", &abc, stencils), + abc(*this) { } + }; -REGISTER_STENCIL(FSGElasticStencil); -REGISTER_STENCIL(FSGABCElasticStencil); + REGISTER_STENCIL(FSGElasticStencil); + REGISTER_STENCIL(FSGABCElasticStencil); } diff --git a/src/foldBuilder/stencils/SSGElasticStencil.hpp b/src/foldBuilder/stencils/SSGElasticStencil.hpp index 8aa907ee..0a583d89 100644 --- a/src/foldBuilder/stencils/SSGElasticStencil.hpp +++ b/src/foldBuilder/stencils/SSGElasticStencil.hpp @@ -76,36 +76,34 @@ class SSGElasticStencil : public ElasticStencilBase { // StencilContex specific code REGISTER_STENCIL_CONTEXT_EXTENSION( - void init ( ) - { - initDiff(); - } + virtual void initData() { + initDiff(); + } ); } - GridValue interp_mu( GridIndex x, GridIndex y, GridIndex z, const BR ) { - return ( 2.0f/ (mu(x , y , z ) + - mu(x , y+1, z ) + - mu(x , y , z+1) + - mu(x , y+1, z+1)) ); + return ( 2.0/ (mu(x , y , z ) + + mu(x , y+1, z ) + + mu(x , y , z+1) + + mu(x , y+1, z+1)) ); } GridValue interp_mu( GridIndex x, GridIndex y, GridIndex z, const BL ) { - return ( 2.0f/ (mu(x , y , z ) + - mu(x+1, y , z ) + - mu(x , y , z+1) + - mu(x+1, y , z+1)) ); + return ( 2.0/ (mu(x , y , z ) + + mu(x+1, y , z ) + + mu(x , y , z+1) + + mu(x+1, y , z+1)) ); } GridValue interp_mu( GridIndex x, GridIndex y, GridIndex z, const TR ) { - return ( 2.0f/ (mu(x , y , z ) + - mu(x+1, y , z ) + - mu(x , y+1, z ) + - mu(x+1, y+1, z )) ); + return ( 2.0/ (mu(x , y , z ) + + mu(x+1, y , z ) + + mu(x , y+1, z ) + + mu(x+1, y+1, z )) ); } template @@ -143,8 +141,8 @@ class SSGElasticStencil : public ElasticStencilBase { void define_str_TL(GridIndex t, GridIndex x, GridIndex y, GridIndex z ) { - GridValue ilambdamu2 = 1.f / lambdamu2(x,y,z); - GridValue ilambda = 1.f / lambda (x,y,z); + GridValue ilambdamu2 = 1.0 / lambdamu2(x,y,z); + GridValue ilambda = 1.0 / lambda (x,y,z); GridValue vtx = stencil_O8( t, x, y, z, v_tr_u ); GridValue vty = stencil_O8( t, x, y, z, v_tl_v ); From 2686cabdb3cfdac61feb91b70cdba36db360d965 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Wed, 5 Apr 2017 11:40:32 -0700 Subject: [PATCH 16/20] Fix typos in examples. --- src/foldBuilder/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/foldBuilder/main.cpp b/src/foldBuilder/main.cpp index 590908f1..29d688c7 100644 --- a/src/foldBuilder/main.cpp +++ b/src/foldBuilder/main.cpp @@ -169,9 +169,9 @@ void usage(const string& cmd) { //" -pp Print POV-Ray code.\n" "\n" "Examples:\n" - " " << cmd << " -st 3axis -or 2 -fold x=4,y=4 -ph - # '-' for stdout\n" - " " << cmd << " -st awp -fold y=4,y=2 -p256 stencil_code.hpp\n" - " " << cmd << " -st iso3dfd -or 8 -fold x=4,y=4 -cluster y=2 -p512 stencil_code.hpp\n"; + " " << cmd << " -st 3axis -r 2 -fold x=4,y=4 -ph - # '-' for stdout\n" + " " << cmd << " -st awp -fold x=4,y=2 -p256 stencil_code.hpp\n" + " " << cmd << " -st iso3dfd -r 8 -fold x=4,y=4 -cluster y=2 -p512 stencil_code.hpp\n"; exit(1); } From 50b27a00d6edd72081894cf709af6b3ee1302c5e Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Tue, 11 Apr 2017 09:43:20 -0700 Subject: [PATCH 17/20] Add more info to csv filename. --- stencil-tuner.pl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/stencil-tuner.pl b/stencil-tuner.pl index b3f475e9..cd4d2308 100755 --- a/stencil-tuner.pl +++ b/stencil-tuner.pl @@ -49,14 +49,15 @@ my $oneT = 1e12; # command-line options. +my $stencil; # type of stencil. +my $arch; # target architecture. +my $sweep = 0; # if true, sweep instead of search. my $testing = 0; # if true, don't run real trials. my $checking = 0; # if true, don't run at all. my $mic; # set to 0, 1, etc. for KNC mic. my $host; # set to run on a different host. my $sde = 0; # run under sde (for testing). my $sim = 0; # run under any simulator/emulator. -my $sweep = 0; # sweep instead of search. -my $stencil; # type of stencil. my $radius; # stencil radius. my $zLoop = 0; # Force inner loop in 'z' direction. my $zLayout = 0; # Force inner memory layout in 'z' direction. @@ -69,7 +70,6 @@ my $runArgs = ''; # extra run arguments. my $maxGB = 16; # max mem usage. my $minGB = 0; # min mem usage. -my $arch; # target architecture. my $nranks = 1; # num ranks. my $debugCheck = 0; # print each initial check result. my $doBuild = 1; # do compiles. @@ -298,10 +298,6 @@ sub usage { # disable folding for DP MIC (no valignq). $folding = 0 if (defined $mic && $dp); -# date. -my $date=`date +%Y-%m-%d_%H-%M`; -chomp $date; - # clean up restrictions. for my $key (keys %geneRanges) { @@ -313,8 +309,11 @@ sub usage { } # csv filename. -my $searchTypeStr = $sweep ? 'sweep' : 'search'; -my $outFile = "stencil-$searchTypeStr.$date.csv"; +my $searchTypeStr = $sweep ? '-sweep' : ''; +my $hostStr = defined $host ? $host : hostname(); +my $timeStamp=`date +%Y-%m-%d_%H-%M-%S`; +chomp $timeStamp; +my $outFile = "stencil-tuner$searchTypeStr.$stencil.$arch.$hostStr.$timeStamp.csv"; print "Output will be saved in '$outFile'.\n"; $outFile = '/dev/null' if $checking; From fc31e9ac3583b4919768dd51b0dd4489078acea7 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Tue, 11 Apr 2017 16:10:18 -0700 Subject: [PATCH 18/20] Enable arbitrary default options via DEF_ARGS. Makes it easier to set high-perf defaults for certain stencil/arch combos. Was able to delete several specific default macros, like DEF_BLOCK_SIZE. --- Makefile | 94 +++++++++++++++++++++++++++++--------------- src/stencil_calc.hpp | 18 +++------ src/stencil_main.cpp | 6 +++ src/utils.cpp | 41 ++++++++++++++++++- src/utils.hpp | 3 ++ 5 files changed, 117 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index c8e09361..3a35a898 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,6 @@ # arch: see list below. # # mpi: 0, 1: whether to use MPI. -# Currently, MPI is only used in X dimension. # # radius: sets size of certain stencils. # @@ -58,8 +57,8 @@ # # def_block_threads: Number of threads to use in nested OpenMP block loop by default. # def_thread_divisor: Divide number of OpenMP threads by this factor by default. -# -# def_*_size, def_pad: Default sizes used in executable. +# def_*_args: Default cmd-line args for specific settings. +# more_def_args: Additional default cmd-line args. # Initial defaults. stencil = unspecified @@ -111,13 +110,22 @@ radius ?= 2 cluster ?= x=2 else ifneq ($(findstring awp,$(stencil)),) -eqs ?= velocity=vel,stress=str -time_alloc ?= 1 -def_block_size ?= 32 ifeq ($(arch),knl) def_thread_divisor ?= 2 def_block_threads ?= 4 +def_block_args ?= -b 32 -bx 196 +else ifeq ($(arch),hsw) +REGION_LOOP_OUTER_VARS = rw,ry,rx,rz +SUB_BLOCK_LOOP_INNER_MODS = prefetch(L1,L2) +omp_block_schedule = dynamic,1 +fold = x=4,y=2,z=1 +cluster = x=2 +def_block_args ?= -bx 8 -by 28 -bz 70 +more_def_args += -sbx 8 -sby 18 -sbz 40 endif +time_alloc ?= 1 +def_block_args ?= -b 32 +eqs ?= velocity=vel,stress=str FB_FLAGS += -min-es 1 else ifeq ($(stencil)),ssg) @@ -128,10 +136,9 @@ eqs ?= v_br=v_br,v_bl=v_bl,v_tr=v_tr,v_tl=v_tl,s_br=s_br,s_bl=s time_alloc ?= 1 ifeq ($(arch),knl) omp_schedule ?= guided -def_block_size ?= 16 +def_block_args ?= -b 16 def_thread_divisor ?= 4 def_block_threads ?= 1 -def_pad ?= 2 SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L2) endif @@ -152,7 +159,7 @@ ISA ?= -xMIC-AVX512 GCXX_ISA ?= -march=knl MACROS += USE_INTRIN512 USE_RCP28 FB_TARGET ?= 512 -def_block_size ?= 96 +def_block_args ?= -b 96 def_block_threads ?= 8 streaming_stores ?= 0 SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L1) @@ -163,6 +170,7 @@ ISA ?= -xCORE-AVX512 GCXX_ISA ?= -march=knl -mno-avx512er -mno-avx512pf MACROS += USE_INTRIN512 FB_TARGET ?= 512 +mpi = 1 else ifeq ($(arch),hsw) @@ -170,6 +178,7 @@ ISA ?= -xCORE-AVX2 GCXX_ISA ?= -march=haswell MACROS += USE_INTRIN256 FB_TARGET ?= 256 +mpi = 1 else ifeq ($(arch),ivb) @@ -177,6 +186,7 @@ ISA ?= -xCORE-AVX-I GCXX_ISA ?= -march=ivybridge MACROS += USE_INTRIN256 FB_TARGET ?= 256 +mpi = 1 else ifeq ($(arch),snb) @@ -184,6 +194,7 @@ ISA ?= -xAVX GCXX_ISA ?= -march=sandybridge MACROS += USE_INTRIN256 FB_TARGET ?= 256 +mpi = 1 else ifeq ($(arch),intel64) @@ -209,9 +220,9 @@ layout_xyz ?= Layout_123 layout_txyz ?= Layout_2314 layout_wxyz ?= Layout_1234 layout_twxyz ?= Layout_23415 -def_rank_size ?= 128 -def_block_size ?= 64 -def_pad ?= 1 +def_rank_args ?= -d 128 +def_block_args ?= -b 64 +def_pad_args ?= -p 1 cluster ?= x=1 # default folding depends on HW vector size. @@ -273,6 +284,11 @@ ifneq ($(time_alloc),) FB_FLAGS += -step-alloc $(time_alloc) endif +# Default cmd-line args. +DEF_ARGS += -thread_divisor $(def_thread_divisor) +DEF_ARGS += -block_threads $(def_block_threads) +DEF_ARGS += $(def_rank_args) $(def_block_args) $(def_pad_args) +MACROS += DEF_ARGS='"$(DEF_ARGS) $(more_def_args) $(EXTRA_DEF_ARGS)"' # Set more MACROS based on individual makefile vars. # MACROS and EXTRA_MACROS will be written to a header file. @@ -281,11 +297,9 @@ MACROS += LAYOUT_XYZ=$(layout_xyz) MACROS += LAYOUT_TXYZ=$(layout_txyz) MACROS += LAYOUT_WXYZ=$(layout_wxyz) MACROS += LAYOUT_TWXYZ=$(layout_twxyz) -MACROS += DEF_RANK_SIZE=$(def_rank_size) -MACROS += DEF_BLOCK_SIZE=$(def_block_size) -MACROS += DEF_BLOCK_THREADS=$(def_block_threads) -MACROS += DEF_THREAD_DIVISOR=$(def_thread_divisor) -MACROS += DEF_PAD=$(def_pad) +ifeq ($(streaming_stores),1) +MACROS += USE_STREAMING_STORE +endif # arch. ARCH := $(shell echo $(arch) | tr '[:lower:]' '[:upper:]') @@ -337,10 +351,6 @@ CXXFLAGS += $(GCXX_ISA) -Wno-unknown-pragmas -Wno-unused-variable endif # compiler. -ifeq ($(streaming_stores),1) -MACROS += USE_STREAMING_STORE -endif - # gen-loops.pl args: # Rank loops break up the whole rank into smaller regions. In order for @@ -349,8 +359,9 @@ endif # indices. Those that do not (e.g., grouped, serpentine, square-wave) may # *not* be used here when using temporal wavefronts. The time loop may be # found in StencilEquations::calc_rank(). -RANK_LOOP_OPTS = -dims 'dw,dx,dy,dz' -RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop(dw,dx,dy,dz) \ +RANK_LOOP_OPTS ?= -dims 'dw,dx,dy,dz' +RANK_LOOP_OUTER_VARS ?= dw,dx,dy,dz +RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop($(RANK_LOOP_OUTER_VARS)) \ { $(RANK_LOOP_INNER_MODS) calc(region(start_dt, stop_dt, eqGroup_ptr)); } # Region loops break up a region using OpenMP threading into blocks. The @@ -358,11 +369,12 @@ RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop(dw,dx,dy,dz) \ # to a top-level OpenMP thread. The region time loops are not coded here to # allow for proper spatial skewing for temporal wavefronts. The time loop # may be found in StencilEquations::calc_region(). -REGION_LOOP_OPTS = -dims 'rw,rx,ry,rz' \ +REGION_LOOP_OPTS ?= -dims 'rw,rx,ry,rz' \ -ompConstruct '$(omp_par_for) schedule($(omp_schedule)) proc_bind(spread)' \ -calcPrefix 'eg->calc_' +REGION_LOOP_OUTER_VARS ?= rw,rx,ry,rz REGION_LOOP_OUTER_MODS ?= omp grouped -REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) { \ +REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop($(REGION_LOOP_OUTER_VARS)) { \ $(REGION_LOOP_INNER_MODS) calc(block(rt)); } # Block loops break up a block into sub-blocks. The 'omp' modifier creates @@ -371,8 +383,9 @@ REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) { \ # not yet supported. BLOCK_LOOP_OPTS = -dims 'bw,bx,by,bz' \ -ompConstruct '$(omp_par_for) schedule($(omp_block_schedule)) proc_bind(close)' +BLOCK_LOOP_OUTER_VARS ?= bw,bx,by,bz BLOCK_LOOP_OUTER_MODS ?= omp grouped -BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bw,bx,by,bz) { \ +BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop($(BLOCK_LOOP_OUTER_VARS)) { \ $(BLOCK_LOOP_INNER_MODS) calc(sub_block(bt)); } # Sub-block loops break up a sub-block into vector clusters. The indices at @@ -383,10 +396,13 @@ SUB_BLOCK_LOOP_OPTS = -dims 'sbwv,sbxv,sbyv,sbzv' ifeq ($(split_L2),1) SUB_BLOCK_LOOP_OPTS += -splitL2 endif +SUB_BLOCK_LOOP_OUTER_VARS ?= sbwv,sbxv,sbyv SUB_BLOCK_LOOP_OUTER_MODS ?= square_wave serpentine +SUB_BLOCK_LOOP_INNER_VARS ?= sbzv SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L2) -SUB_BLOCK_LOOP_CODE ?= $(SUB_BLOCK_LOOP_OUTER_MODS) loop(sbwv,sbxv,sbyv) { \ - $(SUB_BLOCK_LOOP_INNER_MODS) loop(sbzv) { calc(cluster(begin_sbtv)); } } +SUB_BLOCK_LOOP_CODE ?= $(SUB_BLOCK_LOOP_OUTER_MODS) loop($(SUB_BLOCK_LOOP_OUTER_VARS)) { \ + $(SUB_BLOCK_LOOP_INNER_MODS) loop($(SUB_BLOCK_LOOP_INNER_VARS)) { \ + calc(cluster(begin_sbtv)); } } # Halo pack/unpack loops break up a region face, edge, or corner into vectors. # The indices at this level are by vector instead of element; @@ -395,7 +411,8 @@ SUB_BLOCK_LOOP_CODE ?= $(SUB_BLOCK_LOOP_OUTER_MODS) loop(sbwv,sbxv,sbyv) { \ HALO_LOOP_OPTS = -dims 'wv,xv,yv,zv' \ -ompConstruct '$(omp_par_for) schedule($(omp_halo_schedule)) proc_bind(spread)' HALO_LOOP_OUTER_MODS ?= omp -HALO_LOOP_CODE ?= $(HALO_LOOP_OUTER_MODS) loop(wv,xv,yv,zv) \ +HALO_LOOP_OUTER_VARS ?= wv,xv,yv,zv +HALO_LOOP_CODE ?= $(HALO_LOOP_OUTER_MODS) loop($(HALO_LOOP_OUTER_VARS)) \ $(HALO_LOOP_INNER_MODS) { calc(halo(t)); } # compile with model_cache=1 or 2 to check prefetching. @@ -432,6 +449,13 @@ echo-settings: @echo "Build environment for" $(STENCIL_EXEC_NAME) on `date` @echo arch=$(arch) @echo stencil=$(stencil) + @echo def_thread_divisor=$(def_thread_divisor) + @echo def_block_threads=$(def_block_threads) + @echo def_rank_args=$(def_rank_args) + @echo def_block_args=$(def_block_args) + @echo def_pad_args=$(def_pad_args) + @echo more_def_args=$(more_def_args) + @echo EXTRA_DEF_ARGS=$(EXTRA_DEF_ARGS) @echo fold=$(fold) @echo cluster=$(cluster) @echo radius=$(radius) @@ -441,7 +465,6 @@ echo-settings: @echo layout_wxyz=$(layout_wxyz) @echo layout_twxyz=$(layout_twxyz) @echo streaming_stores=$(streaming_stores) - @echo def_block_threads=$(def_block_threads) @echo omp_schedule=$(omp_schedule) @echo omp_block_schedule=$(omp_block_schedule) @echo omp_halo_schedule=$(omp_halo_schedule) @@ -456,25 +479,32 @@ echo-settings: @echo CXXFLAGS="\"$(CXXFLAGS)\"" @echo RANK_LOOP_OPTS="\"$(RANK_LOOP_OPTS)\"" @echo RANK_LOOP_OUTER_MODS="\"$(RANK_LOOP_OUTER_MODS)\"" + @echo RANK_LOOP_OUTER_VARS="\"$(RANK_LOOP_OUTER_VARS)\"" @echo RANK_LOOP_INNER_MODS="\"$(RANK_LOOP_INNER_MODS)\"" @echo RANK_LOOP_CODE="\"$(RANK_LOOP_CODE)\"" @echo REGION_LOOP_OPTS="\"$(REGION_LOOP_OPTS)\"" @echo REGION_LOOP_OUTER_MODS="\"$(REGION_LOOP_OUTER_MODS)\"" + @echo REGION_LOOP_OUTER_VARS="\"$(REGION_LOOP_OUTER_VARS)\"" @echo REGION_LOOP_INNER_MODS="\"$(REGION_LOOP_INNER_MODS)\"" @echo REGION_LOOP_CODE="\"$(REGION_LOOP_CODE)\"" @echo BLOCK_LOOP_OPTS="\"$(BLOCK_LOOP_OPTS)\"" @echo BLOCK_LOOP_OUTER_MODS="\"$(BLOCK_LOOP_OUTER_MODS)\"" + @echo BLOCK_LOOP_OUTER_VARS="\"$(BLOCK_LOOP_OUTER_VARS)\"" @echo BLOCK_LOOP_INNER_MODS="\"$(BLOCK_LOOP_INNER_MODS)\"" @echo BLOCK_LOOP_CODE="\"$(BLOCK_LOOP_CODE)\"" @echo SUB_BLOCK_LOOP_OPTS="\"$(SUB_BLOCK_LOOP_OPTS)\"" @echo SUB_BLOCK_LOOP_OUTER_MODS="\"$(SUB_BLOCK_LOOP_OUTER_MODS)\"" + @echo SUB_BLOCK_LOOP_OUTER_VARS="\"$(SUB_BLOCK_LOOP_OUTER_VARS)\"" @echo SUB_BLOCK_LOOP_INNER_MODS="\"$(SUB_BLOCK_LOOP_INNER_MODS)\"" + @echo SUB_BLOCK_LOOP_INNER_VARS="\"$(SUB_BLOCK_LOOP_INNER_VARS)\"" @echo SUB_BLOCK_LOOP_CODE="\"$(SUB_BLOCK_LOOP_CODE)\"" @echo HALO_LOOP_OPTS="\"$(HALO_LOOP_OPTS)\"" - @echo HALO_LOOP_OUTER_MODS="\"$(RANK_LOOP_OUTER_MODS)\"" - @echo HALO_LOOP_INNER_MODS="\"$(RANK_LOOP_INNER_MODS)\"" + @echo HALO_LOOP_OUTER_MODS="\"$(HALO_LOOP_OUTER_MODS)\"" + @echo HALO_LOOP_OUTER_VARS="\"$(HALO_LOOP_OUTER_VARS)\"" + @echo HALO_LOOP_INNER_MODS="\"$(HALO_LOOP_INNER_MODS)\"" @echo HALO_LOOP_CODE="\"$(HALO_LOOP_CODE)\"" @echo CXX=$(CXX) + @echo CXXOPT=$(CXXOPT) @$(CXX) -v; $(CXX_VER_CMD) code_stats: diff --git a/src/stencil_calc.hpp b/src/stencil_calc.hpp index 3de0e122..a4b88597 100644 --- a/src/stencil_calc.hpp +++ b/src/stencil_calc.hpp @@ -108,8 +108,8 @@ namespace yask { // Sizes in elements (points). // - time sizes (t) are in steps to be done. // - spatial sizes (w, x, y, z) are in elements (not vectors). - // Sizes are the same for all grids. TODO: relax this restriction. - idx_t dt=1, dw=0, dx=0, dy=0, dz=0; // rank size (without halos). + // Sizes are the same for all grids. + idx_t dt=50, dw=1, dx=100, dy=100, dz=100; // rank size (without halos). idx_t rt=1, rw=0, rx=0, ry=0, rz=0; // region size (used for wave-front tiling). idx_t bgw=0, bgx=0, bgy=0, bgz=0; // block-group size (only used for 'grouped' region loops). idx_t bt=1, bw=0, bx=0, by=0, bz=0; // block size (used for each outer thread). @@ -124,18 +124,12 @@ namespace yask { int msg_rank=0; // rank that prints informational messages. // OpenMP settings. - int max_threads=1; // Initial number of threads to use overall. - int thread_divisor; // Reduce number of threads by this amount. - int num_block_threads; // Number of threads to use for a block. + int max_threads; // Initial number of threads to use overall. + int thread_divisor=1; // Reduce number of threads by this amount. + int num_block_threads=1; // Number of threads to use for a block. // Ctor. - StencilSettings() : - dt(50), dw(1), dx(DEF_RANK_SIZE), dy(DEF_RANK_SIZE), dz(DEF_RANK_SIZE), - bt(1), bw(1), bx(DEF_BLOCK_SIZE), by(DEF_BLOCK_SIZE), bz(DEF_BLOCK_SIZE), - pw(0), px(DEF_PAD), py(DEF_PAD), pz(DEF_PAD), - thread_divisor(DEF_THREAD_DIVISOR), - num_block_threads(DEF_BLOCK_THREADS) - { + StencilSettings() { max_threads = omp_get_max_threads(); } diff --git a/src/stencil_main.cpp b/src/stencil_main.cpp index baf525a1..d8d0a896 100644 --- a/src/stencil_main.cpp +++ b/src/stencil_main.cpp @@ -83,6 +83,10 @@ struct AppSettings : public StencilSettings { } }; +#ifndef DEF_ARGS +#define DEF_ARGS "" +#endif + // Parse options from the command-line and set corresponding vars. // Exit with message on error or request for help. void parse(int argc, char** argv) { @@ -115,6 +119,7 @@ struct AppSettings : public StencilSettings { // Parse cmd-line options. // Any remaining strings will be left in args. vector args; + parser.set_args(DEF_ARGS, args); parser.parse_args(argc, argv, args); if (help) { @@ -152,6 +157,7 @@ struct AppSettings : public StencilSettings { "\nStencil name: " YASK_STENCIL_NAME << endl; // Echo invocation parameters for record-keeping. + os << "Default arguments: " DEF_ARGS << endl; os << "Invocation:"; for (int argi = 0; argi < argc; argi++) os << " " << argv[argi]; diff --git a/src/utils.cpp b/src/utils.cpp index 57bca7db..3c6b55f6 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -365,5 +365,44 @@ namespace yask { // Return unused args in args var. args.swap(non_args); } - + + // Tokenize args from a string. + void CommandLineParser::set_args(std::string arg_string, + std::vector& args) { + string tmp; + bool in_quotes = false; + for (char c : arg_string) { + + // If WS, start a new string unless in quotes. + if (isspace(c)) { + if (in_quotes) + tmp += c; + else { + if (tmp.length()) + args.push_back(tmp); + tmp.clear(); + } + } + + // If quote, start or end quotes. + else if (c == '"') { + if (in_quotes) { + if (tmp.length()) + args.push_back(tmp); + tmp.clear(); + in_quotes = false; + } + else + in_quotes = true; + } + + // Otherwise, just add to tmp. + else + tmp += c; + } + + // Last string. + if (tmp.length()) + args.push_back(tmp); + } } diff --git a/src/utils.hpp b/src/utils.hpp index f2d915e1..8b54b513 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -343,6 +343,9 @@ namespace yask { parse_args(pgmName, args); } + // Tokenize args from a string. + virtual void set_args(std::string arg_string, + std::vector& args); }; } From 52eab0edbe73c8a18d3d51a979be7bbf1acca333 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Wed, 12 Apr 2017 16:03:13 -0700 Subject: [PATCH 19/20] Enable arbitrary default options via DEF_ARGS. Makes it easier to set high-perf defaults for certain stencil/arch combos. Was able to remove several specific default macros, like DEF_BLOCK_SIZE. Modified tuner to set individual gen-loop parts instead of whole input. Added pfd_l1 and pfd_l2 make vars. Set new defaults for iso3dfd and awp. --- Makefile | 358 ++++++++++++++++++++++---------------- src/stencil_calc.hpp | 18 +- src/stencil_main.cpp | 6 + src/utils.cpp | 41 ++++- src/utils.hpp | 3 + stencil-tuner-summary.csh | 2 +- stencil-tuner.pl | 159 ++++++----------- 7 files changed, 315 insertions(+), 272 deletions(-) diff --git a/Makefile b/Makefile index c8e09361..fb3f1864 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,6 @@ # arch: see list below. # # mpi: 0, 1: whether to use MPI. -# Currently, MPI is only used in X dimension. # # radius: sets size of certain stencils. # @@ -52,143 +51,170 @@ # this can provide the ability to fine-tune which grids use # HBW and which use default memory. # -# omp_schedule: OMP schedule policy for region loop. +# pfd_l1: L1 prefetch distance (only if enabled in sub-block loop). +# pfd_l2: L2 prefetch distance (only if enabled in sub-block loop). +# +# omp_region_schedule: OMP schedule policy for region loop. # omp_block_schedule: OMP schedule policy for nested OpenMP block loop. # omp_halo_schedule: OMP schedule policy for OpenMP halo loop. # # def_block_threads: Number of threads to use in nested OpenMP block loop by default. # def_thread_divisor: Divide number of OpenMP threads by this factor by default. -# -# def_*_size, def_pad: Default sizes used in executable. +# def_*_args: Default cmd-line args for specific settings. +# more_def_args: Additional default cmd-line args. # Initial defaults. stencil = unspecified arch = knl mpi = 0 +real_bytes = 4 -# Defaults based on stencil type. +# Defaults based on stencil type (and arch for some stencils). ifeq ($(stencil),) -$(error Stencil not specified; use stencil=iso3dfd, 3axis, 9axis, 3plane, cube, ave, stream, awp, awp_elastic, ssg, fsg, or fsg_abc) + $(error Stencil not specified; use stencil=iso3dfd, 3axis, 9axis, 3plane, cube, ave, stream, awp, awp_elastic, ssg, fsg, or fsg_abc) else ifeq ($(stencil),ave) -radius ?= 1 -real_bytes ?= 8 + radius ?= 1 else ifeq ($(stencil),3axis) -MACROS += MAX_EXCH_DIST=1 -radius ?= 6 + MACROS += MAX_EXCH_DIST=1 + radius ?= 6 else ifeq ($(stencil),9axis) -MACROS += MAX_EXCH_DIST=2 -radius ?= 4 + MACROS += MAX_EXCH_DIST=2 + radius ?= 4 else ifeq ($(stencil),3plane) -MACROS += MAX_EXCH_DIST=2 -radius ?= 3 + MACROS += MAX_EXCH_DIST=2 + radius ?= 3 else ifeq ($(stencil),cube) -MACROS += MAX_EXCH_DIST=3 -radius ?= 2 + MACROS += MAX_EXCH_DIST=3 + radius ?= 2 else ifeq ($(stencil),iso3dfd) -MACROS += MAX_EXCH_DIST=1 -radius ?= 8 -real_bytes ?= 4 -ifeq ($(arch),knl) -ifeq ($(real_bytes),4) -fold ?= x=2,y=8,z=1 -else -fold ?= x=2,y=4,z=1 -endif -cluster ?= x=2 -else -cluster ?= z=2 -endif + MACROS += MAX_EXCH_DIST=1 + radius ?= 8 + ifeq ($(arch),knl) + def_block_args ?= -b 96 -bx 192 + ifeq ($(real_bytes),4) + fold ?= x=2,y=8,z=1 + else + fold ?= x=2,y=4,z=1 + endif + cluster ?= x=2 + else ifeq ($(arch),hsw) + def_thread_divisor = 2 + def_block_threads = 1 + def_block_args ?= -bx 296 -by 5 -bz 290 + SUB_BLOCK_LOOP_INNER_MODS = + cluster ?= z=2 + endif else ifeq ($(stencil),stream) -MACROS += MAX_EXCH_DIST=0 -radius ?= 2 -cluster ?= x=2 + MACROS += MAX_EXCH_DIST=0 + radius ?= 2 + cluster ?= x=2 else ifneq ($(findstring awp,$(stencil)),) -eqs ?= velocity=vel,stress=str -time_alloc ?= 1 -def_block_size ?= 32 -ifeq ($(arch),knl) -def_thread_divisor ?= 2 -def_block_threads ?= 4 -endif -FB_FLAGS += -min-es 1 + time_alloc = 1 + def_block_args = -b 32 + eqs = velocity=vel,stress=str + FB_FLAGS += -min-es 1 + ifeq ($(arch),knl) + def_thread_divisor = 2 + def_block_threads = 4 + def_block_args = -b 48 -bx 112 + else ifeq ($(arch),hsw) + REGION_LOOP_OUTER_VARS = rw,ry,rx,rz + SUB_BLOCK_LOOP_INNER_MODS = prefetch(L1,L2) + omp_block_schedule = dynamic,1 + ifeq ($(real_bytes),4) + fold = x=4,y=2,z=1 + endif + cluster = x=2 + def_block_args = -bx 8 -by 28 -bz 70 + more_def_args += -sbx 8 -sby 18 -sbz 40 + else ifeq ($(arch),skx) + ifeq ($(real_bytes),4) + fold ?= x=2,y=8,z=1 + endif + def_block_args = -b 32 -bx 96 + SUB_BLOCK_LOOP_INNER_MODS = prefetch(L1) + endif else ifeq ($(stencil)),ssg) -eqs ?= v_bl=v_bl,v_tr=v_tr,v_tl=v_tl,s_br=s_br,s_bl=s_bl,s_tr=s_tr,s_tl=s_tl + eqs ?= v_bl=v_bl,v_tr=v_tr,v_tl=v_tl,s_br=s_br,s_bl=s_bl,s_tr=s_tr,s_tl=s_tl else ifeq ($(stencil),fsg) -eqs ?= v_br=v_br,v_bl=v_bl,v_tr=v_tr,v_tl=v_tl,s_br=s_br,s_bl=s_bl,s_tr=s_tr,s_tl=s_tl -time_alloc ?= 1 -ifeq ($(arch),knl) -omp_schedule ?= guided -def_block_size ?= 16 -def_thread_divisor ?= 4 -def_block_threads ?= 1 -def_pad ?= 2 -SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L2) -endif + eqs ?= v_br=v_br,v_bl=v_bl,v_tr=v_tr,v_tl=v_tl,s_br=s_br,s_bl=s_bl,s_tr=s_tr,s_tl=s_tl + time_alloc ?= 1 + ifeq ($(arch),knl) + omp_region_schedule ?= guided + def_block_args ?= -b 16 + def_thread_divisor ?= 4 + def_block_threads ?= 1 + SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L2) + endif endif # stencil-specific. # Defaut settings based on architecture. ifeq ($(arch),knc) -ISA ?= -mmic -MACROS += USE_INTRIN512 -FB_TARGET ?= knc -def_block_threads ?= 4 -SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L1,L2) + ISA ?= -mmic + MACROS += USE_INTRIN512 + FB_TARGET ?= knc + def_block_threads ?= 4 + SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L1,L2) else ifeq ($(arch),knl) -ISA ?= -xMIC-AVX512 -GCXX_ISA ?= -march=knl -MACROS += USE_INTRIN512 USE_RCP28 -FB_TARGET ?= 512 -def_block_size ?= 96 -def_block_threads ?= 8 -streaming_stores ?= 0 -SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L1) + ISA ?= -xMIC-AVX512 + GCXX_ISA ?= -march=knl + MACROS += USE_INTRIN512 USE_RCP28 + FB_TARGET ?= 512 + def_block_args ?= -b 96 + def_block_threads ?= 8 + streaming_stores ?= 0 + SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L1) else ifeq ($(arch),skx) -ISA ?= -xCORE-AVX512 -GCXX_ISA ?= -march=knl -mno-avx512er -mno-avx512pf -MACROS += USE_INTRIN512 -FB_TARGET ?= 512 + ISA ?= -xCORE-AVX512 + GCXX_ISA ?= -march=knl -mno-avx512er -mno-avx512pf + MACROS += USE_INTRIN512 + FB_TARGET ?= 512 + mpi = 1 else ifeq ($(arch),hsw) -ISA ?= -xCORE-AVX2 -GCXX_ISA ?= -march=haswell -MACROS += USE_INTRIN256 -FB_TARGET ?= 256 + ISA ?= -xCORE-AVX2 + GCXX_ISA ?= -march=haswell + MACROS += USE_INTRIN256 + FB_TARGET ?= 256 + mpi = 1 else ifeq ($(arch),ivb) -ISA ?= -xCORE-AVX-I -GCXX_ISA ?= -march=ivybridge -MACROS += USE_INTRIN256 -FB_TARGET ?= 256 + ISA ?= -xCORE-AVX-I + GCXX_ISA ?= -march=ivybridge + MACROS += USE_INTRIN256 + FB_TARGET ?= 256 + mpi = 1 else ifeq ($(arch),snb) -ISA ?= -xAVX -GCXX_ISA ?= -march=sandybridge -MACROS += USE_INTRIN256 -FB_TARGET ?= 256 + ISA ?= -xAVX + GCXX_ISA ?= -march=sandybridge + MACROS += USE_INTRIN256 + FB_TARGET ?= 256 + mpi = 1 else ifeq ($(arch),intel64) -ISA ?= -xHOST -FB_TARGET ?= cpp + ISA ?= -xHOST + FB_TARGET ?= cpp else @@ -199,7 +225,7 @@ endif # arch-specific. # general defaults for vars if not set above. streaming_stores ?= 1 omp_par_for ?= omp parallel for -omp_schedule ?= dynamic,1 +omp_region_schedule ?= dynamic,1 omp_block_schedule ?= static,1 omp_halo_schedule ?= static def_block_threads ?= 2 @@ -209,35 +235,41 @@ layout_xyz ?= Layout_123 layout_txyz ?= Layout_2314 layout_wxyz ?= Layout_1234 layout_twxyz ?= Layout_23415 -def_rank_size ?= 128 -def_block_size ?= 64 -def_pad ?= 1 +def_rank_args ?= -d 128 +def_block_args ?= -b 64 +def_pad_args ?= -p 1 cluster ?= x=1 +pfd_l1 ?= 1 +pfd_l2 ?= 2 # default folding depends on HW vector size. ifneq ($(findstring INTRIN512,$(MACROS)),) # 512 bits. -ifeq ($(real_bytes),4) -fold ?= x=4,y=4,z=1 -else -fold ?= x=4,y=2,z=1 -endif + ifeq ($(real_bytes),4) + # 16 SP floats. + fold ?= x=4,y=4,z=1 + else + # 8 DP floats. + fold ?= x=4,y=2,z=1 + endif else # not 512 bits. -ifeq ($(real_bytes),4) -fold ?= x=8 -else -fold ?= x=4 -endif + ifeq ($(real_bytes),4) + # 8 SP floats. + fold ?= x=8 + else + # 4 DP floats. + fold ?= x=4 + endif endif # not 512 bits. # More build flags. ifeq ($(mpi),1) -CXX := mpiicpc + CXX := mpiicpc else -CXX := icpc + CXX := icpc endif LD := $(CXX) MAKE := make @@ -261,18 +293,23 @@ GEN_HEADERS := $(addprefix src/, \ layout_macros.hpp layouts.hpp \ $(ST_MACRO_FILE) $(ST_CODE_FILE) ) ifneq ($(eqs),) - FB_FLAGS += -eq $(eqs) + FB_FLAGS += -eq $(eqs) endif ifneq ($(radius),) - FB_FLAGS += -r $(radius) + FB_FLAGS += -r $(radius) endif ifneq ($(halo),) - FB_FLAGS += -halo $(halo) + FB_FLAGS += -halo $(halo) endif ifneq ($(time_alloc),) - FB_FLAGS += -step-alloc $(time_alloc) + FB_FLAGS += -step-alloc $(time_alloc) endif +# Default cmd-line args. +DEF_ARGS += -thread_divisor $(def_thread_divisor) +DEF_ARGS += -block_threads $(def_block_threads) +DEF_ARGS += $(def_rank_args) $(def_block_args) $(def_pad_args) +MACROS += DEF_ARGS='"$(DEF_ARGS) $(more_def_args) $(EXTRA_DEF_ARGS)"' # Set more MACROS based on individual makefile vars. # MACROS and EXTRA_MACROS will be written to a header file. @@ -281,11 +318,10 @@ MACROS += LAYOUT_XYZ=$(layout_xyz) MACROS += LAYOUT_TXYZ=$(layout_txyz) MACROS += LAYOUT_WXYZ=$(layout_wxyz) MACROS += LAYOUT_TWXYZ=$(layout_twxyz) -MACROS += DEF_RANK_SIZE=$(def_rank_size) -MACROS += DEF_BLOCK_SIZE=$(def_block_size) -MACROS += DEF_BLOCK_THREADS=$(def_block_threads) -MACROS += DEF_THREAD_DIVISOR=$(def_thread_divisor) -MACROS += DEF_PAD=$(def_pad) +MACROS += PFDL1=$(pfd_l1) PFDL2=$(pfd_l2) +ifeq ($(streaming_stores),1) + MACROS += USE_STREAMING_STORE +endif # arch. ARCH := $(shell echo $(arch) | tr '[:lower:]' '[:upper:]') @@ -293,24 +329,24 @@ MACROS += ARCH_$(ARCH) # MPI settings. ifeq ($(mpi),1) -MACROS += USE_MPI + MACROS += USE_MPI endif # HBW settings. ifeq ($(hbw),1) -MACROS += USE_HBW -HBW_DIR = $(HOME)/memkind_build -CXXFLAGS += -I$(HBW_DIR)/include -LFLAGS += -lnuma $(HBW_DIR)/lib/libmemkind.a + MACROS += USE_HBW + HBW_DIR = $(HOME)/memkind_build + CXXFLAGS += -I$(HBW_DIR)/include + LFLAGS += -lnuma $(HBW_DIR)/lib/libmemkind.a endif # VTUNE settings. ifeq ($(vtune),1) -MACROS += USE_VTUNE + MACROS += USE_VTUNE ifneq ($(VTUNE_AMPLIFIER_XE_2017_DIR),) -VTUNE_DIR = $(VTUNE_AMPLIFIER_XE_2017_DIR) + VTUNE_DIR = $(VTUNE_AMPLIFIER_XE_2017_DIR) else -VTUNE_DIR = $(VTUNE_AMPLIFIER_XE_2016_DIR) + VTUNE_DIR = $(VTUNE_AMPLIFIER_XE_2016_DIR) endif CXXFLAGS += -I$(VTUNE_DIR)/include LFLAGS += $(VTUNE_DIR)/lib64/libittnotify.a @@ -319,28 +355,24 @@ endif # compiler-specific settings ifneq ($(findstring ic,$(notdir $(CXX))),) # Intel compiler -CODE_STATS = code_stats -CXXFLAGS += $(ISA) -debug extended -Fa -restrict -ansi-alias -fno-alias -CXXFLAGS += -fimf-precision=low -fast-transcendentals -no-prec-sqrt -no-prec-div -fp-model fast=2 -fno-protect-parens -rcd -ftz -fma -fimf-domain-exclusion=none -qopt-assume-safe-padding -CXXFLAGS += -qoverride-limits -#CXXFLAGS += -vec-threshold0 -CXXFLAGS += -qopt-report=5 -#CXXFLAGS += -qopt-report-phase=VEC,PAR,OPENMP,IPO,LOOP -CXXFLAGS += -no-diag-message-catalog -CXX_VER_CMD = $(CXX) -V + CODE_STATS = code_stats + CXXFLAGS += $(ISA) -debug extended -Fa -restrict -ansi-alias -fno-alias + CXXFLAGS += -fimf-precision=low -fast-transcendentals -no-prec-sqrt -no-prec-div -fp-model fast=2 -fno-protect-parens -rcd -ftz -fma -fimf-domain-exclusion=none -qopt-assume-safe-padding + CXXFLAGS += -qoverride-limits + #CXXFLAGS += -vec-threshold0 + CXXFLAGS += -qopt-report=5 + #CXXFLAGS += -qopt-report-phase=VEC,PAR,OPENMP,IPO,LOOP + CXXFLAGS += -no-diag-message-catalog + CXX_VER_CMD = $(CXX) -V -# work around an optimization bug. -MACROS += NO_STORE_INTRINSICS + # work around an optimization bug. + MACROS += NO_STORE_INTRINSICS else # not Intel compiler -CXXFLAGS += $(GCXX_ISA) -Wno-unknown-pragmas -Wno-unused-variable + CXXFLAGS += $(GCXX_ISA) -Wno-unknown-pragmas -Wno-unused-variable endif # compiler. -ifeq ($(streaming_stores),1) -MACROS += USE_STREAMING_STORE -endif - # gen-loops.pl args: # Rank loops break up the whole rank into smaller regions. In order for @@ -349,8 +381,9 @@ endif # indices. Those that do not (e.g., grouped, serpentine, square-wave) may # *not* be used here when using temporal wavefronts. The time loop may be # found in StencilEquations::calc_rank(). -RANK_LOOP_OPTS = -dims 'dw,dx,dy,dz' -RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop(dw,dx,dy,dz) \ +RANK_LOOP_OPTS ?= -dims 'dw,dx,dy,dz' +RANK_LOOP_OUTER_VARS ?= dw,dx,dy,dz +RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop($(RANK_LOOP_OUTER_VARS)) \ { $(RANK_LOOP_INNER_MODS) calc(region(start_dt, stop_dt, eqGroup_ptr)); } # Region loops break up a region using OpenMP threading into blocks. The @@ -358,11 +391,12 @@ RANK_LOOP_CODE ?= $(RANK_LOOP_OUTER_MODS) loop(dw,dx,dy,dz) \ # to a top-level OpenMP thread. The region time loops are not coded here to # allow for proper spatial skewing for temporal wavefronts. The time loop # may be found in StencilEquations::calc_region(). -REGION_LOOP_OPTS = -dims 'rw,rx,ry,rz' \ - -ompConstruct '$(omp_par_for) schedule($(omp_schedule)) proc_bind(spread)' \ +REGION_LOOP_OPTS ?= -dims 'rw,rx,ry,rz' \ + -ompConstruct '$(omp_par_for) schedule($(omp_region_schedule)) proc_bind(spread)' \ -calcPrefix 'eg->calc_' -REGION_LOOP_OUTER_MODS ?= omp grouped -REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) { \ +REGION_LOOP_OUTER_VARS ?= rw,rx,ry,rz +REGION_LOOP_OUTER_MODS ?= grouped +REGION_LOOP_CODE ?= omp $(REGION_LOOP_OUTER_MODS) loop($(REGION_LOOP_OUTER_VARS)) { \ $(REGION_LOOP_INNER_MODS) calc(block(rt)); } # Block loops break up a block into sub-blocks. The 'omp' modifier creates @@ -371,8 +405,9 @@ REGION_LOOP_CODE ?= $(REGION_LOOP_OUTER_MODS) loop(rw,rx,ry,rz) { \ # not yet supported. BLOCK_LOOP_OPTS = -dims 'bw,bx,by,bz' \ -ompConstruct '$(omp_par_for) schedule($(omp_block_schedule)) proc_bind(close)' -BLOCK_LOOP_OUTER_MODS ?= omp grouped -BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bw,bx,by,bz) { \ +BLOCK_LOOP_OUTER_VARS ?= bw,bx,by,bz +BLOCK_LOOP_OUTER_MODS ?= grouped +BLOCK_LOOP_CODE ?= omp $(BLOCK_LOOP_OUTER_MODS) loop($(BLOCK_LOOP_OUTER_VARS)) { \ $(BLOCK_LOOP_INNER_MODS) calc(sub_block(bt)); } # Sub-block loops break up a sub-block into vector clusters. The indices at @@ -381,12 +416,15 @@ BLOCK_LOOP_CODE ?= $(BLOCK_LOOP_OUTER_MODS) loop(bw,bx,by,bz) { \ # no time loop here because threaded temporal blocking is not yet supported. SUB_BLOCK_LOOP_OPTS = -dims 'sbwv,sbxv,sbyv,sbzv' ifeq ($(split_L2),1) -SUB_BLOCK_LOOP_OPTS += -splitL2 + SUB_BLOCK_LOOP_OPTS += -splitL2 endif +SUB_BLOCK_LOOP_OUTER_VARS ?= sbwv,sbxv,sbyv SUB_BLOCK_LOOP_OUTER_MODS ?= square_wave serpentine +SUB_BLOCK_LOOP_INNER_VARS ?= sbzv SUB_BLOCK_LOOP_INNER_MODS ?= prefetch(L2) -SUB_BLOCK_LOOP_CODE ?= $(SUB_BLOCK_LOOP_OUTER_MODS) loop(sbwv,sbxv,sbyv) { \ - $(SUB_BLOCK_LOOP_INNER_MODS) loop(sbzv) { calc(cluster(begin_sbtv)); } } +SUB_BLOCK_LOOP_CODE ?= $(SUB_BLOCK_LOOP_OUTER_MODS) loop($(SUB_BLOCK_LOOP_OUTER_VARS)) { \ + $(SUB_BLOCK_LOOP_INNER_MODS) loop($(SUB_BLOCK_LOOP_INNER_VARS)) { \ + calc(cluster(begin_sbtv)); } } # Halo pack/unpack loops break up a region face, edge, or corner into vectors. # The indices at this level are by vector instead of element; @@ -395,16 +433,17 @@ SUB_BLOCK_LOOP_CODE ?= $(SUB_BLOCK_LOOP_OUTER_MODS) loop(sbwv,sbxv,sbyv) { \ HALO_LOOP_OPTS = -dims 'wv,xv,yv,zv' \ -ompConstruct '$(omp_par_for) schedule($(omp_halo_schedule)) proc_bind(spread)' HALO_LOOP_OUTER_MODS ?= omp -HALO_LOOP_CODE ?= $(HALO_LOOP_OUTER_MODS) loop(wv,xv,yv,zv) \ +HALO_LOOP_OUTER_VARS ?= wv,xv,yv,zv +HALO_LOOP_CODE ?= $(HALO_LOOP_OUTER_MODS) loop($(HALO_LOOP_OUTER_VARS)) \ $(HALO_LOOP_INNER_MODS) { calc(halo(t)); } # compile with model_cache=1 or 2 to check prefetching. ifeq ($(model_cache),1) -MACROS += MODEL_CACHE=1 -OMPFLAGS = -qopenmp-stubs + MACROS += MODEL_CACHE=1 + OMPFLAGS = -qopenmp-stubs else ifeq ($(model_cache),2) -MACROS += MODEL_CACHE=2 -OMPFLAGS = -qopenmp-stubs + MACROS += MODEL_CACHE=2 + OMPFLAGS = -qopenmp-stubs endif CXXFLAGS += $(OMPFLAGS) $(EXTRA_CXXFLAGS) @@ -432,6 +471,13 @@ echo-settings: @echo "Build environment for" $(STENCIL_EXEC_NAME) on `date` @echo arch=$(arch) @echo stencil=$(stencil) + @echo def_thread_divisor=$(def_thread_divisor) + @echo def_block_threads=$(def_block_threads) + @echo def_rank_args=$(def_rank_args) + @echo def_block_args=$(def_block_args) + @echo def_pad_args=$(def_pad_args) + @echo more_def_args=$(more_def_args) + @echo EXTRA_DEF_ARGS=$(EXTRA_DEF_ARGS) @echo fold=$(fold) @echo cluster=$(cluster) @echo radius=$(radius) @@ -440,9 +486,10 @@ echo-settings: @echo layout_txyz=$(layout_txyz) @echo layout_wxyz=$(layout_wxyz) @echo layout_twxyz=$(layout_twxyz) + @echo pfd_l1=$(pfd_l1) + @echo pfd_l2=$(pfd_l2) @echo streaming_stores=$(streaming_stores) - @echo def_block_threads=$(def_block_threads) - @echo omp_schedule=$(omp_schedule) + @echo omp_region_schedule=$(omp_region_schedule) @echo omp_block_schedule=$(omp_block_schedule) @echo omp_halo_schedule=$(omp_halo_schedule) @echo FB_TARGET="\"$(FB_TARGET)\"" @@ -456,25 +503,32 @@ echo-settings: @echo CXXFLAGS="\"$(CXXFLAGS)\"" @echo RANK_LOOP_OPTS="\"$(RANK_LOOP_OPTS)\"" @echo RANK_LOOP_OUTER_MODS="\"$(RANK_LOOP_OUTER_MODS)\"" + @echo RANK_LOOP_OUTER_VARS="\"$(RANK_LOOP_OUTER_VARS)\"" @echo RANK_LOOP_INNER_MODS="\"$(RANK_LOOP_INNER_MODS)\"" @echo RANK_LOOP_CODE="\"$(RANK_LOOP_CODE)\"" @echo REGION_LOOP_OPTS="\"$(REGION_LOOP_OPTS)\"" @echo REGION_LOOP_OUTER_MODS="\"$(REGION_LOOP_OUTER_MODS)\"" + @echo REGION_LOOP_OUTER_VARS="\"$(REGION_LOOP_OUTER_VARS)\"" @echo REGION_LOOP_INNER_MODS="\"$(REGION_LOOP_INNER_MODS)\"" @echo REGION_LOOP_CODE="\"$(REGION_LOOP_CODE)\"" @echo BLOCK_LOOP_OPTS="\"$(BLOCK_LOOP_OPTS)\"" @echo BLOCK_LOOP_OUTER_MODS="\"$(BLOCK_LOOP_OUTER_MODS)\"" + @echo BLOCK_LOOP_OUTER_VARS="\"$(BLOCK_LOOP_OUTER_VARS)\"" @echo BLOCK_LOOP_INNER_MODS="\"$(BLOCK_LOOP_INNER_MODS)\"" @echo BLOCK_LOOP_CODE="\"$(BLOCK_LOOP_CODE)\"" @echo SUB_BLOCK_LOOP_OPTS="\"$(SUB_BLOCK_LOOP_OPTS)\"" @echo SUB_BLOCK_LOOP_OUTER_MODS="\"$(SUB_BLOCK_LOOP_OUTER_MODS)\"" + @echo SUB_BLOCK_LOOP_OUTER_VARS="\"$(SUB_BLOCK_LOOP_OUTER_VARS)\"" @echo SUB_BLOCK_LOOP_INNER_MODS="\"$(SUB_BLOCK_LOOP_INNER_MODS)\"" + @echo SUB_BLOCK_LOOP_INNER_VARS="\"$(SUB_BLOCK_LOOP_INNER_VARS)\"" @echo SUB_BLOCK_LOOP_CODE="\"$(SUB_BLOCK_LOOP_CODE)\"" @echo HALO_LOOP_OPTS="\"$(HALO_LOOP_OPTS)\"" - @echo HALO_LOOP_OUTER_MODS="\"$(RANK_LOOP_OUTER_MODS)\"" - @echo HALO_LOOP_INNER_MODS="\"$(RANK_LOOP_INNER_MODS)\"" + @echo HALO_LOOP_OUTER_MODS="\"$(HALO_LOOP_OUTER_MODS)\"" + @echo HALO_LOOP_OUTER_VARS="\"$(HALO_LOOP_OUTER_VARS)\"" + @echo HALO_LOOP_INNER_MODS="\"$(HALO_LOOP_INNER_MODS)\"" @echo HALO_LOOP_CODE="\"$(HALO_LOOP_CODE)\"" @echo CXX=$(CXX) + @echo CXXOPT=$(CXXOPT) @$(CXX) -v; $(CXX_VER_CMD) code_stats: @@ -557,7 +611,7 @@ help: @echo "make clean; make arch=knl stencil=iso3dfd" @echo "make clean; make arch=knl stencil=awp mpi=1" @echo "make clean; make arch=skx stencil=ave fold='x=1,y=2,z=4' cluster='x=2'" - @echo "make clean; make arch=knc stencil=3axis radius=4 SUB_BLOCK_LOOP_INNER_MODS='prefetch(L1,L2)' EXTRA_MACROS='PFDL1=2 PFDL2=4'" + @echo "make clean; make arch=knc stencil=3axis radius=4 SUB_BLOCK_LOOP_INNER_MODS='prefetch(L1,L2)' pfd_l2=3" @echo " " @echo "Example debug usage:" @echo "make arch=knl stencil=iso3dfd OMPFLAGS='-qopenmp-stubs' CXXOPT='-O0' EXTRA_MACROS='DEBUG'" diff --git a/src/stencil_calc.hpp b/src/stencil_calc.hpp index 3de0e122..a4b88597 100644 --- a/src/stencil_calc.hpp +++ b/src/stencil_calc.hpp @@ -108,8 +108,8 @@ namespace yask { // Sizes in elements (points). // - time sizes (t) are in steps to be done. // - spatial sizes (w, x, y, z) are in elements (not vectors). - // Sizes are the same for all grids. TODO: relax this restriction. - idx_t dt=1, dw=0, dx=0, dy=0, dz=0; // rank size (without halos). + // Sizes are the same for all grids. + idx_t dt=50, dw=1, dx=100, dy=100, dz=100; // rank size (without halos). idx_t rt=1, rw=0, rx=0, ry=0, rz=0; // region size (used for wave-front tiling). idx_t bgw=0, bgx=0, bgy=0, bgz=0; // block-group size (only used for 'grouped' region loops). idx_t bt=1, bw=0, bx=0, by=0, bz=0; // block size (used for each outer thread). @@ -124,18 +124,12 @@ namespace yask { int msg_rank=0; // rank that prints informational messages. // OpenMP settings. - int max_threads=1; // Initial number of threads to use overall. - int thread_divisor; // Reduce number of threads by this amount. - int num_block_threads; // Number of threads to use for a block. + int max_threads; // Initial number of threads to use overall. + int thread_divisor=1; // Reduce number of threads by this amount. + int num_block_threads=1; // Number of threads to use for a block. // Ctor. - StencilSettings() : - dt(50), dw(1), dx(DEF_RANK_SIZE), dy(DEF_RANK_SIZE), dz(DEF_RANK_SIZE), - bt(1), bw(1), bx(DEF_BLOCK_SIZE), by(DEF_BLOCK_SIZE), bz(DEF_BLOCK_SIZE), - pw(0), px(DEF_PAD), py(DEF_PAD), pz(DEF_PAD), - thread_divisor(DEF_THREAD_DIVISOR), - num_block_threads(DEF_BLOCK_THREADS) - { + StencilSettings() { max_threads = omp_get_max_threads(); } diff --git a/src/stencil_main.cpp b/src/stencil_main.cpp index baf525a1..d8d0a896 100644 --- a/src/stencil_main.cpp +++ b/src/stencil_main.cpp @@ -83,6 +83,10 @@ struct AppSettings : public StencilSettings { } }; +#ifndef DEF_ARGS +#define DEF_ARGS "" +#endif + // Parse options from the command-line and set corresponding vars. // Exit with message on error or request for help. void parse(int argc, char** argv) { @@ -115,6 +119,7 @@ struct AppSettings : public StencilSettings { // Parse cmd-line options. // Any remaining strings will be left in args. vector args; + parser.set_args(DEF_ARGS, args); parser.parse_args(argc, argv, args); if (help) { @@ -152,6 +157,7 @@ struct AppSettings : public StencilSettings { "\nStencil name: " YASK_STENCIL_NAME << endl; // Echo invocation parameters for record-keeping. + os << "Default arguments: " DEF_ARGS << endl; os << "Invocation:"; for (int argi = 0; argi < argc; argi++) os << " " << argv[argi]; diff --git a/src/utils.cpp b/src/utils.cpp index 57bca7db..3c6b55f6 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -365,5 +365,44 @@ namespace yask { // Return unused args in args var. args.swap(non_args); } - + + // Tokenize args from a string. + void CommandLineParser::set_args(std::string arg_string, + std::vector& args) { + string tmp; + bool in_quotes = false; + for (char c : arg_string) { + + // If WS, start a new string unless in quotes. + if (isspace(c)) { + if (in_quotes) + tmp += c; + else { + if (tmp.length()) + args.push_back(tmp); + tmp.clear(); + } + } + + // If quote, start or end quotes. + else if (c == '"') { + if (in_quotes) { + if (tmp.length()) + args.push_back(tmp); + tmp.clear(); + in_quotes = false; + } + else + in_quotes = true; + } + + // Otherwise, just add to tmp. + else + tmp += c; + } + + // Last string. + if (tmp.length()) + args.push_back(tmp); + } } diff --git a/src/utils.hpp b/src/utils.hpp index f2d915e1..8b54b513 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -343,6 +343,9 @@ namespace yask { parse_args(pgmName, args); } + // Tokenize args from a string. + virtual void set_args(std::string arg_string, + std::vector& args); }; } diff --git a/stencil-tuner-summary.csh b/stencil-tuner-summary.csh index c7471302..baabb4ba 100755 --- a/stencil-tuner-summary.csh +++ b/stencil-tuner-summary.csh @@ -26,7 +26,7 @@ # Purpose: find best result from each GA search csv file. if ( "-$1" == "-" ) then - if ( `echo stencil-search.*.csv | wc -l` > 0 ) then + if ( `echo stencil-tuner*.csv | wc -l` > 0 ) then $0 stencil-search.*.csv else echo "usage: $0 " diff --git a/stencil-tuner.pl b/stencil-tuner.pl index cd4d2308..bb5961e3 100755 --- a/stencil-tuner.pl +++ b/stencil-tuner.pl @@ -410,43 +410,6 @@ sub usage { my @pathNames = ('', 'serpentine', 'square_wave serpentine', 'grouped'); -# Possible loops for various levels. -# D0..D3 will get replaced by bv..bz, but not necessarily in that order. -# Modifier placeholders 'PATH*' and 'SBMOD' will be removed or changed -# based on relevant genes. - -# List of possible block-loop templates. -# This is the loop taken by each nested OpenMP task. -my @subBlockLoops = - ( - "PATH0 loop(D0,D1,D2) { SBMOD loop(D3) { calc(cluster(begin_sbtv)); } }", - "PATH0 loop(D0,D1) { loop(D2) { SBMOD loop(D3) { calc(cluster(begin_sbtv)); } } }", - ); - -# List of possible block-loop templates. -# This is the loop that creates nested OpenMP tasks. -# TODO: add other options. -my @blockLoops = - ( - "omp PATH1 loop(D0,D1,D2,D3) { calc(sub_block(bt)); }", - ); - -# List of possible region loop templates. -# This is the loop that creates outer OpenMP tasks. -# TODO: add other options. -my @regionLoops = - ( - "omp PATH2 loop(D0,D1,D2,D3) { calc(block(rt)); }", - ); - -# List of possible rank loop templates. -# This is the loop that creates OpenMP regions. -# TODO: add other options. -my @rankLoops = - ( - "PATH3 loop(D0,D1,D2,D3) { calc(region(start_dt, stop_dt, eqGroup_ptr)); }", - ); - # List of folds. # Start with inline in z only. if ( !@folds ) { @@ -529,19 +492,13 @@ sub usage { ( # Loops, from the list above. - # Each loop consists of the loop structure, index order, and path mods. - [ 0, $#subBlockLoops, 1, 'subBlockLoop' ], - [ 0, $#loopOrders, 1, 'subBlockLoopOrder' ], - [ 0, $#pathNames, 1, 'path0' ], - [ 0, $#blockLoops, 1, 'blockLoop' ], - [ 0, $#loopOrders, 1, 'blockLoopOrder' ], - [ 0, $#pathNames, 1, 'path1' ], - [ 0, $#regionLoops, 1, 'regionLoop' ], - [ 0, $#loopOrders, 1, 'regionLoopOrder' ], - [ 0, $#pathNames, 1, 'path2' ], - [ 0, $#rankLoops, 1, 'rankLoop' ], - [ 0, $#loopOrders, 1, 'rankLoopOrder' ], - [ 0, 0, 1, 'path3' ], # allow only incrementing-index path for rank. + # Each loop consists of index order and path mods. + [ 0, $#loopOrders, 1, 'subBlockOrder' ], + [ 0, $#pathNames, 1, 'subBlockPath' ], + [ 0, $#loopOrders, 1, 'blockOrder' ], + [ 0, $#pathNames, 1, 'blockPath' ], + [ 0, $#loopOrders, 1, 'regionOrder' ], + [ 0, $#pathNames, 1, 'regionPath' ], # how to shape vectors, from the list above. [ 0, $#folds, 1, 'fold' ], @@ -554,9 +511,6 @@ sub usage { # 4D->1D layout, from the list above. [ 0, $#layouts, 1, 'layout' ], - # whether or not to allow pipelining. - #[ 0, 1, 1, 'pipe' ], - # prefetch distances for l1 and l2. # all non-pos numbers => no prefetching, so ~50% chance of being enabled. [ -$maxPfdl1, $maxPfdl1, 1, 'pfdl1' ], @@ -564,7 +518,7 @@ sub usage { # other build options. [ 0, 100, 1, 'exprSize' ], # expression-size threshold. - [ 0, $#schedules, 1, 'ompSchedule' ], # OMP schedule for region loop. + [ 0, $#schedules, 1, 'ompRegionSchedule' ], # OMP schedule for region loop. [ 0, $#schedules, 1, 'ompBlockSchedule' ], # OMP schedule for block loop. ); @@ -1104,26 +1058,42 @@ ($$$$$$$) return $fitness, $results; } -# return loop code. -sub makeLoopCode($$$$$) { +# return loop-ctrl vars. +sub makeLoopVars($$$$$) { my $h = shift; - my $nameLong = shift; # e.g., 'block' + my $makePrefix = shift; # e.g., 'BLOCK' + my $tunerPrefix = shift; # e.g., 'block' my $varPrefix = shift; # e.g., 'b' my $varSuffix = shift; # e.g., 'v' - my $types = shift; - my $order = readHash($h, $nameLong."LoopOrder", 1); - my $type = readHash($h, $nameLong."Loop", 1); + my $order = readHash($h, $tunerPrefix."Order", 1); + my $orderStr = $loopOrders[$order]; # e.g., 'wxzy'. + my $path = readHash($h, $tunerPrefix."Path", 1); + my $pathStr = @pathNames[$path]; # e.g., 'grouped'. - my $dims = $loopOrders[$order]; # e.g., 'wyxz' (outer-to-inner). - my @dims = split '',$dims; # e.g., ('w', 'y', 'x', 'z'); - my $code = $types->[$type]; # e.g., 'loop(D0) { ... }'. + # dimension vars. + my @dims = split '',$orderStr; # e.g., ('w', 'y', 'x', 'z'); for my $ld (0..$#dims) { - $dims[$ld] = "$varPrefix$dims[$ld]$varSuffix"; # e.g., 'bnv'. - $code =~ s/D$ld/$dims[$ld]/g; # e.g., replace 'D0' with 'bnv'; + $dims[$ld] = "$varPrefix$dims[$ld]$varSuffix"; # e.g., 'bxv'. + } + + # vars to create. + my $outerVars = ''; + my $innerVars = ''; + + # special-case for sub-blocks. + if ($makePrefix eq 'SUB_BLOCK') { + $outerVars = join(',',@dims[0..$#dims-1]); # all but last one. + $innerVars = $dims[$#dims]; # just last one. + } else { + $outerVars = join(',',@dims); # all vars. } + my $outerMods = $pathStr; - return $code; + my $loopVars = " ".$makePrefix."_LOOP_OUTER_VARS='$outerVars'"; + $loopVars .= " ".$makePrefix."_LOOP_OUTER_MODS='$outerMods'"; + $loopVars .= " ".$makePrefix."_LOOP_INNER_VARS='$innerVars'"; + return $loopVars; } # sanity-check vars. @@ -1231,43 +1201,16 @@ sub fitness { my $exprSize = readHash($h, 'exprSize', 1); my $thread_divisor_exp = readHash($h, 'thread_divisor_exp', 0); my $bthreads_exp = readHash($h, 'bthreads_exp', 0); - my $pipe = 0; # readHash($h, 'pipe', 0); - my @paths = ( readHash($h, 'path0', 1), - readHash($h, 'path1', 1), - readHash($h, 'path2', 1), - readHash($h, 'path3', 1) ); my $layout = readHash($h, 'layout', 1); my $pfdl1 = readHash($h, 'pfdl1', 1); my $pfdl2 = readHash($h, 'pfdl2', 1); - my $ompSchedule = readHash($h, 'ompSchedule', 1); + my $ompRegionSchedule = readHash($h, 'ompRegionSchedule', 1); my $ompBlockSchedule = readHash($h, 'ompBlockSchedule', 1); # fold numbers. my $foldNums = $folds[$fold]; my @fs = split ' ', $foldNums; - # sub-block loops. - my $subBlockCode = makeLoopCode($h, 'subBlock', 'sb', 'v', \@subBlockLoops); - my $subBlockMods = ''; - $subBlockMods .= 'pipeline ' if $pipe; - if ($pfdl1 > 0 && $pfdl2 > 0) { - $subBlockMods .= 'prefetch(L1,L2) '; - } elsif ($pfdl1 > 0) { - $subBlockMods .= 'prefetch(L1) '; - } elsif ($pfdl2 > 0) { - $subBlockMods .= 'prefetch(L2) '; - } - $subBlockCode =~ s/SBMOD/$subBlockMods/g; - - # block loops. - my $blockCode = makeLoopCode($h, 'block', 'b', '', \@blockLoops); - - # region loops. - my $regionCode = makeLoopCode($h, 'region', 'r', '', \@regionLoops); - - # rank loops. - my $rankCode = makeLoopCode($h, 'rank', 'd', '', \@rankLoops); - # vectors in cluster. my $cvs = mult(@cvs); if ($cvs > $maxVecsInCluster) { @@ -1384,12 +1327,12 @@ sub fitness { return $ok if $justChecking; # OMP settings. - my $scheduleStr = $schedules[$ompSchedule]; + my $regionScheduleStr = $schedules[$ompRegionSchedule]; my $blockScheduleStr = $schedules[$ompBlockSchedule]; # compile-time settings. - my $macros = ''; - my $mvars = ''; + my $macros = ''; # string of macros. + my $mvars = ''; # other make vars. # layouts. 4D layout is selected by GA; then the corresponding 3D layout is # created from it by removing 't' and shifting the other 3 dims. @@ -1416,19 +1359,23 @@ sub fitness { $mvars .= " fold=x=$fs[0],y=$fs[1],z=$fs[2]"; # gen-loops vars. - $mvars .= " RANK_LOOP_CODE='$rankCode'". - " REGION_LOOP_CODE='$regionCode'". - " BLOCK_LOOP_CODE='$blockCode'". - " SUB_BLOCK_LOOP_CODE='$subBlockCode'"; + $mvars .= makeLoopVars($h, 'REGION', 'region', 'r', ''); + $mvars .= makeLoopVars($h, 'BLOCK', 'block', 'b', ''); + $mvars .= makeLoopVars($h, 'SUB_BLOCK', 'subBlock', 'sb', 'v'); - # substitute PATH* placeholders with actual path strings. - for my $pi (0..$#paths) { - my $pathName = $pathNames[$paths[$pi]]; - $mvars =~ s/\bPATH$pi\b/$pathName/g; + # sub-block loops. + my $subBlockMods = ''; + if ($pfdl1 > 0 && $pfdl2 > 0) { + $subBlockMods .= 'prefetch(L1,L2)'; + } elsif ($pfdl1 > 0) { + $subBlockMods .= 'prefetch(L1)'; + } elsif ($pfdl2 > 0) { + $subBlockMods .= 'prefetch(L2)'; } + $mvars .= " SUB_BLOCK_LOOP_INNER_MODS='$subBlockMods'"; # other vars. - $mvars .= " omp_schedule=$scheduleStr omp_block_schedule=$blockScheduleStr expr_size=$exprSize"; + $mvars .= " omp_region_schedule=$regionScheduleStr omp_block_schedule=$blockScheduleStr expr_size=$exprSize"; $mvars .= " mpi=1" if $nranks > 1; # how to make. From 102275146f68cc8722986c97c538ad3b85ab7210 Mon Sep 17 00:00:00 2001 From: Chuck Yount Date: Wed, 12 Apr 2017 16:25:28 -0700 Subject: [PATCH 20/20] Update example recipes and perf data on KNL, BDW, and HSW. --- docs/YASK-intro.pdf | Bin 635980 -> 716621 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/YASK-intro.pdf b/docs/YASK-intro.pdf index c4a30d4df2da78e49284c5ca23d357bac935006b..c3744f5c3747c9ad9c0f3870fa3f0d9719d2f736 100755 GIT binary patch delta 274689 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

    07Al3Wpr(dG;+{+qPXI^P_sWa=UNh*&X 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χAS6`TVr$bJ! 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 N`6i*u-Ap$(`2#We&;9@a