Ir al contenido


Foto

Arrays dinámicos: SetLength vs GetMem/FreeMem


  • Por favor identifícate para responder
27 respuestas en este tema

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 21 octubre 2010 - 11:50

Hola Muchachos,

Yo aquí ando con una duda filosófica (para variar  :D ). ¿Que es más eficiente y rápido para trabajar con arrays dinámicos: SetLength() u optar por el enfoque hacia punteros y utilizar GetMem y FreeMem para reservar la memoria? ¿Existe alguna diferencia entre SetLength y los xxxMem?

Se que la pega de utilizar GetMem y FreeMem me lleva a pensar en punteros y adaptar mis algoritmos, pero en fin es una pregunta válida.

Esto me lo pregunto a fin de evaluar si es posible mejorar aún más mis cálculos. Si bien estoy contento con la rapidez con la que ha realizado algunas de las operaciones y que ya estuve optimizando me preguntaba si para grandes vectores y matrices (del orden de miles de elementos) habría alguna diferencia significativa.

Yo vengo trabajando con SetLength() y noto que se demora al reservar y cargar los datos iniciales... de allí en más mis algoritmos son bastante rápidos (en general). Me estuve pensando si es posible mejorar eso de la reserva y carga...

He intentado buscar algo como "How to work efficiently with large arrays" o algo por el estilo y no encuentro algo más o menos serio como para ilustrarme. Entre mis fuentes está Efg's Computer Lab que en la sección Tips and Tricks de su artículo Math Info - parte C expone algunos ejemplos basados en el uso de GetMem/FreeMem aunque dicha documentación hace referencia a una manera de trabajar con arrays de forma dinámica antes de la introducción de los open arrays en D4. De allí en más mis búsquedas en la web me llevan a cosas elementales y todas hacen referencia al uso de SetLength().

¿Alguien sabría asesorarme o darme alguna pauta?

Saludos,
  • 0

#2 jorgeu

jorgeu

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 179 mensajes
  • LocationMaracaibo

Escrito 21 octubre 2010 - 04:08

Leyendo http://www.delphibas...?Name=SetLength y http://www.delphibas...asp?Name=GetMem vi que setLength mantiene los datos anteriores del arreglo. Pues tiene que reservar memoria para el nuevo arreglo y debe copiar los valores que estaban en el arreglo anterior.

GetMem es para nuevos arreglos y SetLength si se desea aumentar el tamaño de un arreglo existente.
Yo creo que comparas peras y manzanas. No son para lo mismo.

Saludos
  • 0

#3 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 21 octubre 2010 - 04:36

