2012-12-29 5 views
2

저는 템플릿 기반 행렬 클래스를 작성하고 있습니다. 더 큰 행렬에 대해서는 연산자로 값을 반환 할 때 스택 오버플로가 발생합니다. 나는 스택을 완화하기 위해 어떻게 든 참조로 반환하는 것을 선호하고 여분의 복사를 방지하기 위해, 그러나, 나는 새로운로 구성 객체를 반환해야하고 "모든 새로운에 대한을 삭제 를 사용하여"의 일반적인 규칙을 깰 . 오버 헤드 및 스택 제한 문제를 복사하여 값으로 반환 할 수 없으며 메모리 누수로 인해 참조로 되돌릴 수 없으므로 어떻게해야합니까?참조로 반환하는 C++ 행렬 클래스 오버로드 연산자

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); 
} 

내가 (MS 앰프와) GPU에 대한 몇 가지 행렬 연산을 강화하려는 때문에이 클래스를 쓰고 있어요 :

여기 내 제품 기능 (매트릭스 2 차원 배열 elems를 포함)이다. 기존 솔루션을 찾고 GPU 가속 선형 대수 라이브러리를 찾았지만 찾을 수없는 것은 +, -, * 연산자가있는 간단한 행렬 클래스였습니다. 누군가 나 한테 추천 할 수 있을까?

+0

공유 포인터를 돌려 줄 수 있습니까? – andre

+0

''pImpl' 패턴에 대해 들어 보셨습니까? – Yakk

답변

3

세 빠른 코멘트 :

  • 전통적으로 Matrix 클래스는 동적 할당을 사용했다. 당신은 당신의 Matrix 클래스를 표시하지 않습니다,하지만 데이터가있는 경우 다음 operator[]에서 단일 인덱스를 계산 생성자에서 크기 n * m로 초기화
     
        std::vector myData; 
    
    및 :
     
    T myData[n][m]; 
    
    당신은 그것을 변경할 수 있습니다 (범위 검사를 수행하려면 프록시를 반환해야 함). 에 액세스하려면 operator()(int i, int j)을 사용할 수도 있습니다. myMatrix(i, j) 또는 myMatrix[i][j]이 누구를 요구하는지에 따라 액세스 여부가 결정됩니다. 이 솔루션은 총 메모리 사용량을 약간 늘리지 만 ( 매우 미미한) 행렬의 크기에 관계없이 스택 풋 프린트를 12 비트 수로 줄입니다.
  • 또한 전통적으로 행렬 클래스는 템플릿 인수의 일부인 의 크기를 갖지 않았습니다.이것이 좋은 것인지 인지 아닌지는 논쟁의 여지가 있습니다. 솔루션과 함께 (런타임이 아닌 컴파일시 오류) 하지만 치수가 템플릿 인수보다 이 아니라 생성자에 대한 인수 인 경우 명령 행 에서 읽을 수 있습니다. 구성 파일 또는 뭐든간에. 고전적인 안전 대 유연성의 트레이드 오프입니다. 문제와 관련하여 차원을 템플릿 매개 변수로 지정하지 않으면 T 유형의 모든 행렬에 동일한 유형이 있음을 의미합니다. 따라서 회원의 기능에서 반환 매트릭스의 내부에 액세스 할 수 있으며 더 이상 중간 필요하지 않습니다. 물론 인스턴스를 Matrix 친구로 만들거나 액세스 기능을 사용하여 값을 설정할 수 있습니다. 어쨌든 이 아니며 중간에 T prod[n][m2]을 입력하고 싶습니다. 이것은 단지 스택 메모리를 필요로 할뿐만 아니라 결과를 복사해야한다는 것을 의미합니다. 템플릿 클래스 MatrixMultiply { L의 CONST :의 라인을 따라 클래스, operator*이 행렬을 반환하지 않는 최선의 매트릭스,하지만 도우미 클래스 :
  • 마지막으로 , 이것은 좀 더 고급이다 * myLhs; R const * myRhs; 공개 : typedef T value_type; MatrixMultiply (L & CONST의 좌, R의 CONST 및 우) : 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 & CONST의 좌, R의 CONST 및 우) { 복귀 MatrixMultiply (좌, 우)를; } 은 (L::value_typeR::value_type이 동일하지 않은 경우,이 컴파일되지 않습니다. 오류 메시지가 명확하지 될 것으로 제외하고, 당신이 원하는 어느.) 그 결과 당신이 결코 실제로 중급, 임시 매트릭스를 구축하십시오. 위의 솔루션이 크게 단순화되었습니다. 오류 처리를 위해 추가 코드가 필요하며 병렬 처리가 간단하지 않다고 생각합니다. 그러나 복잡한 표현에서도 모든 중간 행렬을 구성하지 않습니다. (추상 기본 클래스를 사용하여 사용할 수있는 동일한 기술은, 가 순수 가상 게터와, MatrixAccessor 말을하고 Matrix 및 그것. 이럴에서 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"와 같은 메서드를 만들 수없는 경우에는 개인 데이터 및 메서드에 액세스 할 수 있습니다 (인수 또는 새로 가져온 개체에서도 가능) 클래스 메서드 안에 있기 때문에 그러한 연산자에서 생성됩니다.

"else"부분에서 행해졌 듯이 원시 포인터를 다른 계산 함수에 전달해야하는 경우 포인터를 복사하는 포인터로 생성자를 만들 수 있습니다 (그러나 이것은 위험한 방법입니다. 포인터를 전달하면 매트릭스 클래스가 보스이기 때문에 어디에도 접근하지 말아야한다.) 또는 데이터를 완전히 복사한다. (속도는 느리지 만 스택이나 포인터에서 포인터를 전달할 수있다.) 소원에 따라 다르다. 소멸자가 그것을 지울 것이다. 행렬이 파괴 될 때. 또는 행렬의 데이터에 원시 포인터를 반환하고이 포인터를 계산 함수에 전달하거나 심지어 행 데이터 클래스의 메서드 내부에 있기 때문에 원시 데이터 포인터를 가져 오는 "getRawMatrix()"와 같은 개인 메서드를 만들 수도 있습니다 .

일반적으로 행렬이 커질 수 있으므로 생성자에서 메모리를 할당하고 "지연 복사"모델을 만듭니다. 클래스 내부에서 개인 데이터와 멤버에 액세스 할 수 있습니다. 클래스가 만들어집니다. 나는 그것이 유용 할 것이기를 바랍니다.