3

我正在编写模板化的矩阵类,当从运算符按值返回时出现堆栈溢出:+、-、* 对于较大的矩阵。我宁愿以某种方式通过引用返回以减轻堆栈并避免额外的复制,但是,我将不得不返回一个用new构造的对象,并打破“对每个new使用delete ”的一般规则。由于复制开销和堆栈限制问题,我无法按值返回,并且由于内存泄漏,我也无法按引用返回,那我该怎么办?

这是我的产品功能(矩阵包含二维数组元素):

    template<typename T, unsigned int n, unsigned int m> template<unsigned int m2>
Matrix<T,n,m2> Matrix<T,n,m>::operator*(Matrix<T,m,m2>& M) {
    T prod[n][m2];
    if(n*m < GPUAccelerationThreshold)
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m2; j++) {
                prod[i][j] = elems[i][0] * M(0, j); 
                for(int p = 1; p < m; p++)
                    prod[i][j] += elems[i][p] * M(p, j); 
            }
    else {
        array_view<T, 2> product(n, m2, *prod);
        array_view<T, 2> a(n, m, *elems);
        array_view<T, 2> b(m, m2, M.elems[0]);

        parallel_for_each(
            product.extent, 
             [=](index<2> idx) restrict(amp) {
                int row = idx[0];
                int col = idx[1];
                for (int inner = 0; inner < m; inner++) {
                    product[idx] += a(row, inner) * b(inner, col);
                }
            }
        );
        product.synchronize();
    }


    return Matrix<T,n,m2>(prod);
}

我正在写这门课,因为我想在 GPU(使用 MS amp)上增强一些矩阵运算。我搜索了一个现有的解决方案,找到了 GPU 加速的线性代数库,但我在其中找不到一个带有 +、-、* 运算符的简单矩阵类。也许有人可以推荐我吗?

4

3 回答 3

3

