如何将一个数组分成块

10

我有一个表示长方体中点的数组。它是一个一维数组,使用以下索引函数来实现三个维度:

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

该域中的单元格数量为:

numCells = (numX + 2) * (numY + 2) * (numZ + 2)

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

numX = 5 * numY
numZ = numY/2
numY = userInput

对于每个单元格,我想根据其邻居的值(即模板)计算出该单元格的新值,其中邻居在上方、下方、左侧、右侧、前面和后面。但是,我只想对不是坏单元格进行这个计算。我有一个布尔数组,跟踪单元格是否是坏的。目前计算的样子如下:

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
            }
        }
    }
}

从性能方面来看,这并不理想。我希望能够对循环进行矢量化以提高性能,但由于if语句的存在,我无法这样做。我知道哪些单元格事先就是坏的,并且在计算过程中不会改变。我希望将域分成块,最好是4x4x4的块,这样我就可以事先计算每个块是否包含坏单元格,如果是,则像通常一样处理它,否则使用一个可以利用矢量化优势的优化函数。

for(block : blocks) {
    if(isBadBlock[block]) {
        slowProcessBlock(block) // As above
    } else {
        fastVectorizedProcessBlock(block)
    }
}

注意:并不需要块在物理上存在,即可以通过更改索引函数并使用不同的索引来循环数组来实现。我开放接受任何最佳解决方案。

fastVectorizedProcessBlock() 函数看起来与 slowProcessBlock() 函数相似,但去掉了if语句(因为我们知道它不包含坏单元),并添加了一个向量化预处理器指令。

如何将我的域分成块以便我可以完成这个任务?这似乎很棘手,因为 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
                }
             }
         }
     }
 }

现在细胞将存储在块中,即第一个4x4x4块中的所有细胞将存储在位置0-63,然后第二个块中的所有细胞将存储在位置64-127等。

然而,如果numX/numY/numZ值不是很好,我认为这种方法行不通。例如,如果numY = 2,numZ = 1,numX = 10怎么办?for循环将期望z方向至少深入4个单元格。有没有好的方法来解决这个问题?

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

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;
}

3
建议使用连锁计算方式 ix + numCellsX*(iy + iz * numCellsY) 代替 ix + (iy * numCellsX) + (iz * numCellsX * numCellsY),这样可以少用一个乘号。 - chux - Reinstate Monica
@Lundin 如果块的大小不是4x4x4,可以通过填充来使它们变成4x4x4,但这样我就需要知道哪些单元格是填充单元格,当我循环遍历每个块时,这将需要一个if语句来停止向量化。有什么想法可以解决这个问题吗? - JC2188
@technosaurus,单元格被声明为cells[numCells],其中numCells = (numX+2) + (numY+2) + (numZ+2)。请参见索引函数以获取布局。您能否更详细地解释一下您的解决方案?我认为它只会给我单元格的全局索引,这就是get index函数所做的。 - JC2188
@chqrlie 在域的周围有一层填充,以便非填充单元格的外层在其图案计算中使用填充单元格的值。填充单元格在每次对域进行运行之后分别标准化其值。基本上,坏单元被聚集在一起,例如形成正方形/长方体。对于与坏单元相邻的好单元,将使用坏单元的值,然而这意味着只有外层的坏单元会被使用。外层坏单元的值在每次对域进行运行之后进行更新。 - JC2188
阻塞并不能帮助太多。你可以通过从坏单元格的布尔映射切换到坏单元格索引列表来解决几乎所有问题。请参见下面的建议以获取详细信息。(这不是真正的答案,因为您将“阻塞”列为先决条件。根据我的经验,在这里并没有帮助。) - Nominal Animal
显示剩余6条评论
4个回答

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
            }
        }
    }
}

您也可以使用一些本地变量来消除所有的乘法:

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
}

这些方向是否有前途,很大程度上取决于执行计算以获取模板值的实际情况。您也应该发布这一点。
如果您可以更改坏单元格的布尔数组格式,将行填充为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
            }
        }
    }
}

编辑:

