3D数组删除C ++的性能降低

时间:2015-04-11 16:59:53

标签: c++ memory-management

int newHeight = _height/2;
    int newWidth = _width/2;

    double*** imageData = new double**[newHeight];
    for (int i = 0; i < newHeight; i++)
    {
        imageData[i] = new double*[newWidth];
        for (int j = 0; j < newWidth; j++)
        {
            imageData[i][j] = new double[4];
        }
    }

我动态分配了这个3D矩阵。 什么是最快最安全的释放内存的方法?

这是我已经完成但是这需要几秒钟我的矩阵很大(1500,2000,4)

  for (int i = 0; i != _height/2; i++)
        {
            for (int j = 0; j != _width/2; j++)
            {
                delete[] imageData[i][j];
            }
            delete[] imageData[i];
        }
        delete[] imageData;

更新
正如所建议的,我选择了这个解决方案:

std::vector<std::vector<std::array<double,4>>>

表现非常适合我的情况

4 个答案:

答案 0 :(得分:5)

将整个图像数据分配为一个块,以便将其作为一个块释放,即。 double* imageData = new double[width*height*4]; delete [] imageData;并使用偏移量对其进行索引。现在你正在进行 300万 单独的分配,这会破坏你的堆。

答案 1 :(得分:2)

我同意qartar的回答,直到他说“使用抵消索引”。这不是必要的。您也可以使用单个分配和多个下标访问(imageData[i][j][k])。我之前展示了这种方法here,为三维情况调整它并不困难:

分配代码如下:

double*** imageData;
imageData = new double**[width];
imageData[0] = new double*[width * height];
imageData[0][0] = new double[width * height * 4];
for (int i = 0; i < width; i++) {
    if (i > 0) {
        imageData[i] = imageData[i-1] + height;
        imageData[i][0] = imageData[i-1][0] + height * 4;
    }
    for (int j = 1; j < height; j++) {
        imageData[i][j] = imageData[i][j-1] + 4;
    }
}

解除分配变得更简单:

delete[] imageData[0][0];
delete[] imageData[0];
delete[] imageData;

当然,您可以而且应该使用std::vector自动进行解除分配:

std::vector<double**> imageData(width);
std::vector<double*> imageDataRows(width * height);
std::vector<double> imageDataCells(width * height * 4);
for (int i = 0; i < width; i++) {
    imageData[i] = &imageDataRows[i * height];
    for (int j = 0; j < height; j++) {
        imageData[i][j] = &imageDataCells[(i * height + j) * 4];
    }
}

并且释放是完全自动的。

有关详细说明,请参阅my other answer

或者使用std::array<double,4>作为最后一个下标,并通过此方法使用二维动态分配。

答案 2 :(得分:2)

Ben Voigt's回答的第一个想法略有不同:

double ***imagedata = new double**[height];
double **p = new double*[height * width];
double *q = new double[height * width * length];
for (int i = 0; i < height; ++i, p += width) {
    imagedata[i] = p;
    for (int j = 0; j < width; ++j, q += length) {
        imagedata[i][j] = q;
    }
}
// ...
delete[] imagedata[0][0];
delete[] imagedata[0];
delete[] imagedata;

可以通过单一分配来完成整个过程,但这会带来一些您可能不想支付的复杂性。

现在,每个表查找涉及从内存中对指针进行几次背靠背读取的事实,这个解决方案几乎总是不如分配平面数组,并且进行索引计算以转换三倍索引到一个平面索引(你应该编写一个包装类来为你做这些索引计算)。

使用指向数组指针数组的指针数组的主要原因是你的数组是不规则的 - 也就是说,imagedata[a][b]imagedata[c][d]具有不同的长度 - 或者可能用于交换行,例如为swap(imagedata[a][b], imagedata[c][d])。在这种情况下,vector正如您所使用的那样,最好在使用之前使用。

答案 3 :(得分:1)

算法的主要部分是杀死性能,这是您正在进行的分配的粒度和数量。总共产生 3001501 ,细分为:

  • 分配1500 double**
  • 1500次分配,每次分配获得2000 double*
  • 3000000分配,每个分配获得double[4]

这可以相当减少。您当然可以像其他建议那样做,并简单地分配1个大量的double数组,将索引计算留给存取函数。当然,如果你这样做,你需要确保你带上尺寸。但是,结果将轻松实现最快的分配时间和访问性能。使用std::vector<double> arr(d1*d2*4);并根据需要进行偏移计算将非常有用。


另一种方式

如果您已经设置了使用指针数组方法,则可以通过获取单个分配中的两个较低维度来消除3000000分配。你最低级的维度是固定的(4),因此你可以这样做:(但你会立刻看到有更多以C ++为中心的机制):

