Jump to content


Photo

Arrays dinámicos: SetLength vs GetMem/FreeMem


  • Please log in to reply
27 replies to this topic

#21 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 24 October 2010 - 03:07 PM

...(Continúa del anterior mensaje)

Como ilustración al manejo del casting explicado, te pongo el código usando esa técnica:


delphi
  1. type
  2.   AMatrix = array [0..0] of array of Double;
  3.   PMatrix = ^AMatrix;



El código, creo que bastante claro:


delphi
  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7.   Dialogs, StdCtrls;
  8.  
  9. const
  10.   // Tamaño de las matrices
  11.   Rows: integer = 92 * 112; //10304
  12.   Cols: Integer = 40;
  13.  
  14.   // Precisión del reloj: 1/10000 seg.
  15.   // Idea y forma de cálculo basada en:
  16.   // [url]http://www.marteens.com/trick4c.htm[/url]
  17.   // En teoría es lo más preciso
  18.   PrecisionCounter = 10000;
  19.  
  20. type
  21.   // Nuestro tipo para matrices
  22.   // 10304 * 40 * 8 = 3297280 bits
  23.   //                = 3220 Kb
  24.   //                aprox. 3,1445 Mb
  25.   TMatrix = array of array of Double;
  26.   AMatrix = array [0..0] of array of Double;
  27.   PMatrix = ^AMatrix;
  28.  
  29.   TForm1 = class(TForm)
  30.     Button1: TButton;
  31.     Button2: TButton;
  32.     Label1: TLabel;
  33.     Label2: TLabel;
  34.     procedure Button1Click(Sender: TObject);
  35.     procedure Button2Click(Sender: TObject);
  36.     procedure FormCreate(Sender: TObject);
  37.   private
  38.     { Private declarations }
  39.   public
  40.     { Public declarations }
  41.   end;
  42.  
  43. var
  44.   Form1: TForm1;
  45.  
  46.   Matrix1: TMatrix;
  47.   Matrix2: PMatrix;
  48.   Start, Finish, Freq: Int64;
  49.  
  50. implementation
  51.  
  52. {$R *.dfm}
  53.  
  54. procedure TForm1.Button1Click(Sender: TObject);
  55. var
  56.   i,j: Integer;
  57. begin
  58.   // Aquí emplearemos el método "tradicional"
  59.   // Activamos "cronómetro"
  60.   QueryPerformanceCounter(Start);
  61.  
  62.   //Aqui el algoritmo:
  63.   SetLength(Matrix1,Rows,Cols);
  64.  
  65.   // Método seguro. Si bien puede usarse
  66.   // for <variable> := 0 to <valor-max> - 1 do
  67.   // es conveniente el uso de Low() y High()
  68.   for i := Low(Matrix1) to High(Matrix1) do
  69.     for j := Low(Matrix1[1]) to High(Matrix1[1]) do
  70.       Matrix1[i][j] := i + j;
  71.  
  72.   SetLength(Matrix1,0,0); // O también puede usarse Finalize()
  73.   Matrix1 := nil; // preventiva
  74.  
  75.   // Paramos "cronómetro"
  76.   QueryPerformanceCounter(Finish);
  77.   Label1.Caption := &#39;1/10000 seg: &#39; +
  78.   FormatFloat(&#39;0,&#39;,(Finish - Start) * PrecisionCounter div Freq);
  79. end;
  80.  
  81. procedure TForm1.Button2Click(Sender: TObject);
  82. var
  83.   i,j: Integer;
  84.   k: Cardinal;
  85. begin
  86.   // Aquí emplearemos el método GetMem/FreeMem
  87.   // Activamos "cronómetro"
  88.   QueryPerformanceCounter(Start);
  89.  
  90.   //Aqui el algoritmo:
  91.   Matrix2:= VirtualAlloc(nil, SizeOf(Pointer) * Rows + Rows * Cols * SizeOf(Double), MEM_COMMIT, PAGE_READWRITE);
  92.   for k := 0 to Rows - 1 do
  93.     PPointer(Cardinal(Matrix2) + k*SizeOf(Pointer))^:= Pointer(Cardinal(Matrix2)+SizeOf(Pointer)* Rows + Cols * SizeOf(double)* k);
  94.  
  95.   for i := 0 to Rows-1 do
  96.     for j := 0 to Cols-1 do
  97.       Matrix2[i][j] := i + j;
  98.  
  99.   VirtualFree(Matrix2, 0, MEM_RELEASE);
  100.  
  101.   // Paramos "cronómetro"
  102.   QueryPerformanceCounter(Finish);
  103.   Label2.Caption := &#39;1/10000 seg: &#39; +
  104.   FormatFloat(&#39;0,&#39;,(Finish - Start) * PrecisionCounter div Freq);
  105. end;
  106.  
  107. procedure TForm1.FormCreate(Sender: TObject);
  108. begin
  109.   // Calculamos la frecuencia a la que corre el reloj
  110.   QueryPerformanceFrequency(freq);
  111. end;
  112.  
  113. end.



Saludos.
  • 0

#22 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 24 October 2010 - 03:57 PM


Bueno, en esta ocasión la uso para poder declarar un puntero a ese tipo y asignarle la memoria localizada o cualquier buffer, así podemos tratar ese bloque como una matriz. Dicho de otra forma, hacemos un casting.

Disculpame escafandra pero ahorita no entendí del todo :(

Entiendo que se asigna una porción de memoria  o un buffer, pero no termino de comprender el como es que ese simple [0..0] hace la diferencia entre poder hacer esto:



delphi
  1. TMatrix(Matrix2)[i][j]



A esto:



delphi
  1. Matrix2[i][j]



Entiendo parcialmente que se trata de un "casting". El asunto es que no logro comprender la relación entre la asignación de la memoria y la diferencia en el acceso a la misma.
No se si me explico: a los conceptos de forma separada los entiendo medianamente... es a la unión de éstos en como no logro interpretar.  :(

Existen muchas formas de asignar bloques de memoria en la API. Cualquier función que trate de asignar memoria desde un lenguaje de programación al final terminan llamando a la API tras mas o menos tareas y comprobaciones, así que ¿Porque no llamar directamente a la API?.

Pues si, tienes razón.  :) Mejor evitarse los intermediarios.
Yo estaba pensando y asumiendo que GetMem y FreeMem era parte de la API. Ese fue un error mio, y por ello me quedé pensando en si había alguna diferencia.

Pues este es uno de los problemas que podemos tener. En un array de este tipo deberemos asignar toda la memoria de nuevo mas/menos el espacio nuevo, copiar los datos y destruir el bloque antiguo.

Ummm. Va veo, entonces estaba en lo cierto mi presunción.
Tendré que estudiar mejor mis implementaciones y determinar en que punto hago algunas redimensiones y en que medida podría evitarlas o llevar algunas alternativas.
Por ahora me hago la idea de que podría disponer de una matriz auxiliar de forma temporal para no perder los datos. En términos abstractos:

Aux = Original
Redimensionar(Original)
Original = Aux + Nuevo

Una pequeña desventaja ante el poder de la velocidad. Una vez más, si le buscas por un lado descubres que te falta por el otro, que no todo es totalmente malo ni bueno.

Bueno, es un tema que puede resultar un poco engorroso. Algunos lenguajes como el C, están muy orientados a los mismos y quizás por eso son o muy queridos o muy odiados. Delphi no los maneja mal paro a mi parecer, en este asunto es mucho mas claro el C ya que no realiza casting "automáticos" y te permite hacer cualquier cosa.

Saludos.

Yo preferiría en lo posible escapar a la sintaxis de C, aunque le tengo mi respeto al lenguaje. No en vano es uno de los lenguajes que más trascendencia ha tenido y ha inspirado a otros, además que si se analiza... una enorme cantidad de aplicaciones están escritas en C y su variante, o quizá sea más adecuado el término extensión, C++. No sólo permite un acceso a bajo nivel sino que ofrece una buena ventaja de ser llevado hacia varias plataformas.

Me pondré a evaluar y hacer pruebas... a estudiar bien esto del [0..0] que me ha dejado intrigado.

Saludos,
  • 0

#23 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 24 October 2010 - 04:53 PM



Bueno, en esta ocasión la uso para poder declarar un puntero a ese tipo y asignarle la memoria localizada o cualquier buffer, así podemos tratar ese bloque como una matriz. Dicho de otra forma, hacemos un casting.

Disculpame escafandra pero ahorita no entendí del todo :(

Entiendo que se asigna una porción de memoria  o un buffer, pero no termino de comprender el como es que ese simple [0..0] hace la diferencia entre poder hacer esto:



delphi
  1. TMatrix(Matrix2)[i][j]



A esto:



delphi
  1. Matrix2[i][j]



Entiendo parcialmente que se trata de un "casting". El asunto es que no logro comprender la relación entre la asignación de la memoria y la diferencia en el acceso a la misma.
No se si me explico: a los conceptos de forma separada los entiendo medianamente... es a la unión de éstos en como no logro interpretar.  :(
.....
Me pondré a evaluar y hacer pruebas... a estudiar bien esto del [0..0] que me ha dejado intrigado.


En realidad tiene que ver con los casting automáticos que hace delphi. En C no precisaríamos de este tipo de trucos.



delphi
  1. AMatrix = array [0..0] of Double; // Crea un tipo que es una matriz estática de un elemento.
  2.  
  3. PMatrix = ^AMatrix ; // Crea el tipo puntero a esa matriz.


En delphi podemos usar en las matrices estáticas, un puntero como si fueran la matriz. Aunque en realidad el puntero no es la matriz, delphi se encarga de que lo parezca realizando un casting implícito. En C/C++ la matriz estática o dinámica si es el puntero al primer elemento (sin casting implícito o explícito) y podemos hacer esto:


cpp
  1.   double *Matriz = new double[10];    // Asigno memoria para 10 elementos dinámicamente
  2.   Matriz[5] = 1234;                  // referencio  por índice


o esto:


cpp
  1.   double Matriz[10]    // Matriz estática
  2.   Matriz[5] = 1234;    // referencio  por índice




En las matrices dinámicas de delphi, la variable Matriz es el puntero al primer elemento de la misma, exactamente igual que en C/C++. Sin embargo plantean un problema, delphi no nos deja realizar la asignación de ese puntero a un bloque de memoria, generando un error de escritura en memoria:



delphi
  1. var
  2.   Matrix: array of double;
  3. begin
  4.   Matrix:= VirtualAlloc(nil, SizeOf(Pointer) * Rows + Rows * Cols * SizeOf(Double), MEM_COMMIT, PAGE_READWRITE);  // Esto genera un error inmediato o diferido



Asi que o usamos el truco de un tipo puntero a un array estático o usamos un puntero a la matriz dinámica.

En el primer caso usaremos tranquilamente Matriz[i] (casting automático), en el segundo deberemos realizar el cast de PMatrix a TMatrix o no compila. Fijaté en este detalle:



delphi
  1. type
  2.   TMatrix = array of Double;
  3.   PMatrix = ^TMatrix;
  4.  
  5. var
  6.   Matriz: PMatrix;
  7. begin
  8.   .....
  9.   TMatrix(Matriz)[i]; // Correcto ¡no usamos el contenido del puntero Matriz!!!
  10.   Matriz^[i];        // Parecería lo correcto pero no lo es pues la matriz es el puntero y Matriz^ se refiere el primer elemento de la misma



No se si te lo aclaro o te lo embrollo mas...

Saludos.
 
  • 0

#24 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 24 October 2010 - 05:12 PM

Te agradezco enormemente que te tomes el tiempo de responderme.
No es necesario que me des una respuesta tan prontamente, no quiero que te tomes tantas molestias. Después de todo están tus obligacaciones primero, y tus horas de sueño.

Pues a decir verdad, me embrollo yo mismo... aún no he podido estudiarlo bien como para decir a ciencia cierta si lo comprendí bien. Necesito de más tiempo como para verlo con más tranquilidad. Me tomaré la mañana de mañana para examinar tus palabras y ver a que conclusión llego. Esta noche tengo cita con el papel y el lápiz.

Gracias amigo,
  • 0

#25 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 31 October 2010 - 03:21 PM

Hola,
Después de días de ausencia en el hilo vuelvo con algunos resultados:



delphi
  1.                     +-----------+--------------------------+
  2.                     | SetLength | VirtualAlloc/VirtualFree |
  3. +-------------------+-----------+--------------------------+
  4. |Tiempo (1/10000 s) | 651,6    | 289                      |
  5. |Uso CPU (%)        | 11,8      | 9,33                    |
  6. |Memoria Física    |          |                          |
  7. |* Disponible (K)  | 299694,8  | 297170,4                |
  8. |* Caché            | 220451,2  | 232651,2                |
  9. +-------------------+-----------+--------------------------+
  10. |Maximo            |          |                          |
  11. |* Tiempo          | 1227      | 628                      |
  12. |* Uso CPU          | 13        | 10,38                    |
  13. +-------------------+-----------+--------------------------+
  14. |Mínimo            |          |                          |
  15. |* Tiempo          | 460      | 204                      |
  16. |* Uso CPU          | 10%      | 7,48                    |
  17. +-------------------+-----------+--------------------------+



Las condiciones iniciales mostraron un uso de CPU del 6,5%, 303740 K de memoria disponible y unos 210892 K para Caché.

Los resultados son el promedio tras 5 pruebas. Se ha colocado referencias de los máximos y mínimos para ilustrar los picos observados en ambos casos. En el caso del VirtualAlloc/VirtualFree el pico de tiempo se ha presentado en la primera prueba, de allí en más los valores se han mantenido en 204, salvo en la 4ta que fue de 205. Por su parte, en la forma tradicional (con SetLength) los valores oscilan en los 460 a 464 salvo en dos picos, uno de 642 que se presentó en la última prueba y el máximo en la 4ta.

Tras este análisis se estima que hay una mejora bastante considerable con el uso de VirtualAlloc/VirtualFree tanto en tiempo como en uso de CPU. En lo que hace a tiempos se ha reducido en promedio a la mitad y en el uso de CPU la reducción es de 2,47%.

Como contraparte de VirtualAlloc/VirtualFree se experimenta un aumento de uso de la caché en casi 12 MB. La memoria física disponible casi se mantiene entre ambos métodos. Habiendo más disponibidad en el caso de SetLength a favor de unos 2,47 MB. La diferencia es del casi un 60% del tamaño de la matriz que se ha empleado como prueba.
Creo que la explicación del porqué esta diferencia en los valores de caché es evidente, o casi evidente. Con SetLength se gana una liberación rápida y casi en su totalidad de la memoria utilizada, por otra parte cuando hacemos uso de VirtualAlloc y VirtualFree estamos trabajando con parte de la memoria virtual y Caché con lo que la liberación no es inmediata...esta queda marcada "disponible" por si se desea volver a utilizarla y nos permite acceder a ésta de forma más rápida.

Si bien esta prueba ha sido bastante simple y para sacar conclusiones más precisas se deberían hacer pruebas más reales (un buen ejemplo podría ser calcular multiplicaciones de matrices, o la inversa de una matriz). Un primer razonamiento nos conducirá a que se mantendrá esta linealidad y proporción.

Saludos,
  • 0

#26 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 01 November 2010 - 09:11 AM

Hola,
Gracias escafandra. Sin tu aporte estaría a los tumbos.
Ahorita estoy analizando la posibilidad de implementar funciones y procedimientos que trabajen con matrices basados en el uso de VirtualAlloc y VirtualFree.

Lo primero y principal, tratar de implementar algo como:



delphi
  1. procedure DimMatix(Matrix: PMatrix; Order: integer);



Aunque estoy en la duda si no será más bien:



delphi
  1. procedure DimMatix(var Matrix: PMatrix; Order: integer);



Sabiendo que se está alterando y modificando el tamaño y direcciones de la memoria. Y Considerando además que PMatrix es un puntero:



delphi
  1. TMatrix = array of array of Double;
  2. PMatrix = ^TMatrix;



Lo que me extraña es que como la llamada a VirtualAlloc regresa el puntero a memoria, mi cabeza piensa que no es necesario el var ya se el puntero de por sí está "abierto".

Es decir, si será válido algo como:



delphi
  1. procedure DimMatrix(Matrix: PMatrix, Order: integer);
  2. begin
  3. ....
  4.   Matrix := VirtualAlloc(...);
  5. ....
  6. end;



Naturalmente, esto afectará en como se implementen el resto de las rutinas.

Anoche mientras seguía leyendo sobre el uso de VirtualAlloc y VirtualFree encontré algunos artículos que comentaban que el uso de GetMem/FreeMem es equivalente a VirtualAlloc/VirtualFree y el uso de SetLength() se traduce en reservas de memoria justamente en GetMem con algunas comprobaciones adicionales como determinar el tamaño y si hay memoria disponible. Por lo que en última todo se traduciría a lo mismo, sólo en una lentitud en el caso de SetLength.

Esto me hace pensar si vale la pena seguirle por este método y mejor trabajar con SetLenth() que me facilita mucho el trabajo.

Tengo que hacer unas pruebas. Si logro implementar métodos que reciban como parámetros PMatrix y que funcionen bien... creo que podría ser un buen logro y me podría aventurar a hacer cosas más elaboradas.

Saludos,
  • 0

#27 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 01 November 2010 - 05:14 PM

Yo utilizaría el sistema de bajo nivel en casos críticos. Para casos normales no creo que merezca la pena el esfuerzo.

Casos críticos pueden ser aquellos en los que la velocidad sea una cuestión verdaderamente importante o aquellos en los que estemos obligados al uso de bajo nivel y programación sólo con API de Win32.

De todas formas estos ejercicios siempre enseñan mucho.

Saludos.
  • 0

#28 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 01 November 2010 - 05:41 PM

Yo utilizaría el sistema de bajo nivel en casos críticos. Para casos normales no creo que merezca la pena el esfuerzo.

Casos críticos pueden ser aquellos en los que la velocidad sea una cuestión verdaderamente importante o aquellos en los que estemos obligados al uso de bajo nivel y programación sólo con API de Win32.

De todas formas estos ejercicios siempre enseñan mucho.

Saludos.

Hola escafandra,
Te agradezco tu opinión.
Yo me inclino a probar esta alternativa debido a que requiero de entre 2 a 3 matrices del tamaño que se ha puesto en práctica a lo largo de este hilo, y otras 2 a 4 matrices de 40 x 40. Sumando todo tenemos una gran cantidad de memoria utilizada.

Creo que esto puede considerarse como crítico, si bien no siempre deberán estar reservadas todas las matrices al mismo tiempo.

Saludos,
  • 0




IP.Board spam blocked by CleanTalk.