如何将数组分成块

JC2*_*188 10 c arrays optimization tiling multidimensional-array

我有一个数组代表长方体中的点.它是一维数组,使用以下索引函数实现3维:

int getCellIndex(int ix, int iy, int iz) {
    return ix + (iy * numCellsX) + (iz * numCellsX * numCellsY);
}
Run Code Online (Sandbox Code Playgroud)

域中的单元数是:

numCells = (numX + 2) * (numY + 2) * (numZ + 2)
Run Code Online (Sandbox Code Playgroud)

其中numX/numY/numZ是X/Y/Z方向上的单元格数.每个方向的+2是在域的外部创建填充单元.每个方向的单元格数由下式给出:

numX = 5 * numY
numZ = numY/2
numY = userInput
Run Code Online (Sandbox Code Playgroud)

对于每个单元格,我想基于它的邻居值(即模板)计算该单元格的新值,其中它的邻居在上方,下方,左侧,右侧,前方和后方.但是,我只想对不坏的单元格进行此计算.我有一个布尔数组来跟踪一个单元格是坏的.这就是计算目前的样子:

for(int z = 1; z < numZ+1; z++) {
    for(int y = 1; y < numY+1; y++) {
        for(int x = 1; x < numX+1; x++) {
            if(!isBadCell[ getCellIndex(x,y,z) ] {
                // Do stencil Computation
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这不是很好的表现.我希望能够对循环进行矢量化以提高性能,但是由于if语句,我不能.我知道细胞是否提前坏了,这在整个计算过程中都没有变化.我想将域拆分成块,最好是4x4x4块,这样我就可以计算每个块的先验值,如果它包含坏单元格,如果是这样,就像往常一样处理它,或者如果没有,使用可以采用的优化函数矢量化的优点,例如

for(block : blocks) {
    if(isBadBlock[block]) {
        slowProcessBlock(block) // As above
    } else {
        fastVectorizedProcessBlock(block)
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:块不需要物理存在,即这可以通过更改索引功能,并使用不同的索引循环数组来实现.我对任何最好的方式持开放态度.

fastVectorizedProcessBlock()函数看起来类似于slowProcessBlock()函数,但if语句删除(因为我们知道它不包含坏单元格)和vectorization pragma.

如何将我的域分成块,以便我可以完成此操作?这似乎很棘手,因为a)每个方向上的单元格数量不相等,b)我们需要考虑填充单元格,因为我们绝不能尝试计算它们的值,因为这会导致内存访问边界

如何在不使用if语句的情况下处理不包含坏单元格的块?

编辑:

这是我最初的想法:

for(int i = 0; i < numBlocks; i++) { // use blocks of 4x4x4 = 64
    if(!isBadBlock[i]) {
        // vectorization pragma here
        for(int z = 0; z < 4; z++) {
            for(int y = 0; y < 4; y++) {
                for(int x = 0; x < 4; x++) {
                    // calculate stencil using getCellIndex(x,y,z)*i
                }
             }
         }
     } else {
         for(int z = 0; z < 4; z++) {
            for(int y = 0; y < 4; y++) {
                for(int x = 0; x < 4; x++) {
                    if(!isBadCell[i*getCellIndex(x,y,z)]) {    
                    // calculate stencil using getCellIndex(x,y,z)*i
                }
             }
         }
     }
 }
Run Code Online (Sandbox Code Playgroud)

现在将单元存储在块中,即第一个4x4x4块中的所有单元将存储在位0-63中,然后第二个块中的所有单元将存储在位64-127等中.

但是,如果numX/numY/numZ值不合适,我认为不会起作用.例如,如果numY = 2,numZ = 1和numX = 10,该怎么办?for循环期望z方向至少为4个单元格深.有没有一个好方法来克服这个?

更新2 - 这是模板计算的样子:

if ( isBadCell[ getCellIndex(x,y,z) ] ) {
  double temp = someOtherArray[ getCellIndex(x,y,z) ] +
                    1.0/CONSTANT/CONSTANT*
                    (
                      - 1.0 * cells[ getCellIndex(x-1,y,z) ]
                      - 1.0 * cells[ getCellIndex(x+1,y,z) ]
                      - 1.0 * cells[ getCellIndex(x,y-1,z) ]
                      - 1.0 * cells[ getCellIndex(x,y+1,z) ]
                      - 1.0 * cells[ getCellIndex(x,y,z-1) ]
                      - 1.0 * cells[ getCellIndex(x,y,z+1) ]
                      + 6.0 * cells[ getCellIndex(x,y,z) ]
                      );
  globalTemp += temp * temp;
  cells[ getCellIndex(x,y,z) ] += -omega * temp / 6.0 * CONSTANT * CONSTANT;
}
Run Code Online (Sandbox Code Playgroud)

chq*_*lie 7

哪里getCellIndex()检索的价值观numCellXnumCellY?最好将它们作为参数传递,而不是依赖于全局变量,并使此函数static inline允许编译器进行优化.

static line int getCellIndex(int ix, int iy, int iz, int numCellsX, numCellsY) {
    return ix + (iy * numCellsX) + (iz * numCellsX * numCellsY);
}

for (int z = 1; z <= numZ; z++) {
    for (int y = 1; y <= numY; y++) {
        for (int x = 1; x <= numX; x++) {
            if (!isBadCell[getCellIndex(x, y, z, numX + 2, numY + 2)] {
                // Do stencil Computation
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以使用一些局部变量删除所有乘法:

int index = (numY + 2) * (numX + 2);  // skip top padding plane
for (int z = 1; z <= numZ; z++) {
    index += numX + 2;  // skip first padding row
    for (int y = 1; y <= numY; y++) {
        index += 1;   // skip first padding col
        for (int x = 1; x <= numX; x++, index++) {
            if (!isBadCell[index] {
                // Do stencil Computation
            }
        }
        index += 1;   // skip last padding col
    }
    index += numX + 2;   // skip last padding row
}
Run Code Online (Sandbox Code Playgroud)

这些方向是否正在取决于很大程度上取决于为获得模板值而执行的实际计算.你也应该张贴.

如果你可以改变坏单元的布尔数组的格式,那么将这些行填充为8的倍数并使用8列的水平填充以改善对齐将是有用的.使布尔数组成为位数组允许通过单次测试一次检查8,16,32或甚至64个单元.

您可以调整数组指针以使用基于0的坐标.

以下是它的工作原理:

int numCellsX = 8 + ((numX + 7) & ~7) + 8;
int numCellsY = 1 + numY + 1;
int numCellsXY = numCellsX * numCellsY;
// adjusted array_pointer
array_pointer = allocated_pointer + 8 + numCellsX + numCellsXY;
// assuming the isBadCell array is 0 based too.
for (int z = 0, indexZ = 0; z < numZ; z++, indexZ += numCellsXY) {
    for (int y = 0, indexY = indexZ; y < numY; y++, indexY += numCellsX) {
        for (int x = 0, index = indexY; x <= numX - 8; x += 8, index += 8) {
            int mask = isBadCell[index >> 3];
            if (mask == 0) {
                // let the compiler unroll computation for 8 pixels with
                for (int i = 0; i < 8; i++) {
                   // compute stencil value for x+i,y,z at index+i
                }
            } else {
                for (int i = 0; i < 8; i++, mask >>= 1) {
                    if (!(mask & 1)) {
                       // compute stencil value for x+i,y,z at index+i
                    }
                }
            }
        }
        int mask = isBadCell[index >> 3];
        for (; x < numX; x++, index++, mask >>= 1) {
            if (!(mask & 1)) {
                // compute stencil value for x,y,z at index
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

模板函数对getCellIndex使用过多调用.以下是如何使用上面代码中计算的索引值对其进行优化:

// index is the offset of cell x,y,z
// numCellsX, numCellsY are the dimensions of the plane
// numCellsXY is the offset between planes: numCellsX * numCellsY

if (isBadCell[index]) {
    double temp = someOtherArray[index] +
                1.0 / CONSTANT / CONSTANT *
                ( - 1.0 * cells[index - 1]
                  - 1.0 * cells[index + 1]
                  - 1.0 * cells[index - numCellsX]
                  - 1.0 * cells[index + numCellsX]
                  - 1.0 * cells[index - numCellsXY]
                  - 1.0 * cells[index + numCellsXY]
                  + 6.0 * cells[index]
                );
    cells[index] += -omega * temp / 6.0 * CONSTANT * CONSTANT;
    globalTemp += temp * temp;
}
Run Code Online (Sandbox Code Playgroud)

预计算&cells[index]作为指针可能会改进代码,但编译应该能够检测到这个公共子表达式并生成有效的代码.

EDIT2:

这是一个平铺方法:您可以添加缺少的参数,假设大多数大小是全局的,但您应该将指针传递给具有所有这些值的上下文结构.它使用isBadTile[]isGoodTile[]:布尔数组,告诉给定的tile是否所有单元格都坏,所有单元格分别是好的.

void handle_tile(int x, int y, int z, int nx, int ny, int nz) {
    int index0 = x + y * numCellsX + z * numCellsXY;
    // skipping a tile with all cells bad.
    if (isBadTile[index0] && nx == 4 && ny == 4 && nz == 4)
        return;
    // handling a 4x4x4 tile with all cells OK.
    if (isGoodTile[index0] && nx == 4 && ny == 4 && nz == 4) {
        for (int iz = 0; iz < 4; iz++) {
            for (int iy = 0; iy < 4; iy++) {
                for (int ix = 0; ix < 4; ix++) {
                    int index = index0 + ix + iy * numCellsX + iz + numCellsXY;
                    // Do stencil computation using `index`
                }
            }
        }
    } else {
        for (int iz = 0; iz < nz; iz++) {
            for (int iy = 0; iy < ny; iy++) {
                for (int ix = 0; ix < nx; ix++) {
                    int index = index0 + ix + iy * numCellsX + iz + numCellsXY;
                    if (!isBadCell[index] {
                        // Do stencil computation using `index`
                }
            }
        }
    }
}

void handle_cells() {
    int x, y, z;
    for (z = 1; z <= numZ; z += 4) {
        int nz = min(numZ + 1 - z, 4);
        for (y = 1; y <= numY; y += 4) {
            int ny = min(numY + 1 - y, 4);
            for (x = 1; x <= numX; x += 4) {
                int nx = min(numX + 1 - x, 4);
                handle_tile(x, y, z, nx, ny, nz);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个计算isGoodTile[]数组的函数.正确计算的唯一偏移对应于从其最大值开始的4 + 1,y和z的x倍数小于3的值.

由于可以计算更少的元素,因此该实现是次优的.不完整的边框拼贴(边缘小于4)可能被标记为不好用单个案例跳过好的情况.如果isBadTile正确计算了边缘图块的数组,那么对于这些​​边缘图块来说,坏图块的测试可以起作用,目前情况并非如此.

void computeGoodTiles() {
    int start = 1 + numCellsX + numCellsXY;
    int stop = numCellsXY * numCellsZ - 1 - numCellsX - numCellsXY;

    memset(isGoodTile, 0, sizeof(*isGoodTile) * numCellsXY * numCellsZ);
    for (int i = start; i < stop; i += 4) {
        isGoodTile[i] = (isBadCell[i + 0] | isBadCell[i + 1] |
                         isBadCell[i + 2] | isBadCell[i + 3]) ^ 1;
    }
    for (int i = start; i < stop - 3 * numCellsX; i += 4) {
        isGoodTile[i] = isGoodTile[i + 0 * numCellsX] &
                        isGoodTile[i + 1 * numCellsX] &
                        isGoodTile[i + 2 * numCellsX] &
                        isGoodTile[i + 3 * numCellsX];
    }
    for (int i = start; i < stop - 3 * numCellsXY; i += 4) {
        isGoodTile[i] = isGoodTile[i + 0 * numCellsXY] &
                        isGoodTile[i + 1 * numCellsXY] &
                        isGoodTile[i + 2 * numCellsXY] &
                        isGoodTile[i + 3 * numCellsXY];
    }
}

void computeBadTiles() {
    int start = 1 + numCellsX + numCellsXY;
    int stop = numCellsXY * numCellsZ - 1 - numCellsX - numCellsXY;

    memset(isBadTile, 0, sizeof(*isBadTile) * numCellsXY * numCellsZ);
    for (int i = start; i < stop; i += 4) {
        isBadTile[i] = isBadCell[i + 0] & isBadCell[i + 1] &
                       isBadCell[i + 2] & isBadCell[i + 3];
    }
    for (int i = start; i < stop - 3 * numCellsX; i += 4) {
        isBadTile[i] = isBadTile[i + 0 * numCellsX] &
                       isBadTile[i + 1 * numCellsX] &
                       isBadTile[i + 2 * numCellsX] &
                       isBadTile[i + 3 * numCellsX];
    }
    for (int i = start; i < stop - 3 * numCellsXY; i += 4) {
        isBadTile[i] = isBadTile[i + 0 * numCellsXY] &
                       isBadTile[i + 1 * numCellsXY] &
                       isBadTile[i + 2 * numCellsXY] &
                       isBadTile[i + 3 * numCellsXY];
    }
}
Run Code Online (Sandbox Code Playgroud)