【问题标题】:How to organize time-intensive code to avoid overhead如何组织时间密集型代码以避免开销
【发布时间】:2017-11-13 04:54:23
【问题描述】:

我正在尝试自学如何编写更快的代码(使用更少指令的代码)。我想创建一个人工神经网络(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 和 delete 完成的 或使用 malloc 和 free 函数。这些运算符和函数 消耗大量时间。内存的一部分称为堆 保留用于动态分配。堆很容易变成 分配不同大小的对象时碎片化 以随机顺序释放。堆管理器可以花很多时间 清理不再使用的空间并寻找空置空间 空格。这称为垃圾收集。分配的对象 in sequence 不一定按顺序存储在内存中。他们 当堆变成 支离破碎。这使得数据缓存效率低下”

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

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

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

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

【问题讨论】:

  • 你不需要使用new。只需使用std::vector
  • 继续您的设计。在设计级别进行优化,优化将流向实施。删除不必要的循环。数据缓存优化设计。执行缓存优化设计。
  • 更快的代码 != 更少的指令 通常是这样,但是更好地组织数据结构,即使需要更多的代码,也会带来惊人的性能提升。
  • 推荐阅读What is The Rule of Three?,因为你的班级违反了它。以上std::vector的推荐,瞬间解决了这个问题。
  • 您不需要也不想要手动内存管理。 至少使用智能指针。更好的是,只需使用 std::vectorstd:array

标签: c++ arrays memory optimization memory-management


【解决方案1】:

堆内存没有什么不好或慢的。但是,在某些情况下,您应该小心使用它。真正影响性能的主要问题之一是当有很多小对象分散在堆内存中时(因为每次分配都可以将新对象放置在内存中的几乎任何位置)。在这种情况下会发生这种情况,例如:

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

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

另一个问题是当您在应用程序中很长时间没有使用分配的内存并经常重新分配内存时。这也会影响您的性能,因为动态内存分配操作并非微不足道,它源于动态内存的性质。该内存不知道您要分配的数据以及何时分配它们。所以,简单地说,它是一张记忆片段表,知道哪些忙哪些不忙。现在成像你经常分配很多小对象,删除一些旧的。这就失控了。根据动态内存的实现,可能是内存碎片等操作,这种操作可能取决于您分配/取消分配内存的频率。当然,这也会影响性能。

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

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

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

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

【讨论】:

    猜你喜欢
    • 2016-10-26
    • 2019-03-16
    • 2023-04-01
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多