2010-03-17 22 views
5

supongo que la aplicación ingenua de un operador + para las matrices (2D por ejemplo) en C++ sería:operador + para matrices en C++

class Matrix { 

    Matrix operator+ (const Matrix & other) const { 
     Matrix result; 
     // fill result with *this.data plus other.data 
     return result; 
    } 
} 

por lo que podría utilizarlo como

Matrix a; 
Matrix b; 
Matrix c; 

c = a + b; 

¿verdad?

Pero si las matrices son grandes, esto no es eficiente ya que estamos haciendo una copia no necesaria (resultado de devolución).

Por lo tanto, si nos wa no ser eficiente tenemos que olvidar la llamada limpia:

c = a + b; 

derecho?

¿Qué sugeriría/preferiría? Gracias.

+1

¿Qué tan grandes son sus matrices? ¿Cuáles son tus objetivos de rendimiento? ¿Has intentado medir el rendimiento o el costo de la copia? –

+0

Si las matrices son incluso un poco grandes, creo que el gasto de devolver la matriz sería mucho menor que realizar la adición – randomThought

+5

¿Por qué no pasar como 'const Matrix & other'? – kennytm

Respuesta

12

El estándar de C++ otorga permiso al compilador para eludir la copia innecesaria en este caso (se denomina "optimización del valor de retorno denominado", generalmente abreviado a NRVO). Hay un "RVO" coincidente para cuando devuelve una variable temporal en lugar de una nombrada.

Casi todos los compiladores de C++ razonablemente recientes implementan NRVO y RVO, por lo que, en general, puede ignorar el hecho de que esta construcción no sería particularmente eficaz.

Edit: Yo estaba, por supuesto, hablando de la copia involucrada en devolver la nueva matriz que contiene el resultado de la adición. Probablemente no desea pasar ya sea por la entrada de referencia constante:

Matrix operator+(Matrix const &other) const { 
    Matrix result; 
    // ... 
    return result; 
} 

... o de lo contrario, paso por valor, pero devolver el valor pasado:

Matrix operator+(Matrix other) const { 
    other += *this; 
    return other; 
} 

Tenga en cuenta que esto depende de conmutatividad (es decir, realmente está haciendo b+a en lugar de a+b) así que aunque está bien para la suma, no funcionará para otras operaciones.

+0

Dave Abrahams tiene un excelente blog sobre el enfoque de paso por valor aquí: http://cpp-next.com/archive/2009/08/want-speed-pass -by-value/ – Void

+0

¿No sería el segundo ejemplo de código bastante llamado operator + =? Eso coincide con lo que realmente hace la implementación y luego puede devolver una referencia, no un valor. –

+0

@Laurynas: ¿Por qué el segundo ejemplo de código debe llamarse 'operator + ='? 'this' no se modifica allí. – kennytm

3

Puede devolver un valor sin activar una construcción de copia. Se llama R-Value referencias Explicado en un poco de detalle aquí http://www.artima.com/cppsource/rvalue.html

+5

Las referencias rvalue son una nueva característica para C++ 0x, por lo que no todos los compiladores actuales las incluyen. También son innecesarios en este caso. –

0

Dos maneras de resolverlo.

1) utilizar referencias:

Matrix& operator+ (Matrix& other) const { 

2) Uso copia superficial en constructor de copia. Sí, se creará nuevo objeto Matrix pero la matriz real no se creará de nuevo

+0

Su primera versión "Matrix & operator + (Matrix & other)" modificará, según creo, el operador del lado izquierdo de "+". Entonces, si escribe "a = b + c;", b se modificará. Por lo tanto, debe limitarse a "b + c". para tener resultados correctos ... – paercebal

+0

hmmm. ¿cómo modificará b? solo invocará el operador de método + para b, referencia de paso de c y referencia de retorno. – Andrey

+0

En pocas palabras: en este caso, devolver una referencia no le comprará nada. La suma de las dos entradas será (normalmente) diferente de esas dos entradas. de una forma u otra, necesita crear una nueva matriz para mantener ese resultado. –

0

me gustaría tratar de construir la matriz en la instrucción de retorno:

Matrix Matrix::operator + (const Matrix& M) 
{ 
    return Matrix(
     // 16 parameters defining the 4x4 matrix here 
     // e.g. m00 + M.m00, m01 + M.m01, ... 
    ); 
} 

De esa manera usted no va a utilizar ningún temporales .

+0

Generalmente no es factible para dimensiones arbitrarias. Además, podría dar como resultado un mal rendimiento, ya que los resultados de la matriz primero se envían a la pila de llamadas (las funciones generalmente no pueden poner 16 parámetros en los registros al optimizar su llamada ABI), luego el constructor los copia en el objeto. –

+0

Si se trata de una clase de matriz 4x4 fija, eso funciona porque la mayoría de los compiladores pueden aplicar la optimización del valor de retorno (RVO) para elide el temporal implícito. Si se trata de una clase de matriz general con números arbitrarios de filas y columnas, tendrá que hacer una temporal. Como muestra una respuesta anterior, ese temporal también puede optimizarse mediante la optimización del valor de retorno (NRVO). –

1

Como han señalado otros, su implementación no es tan cara como cree. Sin embargo, puede tener algún sentido definir métodos adicionales que modifiquen el objeto in situ para su uso en circuitos internos críticos.

EDITAR - fija el párrafo siguiente

El punto aquí es que incluso con optimizaciones valor de retorno, que aún así terminar la construcción de una variable local y luego asignar a que la variable resultado después de operador + salidas. Y destruir ese objeto extra, por supuesto. Todavía hay un objeto adicional utilizado para mantener temporalmente el resultado. Es posible hacer recuento de referencias con copy-on-write, pero eso agrega una tara descendente a cada uso de una matriz.

