- 堆分配确实非常昂贵。
- 过早的优化是不好的,但是如果您的库非常通用并且矩阵很大,那么寻求有效的设计可能还为时过早。毕竟,你不想在积累了很多依赖之后修改你的设计。
- 你可以在不同的层次上解决这个问题。例如,您可以通过在内存分配器级别(例如,每线程内存池)处理堆分配费用来避免堆分配费用
- 虽然堆分配很昂贵,但您创建一个巨大的矩阵只是为了对矩阵进行一些相当昂贵的操作(通常是线性复杂性或更糟)。相对而言,在免费存储中分配一个矩阵可能并不像你随后不可避免地要处理的那样昂贵,因此与排序等函数的整体逻辑相比,它实际上可能相当便宜。
我建议您自然地编写代码,考虑到 #3 作为未来的可能性。也就是说,不要在中间计算中引用矩阵缓冲区来加速临时对象的创建。制作临时物品并按价值返回。正确性和良好、清晰的界面是第一位的。
这里的主要目标是分离矩阵的创建策略(通过分配器或其他方式),这为您提供了在不更改太多现有代码的情况下进行优化的喘息空间。如果您可以通过仅修改所涉及函数的实现细节来做到这一点,或者更好的是,只修改矩阵类的实现,那么您真的很富裕,因为您可以在不更改设计的情况下自由优化,并且从效率的角度来看,任何允许这样做的设计通常都是完整的。
警告:以下内容仅适用于您真的想充分利用每个周期的情况。理解 #4 并让自己成为一个好的分析器是很重要的。还值得注意的是,通过优化这些矩阵算法的内存访问模式,您可能会比尝试优化堆分配做得更好。
如果您需要优化内存分配,请考虑使用诸如每线程内存池之类的通用方法对其进行优化。例如,您可以让您的矩阵采用可选的分配器,但我在这里强调可选,并且我也会首先通过简单的分配器实现来强调正确性。
换句话说:
在每个函数中声明 M1(n,p) 是更好的做法,还是在 main() 中一劳永逸地声明,并将其作为一种桶传递给每个函数,每个函数都可以将其用作废料空间。
继续在每个函数中创建 M1 作为临时。尽量避免要求客户制作一些对他/她没有意义的矩阵,仅用于计算中间结果。那将暴露一个优化细节,这是我们在设计界面时不应该做的事情(隐藏客户不应该知道的所有细节)。
相反,如果您绝对希望该选项加速创建这些临时对象,例如可选分配器,请关注更一般的概念。这符合实际设计,例如std::set
:
std::set<int, std::less<int>, MyFastAllocator<int>> s; // <-- okay
即使大多数人只是这样做:
std::set<int> s;
在您的情况下,它可能只是: M1 my_matrix(n, p, alloc);
这是一个细微的区别,但是分配器是一个比缓存矩阵更通用的概念,我们可以使用缓存矩阵,否则它对客户端没有任何意义,除非它是您的函数需要的某种缓存,以帮助它们更快地计算结果。请注意,它不必是通用分配器。它可能只是传递给矩阵构造函数的预分配矩阵缓冲区,但从概念上讲,仅仅因为它对客户来说有点不透明,所以将它分开可能会很好。
此外,构建这个临时矩阵对象还需要注意不要跨线程共享它。这是另一个原因,如果您确实采用优化路线,您可能希望稍微概括一下这个概念,因为像矩阵分配器这样更通用的东西可以考虑线程安全,或者至少在设计上更加强调单独的分配器应该每个线程都可以创建,但原始矩阵对象可能不能。
仅当您首先真正关心界面的质量时,上述内容才有用。如果没有,我会推荐 Matthieu 的建议,因为它比创建分配器要简单得多,但我们都强调使加速版本optional。