如何组织耗时的代码以避免开销

How to organize time-intensive code to avoid overhead

本文关键字:开销 代码 何组织      更新时间:2023-10-16

我正在尝试自学如何编写更快的代码(使用更少的指令编写代码)。我想创建一个人工神经网络(ANN)。如果你对ANN一无所知,你仍然可以帮助我,因为我的问题更多地涉及编写比ANN更快的代码。基本上,我将有大量的双打,我需要对其进行大量数学运算。我可以像这样分配我的数组:

class Network {
double *weights;
double *outputs;
public:
Network()
{
}
Network(int * sizes, int numofLayers)
{
int sum = 0;
int neuron_count = 0;
// this just ensures my weight array is the right size
for(int i = 0; i < numofLayers-1; i++)
{
neuron_count += sizes[i];
sum = sum + sizes[i]*sizes[i+1];
}
neuron_count += sizes[numofLayers-1];
weights = new double[sum];
outputs = new double[neuron_count];
}
~Network()
{
delete[] weights;
delete[] outputs;
}
};

但是,我不喜欢这样,因为我使用"new",我知道我以后可能会遇到一堆内存管理问题。我知道堆栈分配更快,如果我可以根据以下摘录提供帮助,我不应该使用动态内存:

"动态内存分配是用运算符 new 和删除完成的 或具有 malloc 和 free 的功能。这些运算符和函数 消耗大量时间。内存的一部分称为堆 保留用于动态分配。堆很容易变成 分配不同大小的对象时碎片化和 以随机顺序解除分配。堆管理器可能会花费大量时间 清理不再使用的空间并寻找空置空间 空间。这称为垃圾回收。分配的对象 按顺序不一定按顺序存储在内存中。他们 当堆变成时,可能会分散在不同的地方 支离破碎。这使得数据缓存效率低下">

在C++中优化软件 Windows 优化指南,Linux 和 Mac 平台 作者:Agner Fog。

但是,数组权重和输出将被我想在类 Network 中创建的许多函数使用;如果它们是本地的并且被解除分配,那将不起作用。我觉得要么使用新关键字,要么只是为几乎整个神经网络制作一个巨大的函数。

在正常情况下,我会说可读性对于维护代码更为重要,但我并不担心这一点,因为这更多的是学习编写快速代码。如果为耗时较长的算法编写代码的人为了使事情变得最快而编写大函数,那很好。我只想知道。

另一方面,如果我的数组只分配一次并且将在整个程序中使用,那么使用堆分配是否更明智,因为有问题的开销应该只发生一次?然后专注于使用内联函数或数学繁重代码中的某些东西。是否有任何其他缺点,例如,如果我大量访问堆以便将那里的内存移动到缓存中,这是否比大量访问堆栈上的内存以使堆栈移动到缓存(即使堆应该保持不变)更密集?写非常快的代码的人绝对避免总是新的吗?如果是这样,我有什么选择可以通过这种方式组织我的程序,并且仍然保持我的数组为用户指定的任意大小?

堆内存中没有特别的不好或缓慢的东西。但是,在某些情况下,您应该小心使用它。真正损害性能的主要问题之一是当堆内存中散布着大量小对象时(因为每个分配几乎可以在内存中的任何位置放置一个新对象)。在这种情况下会发生这种情况,例如:

for(int i = 0; i < some_size; ++i)
some_array = new MySmallObject;

这里的问题是,当您长时间将some_array用于许多操作时,缓存命中率可能非常低(这是您在与性能作斗争时可以在现代硬件上使用的主要参数之一),最终导致性能下降。这是因为每次下一次访问内存some_array[i]都可能需要访问分散的内存位置,并且无法适应缓存预测算法。

另一个问题是,当您长时间不使用分配的内存,并且长时间在应用程序中经常重新分配内存时。这也会影响您的性能,因为动态内存分配操作并非易事,它源于动态内存的性质。此内存不知道要分配的数据以及何时分配它们。所以,简单地说,它是一个记忆碎片表,知道哪些是忙碌的,哪些不是。现在成像,您经常分配许多小对象,擦除一些旧对象。这就失控了。根据动态内存的实现,可能会进行内存碎片等操作,其发生可能取决于您分配/释放内存的频率。当然,这也影响了性能。

这是与两个动态内存分配相关的两个性能杀手。一个合法的问题出现了怎么办?许多问题可以通过重新设计程序或/和重新设计算法来解决。但是,在某些情况下无法做到,那么该选项将是自定义分配器。Allocaters 是提供分配/释放内存的操作的类型。这些分配器是模板参数和构造函数参数,例如,遍布std容器,如vectorstring等。另一种选择是重载全局或成员operator new。当您自定义所有内容时,当然,您可以使用自定义设计来定义对象如何分配内存,它可能只是一个单例内存池。

手动内存管理可能是一项非常复杂的任务。一个微不足道的解决方案可能只是使用malloc来获取内存片段,并将其用作堆栈,您只需向下和向上移动指针即可。如果池内存不足,您可以中止或求助于操作系统动态内存。为什么我说malloc因为在堆栈上使用大内存预留是非常危险的,堆栈非常有限,您无法控制它,并且使用自定义分配器,在现代操作系统上非常容易超出堆栈大小。该选项可能是静态内存,但同样它可能非常不灵活,您将永远无法再扩展池,使用malloc,您可以拥有大量内存片段的琐碎列表,并在前一个耗尽时添加新内存块。

这都是关于记忆的,当然,还有很多话要说,但我想启发的要点。其他性能改进在于填补所有处理器能力:加载所有内核,采用矢量化。

对不起,我没有直接回答你的问题。因为在处理这类问题时,太担心了。这取决于您的情况:什么是域区域,要求是什么,硬件类型,任务是什么等等。