模板函数使用了过多的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;
}
预计算&cells[index]作为指针可能会改善代码,但编译器应该能够检测到这个常见子表达式并生成高效的代码。
编辑2:
这里有一个平铺的方法:您可以添加缺失的参数,大多数大小被视为全局,但您应该传递一个指向上下文结构的指针,其中包含所有这些值。它使用isBadTile[]isGoodTile[]:布尔数组告诉是否给定的瓷砖具有全部坏单元格和全部良好单元格。
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);
            }
        }
    }
}

下面是计算isGoodTile[]数组的函数。目前正确计算的偏移量只对应于x的4的倍数加1,以及y和z与它们的最大值相差不超过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];
    }
}

好的,我想我明白这是如何工作的。是否有可能将其改为平铺方式,而不是行向方式?我认为这样会更好地利用缓存。 - JC2188
@JC2188:它可能会提供更好的缓存行为,也可能不会,只有仔细的基准测试才能告诉你哪个更高效,并且仅适用于已测试的架构、大小和编译器。通过添加更多循环可以尝试使用平铺方法,但索引计算的增量计算将变得更加复杂和昂贵,可能不值得。我将发布这样的替代方案。 - chqrlie
@JC2188:我更新了答案,加入了瓦片版本。如果你能计算出一个isBadTile数组来在handle_tile中使用,就可以删除像素级别的测试并查看它是否进一步提高了性能。计算这个数组有点棘手,但你只需要计算xyz值等于1模4的值。 - chqrlie
谢谢更新。我尝试运行早期版本的EDIT2,但是我认为它有问题。在每次迭代之后,我期望一个值大约为3x10^-6,并且在计算过程中保持大致相同的顺序,但由于某种原因,我在第一次运行时得到了12的值,然后这个值在整个计算过程中不断增长。这可能与我如何转换您的示例有关。我应该在这里发布代码还是可以私信给您?这是很多代码,调试完成后我认为它不会相关。 - JC2188
我还应该提到的是,好和坏单元格/瓷砖的计算仅在程序开始时发生,因此没有真正需要优化它们。 - JC2188
显示剩余4条评论

4
尽管OP需要使用阻塞方法,但我建议不要这样做。你会发现,每个连续的单元序列(沿X轴的1D单元)已经是一个这样的块了。阻塞只是用固定大小的小副本代替原始问题,而没有简化问题。简而言之,阻塞对于实际问题毫无帮助。它根本不应该成为解决方案的必要特性。
相反,我建议完全避免根本问题--只是以不同的方式。
你看,与其为每个需要测试的单元格设置“坏单元格”标志(每个单元格都需要测试一次),不如保留一个(排序的)坏单元格索引列表。然后,您可以一次处理整个数据集,接着在坏单元格索引列表中列出的单元格上进行修复循环。
还要注意,除非您在单元值的副本上工作,否则计算新单元值的顺序将影响结果。这几乎肯定不是您想要的。
所以,这是我的建议:
#include <stdlib.h>
#include <errno.h>

typedef struct {
    /* Core cells in the state, excludes border cells */
    size_t   xsize;
    size_t   ysize;
    size_t   zsize;

    /* Index calculation: x + y * ystride + z * zstride */
    /* x is always linear in memory; xstride = 1 */
    size_t   ystride; /* = xsize + 2 */
    size_t   zstride; /* = ystride * (ysize + 2) */

    /* Cell data, points to cell (0,0,0) */
    double  *current;
    double  *previous;

    /* Bad cells */
    size_t   fixup_cells;  /* Number of bad cells */
    size_t  *fixup_index;  /* Array of bad cells' indexes */

    /* Dynamically allocated memory */
    void    *mem[3];
} lattice;

void lattice_free(lattice *const ref)
{
    if (ref) {
        /* Free dynamically allocated memory, */
        free(ref->mem[0]);
        free(ref->mem[1]);
        free(ref->mem[2]);
        /* then initialize/poison the contents. */
        ref->xsize = 0;
        ref->ysize = 0;
        ref->zsize = 0;
        ref->ystride = 0;
        ref->zstride = 0;
        ref->previous = NULL;
        ref->current = NULL;
        ref->fixup_cells = 0;
        ref->fixup_index = NULL;
        ref->mem[0] = NULL;
        ref->mem[1] = NULL;
        ref->mem[2] = NULL;
    }
}