Hola jorgeu,
Te agradezco que me hayas dado esa aclaración. La verdad es que no lo veía demasiado claro cuando leía la propia ayuda de Delphi, mi inglés no es del todo bueno.  :(

Yo ya estoy un tanto confundido... en la ayuda sobre el tema de los arrays abiertos indica que ha de emplearse SetLength() para reservar la memoria e indicar el tamaño del arreglo. No menciona nada del hecho que deba emplearse GetMem.

La gran mayoría de los ejemplos que estuve viendo y del material que he estado consultando habla de SetLength() para trabajar con matrices, sea para indicar la memoria para comenzar a trabajar como si se desease redimensionarlo.

La poca referencia sobre el uso de arreglos y GetMem/FreeMem data de antes de D4 y menciona que se lo utilizaba para redimensionar los arreglos. A partir de D4 es que existen los arreglos dinámicos.
Pero recuerdo haber visto en una biblioteca de matemática que había probado que también utilizaba esta técnica.

Por ello es que me ando preguntando como sería adecuado estar trabajando... Yo siempre he utilizado SetLength() aunque en la última semana me he estado preguntando si es que habrá algo más eficiente para trabajar con las matrices, reservar memoria y redimensionar.
En mi código poco tengo que estar cambiando el tamaño a mis vectores y matrices. Una vez dado el tamaño la gran mayoría de las veces quedan así hasta su liberación.
Pero como dije, noté que la carga de datos ni bien se dimensiona es lenta a comparación con el resto de los cálculos.

Es así que recordé el uso de GetMem/FreeMem para reservar memoria e inició mi duda.

Saludos,
  • 0

#4 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 21 octubre 2010 - 05:06

En Ansi C no existen los arrays dinámicos al estilo delphi. Debe reservarse la memoria necesaria (estáticamente o dinámicamente) y el puntero obtenido es en si mismo el array. Desde esta base, C tiene dos formas de manejar los arrays. La primera es mediante un índice, como en delphi y la segunda con aritmética de punteros:



cpp
  1.   MyTipo *T = malloc(sizeof(MyTipo)*N_elementos);  // reservo memoria
  2.   MyTipo Uno = T[27]    // Acceso por índice: es el elemento 27 siendo 0 el primero
  3.   MyTipo Dos = *(T+27)  // Acceso por aritmética de punteros: También es el elemento 27



Como se ve el acceso por índice es mas claro sintácticamente, sin embargo la aritmética de punteros es algo más rápida. Cuando quiero algo mas de velocidad uso la aritmética de punteros.

Pienso, pero debería comprobarlo, que en delphi sucede algo parecido. Para verlo claramente se puede ver el código ensamblador generado, este tipo de ejercicio suele ser bastante esclarecedor.

Saludos.
  • 0

#5 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 21 octubre 2010 - 05:21

Hola escafandra,
Entonces... ¿tu sospechas que en caso de poder tratarse con punteros sobre vectores y matrices con Delphi sería más o menos parecido a C y que podría ser más rápido?

Saludos,
  • 0

#6 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 22 octubre 2010 - 06:53

Hola escafandra,
Entonces... ¿tu sospechas que en caso de poder tratarse con punteros sobre vectores y matrices con Delphi sería más o menos parecido a C y que podría ser más rápido?

Saludos,


Exacto, si tengo un rato miro el código asm generado, con esto las sospechas estarán mejor fundadas. La única pega que puede tener en delphi es que la aritmética de punteros es mas engorrosa que en C. Me explico. Si quiero un elemento pongo *(T+27) y me da el elemento 27, es decir al puntero le sumo 27. Pero realmente al puntero se le suma 27*sizeof(MyTipo) directamente. En delphi debes pasar el puntero a un DWORD, le añades lo que quieres y realizas el cast a puntero nuevamente: PMyTipo(DWORD(T)+(27*sizeof(MyTipo))) Esto es lo que puede plantear la duda de eficiencia. Por eso creo que lo mejor es ver como queda una vez compilado.

Saludos
  • 0

#7 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 octubre 2010 - 07:46


Exacto, si tengo un rato miro el código asm generado, con esto las sospechas estarán mejor fundadas. La única pega que puede tener en delphi es que la aritmética de punteros es mas engorrosa que en C. Me explico. Si quiero un elemento pongo *(T+27) y me da el elemento 27, es decir al puntero le sumo 27. Pero realmente al puntero se le suma 27*sizeof(MyTipo) directamente. En delphi debes pasar el puntero a un DWORD, le añades lo que quieres y realizas el cast a puntero nuevamente: PMyTipo(DWORD(T)+(27*sizeof(MyTipo))) Esto es lo que puede plantear la duda de eficiencia. Por eso creo que lo mejor es ver como queda una vez compilado.

Saludos


De que puede resultar más engorroso te lo creo... anoche intenté hacer unas pruebas con GetMem/FreeMem sobre un array como el siguiente:



delphi
  1. type
  2.   TMatrix = array of array of double;
  3.   PMatrix = ^TMatrix;
  4.  
  5. var
  6.   Matrix: PMatrix;



Y no podía lograr que funcionase. Errores por todos lados  :( . Ya me estoy inclinando a no estropearme la cabeza y directamente emplear SetLength. Que después de todo a pesar de que tiene sus demoras para reservar la memoria es bastante rápido considerando las operaciones a la que se verá sometido... Con una de las matrices más grande que empleo (aprox. 10304 x 40 de tipo double que vendría a ser unos 3220 KB) y basándome en GetTickCount (si... ya que no es de lo más preciso... pero es que olvidé del nombre y como se emplea QueryPerfomanceFrecuency) lo más lento que ha llegado a reservar memoria y hacer unas asignaciones fue de 4 segundos y lo más rápido fue cerca a medio segundo. Tengo que admitir que tampoco es que dejaba descansar al sistema y que terminase de liberar los 3 megas...

Estúpidamente se me olvida de medir el rendimiento de la PC con algo más serio y preciso... yo por flojera abrí el Administrador de Tareas y no veía una pérdida considerable... ahora estuve buscando en mi equipo una especie de "Adminsitrador de Tareas" mejorado que recuerdo haberlo descargado desde el mismísimo sitio de Microsoft y no lo encuentro.  :o ¿Alguno sabe por casualidad el nombre de a lo que me refiero para comprobar si lo tengo o si es que estúpidamente lo eliminé?

Saludos,
  • 0

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 octubre 2010 - 08:51

Bueno... al menos me auto respondo en algo, ya encontré al Process Explorer.  :p Lo que hace tenerlo escondido entre tantos instaladores y ejecutables.

Ahora de lo otro... no se para donde apuntar  :(

Saludos,
  • 0

#9 jorgeu

jorgeu

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 179 mensajes
  • LocationMaracaibo

Escrito 22 octubre 2010 - 08:54

Yo en linux uso http://valgrind.org/ para eso. Es una especie de máquina virtual donde se ejecuta el programa y da muy buenos datos sobre memory leaks, memoria total usada y muchas más cosas. Veo que no funciona en Windows. Suerte con eso
  • 0

#10 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 octubre 2010 - 09:20

Yo en linux uso http://valgrind.org/ para eso. Es una especie de máquina virtual donde se ejecuta el programa y da muy buenos datos sobre memory leaks, memoria total usada y muchas más cosas. Veo que no funciona en Windows. Suerte con eso

Utilizaría ese programa... si supiera de Linux. Al menos lo tendré como nota para cuando de el paso hacia la luz y deje el lado oscuro.  ;)

Gracias por el aliento.  :)

Saludos,
  • 0

#11 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 22 octubre 2010 - 03:42

Bueno, voy a tratar de aclarar un poco las cosas.
Un array está representado por un puntero al primer elemento del mismo.
Un Array de dos dimensiones es un array de arrays, es decir se representa por un puntero a punteros. Cada uno de esos punteros es un array.

De forma que un array de dos dimensiones tiene una imagen en memoria de una serie de punteros y cada uno de ellos apunta a un array del tipo que queremos.

Quizás el código clarifique las cosas:



delphi
  1. var
  2.   Matrix: array of array of double;
  3.   a,b: integer;
  4.   n: cardinal;
  5. begin
  6.   // El array lo definimos como a por b : Matrix[a, b];
  7.   a:= 2;  // primera dimensión
  8.   b:= 3;  // Segunda dimensión
  9.  
  10.   // Localizamos la memoria:
  11.   // Necesitamos a punteros para que apunten a cada array de double + a*b doubles
  12.   GetMem(Matrix, sizeof(Pointer)*a + a*b*sizeof(double));
  13.  
  14.   // Asignamos los punteros. Esto puede ser un poco lioso.
  15.   // PPointer(cardinal(Matrix) + n*sizeof(Pointer)) representa a cada puntero a punteros: dimensión primera
  16.   // Pointer(cardinal(Matrix)+sizeof(Pointer)*a) representa donde debe apuntar el primer puntero a puntero. Su contenido es el elemento Matrix[0,0]
  17.   // Asi, Pointer(cardinal(Matrix)+sizeof(Pointer)*a + b*sizeof(double)*n); son los punteros o arrais de la segunda dimensión
  18.   for n:=0 to a-1 do
  19.   begin
  20.     PPointer(cardinal(Matrix) + n*sizeof(Pointer))^:= Pointer(cardinal(Matrix)+sizeof(Pointer)*a + b*sizeof(double)*n);
  21.   end;
  22.  
  23.   // Ahora podemos acceder por índices o por punteros.
  24.   Matrix[0, 0] := 0;  // También lo localizamos así: Double(PPointer(Matrix)^^)
  25.   Matrix[0, 1] := 1;
  26.   Matrix[0, 2] := 2;
  27.   Matrix[1, 0] := 10;
  28.   Matrix[1, 1] := 11;
  29.   Matrix[1, 2] := 12;
  30. end;



Espero haberme explicado con claridad  :( ;)


Saludos.

  • 0

#12 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 octubre 2010 - 05:11

Hola,

¡Gracias escafandra por traer luz a este completo ignorante! Yo estaba intentando algo diferente.

Primero que nada, en vez de pasar una variable de tipo Matrix a GetMem, yo pasaba una variable de tipo puntero a Matrix. Es decir:



delphi
  1. type
  2.   PMatrix = ^TMatrix;
  3.  
  4. ...
  5. var
  6. ptMatrix: PMatrix;
  7.  
  8. ...
  9. GetMem(ptMatrix, ....);



En segundo lugar no tuve en cuenta que la variable apunta a un puntero donde está la verdadera matriz y que debía sumarle, e intentaba pedir memoria basado en el tipo Matriz yo asumí directamente que era posible algo como esto:



delphi
  1. GetMem(ptMatrix, SizeOf(Matrix) * M);



Y luego que debería pedir tamaño para cada fila:



delphi
  1. for i := 0 to N-1 do
  2.   GetMem(ptMatrix^[i], SizeOf(double) * N);



Y que luego para acceder a los datos podría ser algo como:



delphi
  1. ptMatrix^[i]^[j] := i + j;



Luego tras algunas advertencias probé algunas variaciones menores hasta que logré compilar (no me pregunten como  :p ... me dije ¡lo logré!) pero recibí algunos errores de acceso a memoria en tiempo de ejecución.  :p

Como no me gustó el sabor a derrota, cerré el IDE sin guardar el proyecto de prueba y no volví a intentarlo.  :D

Estuve leyendo para ver si encontraba algo que me clarifique las cosas pero no entra nada en esta cabeza... y eso que está hueca y vacía.  :D  :(

Voy a estudiar tus explicaciones con mayor detenimiento. Voy entendiendo mejor gracias a tus palabras; pondré en práctica lo que comentas con mayor tranquilidad mañana. Ahorita mi cabeza está preocupada en lo que hace a documentación.

Nuevamente gracias. Luego pondré algunas comparaciones sobre ambas técnicas.

Saludos,
  • 0

#13 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 23 octubre 2010 - 03:24

Me congratula que mis explicaciones te resulten claras, así que voy a comentarte dos cosas mas.

Aunque en el ejemplo localizo toda la memoria de golpe para toda la matriz (creo que es mas rápido y eficiente) puedes hacerlo por partes como pretendías. Es decir, creas espacio para los n punteros que representan la dimensión primera y después, en el bucle de asignación de punteros, creas el espacio para cada matriz del tamaño de la segunda dimensión. Quizás esta segunda forma es más fácil de entender pero multiplica las llamadas a GetMem y obliga a liberar la memoria de la misma forma. Otro efecto es que dicha memoria no es consecutiva.

Es mas rápido localizar la memoria de golpe y su liberación también lo es, requiriendo una sola llamada a FreeMem.

Tu decides como localizar la memoria  ;)

Por otro lado el acceso a los elementos puedes hacerlo por índice o por punteros, una vez entendido como funciona el asunto. Piensa que en realidad en cualquier matriz puedes acceder con punteros directamente independientemente de como asignaste la memoria, si por SetLength o por el método que he expuesto.

Saludos.

  • 0

#14 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 23 octubre 2010 - 06:05

Hola,

Gracias por tus consejos. Ahora que comentas de que se llamaría a GetMem varias veces... y recordando lo que antes me había dicho jorgeu creo que la única opción es pedir toda la memoria de una como tu haces porque de otro modo estaría reservando y liberando memoria en cada iteración con lo que se me hace que podría estar perdiendo algunas referencias.

En la tarde estaré más tranquilo y por fin haré pruebas  :) , ahorita quiero terminar el tema de la documentación que me ha quedado a medias ayer. Muy posiblemente el uso de GetMem y FreeeMem tenga cierta ventaja para el caso de mis matrices que no necesitan estar redimensionandose. Según mis procedimientos casi ninguna necesita estar cambiando de tamaño... es más hasta me atrevería a decir que ninguna lo necesitaría.

