2

假设我有一个包含许多小函数的 c++ 代码,在每个函数中,我通常需要一个矩阵 float M1(n,p),其中 n,p 在运行时已知以包含中间计算的结果(无需初始化 M1 ,只是为了声明它,因为每个函数只会覆盖 M1 的所有行)。

部分原因是每个函数都在无法修改的原始数据矩阵上工作,因此需要在“其他地方”完成许多操作(排序、去意义、球化)。

在每个函数中创建一个临时的 M1(n,p) 是更好的做法,还是在 main() 中一劳永逸地把它作为一种桶传递给每个函数,每个函数都可以将其用作废料空间?

n 和 p 通常对于 n 和 p 来说是中等大 [10^2-10^4] 和 [5-100]。

(最初发布在 codereview stackexchange 但移至此处)。

最好的,

4

4 回答 4

2
  1. 堆分配确实非常昂贵。
  2. 过早的优化是不好的,但是如果您的库非常通用并且矩阵很大,那么寻求有效的设计可能还为时过早。毕竟,你不想在积累了很多依赖之后修改你的设计。
  3. 你可以在不同的层次上解决这个问题。例如,您可以通过在内存分配器级别(例如,每线程内存池)处理堆分配费用来避免堆分配费用
  4. 虽然堆分配很昂贵,但您创建一个巨大的矩阵只是为了对矩阵进行一些相当昂贵的操作(通常是线性复杂性或更糟)。相对而言,在免费存储中分配一个矩阵可能并不像你随后不可避免地要处理的那样昂贵,因此与排序等函数的整体逻辑相比,它实际上可能相当便宜。

我建议您自然地编写代码,考虑到 #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

于 2012-03-02T08:06:12.543 回答
2

不要使用过早的优化。创建一些可以正常工作的东西,如果显示速度很慢,可以稍后进行优化。

(顺便说一句,我也不认为 stackoverflow 是正确的地方)。

实际上,如果您想加快在大型矩阵上运行的应用程序,使用并发将是您的解决方案。如果你使用并发,如果你有一个大的全局矩阵,你可能会遇到更多的麻烦。

从本质上讲,这意味着您一次不能进行多个计算,即使您有足够的内存。

您的矩阵设计必须是最优的。我们将不得不看看这个设计。

因此,我通常会在你的代码中说,不,不要创建一个大的全局矩阵,因为你想要用它做的事情听起来是错误的。

于 2012-03-02T07:47:48.850 回答
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(采用 a buffer)也稍微不安全,因为buffer必须尊重参数的一些大小限制,因此您可能希望进一步隔离快速api(例如在命名空间后面)以鼓励用户使用较慢但更安全首先是接口,只有在证明有必要时才尝试快速接口。

于 2012-03-02T08:29:01.083 回答
1

首先尝试在函数内部定义矩阵。这绝对是更好的设计选择。但是如果你得到你无法承受的性能损失,我认为“每个引用传递缓冲区”是可以的,只要你记住这些函数不再是线程安全的。如果您在任何时候使用线程,每个线程都需要它自己的缓冲区。

于 2012-03-02T07:47:29.850 回答