int lattice_init(lattice *const ref, const size_t xsize, const size_t ysize, const size_t zsize)
{
    const size_t  xtotal = xsize + 2;
    const size_t  ytotal = ysize + 2;
    const size_t  ztotal = zsize + 2;
    const size_t  ntotal = xtotal * ytotal * ztotal;
    const size_t  double_bytes = ntotal * sizeof (double);
    const size_t  size_bytes = xsize * ysize * zsize * sizeof (size_t);

    /* NULL reference to the variable to initialize? */
    if (!ref)
        return EINVAL;

    /* Initialize/poison the lattice variable. */
    ref->xsize = 0;
    ref->ysize = 0;
    ref->zsize = 0;
    ref->ystride = 0;
    ref->zstride = 0;
    ref->previous = NULL;
    ref->current = NULL;
    ref->fixup_cells = 0;
    ref->fixup_index = NULL;
    ref->mem[0] = NULL;
    ref->mem[1] = NULL;
    ref->mem[2] = NULL;

    /* Verify size is nonzero */
    if (xsize < 1 || ysize < 1 || zsize < 1)
        return EINVAL;        

    /* Verify size is not too large */
    if (xtotal <= xsize || ytotal <= ysize || ztotal <= zsize ||
        ntotal / xtotal / ytotal != ztotal ||
        ntotal / xtotal / ztotal != ytotal ||
        ntotal / ytotal / ztotal != xtotal ||
        double_bytes / ntotal != sizeof (double) ||
        size_bytes / ntotal != sizeof (size_t))
        return ENOMEM;

    /* Allocate the dynamic memory needed. */
    ref->mem[0] = malloc(double_bytes);
    ref->mem[1] = malloc(double_bytes);
    ref->mem[2] = malloc(size_bytes);
    if (!ref->mem[0] || !ref->mem[1] || !ref->mem[2]) {
        free(ref->mem[2]);
        ref->mem[2] = NULL;
        free(ref->mem[1]);
        ref->mem[1] = NULL;
        free(ref->mem[0]);
        ref->mem[0] = NULL;
        return ENOMEM;
    }

    ref->xsize = xsize;
    ref->ysize = ysize;
    ref->zsize = zsize;

    ref->ystride = xtotal;
    ref->zstride = xtotal * ytotal;

    ref->current = (double *)ref->mem[0] + 1 + xtotal;
    ref->previous = (double *)ref->mem[1] + 1 + xtotal;

    ref->fixup_cells = 0;
    ref->fixup_index = (size_t *)ref->mem[2];

    return 0;
}

请注意,我更喜欢使用 x + ystride * y + zstride * z 的索引计算方式,而不是 x + xtotal * (y + ytotal * z),因为前者中的两个乘法可以并行执行(在超标量流水线上,在可以在单个CPU核心上同时执行两个不相关整数乘法的体系结构中),而后者中的乘法必须是顺序执行。
请注意,ref->current[-1 - ystride - zstride] 是指单元格(-1, -1, -1)处的当前单元格值,即从原始单元格(0, 0, 0)对角线的边框单元格。换句话说,如果您有一个索引i的单元格(x, y, z),那么
    i-1 是 (x-1, y, z) 处的单元格
    i+1 是 (x+1, y, z) 处的单元格
    i-ystride 是 (x, y-1, z) 处的单元格
    i+ystride 是 (x, y+1, z) 处的单元格
    i-zstride 是 (x, y, z-1) 处的单元格
    i+zstride 是 (x, y, z-1) 处的单元格
    i-ystride 是 (x, y-1, z) 处的单元格
    i-1-ystride-zstride 是 (x-1, y-1, z-1) 处的单元格
    i+1+ystride+zstride 是 (x+1, y+1, z+1) 处的单元格
等等。 ref->fixup_index 数组足够大,可以列出除边框单元格外的所有单元格。最好保持其排序(或在构建后进行排序),因为这有助于缓存局部性。
如果您的晶格具有周期性边界条件,则可以使用六个2D循环、十二个1D循环和八个副本将第一个和最后一个有效单元格复制到边界上,然后开始新的更新。
因此,您的更新周期基本上是:
  1. 计算或填充->current中的边框。

  2. 交换->current->previous

  3. 使用->previous中的数据计算->current的所有单元格。

  4. 循环遍历->fixup_index中的->fixup_cells索引,并重新计算相应的->current单元格。