Saludos,
  • 0

#15 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 23 octubre 2010 - 02:12

Hola,

Actualizo el hilo. He estado codificando un sistema a modo de prueba. Y hasta el momento solo he estado probando el método tradicional (SetLength) consiguiendo unos resultados bastante optimistas, pero no quisiera aventurarme demasiado.

Luego he estado llevando la codificación con el método GetMem/FreeMem. Al probar mi código algo falla.  :( Recibo unos avisos de error al finalizar el programa, primero un AccessViolation en la dirección 00C4FFFC y finalmente un Runtime Error 216 at 004034BA.

Esto es cuando cierro el programa e intento correr la prueba basada en GetMem. Con el método tradicional no hay problemas.

La verdad es que ando desconcertado... porque poniendo breackpoint en mi código todo parece andar perfecto.

Primeramente con una traza había detectado que la falla estaba en el Finalize(), lo comenté y dejé en FreeMem para comprobar si pasaba por allí la cosa y así fue... se ejecutaba con normalidad FreeMem. Con eso debería bastar... pero no: el error al salir sigue.

Es justo al momento de salir... en principio el error me debería estar indicando que hay un intento de hacer uso de algún objeto no instanciado o ya liberado pero no hago uso de ningún objeto, variable o lo que fuese ni de los eventos OnClose o OnCloseQuery...

¿Que puede ser?  :(  :'(

Aquí dejo el code:



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.  
  27.   TForm1 = class(TForm)
  28.     Button1: TButton;
  29.     Button2: TButton;
  30.     Label1: TLabel;
  31.     Label2: TLabel;
  32.     procedure Button1Click(Sender: TObject);
  33.     procedure Button2Click(Sender: TObject);
  34.     procedure FormCreate(Sender: TObject);
  35.   private
  36.     { Private declarations }
  37.   public
  38.     { Public declarations }
  39.   end;
  40.  
  41. var
  42.   Form1: TForm1;
  43.  
  44.   Matrix1, Matrix2: TMatrix;
  45.   Start, Finish, Freq: Int64;
  46. implementation
  47.  
  48. {$R *.dfm}
  49.  
  50. procedure TForm1.Button1Click(Sender: TObject);
  51. var
  52.   i,j: Integer;
  53. begin
  54.   // Aquí emplearemos el método "tradicional"
  55.   // Activamos "cronómetro"
  56.   QueryPerformanceCounter(Start);
  57.  
  58.   //Aqui el algoritmo:
  59.   SetLength(Matrix1,Rows,Cols);
  60.  
  61.   // Método seguro. Si bien puede usarse
  62.   // for <variable> := 0 to <valor-max> - 1 do
  63.   // es conveniente el uso de Low() y High()
  64.   for i := Low(Matrix1) to High(Matrix1) do
  65.     for j := Low(Matrix1[1]) to High(Matrix1[1]) do
  66.       Matrix1[i][j] := i + j;
  67.  
  68.   SetLength(Matrix1,0,0); // O también puede usarse Finalize()
  69.   Matrix1 := nil; // preventiva
  70.  
  71.   // Paramos "cronómetro"
  72.   QueryPerformanceCounter(Finish);
  73.   Label1.Caption := &#39;1/10000 seg: &#39; +
  74.   FormatFloat(&#39;0,&#39;,(Finish - Start) * PrecisionCounter div Freq);
  75. end;
  76.  
  77. procedure TForm1.Button2Click(Sender: TObject);
  78. var
  79.   i,j: Integer;
  80.   k: Cardinal;
  81. begin
  82.   // Aquí emplearemos el método GetMem/FreeMem
  83.   // Activamos "cronómetro"
  84.   QueryPerformanceCounter(Start);
  85.  
  86.   //Aqui el algoritmo:
  87.   GetMem(Matrix2, SizeOf(Pointer) * Rows + Rows * Cols * SizeOf(Double));
  88.   for k := 0 to Rows - 1 do
  89.     PPointer(Cardinal(Matrix2) + k*SizeOf(Pointer))^:= Pointer(Cardinal(Matrix2)+SizeOf(Pointer)* Rows + Cols * SizeOf(double)* k);
  90.  
  91.   // Método seguro. Si bien puede usarse
  92.   // for <variable> := 0 to <valor-max> - 1 do
  93.   // es conveniente el uso de Low() y High()
  94.   for i := Low(Matrix2) to High(Matrix2) do
  95.     for j := Low(Matrix2[1]) to High(Matrix2[1]) do
  96.       Matrix2[i][j] := i + j;
  97.  
  98.   // Según documentación, cuando se trata de arrays dinámicos
  99.   // y se utiliza GetMem/FreeMem debe emplearse previamente Finalize
  100.   //Finalize(Matrix2);  Comentado por un error aquí!
  101.   FreeMem(Matrix2, SizeOf(Pointer) * Rows + Rows * Cols * SizeOf(Double));
  102.  
  103.   // Paramos "cronómetro"
  104.   QueryPerformanceCounter(Finish);
  105.   Label2.Caption := &#39;1/10000 seg: &#39; +
  106.   FormatFloat(&#39;0,&#39;,(Finish - Start) * PrecisionCounter div Freq);
  107. end;
  108.  
  109. procedure TForm1.FormCreate(Sender: TObject);
  110. begin
  111.   // Calculamos la frecuencia a la que corre el reloj
  112.   QueryPerformanceFrequency(freq);
  113. end;
  114.  
  115. end.




Como ven... no hay demasiadas cosas ocultas. Ojalá alguien me pudiera hechar un cable y ver donde está el problema.

Esto me está terminando de convencer que directamente evite todo uso de GetMem, acceso a memoria, los punteros, etc. Les comento los resultados que he obtenido tras 5 pruebas con el método tradicional:



delphi
  1. +--------------------+-----+
  2. |Tiempo: 1/10000 seg | CPU |
  3. +--------------------+-----+
  4. | //Antes:          | 7%  |
  5. +--------------------+-----+
  6. | 461                | 10% |
  7. +--------------------+-----+
  8. | 462                | 10% |
  9. +--------------------+-----+
  10. | 875                | 13% |
  11. +--------------------+-----+
  12. | 471                | 11% |
  13. +--------------------+-----+
  14. | 463                | 16% |
  15. +--------------------+-----+
  16. | Promedio: 546,4    | 12% |
  17. +--------------------+-----+



Considerando que los valores indicados en las constantes son representativos a una de las matrices de mayor dimensión con la que trabajo, y viendo estos números entonces podría llegarse a la conclusión de que SetLength cumple con su propósito de una manera bastante óptima y aceptable.

El uso de la memoria física y virtual no ha variado significativamente, se ha mantenido en los niveles iniciales. Sólo ha llegado a "disparar" los indicadores de CPU y de I/O pero en unos valores se normalizan en un instante. Entre cada prueba se han esperado en promedio minuto y medio.
No ha estado ejecutando ninguna aplicación más salvo la prueba, el Process Explorer de Microsoft y el Antivirus en segundo plano.

Lo que si se ha observado es que una vez finalizada la aplicación y en la espera de una normalización de los indicadores para la prueba con GetMem se ha detectado un disparo de uso de CPU de hasta el 35%. El mismo Explorer indicaba que este empezaba a consumir los recursos. Con cerrarlo y esperar un minuto y medio se volvió a la normalidad.

Las pruebas se realizaron corriendo en una AMD Duron 1.16 GHz, 512 RAM. Si alguien más se anima a someter a prueba las condiciones en sus equipos y compartir sus resultados, le estaría enormemente agradecido.

Saludos,
  • 0

#16 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 23 octubre 2010 - 06:50

He probado el código con las dimensiones que propones y tengo el mismo error que tu. Por lo que veo delphi no se comporta igual que el C en el manejo de los arrays. En C no se presenta ningún problema. Es posible que se me escape algún detalle en el manejo de la memoria como uso de alguna posición para guardar tamaños como ocurre con los String. En C el array no guarda su tamaño.

Si puedo hacer unas pruebas ya te comento ahora es un poco tarde para mi.

Saludos.
  • 0

#17 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 23 octubre 2010 - 07:00

He probado el código con las dimensiones que propones y tengo el mismo error que tu. Por lo que veo delphi no se comporta igual que el C en el manejo de los arrays. En C no se presenta ningún problema. Es posible que se me escape algún detalle en el manejo de la memoria como uso de alguna posición para guardar tamaños como ocurre con los String. En C el array no guarda su tamaño.

Si puedo hacer unas pruebas ya te comento ahora es un poco tarde para mi.

Saludos.

Hola escafandra,
No hay problemas, no es obligación ni te sientas presionado amigo.

No sabría decir hasta que punto podría ser problema de la liberación de memoria... cuando el breakpoint pasa por FreeMem me indica que efectivamente, se ha liberado la memoria y continúa con las siguientes instrucciones.

Estuve buscando sobre el error 216 pero por ahora nada que indique en concreto a que se debe el error. La mayoría de los sitios que hablan de esto dan a entender que no existe un caso en particular en el que se produce el error sino que es un caso un tanto general que las situaciones que derivan en ese error pueden ser variadas.... Ninguno ofrece una solución, pareciera no haber algo bien concreto sino que debe estudiarse caso a caso.

Saludos,
  • 0

#18 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 24 octubre 2010 - 11:58

Bueno, echando mano del bajo nivel me he dado cuenta de que el problema está en la asignación del puntero de la matriz a su memoria localizada.

Usando un truco que en ocasiones he utilizado para tratar los buffer como arrays en delphi (en C es automático):



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



Si lo usas así tienes el problema de que no puedes acceder a valores de la matriz con indices que sean constantes pues el compilador protesta, aunque con variables no tienes problemas.

Otra forma es usar:


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



Pero entonces tienes que acceder a la matriz como


delphi
  1. TMatrix(Matrix2)[a][b];



Cualquiera de las dos formas funciona bien.

He usado la API VirtualAlloc  y VirtualFree para acceder directamente al S.O. y el resultado es apabullante a favor del bajo nivel 2,5 veces más rápido sin usar aritmética de punteros. Y, además, el error que mencionabas está solucionado.

Coloco tu código modificado por mi sistema:


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 = ^TMatrix;
  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 := '1/10000 seg: ' +
  78.   FormatFloat('0,',(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.       TMatrix(Matrix2)[i][j] := i + j;
  98.  
  99.   VirtualFree(Matrix2, 0, MEM_RELEASE);
  100.  
  101.   // Paramos "cronómetro"
  102.   QueryPerformanceCounter(Finish);
  103.   Label2.Caption := '1/10000 seg: ' +
  104.   FormatFloat('0,',(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.



Realiza pruebas y nos comentas. :)

Saludos.






  • 0

#19 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 24 octubre 2010 - 01:36

¡Hola!

¡Gracias por el dataso!

Me pondré a hacer pruebas.
Hay cosas que no entiendo mucho, de ese "truco" y del uso de VirtualAlloc y VirtualFree. Tendré que documentarme mejor sobre eso.

He visto en código que tu, seoane y javier exponen en más de una ocasión declaración de arrays de la forma:



delphi
  1. TArray = array [0..0] of algo;


Tengo entendido que esa es una técnica de generar arrays dinámicos pero... ¿Que se gana o que ventaja ofrece respecto a la construcción típica?:



delphi
  1. TArray = array of array of algo;



Como en mi ejemplo.

Desconocía VirtualAlloc/VirtualFree. ¿Que de diferente es respecto a GetMem/FreeMem? Leo en el enlace que ofreces que Virtualxxx trabaja con un espacio de la memoria virtual ¿Que acaso GetMem/FreeMem no hacen lo mismo?

Me pondré a hacer algunas pruebas. Aunque ya me adelantaste algunas cosillas...
Si es 2,5 veces más rápido y viendo que dentro de todo es bastante amigable el código creo que es una buena opción a considerar.

Una última preguntita... que no me logro aclarar del todo mientras estoy leyendo el artículo en MSDN... ¿Y si tuviera que redimendionar?  ^o|

Disculpas mis preguntas. Este manejo es nuevo para mi. Hace tiempo que me desacostumbré a los punteros... la culpa la tienen mis profes que no nos "obligaron" a estudiar con mayor profundidad y a tenerle más respeto al trabajo "orientado a punteros". Recuerdo que la última vez que trabajé con punteros en cátedra fue cuando vimos el tema de árboles.

Saludos,
  • 0

#20 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 24 octubre 2010 - 03:04

He visto en código que tu, seoane y javier exponen en más de una ocasión declaración de arrays de la forma:



delphi
  1. TArray = array [0..0] of algo;


Tengo entendido que esa es una técnica de generar arrays dinámicos pero... ¿Que se gana o que ventaja ofrece respecto a la construcción típica?:


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.

Desconocía VirtualAlloc/VirtualFree. ¿Que de diferente es respecto a GetMem/FreeMem? Leo en el enlace que ofreces que Virtualxxx trabaja con un espacio de la memoria virtual ¿Que acaso GetMem/FreeMem no hacen lo mismo?

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?. 

Una última preguntita... que no me logro aclarar del todo mientras estoy leyendo el artículo en MSDN... ¿Y si tuviera que redimendionar?  ^o|

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.

Disculpas mis preguntas. Este manejo es nuevo para mi. Hace tiempo que me desacostumbré a los punteros...


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.
  • 0




IP.Board spam blocked by CleanTalk.