假设我有一个带有许多小函数的c ++代码,在每个函数中我通常需要一个矩阵浮点M1(n,p),其中n,p在运行时已知包含中间计算的结果(不需要)初始化M1,只是为了声明它,因为每个函数都会覆盖M1的所有行。
部分原因是每个函数都在一个无法修改的原始数据矩阵上工作,因此需要在“其他地方”进行许多操作(排序,去意义,sphering)。
更好的做法是在每个函数中创建一个临时的M1(n,p),或者更确切地说是在main()中创建一次,并将其传递给每个函数作为一种存储桶,每个函数都可以用作废料空间?
对于n,n和p通常为中等大[10 ^ 2-10 ^ 4],对于p为<5-100]。(最初发布于codereview stackexchange但在此处移动)。
最佳,
答案 0 :(得分:2)
不要使用过早优化。创建一些正常运行良好的东西,如果显示速度慢,可以稍后进行优化。
(顺便说一下,我认为stackoverflow也不是正确的地方)。
实际上,如果你想加速在大型矩阵上运行的应用程序,使用并发性将是你的解决方案。如果你使用并发,如果你有一个大的全局矩阵,你可能会遇到更多麻烦。
基本上它意味着你一次不会有多个计算,即使你有记忆。
矩阵的设计需要是最佳的。我们必须看看这个设计。
因此,我会在你的代码中说,不,不要创建一个大的全局矩阵,因为你想用它做什么听起来是错误的。
答案 1 :(得分:2)
我建议你自然地编写代码,考虑到#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的建议,因为它比创建分配器简单得多,但我们都强调加速版可选。
答案 2 :(得分:1)
首先尝试在函数内定义矩阵。这绝对是更好的设计选择。但是如果你得到性能损失,你就无法表达,我认为“每个引用的传递缓冲区”是可以的,只要你记住这些函数不再是线程安全的。如果您在任何时候使用线程,则每个线程都需要它自己的缓冲区。
答案 3 :(得分:1)
在需要外部提供的缓冲区时,在性能方面有优势,特别是当您需要链接使用它的函数时。
然而,从用户的角度来看,它很快就会变得烦人。
我经常发现,在C ++中通过简单地提供两种方式来充分利用这两个方面是非常简单的:
int compute(Matrix const& argument, Matrix& buffer);
inline int compute(Matrix const& argument) {
Matrix buffer(argument.width, argument.height);
return compute(argument, buffer);
}
这种非常简单的包装意味着代码只写了一次,并且呈现了两个稍微不同的界面。
涉及越多的api(采用buffer
)也稍微不那么安全,因为buffer
必须遵守参数中的某些大小限制,因此您可能希望进一步隔离 fast api(例如在命名空间后面)以鼓励用户首先使用速度较慢但更安全的接口,并且只有在证明有必要时才尝试快速接口。