请注意,在步骤3中,您可以线性地对0xsize-1 + (ysize-1)*ystride + (zsize-1)*zstride之间的所有索引执行此操作,包括约67%的边框单元格。它们相对于整个体积来说很少,而且一个单一的线性循环可能比跳过边框单元格更快,尤其是如果您可以向量化计算。(在这种情况下,这是非平凡的。)

您甚至可以将工作分配给多个线程,为每个线程提供连续的索引集以进行工作。因为您从->previous读取并写入->current,所以线程不会互相干扰,尽管当一个线程到达其区域的末尾时,另一个线程位于其区域的开头时可能会有一些缓存行来回移动;由于数据的定向方式(和缓存行只是几个单元格大小 -- 通常为2、4或8),这种来回移动在实践中不应该成为问题。(显然,不需要锁。)

这个特定的问题实际上并不是什么新问题。建模康威生命游戏正方形或立方体晶格伊辛模型,以及实现许多其他晶格模型都涉及相同的问题(但通常使用布尔数据而不是双精度,并且没有“坏单元格”)。


2

我认为您可以嵌套一些类似的循环。就像这样:

for(int z = 1; z < numZ+1; z+=4) {
    for(int y = 1; y < numY+1; y+=4) {
        for(int x = 1; x < numX+1; x+=4) {
            if(!isBadBlock[ getBlockIndex(x>>2,y>>2,z>>2) ]) {
                for(int zz = z; zz < z + 4 && zz < numZ+1; zz++) {
                   for(int yy = y; yy < y + 4 && yy < numY+1; yy++) {
                      for(int xx = z; xx < x + 4 && xx < numX+1; xx++) {
                         if(!isBadCell[ getCellIndex(xx,yy,zz) ]) {
                             // Do stencil Computation
                            }
                        }
                    }
                }
            }
        }
    }
}

在上面的代码中,isBadBlock 是一个布尔数组,指示所有单元格都是坏的块。OP似乎假设有更多的块具有所有良好的单元格,并希望优先优化这些块。我猜这两种方法可以根据坏单元格的分布进行组合。 - chqrlie

2

按照你目前的设置,你可以通过使用3D数组来获取索引,如下所示:

#include <sys/types.h>
#define numX 256
#define numY 128
#define numZ 64
//Note the use of powers of 2 - it will simplify things a lot

int cells[numX][numY][numZ];

size_t getindex(size_t x, size_t y,size_t z){
  return (int*)&cells[x][y][z]-(int*)&cells[0][0][0];
}

这将按照以下方式布置单元格:
[0,0,0][0,0,1][0,0,2]...[0,0,numZ-1]
[0,1,0][0,1,1][0,1,2]...[0,1,numZ-1]
...
[0,numY-1,0][0,numY-1,1]...[0,1,numZ-1]
...
[1,0,0][1,0,1][0,0,2]...[1,0,numZ-1]
[1,1,0][1,1,1][1,1,2]...[1,1,numZ-1]
...
[numX-1,numY-1,0][numX-1,numY-1,1]...[numX-1,numY-1,numZ-1]

So efficient loops would look like:

for(size_t x=0;x<numX;x++)
  for(size_t y=0;y<numY;y++)
    for(size_t z=0;z<numZ;z++)
      //vector operations on z values

但是,如果您想将其分成4x4x4块,则可以使用类似于4x4x4块的三维数组:

#include <sys/types.h>
#define numX 256 
#define numY 128
#define numZ 64

typedef int block[4][4][4];
block blocks[numX][numY][numZ];
//add a compiler specific 64 byte alignment to  help with cache misses?

size_t getblockindex(size_t x, size_t y,size_t z){
  return (block *)&blocks[x][y][z]-(block *)&blocks[0][0][0];
}

我重新排列了索引,将它们变成了x、y、z的顺序,只是为了更好地记住它们,但请确保你按照最后一个操作的索引在内部循环中进行排序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接