C++ 删除[]性能问题

C++ 删除[]性能问题,c++,arrays,performance,memory-management,C++,Arrays,Performance,Memory Management,我写了一个程序,计算流水车间调度问题 我需要帮助优化程序中最慢的部分: 首先是数组2D数组分配: this->_perm = new Chromosome*[f]; //... for (...) this->_perm[i] = new Chromosome[fM1]; 它工作正常,但当我尝试删除数组时,出现了一个问题: delete [] _perm[i]; 执行上面的行需要非常长的时间。染色体是由大约300k个元素组成的数组——分配它不到一秒钟,但删除它需要一分钟以上的

我写了一个程序,计算流水车间调度问题

我需要帮助优化程序中最慢的部分:

首先是数组2D数组分配:

this->_perm = new Chromosome*[f];

//... for (...)

this->_perm[i] = new Chromosome[fM1];
它工作正常,但当我尝试删除数组时,出现了一个问题:

delete [] _perm[i];
执行上面的行需要非常长的时间。染色体是由大约300k个元素组成的数组——分配它不到一秒钟,但删除它需要一分钟以上的时间


如果您有任何关于删除零件的改进建议,我将不胜感激。

我建议您使用新的位置。分配和解除分配可以在一个语句中完成

int sizeMemory = sizeof(Chromosome) * row * col;
char* buffer = new char[sizeMemory]; //allocation of memory at once!

vector<Chromosome*> chromosomes;
chromosomes.reserve(row);
int j = 0 ;
for(int i = 0 ; i < row ; i++ )
{
    //only construction of object. No allocation!
    Chromosome *pChromosome = new (&buffer[j]) Chromosome[col]; 
    chromosomes.push_back(pChromosome);
    j = j+ sizeof(Chromosome) * col;
}

for(int i = 0 ; i < row ; i++ )
{
      for(int j = 0 ; j < col ; j++ )
      {
         //only destruction of object. No deallocation!
         chromosomes[i][j].~Chromosome();
      }
}
delete [] buffer; //actual deallocation of memory at once!

我建议你使用新的布局。分配和解除分配可以在一个语句中完成

int sizeMemory = sizeof(Chromosome) * row * col;
char* buffer = new char[sizeMemory]; //allocation of memory at once!

vector<Chromosome*> chromosomes;
chromosomes.reserve(row);
int j = 0 ;
for(int i = 0 ; i < row ; i++ )
{
    //only construction of object. No allocation!
    Chromosome *pChromosome = new (&buffer[j]) Chromosome[col]; 
    chromosomes.push_back(pChromosome);
    j = j+ sizeof(Chromosome) * col;
}

for(int i = 0 ; i < row ; i++ )
{
      for(int j = 0 ; j < col ; j++ )
      {
         //only destruction of object. No deallocation!
         chromosomes[i][j].~Chromosome();
      }
}
delete [] buffer; //actual deallocation of memory at once!

检查你的析构函数

如果您正在分配一个内置类型,例如int,那么分配300000个类型将比相应的delete更昂贵。但这是一个相对的术语,在单个块中分配的300k非常快

当您分配300k染色体时,分配器必须为染色体对象分配300k*sizeo,正如您所说,它的速度很快-我看不出它除了ie构造函数调用被优化为空之外还有什么作用


然而,当你开始删除时,它不仅会释放所有内存,还会调用每个对象的析构函数,如果速度慢,我猜当你有300k个对象时,每个对象的析构函数都会花费很小但很明显的时间。

检查你的析构函数

如果您正在分配一个内置类型,例如int,那么分配300000个类型将比相应的delete更昂贵。但这是一个相对的术语,在单个块中分配的300k非常快

当您分配300k染色体时,分配器必须为染色体对象分配300k*sizeo,正如您所说,它的速度很快-我看不出它除了ie构造函数调用被优化为空之外还有什么作用

然而,当您要删除时,它不仅释放所有内存,而且还为每个对象调用析构函数,如果速度慢,我想每个对象的析构函数都需要一个很小但很明显的时间,当您有300k个对象时。

std::vector可能会有所帮助

特殊的内存分配器

std::vector可以帮助您


特殊的内存分配器

>一般注意,你不应该手动管理C++中的内存。这将导致泄漏、双重删除和所有问题。为此使用适当的资源处理类。例如,std::vector是管理动态分配的数组时应该使用的

要回到您手头的问题,首先需要知道delete[]_perm[i]的作用:它为该数组中的每个染色体对象调用析构函数,然后释放内存。现在在循环中执行此操作,这意味着将调用所有染色体析构函数并执行f释放。正如在对你的问题的评论中已经提到的,很可能是染色体析构函数才是真正的罪魁祸首。试着调查一下

