2012-12-29 57 views
2

我正在编写模板矩阵类,并且当从运算符返回值时,得到堆栈溢出:+, - ,*适用于较大的矩阵。我宁愿以某种方式通过参考返回缓解堆栈,以避免额外的复制,但后来,我还得回去与构造一个对象,并打破“使用删除为每”的一般规则。由于复制开销和堆栈限制问题,我无法按值返回,而且由于内存泄漏,我也无法通过引用返回,所以我应该怎么做?通过引用返回的C++矩阵类重载运算符

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

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加速的线性代数库,但是我找不到它们是一个带有+, - ,*运算符的简单矩阵类。也许有人可以推荐我吗?

+0

你能返回一个共享指针吗? – andre

+0

你有没有听说过'pImpl'模式? – Yakk

回答

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*不返回一个矩阵,而是一个辅助 类,沿着线: 模板 类MatrixMultiply函数 { L恒定* myLhs; R const * myRhs; public: typedef T value_type; MatrixMultiply函数(L常量&LHS,R常量&右) :myLhs(左轴) ,myRhs(右轴) { } INT的getX()const的 { 返回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: 模板 MatrixMultiply函数 运算符*(L常量&LHS,R常量&右) { 返回MatrixMultiply函数(左,右); } (请注意,如果L::value_typeR::value_type不 相同,这不会编译。这是你想要什么,除了 的错误信息会很不明朗。) 的结果是,你永远实际上建立了中间体,临时矩阵。 正如你所想象的,上述解决方案大大简化了。 您需要额外的错误处理代码,我不认为并行化是微不足道的。但它避免了所有中间矩阵的构造,即使在复杂的 表达式中。 (同样的技术可以用一个抽象基类一起使用, 说MatrixAccessor,与纯虚吸气剂,并导出 Matrix和从 它。IMHO所有像MatrixMultiply的助手,这是很多的可读性,并且从编译器错误消息 肯定会更容易理解。结果将是 一样,只要编译器实际上是内联的所有成员 功能。但是,这是一个很大的如果,因为可以有 显著函数嵌套。 )
+0

谢谢你的回答(以及其他问题),我现在看到解决方案不会像我想象的那么简单。我认为这将需要很多进一步的阅读才能继续.. – deFenestra

+0

@deFenestra这一切都取决于你想达到什么,以及性能问题。只需将数据移动到一个'std :: vector',并将结果直接放入返回的矩阵,而不是中间的'T prod [n] [m]',就可以解决堆栈溢出问题。我的其他建议实际上只是您可能想要长期思考的想法。 (如果你正在处理大型矩阵和复杂的表达式,这些中间临时对象可能会用完所有的堆,在这种情况下,你需要我最后的建议。) –

1

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

一个解决办法当然有三操作数的操作,这样反而:

a = b + c; 

您使用的功能:

加(A,B,C);

其中a,b和c是参考。

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

1

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

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

但是,我希望你照顾一些问题。复制构造函数必须真正复制矩阵,而不仅仅是一个指针(或者几个析构函数将试图销毁相同的内存),或者你可以编程实际上复制矩阵的“懒惰复制”模型(请参阅wiki)。或者你可以在不实现的情况下使拷贝构造函数保持私有状态(以防止拷贝)。 如果您不允许创建诸如“setElement”之类的方法,因为您不希望库的用户更改矩阵值,则可以访问私有数据和方法(即使在作为参数或新获得的对象中创建)在这样的操作符中,因为你在类方法中。

如果您需要将原始指针传递给其他计算函数,因为它在“其他”部分完成,您可以从一个只复制一个指针的指针构造一个构造函数(但这是危险的方式。你不能在任何地方访问它,因为矩阵类现在是老板),或者完全复制数据(虽然速度较慢,但​​你可以根据需要传递堆栈指针或需要控制的指针),析构函数会清除它矩阵破坏时。或者你甚至可以创建诸如“getRawMatrix()”之类的私有方法,该方法会将原始指针返回给矩阵中的数据,并将此指针传递给您的计算函数,或者甚至只是获取原始数据指针,因为您位于矩阵类的一个方法中。

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