三个快速评论:

  • 传统上,Matrix类使用动态分配。您不显示您的Matrix班级,但如果您的数据是:
    T myData[n][m];
    
    您可能希望将其更改为:
        std::vector myData;
    
    ,将其初始化为n * m构造函数中的大小,并计算其中的单个索引operator[](如果您想进行任何边界检查,它应该返回一个代理)。或者,您可以使用operator()( int i, int j )访问元素:是否访问myMatrix( i, j )myMatrix[i][j]首选访问取决于您询问的人。尽管此解决方案略微增加了总内存使用量(但非常轻微),但它将堆栈占用量减少到几十个字节,而与矩阵的大小无关。
  • 同样传统上,矩阵类没有将维度作为其模板参数的一部分。这是否是一件好事是有争议的。您可以使用您的解决方案获得更好的类型检查(以及编译时的错误,而不是运行时),但如果维度是构造函数的参数,而不是模板参数,您可以从命令行或配置文件或其他任何内容中读取它们. 这是经典的安全性与灵活性的权衡。关于您的问题,没有维度作为模板参数意味着所有类型的矩阵T都具有相同的类型。因此,您可以访问从成员函数返回的矩阵的内部,并且不再需要中间的T prod[n][m2]. 当然,你可以进行所有的实例化Matrix朋友,或者只是使用访问函数来设置值。无论如何,您想要中间件T prod[n][m2];这不仅需要大量的堆栈内存,还意味着您必须复制结果。
  • 最后,这有点更高级:在最好的矩阵类中,operator*不返回矩阵,而是返回一个辅助类,类似于:template class MatrixMultiply { L const* myLhs; R 常量* myRhs; 公共:typedef T value_type;MatrixMultiply( L const& lhs, R const& rhs ) : myLhs( &lhs ) , myRhs( &rhs ) { } int getX() const { return myLhs->getX(); } int getY() const { return myRhs->getY(); } T get( int i, int j ) const { return calculateIJ( myLhs, myRhs ); } }; 然后,您提供一个模板化的构造函数和赋值运算符,它使用getX(),getY()get( i, j ). 您的 operator*也是一个模板,它返回一个 MatrixMultiply: template MatrixMultiply operator*( L const& lhs, R const& rhs ) { return MatrixMultiply( lhs, rhs ); (请注意,如果L::value_typeR::value_type不相同,这将不会编译。这是您想要的,除了错误消息将很不明确。)结果是您实际上从未构建中间的临时矩阵。可以想象,上面的解决方案被大大简化了。您将需要额外的代码来处理错误,而且我认为并行化并不是微不足道的。但它避免了所有中间矩阵的构造,即使是复杂的表达式。(同样的技术也可以用在抽象基类上,比如说MatrixAccessor,用纯虚拟的 getter 和派生 Matrix所有的助手都喜欢MatrixMultiply它。恕我直言,这更具可读性,并且来自编译器的错误消息肯定会更容易理解。只要编译器实际内联所有成员函数,结果就会相同。但这是一个很大的if,因为可能存在重要的函数嵌套。)
于 2012-12-29T15:38:12.287 回答
1

没有简单的方法可以解决这个问题。您不能返回堆栈局部变量作为参考,因为当您返回时,变量“后面”的内存将消失。所以你必须在某个地方有一些专用的存储空间。它不一定来自新/删除,但在制作数据副本时确实需要某种存储。

一种解决方案当然是使用三操作数操作,所以不是:

a = b + c;

你使用一个函数:

添加(a,b,c);

其中 a、b 和 c 是引用。

它确实使代码变得更加混乱,但我想不出任何更明显的方法来解决问题 - 如果您不想编写自己的分配器/删除函数(或垃圾收集器)。

于 2012-12-29T14:59:09.797 回答
1

其实我不能完全理解你的想法......二元运算符需要两个参数并创建结果。事实上,您可以看到您正在返回一个新创建的对象。所以这是编写程序的唯一方法:分配内存,使用它,然后删除它。事实上,我什至不明白你的构造函数是做什么的。如果它只是将指针复制到“prod”,那么当您从函数返回时结果矩阵会被破坏,因为“prod”内存将在函数返回后立即被删除(因为它是在堆栈上创建的)。所以你也不能通过引用返回它。

我看到解决方案在矩阵构造函数中分配内存的方式。如果您根据矩阵大小将其制作为模板,则矩阵的大小可以从模板参数中得知(我发现制作以矩阵大小作为参数的模板很奇怪。这有什么意义?)。因此,您在构造函数中使用“new”分配内存,并在析构函数中使用“delete”将其删除。因此,该内存将根据在 OOP 中运行良好的 RAII 方法进行分配。然后您实现诸如 setElement(i, j, value) 之类的方法,并在二元运算符中将元素设置在新创建的矩阵中并返回它。

有一些问题,但是我希望你照顾。复制构造函数必须真正复制矩阵而不仅仅是一个指针(或者几个析构函数会尝试破坏相同的内存),或者您可以编写“延迟复制”模型,在更改时实际复制矩阵(参见 wiki)。或者您可以在不实现的情况下将复制构造函数设为私有(以完全防止复制)。如果由于您不希望库的用户更改矩阵值而不允许创建诸如“setElement”之类的方法,则可以访问私有数据和方法(即使在我们作为参数或新创建的对象中) ) 在此类运算符中,因为您在类方法中。

如果您需要将原始指针传递给其他计算函数,就像在“else”部分完成的那样,您可以从只复制一个指针的指针创建一个构造函数(但这是危险的方式。如果您传递一个指针,则不能访问它无处可去,因为矩阵类现在是老板)或完全复制数据(速度较慢,但​​您可以根据您的意愿从堆栈中传递指针或需要控制的指针),并且析构函数将在矩阵破坏时清除它。或者您甚至可以创建私有方法,例如“getRawMatrix()”,它将从矩阵返回指向数据的原始指针并将此指针传递给您的计算函数,或者甚至简单地获取原始数据指针,因为您在矩阵类的方法中.

通常您在构造函数中分配内存并制作“惰性复制”模型,因为矩阵可能很大。允许在类内部访问私有数据和成员,这就是类的创建。我希望它会有用..

于 2012-12-29T15:41:21.357 回答