但是,您可以更改内存处理以提高分配和解除分配的速度,您可以分配一大块内存并使用它。我将使用std::vector作为缓冲区:

void f(std::size_t row, std::size_t col)
{
  int sizeMemory = sizeof(Chromosome) * row * col;
  std::vector<unsigned char> buffer(sizeMemory); //allocation of memory at once!

  vector<Chromosome*> chromosomes(row);

  // use algorithm as shown by Nawaz
  std::size_t j = 0 ;
  for(std::size_t i = 0 ; i < row ; i++ )
  {
      //...
  }

  make_baby(chromosomes); //use chromosomes

  in_place_destruct(chromosomes.begin(), chromosomes.end());

  // automatic freeing of memory holding pointers in chromosomes
  // automatic freeing of buffer memory
}

template< typename InpIt >
void in_place_destruct(InpIt begin, InpIt end)
{
  typedef std::iterator_traits<InpIt>::value_type value_type; // to call dtor
  while(begin != end)
    (begin++)->~value_type(); // call dtor
}

一般来说,你不应该手动管理C++中的内存。这将导致泄漏、双重删除和所有问题。为此使用适当的资源处理类。例如,std::vector是管理动态分配的数组时应该使用的

要回到您手头的问题,首先需要知道delete[]_perm[i]的作用:它为该数组中的每个染色体对象调用析构函数,然后释放内存。现在在循环中执行此操作,这意味着将调用所有染色体析构函数并执行f释放。正如在对你的问题的评论中已经提到的,很可能是染色体析构函数才是真正的罪魁祸首。试着调查一下

但是,您可以更改内存处理以提高分配和解除分配的速度,您可以分配一大块内存并使用它。我将使用std::vector作为缓冲区:

void f(std::size_t row, std::size_t col)
{
  int sizeMemory = sizeof(Chromosome) * row * col;
  std::vector<unsigned char> buffer(sizeMemory); //allocation of memory at once!

  vector<Chromosome*> chromosomes(row);

  // use algorithm as shown by Nawaz
  std::size_t j = 0 ;
  for(std::size_t i = 0 ; i < row ; i++ )
  {
      //...
  }

  make_baby(chromosomes); //use chromosomes

  in_place_destruct(chromosomes.begin(), chromosomes.end());

  // automatic freeing of memory holding pointers in chromosomes
  // automatic freeing of buffer memory
}

template< typename InpIt >
void in_place_destruct(InpIt begin, InpIt end)
{
  typedef std::iterator_traits<InpIt>::value_type value_type; // to call dtor
  while(begin != end)
    (begin++)->~value_type(); // call dtor
}

染色体的析构函数中有什么昂贵的东西吗?嗯,perm是2D染色体数组,其中每个染色体都包含基因*基因的数组;。要安排的任务和基因一样多。每个基因都包含两个数组:int start[2]和int end[2]。显而易见的问题是:为什么需要这么多的分配?你为什么需要指针?难道不能全部存储在少量的对象中,以获得更好的缓存位置和引导性能吗?可能在构造函数或析构函数中有磁盘或其他I/O吗?除了对每个元素多次调用destructor之外,这是唯一一件会导致构造和破坏之间如此巨大差异的事情。还有,这是一个什么样的平台

