Uso de operaciones vectoriales en lugar de ciclos for para aumentar el desempeño de Matlab

Consideremos el siguiente ciclo, reescrito directamente de C o Fortran

dx = pi/30;
nx = 1+ 2*pi/dx;
for i = 1:nx
     x(i) = (i-1) * dx;
     y(i) = sin(3*x(i));
end

Todas las declaraciones anteriores son validas en Matlab, pero son ineficientes en el proceso de crear tanto a x como a y. Recordemos, Matlab localiza memoria para las variables cada vez que se les añade un nuevo miembro. Como ya lo comentamos en anteriormente en una entrada sobre reasignación de memoria RAM.
Entonces el mejor modo de crear los mismos dos vectores x y y es mediante los siguientes comandos:

x = 0: pi/30:2*pi;
y = sin(3*x);

La primera declaración crea al vector x; con 61 elementos que son almacenados en zonas continuas del RAM. La segunda declaración crea una nueva matriz, y, con el mismo número de elementos que x.

Matlab está diseñado para desarrollar eficientemente operaciones de matrices y vectores. Para tomar la ventaja máxima del hardware de la computadora; por tanto debemos utilizar operaciones vectoriales lo más que podamos.

Un ejemplo más sofisticado
A pesar que Matlab ajusta automáticamente el tamaño de una matriz (o vector) es mucho mejor predesignar la memoria de la matriz. La preasignación garantiza que los elementos de la matriz se almacenaran en zonas continuas de la memoria RAM y que sólo se realizara una vez este proceso.

Consideremos este ejemplo (que admito es artificial, pero didáctico) para crear dos vectores x y y:

dx = pi/30;
nx = 1 + 2*pi/dx;
nx2 = nx/2;

for i = 1:nx2
    x(i) = (i-1)*dx;
    y(i) = sin(3*x(i));
end

for i = round(nx2):nx
     x(i) = (i-1)*dx;
     y(i) = sin(5*x(i));
end

Aquí, conocemos desde el principio el tamaño de los vectores x y y. Así que reasignaremos la memoria creando un vector antes de que sus elementos sean individualmente asignados. Por lo común se utilizan las funciones ones y zeros para la reasignación de memoria; por ejemplo, de este modo:

dx = pi/30;
nx = 1 + 2*pi/dx;
nx2 = nx/2;

x = zeros(1,nx); % pre-allocate row-vectors, x
y = zeros(1,nx); % and y

for i = 1:nx2
x(i) = (i-1)*dx;
y(i) = sin(3*x(i));
end

for i = round(nx2):nx
x(i) = (i-1)*dx;
y(i) = sin(5*x(i));
end

Sin embargo, las declaraciones x(i) y y(i) no han tomado ventaja de la vectorización, aunque los elementos de x y y han sido almacenados continuamente en la RAM.

Así que mejoraremos estas declaraciones mediante un poco de vectorización. Por ejemplo, escribiendo:

x = 0:pi/30:2*pi; % vectorized calculation of x
nx = length(x);
nx2 = nx/2;

y = x; % pre-allocate memory for y

for i = 1:nx2
     y(i) = sin(3*x(i));
end

for i = round(nx2):nx
    y(i) = sin(5*x(i));
end

Finalmente, podemos observar que los cálculos de y puede también ser vectorizados. Por ejemplo, de este modo:

x = 0:pi/30:2*pi; % vectorized calculation of x
nx = length(x);
nx2 = nx/2;

y = x; % pre-allocate memory for y

y(1:nx2) = sin(3*x(1:nx2)); % compute first part of y
y(round(nx2):nx) = sin(5*x(round(nx2):nx)); % and the second part


En mi computadora, resulta que el aumento de velocidad fue de aprox. 1.2 veces. Es decir, utilizar los cálculos basados en vectores fue notoria la mejora. Sin embargo, en este ejemplo son pocos los elementos de los vectores. Mientras más grande es la matriz o vector, más significativo será utilizar vectorización en lugar de una reconstrucción de la matriz por medio del ciclo for.

Por cierto, para medir el tiempo de estos ejemplos, los he escrito como rutinas (scripts que los nombre ejemplo_vector.m) y utilice los siguientes comandos para medir el tiempo.

clear; tic; ejemplo_vector; disp( ['se tardo ' num2str(toc) 's.'] );

Y la salida de este ejemplo es la siguiente imagen:


No hay comentarios:

Publicar un comentario

Related Posts Plugin for WordPress, Blogger...