double (**allocPtrsN(size_t d1, size_t d2))[4]
{
    typedef double (*Row)[4];
    Row *res = new Row[d1];

    for (size_t i=0; i<d1; ++i)
        res[i] = new T[d2][4];

    return res;
}

并简单地调用:

double (**arr3D)[4] = allocPtrsN(d1,d2);

其中d1d2是您的两个优势维度。这会产生恰好d1 + 1个分配,第一个是d1个指针,剩余的是d1个分配,每个double[d2][4]分配一个。


使用C ++标准容器

先前的代码显然很乏味,坦率地说容易出现相当大的错误。 C ++使用固定数组向量向量提供了一个整洁的解决方案,执行此操作:

std::vector<std::vector<std::array<double,4>>> arr(1500, std::vector<std::array<double,4>>(2000));

最终,这将使几乎与前面所示的相当钝的代码相同,但在执行此操作时为您提供标准库的所有可爱优势。您可以获得std::vectorstd::array模板的所有方便成员,以及RAII功能作为额外奖励。

然而,这是一个显着的差异。前面显示的原始指针方法将值初始化每个分配的实体;数组方法的向量向量。如果你认为它没有什么区别......

#include <iostream>
#include <vector>
#include <array>
#include <chrono>

using Quad = std::array<double, 4>;
using Table = std::vector<Quad>;
using Cube = std::vector<Table>;

Cube allocCube(size_t d1, size_t d2)
{
    return Cube(d1, Table(d2));
}

double ***allocPtrs(size_t d1, size_t d2)
{
    double*** ptrs = new double**[d1];
    for (size_t i = 0; i < d1; i++)
    {
        ptrs[i] = new double*[d2];
        for (size_t j = 0; j < d2; j++)
        {
            ptrs[i][j] = new double[4];
        }
    }
    return ptrs;
}

void freePtrs(double***& ptrs, size_t d1, size_t d2)
{
    for (size_t i=0; i<d1; ++i)
    {
        for (size_t j=0; j<d2; ++j)
            delete [] ptrs[i][j];
        delete [] ptrs[i];
    }
    delete [] ptrs;
    ptrs = nullptr;
}

double (**allocPtrsN(size_t d1, size_t d2))[4]
{
    typedef double (*Row)[4];
    Row *res = new Row[d1];

    for (size_t i=0; i<d1; ++i)
        res[i] = new double[d2][4];

    return res;
}

void freePtrsN(double (**p)[4], size_t d1, size_t d2)
{
    for (size_t i=0; i<d1; ++i)
        delete [] p[i];
    delete [] p;
}

std::vector<std::vector<std::array<double,4>>> arr(1500, std::vector<std::array<double,4>>(2000));

template<class C>
void print_duration(const std::chrono::time_point<C>& beg,
                    const std::chrono::time_point<C>& end)
{
    std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count() << "ms\n";
}

int main()
{
    using namespace std::chrono;
    time_point<system_clock> tp;
    volatile double vd;

    static constexpr size_t d1 = 1500, d2 = 2000;

    tp = system_clock::now();
    for (int i=0; i<10; ++i)
    {
        double ***cube = allocPtrs(d1,d2);
        cube[d1/2][d2/21][1] = 1.0;
        vd = cube[d1/2][d2/2][3];
        freePtrs(cube, 1500, 2000);
    }
    print_duration(tp, system_clock::now());

    tp = system_clock::now();
    for (int i=0; i<10; ++i)
    {
        Cube cube = allocCube(1500,2000);
        cube[d1/2][d2/21][1] = 1.0;
        vd = cube[d1/2][d2/2][3];
    }
    print_duration(tp, system_clock::now());

    tp = system_clock::now();
    for (int i=0; i<10; ++i)
    {
        auto cube = allocPtrsN(d1,d2);
        cube[d1/2][d2/21][1] = 1.0;
        vd = cube[d1/2][d2/21][1];
        freePtrsN(cube, d1, d2);
    }
    print_duration(tp, system_clock::now());
}

<强>输出

5328ms
418ms
95ms

因此,如果您计划用零以外的东西加载每个元素,那么请记住这一点。


<强>结论

如果性能很关键,我会使用24MB(在我的实现中,无论如何)单一分配,可能在std::vector<double> arr(d1*d2*4);中,并根据需要使用一种形式的二级索引或另一种形式进行偏移计算。其他答案提出了有趣的想法,特别是Ben,它从根本上减少了两个三个块(数据和两个辅助指针数组)的分配数量。对不起,我没有时间进行替补,但我怀疑表现会很出色。但是如果确实希望保留现有技术,请考虑在C ++容器中进行,如上所示。如果额外的周期花费值初始化世界并不需要付出太高的代价,那么管理起来就会容易得多(与原始指针相比,显然需要处理更少的代码)。

祝你好运。