Estos no son problemas en el 99% de los casos, pero de vez en cuando se obtiene un caso crítico. Si tuviera que lidiar con matrices grandes, los costos indirectos de recuento de referencias serían insignificantes, pero para 2D hasta 4D hay momentos en los que puede preocuparse mucho por esos pocos ciclos adicionales, o más al respecto, aproximadamente y no poniendo el Matriz en el montón cuando lo desee en la pila o incrustado en alguna estructura/clase/matriz.

Dicho esto, en esos casos, probablemente no vayas a escribir tu propio código matricial, solo usarás las operaciones matriciales de DirectX u OpenGL o lo que sea.

+0

@ Steve314: el objetivo de [N] RVO es que * no * copie la construcción del valor de retorno. En cambio, simplemente usa el lugar donde la devolución irá * como * el objeto "local". –

+0

@Jerry - Debería haber dicho "asignación". En la línea "c = a + b", obtendrá una asignación "c = ". El está separado de a, byc, y es una tara. Esa sobrecarga está garantizada porque de otro modo la evaluación de la expresión no involucraría tanto al operador + como al operador =. Necesitas un temporal allí para darle a la tarea algo (que no es ni a ni b) para copiar. [N] RVO optimiza la copia desde dentro del operador + hacia afuera, pero un objeto intermedio todavía está allí y una operación que en su lugar modifica c en su lugar evitaría esa sobrecarga. – Steve314

+0

@Jerry - ese es el "constructor de copia" del que estaba hablando era porque de alguna manera estaba pensando "Matrix c (a + b);" en lugar de "c = a + b;". Ese constructor de copia no tiene nada que ver con cómo se implementa u optimiza operator +; simplemente usa el resultado (objeto temporal). – Steve314

0
class Matrix { 

    Matrix & operator+=(const Matrix & other) { 
     // fill result with *this.data plus other.data 
     // elements += other elements 
     return *this; 
    } 
    Matrix operator+ (const Matrix & other) { 
     Matrix result = *this; 
     return result += other; 
    } 
} 
1

Si usted está realmente preocupado por el rendimiento (¿ha perfilado?) Que probablemente no debería poner en práctica operador + en absoluto, ya que no puede controlar si va a resultar en un ser temporal no óptima creado. Simplemente implemente el operador + = y/o la función miembro add(Matrix& result, const Matrix& in1, const Matrix& in2) y deje que sus clientes creen los temporales correctos.

Si quiere que el operador + cualquiera de Jerry Coffin funcione bien.

2

Tenga en cuenta que su primera implementación ingenua es muy nativa, ya que no se pasa nada por referencia. Asumiré que este fue un ejemplo realmente ingenuo y que no hay necesidad de recordar a los lectores los beneficios de pasar por referencia en lugar de por valor.

Tenga en cuenta, también, que he utilizado los operadores de función no miembro, en lugar de las funciones de miembro, pero al final, los resultados son (casi) los mismos.

Si usted quiere estar seguro de no se creará una copia necesario, usted debe tratar de una versión no-operador:

void add(Matrix & result, const Matrix & lhs, const Matrix & rhs) ; 

Si desea hacerlo de la manera operador (que es mi solución preferida) , entonces debes asumir que el operador + creará un temporal. A continuación, debe definir tanto el operador + y el operador + =:

Matrix & operator += (Matrix & result, const Matrix & rhs) ; 
{ 
    // add rhs to result, and return result 
    return result ; 
} 

Matrix operator + (const Matrix & lhs, const Matrix & rhs) ; 
{ 
    Matrix result(lhs) ; 
    result += rhs ; 
    return result ; 
} 

Ahora, usted podría tratar de "apalancamiento" optimizaciones del compilador y escribir como:

Matrix & operator += (Matrix & result, const Matrix & rhs) ; 
{ 
    // add rhs to result, and return result 
    return result ; 
} 

Matrix operator + (Matrix lhs, const Matrix & rhs) 
{ 
    return lhs += rhs ; 
} 

Según lo propuesto por Herb Sutter en C++ Estándares de Codificación, 27. Prefiero las formas canónicas de la aritmética y de asignación de los operadores de, p48-49:

Una variación es tener operador @ [@ siendo +, -, lo que sea] aceptar su primer parámetro por valor. De esta forma, usted hace los arreglos para que el compilador realice la copia para usted implícitamente, y esto puede darle más flexibilidad al compilador al aplicar optimizaciones.

+0

No puedo creer que utilicé la palabra de moda "apalancamiento". Podría modificarme por eso ... :-D ... – paercebal

+0

Una matriz de 2x2 es bastante pequeña.La sobrecarga de copiarlo en los parámetros puede ser menor que la sobrecarga de llamada por referencia. Asumiendo, por supuesto, que 2D significa 2 * 2 - en mi experiencia, las matrices son siempre 2D pero "2D" se usa mucho para significar 2 * 2, "3D" para significar 3 * 3 etc. – Steve314

+0

@ Steve314: Cuando trabajamos en física, o en procesamiento de imágenes, las matrices que usamos eran matrices 2D 4x4 con valores reales, lo que significa que incluso con una matriz optimizada de doble [16], el costo de copiar el objeto es mayor que el costo de copiar su dirección . Incluso con matrices "cortas" de 2x2 2D, tendríamos un short [4], que es de 64 bits, que es de nuevo, ya sea mayor que una dirección en 32 bits, o estrictamente igual a una dirección en 64 bits ... [PARA CONTINUAR] – paercebal