继续跑?嵌入式PC?染色体的析构函数中有什么昂贵的东西吗?嗯,perm是2D染色体数组,其中每个染色体都包含基因*基因的数组;。要安排的任务和基因一样多。每个基因都包含两个数组:int start[2]和int end[2]。显而易见的问题是:为什么需要这么多的分配?你为什么需要指针?难道不能全部存储在少量的对象中,以获得更好的缓存位置和引导性能吗?可能在构造函数或析构函数中有磁盘或其他I/O吗?除了对每个元素多次调用destructor之外,这是唯一一件会导致构造和破坏之间如此巨大差异的事情。还有,这是在什么样的平台上运行的?PC,嵌入式?问题是我不能一次完成所有操作-这就是为什么我必须在使用new的循环中使用delete。我的程序最初是Flow Shop的遗传算法,但现在我在同样的结构上编写蛮力,因为大多数表示保持不变。对于5个任务,有factorialnTasks*factorialnTasks+nTasks-1种可能性-我没有足够的RAM来分配所有任务once@Overpain:可以立即完成内存分配和释放。至少这部分可以快速完成,而不需要太多了解您的情况。其他一切都在你的控制之下。您可以随意调用对象的析构函数;你也可以一个一个地删除它们@纳瓦兹:我不知道染色体ctor是否会抛出,更不知道实际使用这些染色体的代码。这就是为什么我坚持使用异常安全的解决方案。即使代码现在不能抛出,也可能在维护之后抛出。由于需要执行销毁所有对象的循环,因此无法轻松确保此异常的安全性。解决这个问题的最佳方法是将该循环放入类的析构函数中。但一旦这样做了,您就可以将二维访问作为一维数组的纯包装。看到我的答案了。@sbi:但你还是得破坏这些物体;所以当你说如果我破坏循环中的对象不是异常安全的时候,我很困惑。你能进一步解释一下它与将这个循环放入类的析构函数有什么不同吗?@Nawaz:你的代码所做的就是构造所有这些对象,然后立即将它们析构函数。首先,如果其中一个CTOR抛出,您将不得不调用迄今为止构造的所有对象的DTOR,但不能调用从未构造过的对象的DTOR。此外,在构建和销毁之间,可能会有使用这些对象的代码。在这种情况下,所有对象都需要调用其dtor。在这两种情况下,您的代码都不会破坏任何对象。这是错误的-尤其是这样,因为它们所在的内存将被自动破坏。问题是我不能同时处理所有内容-这就是为什么我必须在使用new的同一循环中使用delete。我的程序最初是Flow Shop的遗传算法,但现在我在同样的结构上编写蛮力,因为大多数表示保持不变。对于5个任务,有factorialnTasks*factorialnTasks+nTasks-1种可能性-我没有足够的RAM来分配所有任务once@Overpain:可以立即完成内存分配和释放。至少这部分可以快速完成,而不需要太多了解您的情况。其他一切都在你的控制之下。您可以随意调用对象的析构函数;你也可以一个一个地删除它们@纳瓦兹:我不知道染色体ctor是否会抛出,更不知道实际使用这些染色体的代码。这就是为什么我坚持使用异常安全的解决方案。即使代码现在不能抛出,也可能在维护之后抛出。由于需要执行销毁所有对象的循环,因此无法轻松确保此异常的安全性。解决这个问题的最佳方法是将该循环放入类的析构函数中。但一旦这样做了,您就可以将二维访问作为一维数组的纯包装。看到我的答案了。@sbi:但你还是得破坏这些物体;所以当你说如果我破坏循环中的对象不是异常安全的时候,我很困惑。你能进一步解释一下它与将这个循环放入类的析构函数有什么不同吗?@Nawaz:你的代码所做的就是构造所有这些对象,然后立即将它们析构函数。首先,如果其中一个CTOR抛出,您将不得不调用迄今为止构造的所有对象的DTOR,但不能调用从未构造过的对象的DTOR。此外,在构建和销毁之间,可能会有使用这些对象的代码。在这种情况下,所有对象都需要调用其dtor。在这两种情况下,您的代码都不会破坏任何对象。这是错误的-尤其是这样,因为它们所在的内存将被自动破坏
pact是可以忽略的,这不是不可能的,但是dtor执行任何明显的操作是非常不寻常的。不一定-有一个范例,其中ctor为空,然后在构造后通过初始化调用初始化对象。我见过,甚至可能用过一次。即使你有两个阶段的建设哦,恐怖!,初始化必须以某种方式进行。正如我所说的,构造初始化比去初始化便宜得多的示例很容易,但在实践中它们很少。这取决于类中的数据存储。在实践中发生什么并不重要,重要的是在提问者的课堂上发生了什么。因为他使用new来分配一个对象数组而不是向量,所以我猜他在类中有POD类型,所以分配应该和单个malloc一样快!我还假设他正在做大量的检查,对其他对象进行扩展清理,甚至登录他的dtor,这导致了问题。如果ctor的性能影响可以忽略,这不是不可能的,但dtor执行任何明显的操作是非常不寻常的。不一定-存在ctor为空的范例,然后在构造之后通过初始化调用初始化对象。我见过,甚至可能用过一次。即使你有两个阶段的建设哦,恐怖!,初始化必须以某种方式进行。正如我所说的,构造初始化比去初始化便宜得多的示例很容易,但在实践中它们很少。这取决于类中的数据存储。在实践中发生什么并不重要,重要的是在提问者的课堂上发生了什么。因为他使用new来分配一个对象数组而不是向量,所以我猜他在类中有POD类型,所以分配应该和单个malloc一样快!我还假设他正在做大量的检查,对其他对象进行扩展清理,甚至登录他的dtor,这会导致问题。