1 #LyX 1.3 created this file. For more info see http://www.lyx.org/
11 \paperpackage widemarginsa4
15 \use_numerical_citations 0
16 \paperorientation portrait
19 \paragraph_separation indent
21 \quotes_language english
25 \paperpagestyle default
29 Organización de Datos (75.06)
34 \begin_inset Formula $\mu$
50 Leandro Lucarella (77891)
52 Ricardo Markiewicz (78226)
55 Segunda Entrega, 31 de Mayo de 2004
59 \begin_inset LatexCommand \tableofcontents{}
69 En esta entrega el trabajo estuvo concentrado en el manejo de índices para
70 los tipos de archivos implementados en la primer entrega.
71 Los índices se implementaron con:
83 Además de esto, se pide 3 funciones distintas para estos índices:
95 Con la autorización de los ayudantes de la cátedra decidimos que el árbol
96 B+ sólo pueda ser utilizado para índices principal ya que de otra manera
97 no tiene sentido el set secuencial (ver página
98 \begin_inset LatexCommand \pageref{sub:justificacion}
102 para una justificación más detallada).
105 Finalmente, para obtener listados basados en campos de los cuales no se
106 tiene un índice, se implementó un ordenamiento externo.
109 A continuación se presenta una descripción un poco más detallada sobre todas
110 herramientas utilizadas para resolver el trabajo práctico.
113 Documentación de la API
116 Para obtener una documentación de la API más completa, se incluye en formato
117 HTML en el CD-ROM la documentación generado con Doxygen.
118 Esta documentación se encuentra en el directorio
120 doc/api/html/index.html
131 Se detallan a continuación los tipos de datos definidos y utilizados en
132 las distintas implementaciones que conforman nuestro sistema, siendo el
133 más importante de ellos en esta entrega, la estructura
137 que actúa como interfaz común para el manejo de cualquier tipo de índice
138 (no importa que tipo de organización física ni de que forma esté implementado,
139 esta estructura proveerá una interfaz abstracta para su manejo).
145 Se agregaron varios tipos comunes nuevos en esta entrega, en su mayoría
146 relacionados a los índices.
147 Estos tipos son brevemente descriptos a continuación y pueden ser hallados
159 : usado para representar el conjunto de un ID más su dato.
166 : indica el tipo de índice (B, B* o B+).
173 : indica la función que cumple el índice (principal, selectivo o exhaustivo).
180 : indica el tipo de dato que se usa como clave.
187 : representa una clave de un índice.
200 es la estructura principal que encapsula todas las funciones para el manejo
202 Posee punteros a funciones que permite utilizar la misma interfaz para
203 distintas implementaciones de árboles.
207 Su declaración puede ser observada en el archivo
214 y cuenta con la siguiente información:
220 Tipo de dato que maneja.
226 Información sobre el desplazamiento para ubicar el dato dentro de la estructura
227 a indexar (para poder tener una implementación genérica que sirva para
228 cualquier estructura).
231 Información sobre archivos auxiliares para almacenar cadenas de texto y
232 otras estructuras que eventualmente requiera un índice.
235 Punteros a funciones para:
245 Verificar la existencia de una entrada.
251 Obtener clave menor o mayor del índice.
254 Obtener siguiente clave (para recorrido secuencial).
258 Esta estructura define los valores de sus punteros según el tipo de implementaci
259 ón que se desee manejar y esto se realiza a través de la API
268 Esta API posee funciones para crear y destruir índices, agregarlos y quitarlos
273 , comparar claves y otras, necesarias para la correcta y completa utilización
274 de los índices a través de la interfaz de
278 descripta en la entrega anterior.
279 \layout Subsubsection
288 Para integrar la utilización de índices a
292 fueron necesarios los siguientes cambios:
295 Nuevos tipos de archivo.
298 Se incluyen dos tipos de archivo nuevos T4 y T5, que representan, respectivament
299 e, un archivo T1 (registros variables, bloques fijos) y un archivo T3 (registros
300 y bloques fijos), ambos organizados como un set secuencial indexado.
301 De esta manera se conserva la interfaz de
305 (los punteros a funciones) incluso cuando se debe insertar de forma ordenada,
306 ya que al saber que es T4 o T5 siempre se inserta de forma ordenada.
309 Puntero a un arreglo de índices.
312 Se agrega a la estructura
316 un puntero a un arreglo de
320 , donde el primero es siempre el índice principal.
323 Especificaciones de índices
329 En esta sección no se explicará como funciona la implementación del árbol
330 B, sino que se darán algunos detalles de la implementación para algunos
334 Cada nodo del árbol cuenta con un header y un array de datos, donde cada
335 dato contiene, entre otras cosas la clave, el dato al que apunta (id/bloque
336 del archivo de datos) y el hijo derecho (aquel nodo que contiene las claves
338 En el header se guarda el puntero (número de nodo) al hijo izquierdo, que
339 viene a ser el nodo que contiene claves menores a la primer clave de nodo
343 Existen 2 casos particulares para lo que contiene la clave y el dato.
344 El primer caso se da cuando la clave es de tipo string.
347 Cuando un índice debe almacenar string, estos no son guardados en el árbol,
348 sino que se reutiliza la estructura EMUFS de la primer entrega para guardar
349 las cadenas de texto, utilizano para ello una organización de registros
350 de longitud variable sin bloques, elegida de forma arbitraria.
351 En la clave del arbol se guardará entonces el ID del registro que contiene
352 el texto en la estructura mencionada anteriormente.
353 Cuando se quiere recuperar una clave, se lee el archivo que contiene las
354 claves de texto (que permanecen abreviadas).
357 El otro caso particular es para los índices con clave repetida (como ser
358 el selectivo y el exahustivo).
359 Para este caso lo que cambia es lo que se almacena en el campo DATO que
361 Este DATO contendra el ID de un registro que se guarda nuevamente en un
362 EMUFS en formato de registro de longitud variable sin bloques, donde estarán
363 los ID/Bloque reales del archivo de dato de todas las ocurrencias de la
364 clave correspondiente.
367 Cada vez que se inserta una clave y ya existia una previa, se agrega a dicho
368 arreglo la nueva posicion y luego se guarda.
369 Si al eliminar todos los datos de una clave este array quedara vacio, la
370 clave es eliminada del árbol.
373 Puede darse el caso (es más, casi todos los índices utilizados en el TP
374 son de esta manera) que ocurran ambas situaciones descriptas anteriormente,
375 por lo que para un índice, por ejemplo de presentación de los artículos,
376 se tenga que acceder a 9 archivos (el arbol B, 4 para los string, 4 para
377 las claves repetidas) para obtener todos los ID del archivo de datos para
379 Con esta falla de diseño y todo el acceso a registros por campos de identificac
380 ión no único es muy superior a realizar una búsqueda secuencial sobre todo
381 el archivo para realizar una consultas.
387 Lo único que se reescribió fue la función insertar, que aunque es muy similar
388 al del otro árbol, meter más codigo en la misma función hacía aún más dificil
389 de mantener y debuggear el árbol.
392 Otro cambio a la API de árbol B fue en el borrar, que mediante una macro
393 se verifica que tipo de árbol se está tratando y en base a eso se calcula
394 la cantidad de hijos mínimos antes de fundir 2 nodos (o pedir una clave
401 Una de las pocas decisiones que tuvimos que tomar fue la forma de manejar
403 Hay dos formas comunes de hacerlo:
406 Permitir que el nodo raíz pueda almacenar 2N+1 claves (siendo N el número
407 máximo de claves permitido por nodo).
410 Hacer que se comporte como un árbol B.
413 La primera forma garantiza un mejor aprovechamiento del espacio, ya que
414 se sigue haciendo una partición en 3 nodos hijo con 2/3 de los espacios
416 El problema que encontramos para hacerlo de esa forma fue que usamos un
417 tamaño de nodo fijo de 512 para poder leer un sector completo del disco
418 y ganar algo de velocidad, por lo que para poder mantener este esquema
419 hubiéramos necesitado de 3 bloques de 512 para poder guardar los 2N+1 claves,
420 desperdiciando 512-tamaño_de_clave espacio en el bloque final y haciendo
421 que cualquier ahorro de espacio en los hijos del nodo raíz difícilmente
425 Además de esto, el utilizar la segunda forma trae como ventaja la reutilización
426 de código del árbol B, lo que facilita la implementación y el mantenimiento
430 Estas son las dos razones principales por las cuales elegimos tratar el
431 nodo raíz como lo hace el árbol B.
434 Indice B+ Organizacion Secuencial Indexada
440 Para la implementación de la organización secuencial indexada de archivos,
441 se ha utilizado un árbol B+, conocido por ser utilizado en términos generales
442 para implementaciones de esta índole.
445 Como particularidad el arbol B+, poseerá en sus hojas todas las claves que
446 se hayan insertado en el árbol.
447 No obstante, las mismas no serán todas las claves que se encuentren en
448 el archivo de datos, sino la primer clave de cada bloque de datos, también
449 denominadas 'anclas de bloque'.
452 En torno a esta distinción respecto de los demás arboles, el árbol B+ nos
453 indicará a la hora de grabar registros en nuestro archivo de datos con
454 bloques (Organización del TP1, Tipo1 o 3), en que bloque de datos debemos
455 realizar la mencionada inserción.
456 La operativa se detalla más adelante, pero básicamente realizaremos una
457 búsqueda del ancla menor inmediata a la clave del registro que se desea
458 insertar, y esto nos indicará el bloque apropiado (el bloque donde esta
462 Como resultado concreto de este comportamiento (teniendo en cuenta también
463 el borrado y partición de bloques del .dat), obtendremos un archivo secuencial
464 indexado, en donde los registros se encuentran ordenados a nivel de bloques,
465 esto es, dentro de un bloque dado del archivo de datos, los registros estan
466 ordenados por clave primaria.
467 No obstante, los bloques no estarán necesariamente ordenados, pero igualmente
468 la cantidad de accesos para recorrer el archivo en forma secuencial, se
469 ve minimizada respecto de otras organizaciones, gracias al encadenamiento
470 de las hojas y la posesión de las anclas de bloque, cuya lista resultante
471 del encadenamiento es denominada
474 \layout Subsubsection
477 \begin_inset LatexCommand \label{sub:justificacion}
481 Razones por las cuales el B+ es útil sólo para clave principal.
484 El mejor aprovechamiento del Arbol B+ se da en su utilizacion en implementacion
485 ISAM (Indexed Sequential Access Method), en donde se realiza una indexacion
486 parcial de claves, sólo ingresando en el árbol las claves anclas de cada
487 bloque en el archivo de datos.
491 Esta aplicación del árbol B+ a ISAM, además de indicarnos donde grabar y
492 donde buscar los registros por identificación primaria, nos asegura el
493 ordenamiento de los registros parcialmente a nivel de bloque (esto es,
494 los registros en un bloque dado, estarán ordenados, pero los bloques no
496 Así pués, recorriendo el Sequence Set del Arbol B+, minimizaremos los saltos
497 de lectura en disco, pues dentro de un bloque indicado por un ancla dada
498 en el Sequence Set, podremos recorrer los registros secuencialmente.
501 Visto y considerando que la aplicación más importante a nuestro criterio
502 del Arbol B+, era para la indexacion parcial de claves primarias, y que
503 en caso de utilizarlo para otros índices, el B+ se convertiría simplemente
504 en un B con encadenamiento a nivel de hojas, luego de consultar con los
505 ayudantes, decidimos utilizarlo unicamente para el índice primario, y utilizar
506 el B y B* para los restantes índices y/o el primario.
513 Para comprender mejor la implementación particular que hemos dado al árbol
514 B+, damos una breve reseña de la estructura de un nodo del arbol, la cual
530 Esta estructura se encuentra en el archivo
535 Esta organización permite, con la ayuda del árbol, mantener el archivo de
536 datos ordenado por la clave principal.
539 Para lograr esto, como fue expuesto anteriormente, el árbol nos indicará
540 donde (en qué bloque) debe insertarse un registro.
541 (ver 3.3.1 Inserción)
544 En el caso de una hoja, dado que cada nodo posee un hijo mas que la cantidad
545 de claves, el hijo que sobra será utilizado como referencia al nodo
546 \begin_inset Quotes eld
550 \begin_inset Quotes erd
553 , lo cual constituye el
554 \begin_inset Quotes eld
558 \begin_inset Quotes erd
562 Para un nodo que no sea hoja el hijo será el número de nodo correspondiente
563 según la clave, es decir, para la clave
575 contiene claves menores y el hijo
581 contiene las claves mayores.
582 En el caso particular del nivel 1 (index set) el hijo
588 (sequence set) contiene las claves mayores o iguales ya que el
589 \begin_inset Quotes eld
593 \begin_inset Quotes erd
596 debe contener todas las claves insertadas, esto produce que exista una
597 repetición de las claves entre el nivel 1 y el 0.
600 En nuestro caso hemos implementado un Secuencial Indexado tipo ISAM (Indexed
601 Sequential Access Method) el cual posee en sus hojas las anclas de cada
602 bloque en el archivo de datos, es decir, solo se guardan en los nodos del
603 árbol la menor de las claves de un bloque del archivo de datos, acompañada
604 cada clave por el numero de bloque al cual pertenece.
607 Hemos adoptado que la cantidad de claves máxima en un nodo del árbol sea
608 una cantidad impar, ya que esto facilita la elección de la clave que será
609 promovida hacia su nodo padre en caso de que se produzca un overflow en
616 Para realizar una inserción en el archivo de datos se debe realizar una
617 consulta en el árbol, la cual nos indicará el número de bloque donde debemos
618 insertar el nuevo registro.
621 Las consultas se realizan a través de una estructura INDEX_DAT que posee:
630 Esta estructura se encuentra en el archivo
635 El modo de uso es el siguiente:
638 En primer lugar se carga la clave a insertar en el campo Clave, y en el
639 campo Número de Bloque se almacena un número de bloque válido, mas adelante
640 se explica el por qué.
643 Luego se invoca a la función
645 int emufs_b_plus_get_bloque(INDICE, INDEX_DAT)
647 la cual recibe como parámetro una estructura de índice y un INDEX_DAT para
648 realizar la consulta.
651 Esta función recorre recursivamente el árbol y busca una clave mayor inmediata
652 a la enviada, siempre culminando la búsqueda en una hoja.
653 Al encontrar la clave mayor inmediata, el resultado de la búsqueda será
654 la clave anterior en el nodo, pues cada clave en el nodo es un ancla de
655 bloque de datos, de esta manera la clave anterior será menor a la clave
656 enviada, pues las claves en las hojas están ordenadas.
660 En este paso pueden suceder dos cosas:
663 Que exista una clave menor a la enviada.
666 Que la clave enviada sea menor a todas las claves del árbol.
669 En el primer caso, se ha encontrado la clave y se carga la estructura con
670 el hijo de esa clave, que será el número de bloque donde debe insertarse
671 el nuevo registro (por el cual se realizó la consulta), sobreescribiendo
672 el valor que almacenaba al ingresar, y la función retornará código 0 que
673 indica que se ha encontrado un bloque donde insertar.
676 En el segundo caso, puede darse que la clave enviada sea menor a todas las
677 claves del árbol, por lo cual no es posible encontrar un ancla de bloque
679 Aquí la función retornará código -1 lo cual indica que no se ha encontrado
680 un bloque donde insertar el registro nuevo, y es por esto que la estructura
681 debe inicializarse con un número de bloque válido antes de realizarse la
682 consulta.De esta manera el árbol indica donde debe insertarse un nuevo registro
683 en el archivo de datos.
686 Otro detalle de la inserción es que cuando el árbol indica donde debe insertarse
687 un registro pueden pasar dos cosas nuevamente:
690 Que el registro quepa en el bloque.
693 Que el registro no quepa en el bloque.
696 El primer caso es trivial y el registro se insertará sin problemas en el
700 En el caso que el registro no quepa en el bloque, se deberán separar los
701 registros del bloque en 2 bloques, en original y uno nuevo, cada uno con
702 la mitad (aproximadamente) de los registros.
706 Al partir el bloque el ancla del bloque original no se modificará, pero
707 en el bloque nuevo se crea una nueva anlca de bloque, pues una de las claves
708 pertenecientes a los registros que contiene, será la menor.
711 Antes de actualizar el árbol con el ancla nueva, habrá que discriminar en
712 qué bloque se debe insertar el registro nuevo.
713 Para ello se compara la menor de las claves del nuevo bloque con la clave
714 del registro, si la clave del registro es menor que el ancla del nuevo
715 bloque, este debe ir en el bloque original, y se inserta ordenado en él
716 y se le informa al árbol que actualice (inserte) una nueva clave correspondient
717 e al bloque nuevo, sino se inserta en el bloque nuevo de forma ordenada
718 y en este caso cabe la posibilidad de que el nuevo registro posea la clave
719 mas pequeña de todas en el bloque, por ello se lo inserta ordenadamente
720 con ayuda de la función
722 CLAVE grabar_ordenado_en_bloque(EMUFS *emu, void *ptr, EMUFS_REG_SIZE size,
723 void *bloque, int num_bloque, EMUFS_FREE fs, int *err)
725 la cual inserta el registro ordenado por CLAVE y devuelve la menor de las
726 claves del bloque, que se usará para informarle al árbol que inserte una
727 clave nueva junto con el número de bloque, para indexar este bloque.
733 El proceso de eliminación es bastante similar al de inserción en el sentido
734 que también hay que realizar una consulta en el árbol para obtener el número
735 de bloque al que pertenece una clave.
736 Una vez conocido este número se levanta el bloque correspondiente y se
737 busca secuencialmente el registro que se debe eliminar.
740 Si el registro a eliminar fuera el primero del bloque, habrá que modificar
741 el ancla de bloque en el árbol con el ancla que corresponda a la clave
742 del nuevo menor registro, y si el que se elimina fuera el único registro
743 en el bloque habrá que eliminar la clave del árbol.
746 En cualquier otro caso, solo se eliminará el registro correspondiente y
747 se justificarán los registros a izquierda.
753 El proceso de búsqueda de un registro por su clave de identificación primaria
754 en la Organización Secuencial Indexada, es bastante directa en su entendimiento.
755 Para buscar un registro, acudiremos al árbol B+ con la clave anteriormente
756 mencionada, y obtendremos del mismo, el número de bloque donde se debe
757 encontrar el registro.
760 Para obtener dicho número de bloque, el árbol internamente busca el ancla
761 menor inmediata a la clave buscada y luego devuelve el número de bloque
762 de datos donde está dicha ancla (el nro bloque será el dato asociado a
763 la clave ancla, en el árbol), el cual será el bloque potencial donde se
764 encuentre el registro buscado.
767 Una desventaja de esta implementación con indexación parcial, es que no
768 sabremos si el registro se encuentra efectivamente en el bloque indicado,
769 hasta no buscarlo dentro del mismo en formal secuencial.
770 Si lo hallamos, daremos por finalizada la búsqueda del registro.
773 Recorrida secuencial de registros
776 Una consecuencia importante de la organización secuencial indexada, en este
777 caso implementada a través de un árbol B+ con indexación parcial, es que
778 como mencionamos anteriormente, los registros dentro de un bloque se encuetran
779 ordenados, y si bien los bloques en si pueden no estar ordenados en el
780 archivo de datos, algunos lo estarán, y minimizarán en base a estas característ
781 icas, los tiempos de acceso para una recorrida secuencial de registros.
784 Suponiendo que nos encontramos con varios registros cargados en esta organizació
785 n de archivo, y con el correspondiente árbol de indexación primaria B+ en
786 el disco, si se nos pidiera por ejemplo, recorrer los registros de articulos
787 desde el ID_Articulo 40, hasta el ID_Articulo 1406, la operativa será la
791 Acudimos al árbol y obtenemos el numero de bloque para el ID_Articulo 40,
792 buscando el ancla menor inmediata y obteniendo el nro de bloque de datos
793 donde se encuentra, siendo este el nro de bloque donde debería estar el
794 artículo de clave ID_Articulo 40.
797 Levantamos el bloque de datos y lo recorremos secuencialmente, pues como
798 dijimos anteriormente, sus registros se encuentran ordenados por la clave
799 de identificación primaria, en este caso ID_Articulo.
802 Cuando ya hayamos procesado todo el bloque, debemos obtener la siguiente
803 ancla a través del árbol y repetir el proceso.
806 NOTA: Cabe desatacar, que todas las anclas estan en las hojas del B+, y
807 por ello las recorremos a nivel hojas a traves del Sequence Set, que gracias
808 a su encadenamiento, nos permite obtener en forma muy directa y efectiva,
809 las anclas de bloque ordenadas en secuencia, y por consiguiente, recorrer
810 el archivo en forma secuencial minimizando accesos.
816 Descripción del algoritmo
819 Luego de buscar varias alternativas sobre algoritmos de ordenamiento externo,
820 se optó por el siguiente (que resultó una mezcla de las alternativas analizadas
824 Tomar uno a uno los registros del archivo a ordenar e
828 en un buffer ordenado hasta llenar el buffer.
831 Quitar el menor de los valores (
835 uno nuevo desde el archivo a ordenar) e insertarlo en un archivo temporal.
838 Quitar del buffer el mínimo valor mayor al último insertado en el archivo
843 nuevamente un registro obtenido del archivo a ordenar) y se lo inserta
844 en el archivo temporal.
845 De esta forma quedan ordenados los registros en el archivo temporal.
848 Repetir el paso 3 hasta que se vacíe el buffer o hasta que no haya ningún
849 valor mayor al último insertado en el archivo temporal.
850 Cuando esto suceda se crea un nuevo archivo temporal volviendo al paso
854 En este punto ya tenemos el buffer vacío y todos los valores del archivo
855 a ordenar repartidos en 1 o más archivos temporales ordenados, sólo queda
856 unir los archivos para volver a un sólo archivo completo y ordenado.
857 El procedimiento es simple:
860 Obtener el mínimo valor de los archivos temporales e insertarlo en el archivo
864 Repetir 1 hasta agotar los registros de todos los archivos temporales.
867 Debe quedar claro que los archivos temporales se comportan como una cola.
868 Es decir que al obtener un registro de un archivo temporal se obtiene el
869 primer registro que se haya insertado (el mínimo por la forma en la que
876 A continuación se presenta un ejemplo para una más fácil comprensión del
880 Supongamos que queremos ordenar un archivo con registros de números enteros
881 (el archivo se lee de izquierda a derecha): 9 6 34 2 8 3 12 43 23 4 19
885 Supongamos que disponemos de un buffer capaz de almacenar 3 registros.
888 Se llena el buffer ordenado
891 Se lee 9 del archivo original y se lo inserta en el buffer ordenado.
895 Se lee 6 del archivo original y se lo inserta en el buffer ordenado.
899 Se lee 34 del archivo original y se lo inserta en el buffer ordenado.
903 Se crea el archivo temporal ordenado 1
906 Se lee el mínimo valor del buffer (6), se lo inserta en el archivo temporal
907 y se carga un nuevo valor del archivo original al buffer (2).
912 Se lee el mínimo valor del buffer mayor al insertado anteriormente (9),
913 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
914 original al buffer (8).
919 Se lee el mínimo valor del buffer mayor al insertado anteriormente (34),
920 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
921 original al buffer (3).
926 No hay más valores en el buffer mayores al último insertado (34), fin del
930 Se crea el archivo temporal ordenado 2
933 Se lee el mínimo valor del buffer (2), se lo inserta en el archivo temporal
934 y se carga un nuevo valor del archivo original al buffer (12).
939 Se lee el mínimo valor del buffer mayor al insertado anteriormente (3),
940 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
941 original al buffer (43).
946 Se lee el mínimo valor del buffer mayor al insertado anteriormente (8),
947 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
948 original al buffer (23).
953 Se lee el mínimo valor del buffer mayor al insertado anteriormente (12),
954 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
955 original al buffer (4).
960 Se lee el mínimo valor del buffer mayor al insertado anteriormente (23),
961 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
962 original al buffer (19).
964 Archivo2: 2 3 8 12 23
967 Se lee el mínimo valor del buffer mayor al insertado anteriormente (43),
968 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
969 original al buffer (21).
971 Archivo2: 2 3 8 12 23 43
974 No hay más valores en el buffer mayores al último insertado (43), fin del
978 Se crea el archivo temporal ordenado 3
981 Se repite el proceso anterior.
986 Se crea el archivo temporal ordenado 4
989 Se repite el proceso anterior.
991 Archivo4: 1 16 36 42 65
994 Se mezclan los archivos temporales ordenados obteniendo un archivo completo
998 Se obtiene el menor valor de los archivos temporales ordenados (sólo tenemos
999 que elegir entre el primer valor de cada uno).
1003 Archivo2: 2 3 8 12 23 43.
1004 Archivo3: 4 19 21 87.
1005 Archivo4: 1 16 36 42 65
1008 Sólo debo comparar y obtener el menor entre 6, 2, 4, y 1.
1009 Obtengo el 1, lo saco del archivo temporal y lo agrego al de salida:
1013 Archivo2: 2 3 8 12 23 43.
1014 Archivo3: 4 19 21 87.
1015 Archivo4: 16 36 42 65 Salida: 1
1018 Repito hasta que no hayan más valores en los archivos temporales:
1022 Archivo2: 3 8 12 23 43.
1023 Archivo3: 4 19 21 87.
1024 Archivo4: 16 36 42 65.
1029 Archivo2: 8 12 23 43.
1030 Archivo3: 4 19 21 87.
1031 Archivo4: 16 36 42 65.
1036 Archivo2: 8 12 23 43.
1038 Archivo4: 16 36 42 65.
1043 Archivo2: 8 12 23 43.
1045 Archivo4: 16 36 42 65.
1050 Archivo2: 8 12 23 43.
1052 Archivo4: 16 36 42 65.
1059 Archivo4: 16 36 42 65.
1066 Archivo4: 16 36 42 65.
1067 Salida: 1 2 3 4 6 8 9
1073 Archivo4: 16 36 42 65.
1074 Salida: 1 2 3 4 6 8 9 12
1081 Salida: 1 2 3 4 6 8 9 12 16
1088 Salida: 1 2 3 4 6 8 9 12 16 19
1095 Salida: 1 2 3 4 6 8 9 12 16 19 21
1102 Salida: 1 2 3 4 6 8 9 12 16 19 21 23
1109 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34
1116 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36
1123 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36 42
1130 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36 42 43
1137 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36 42 43 65
1144 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36 42 43 65 87
1150 Finalmente, tengo en el archivo de salida el archivo original ordenado.
1156 El algoritmo de ordenamiento es completamente genérico, ya que recibe un
1157 puntero void como registro, su tamaño (para poder manipularlo sin conocer
1158 su tipo) y una función de comparación, para poder comparar dos registros
1159 (sin saber su tipo) a través de una relación de orden (descripta por dicha
1163 Decisiones de diseño.
1166 El algoritmo se eligió en base a una serie de razones y cuenta con una serie
1167 de ventajas y desventajas.
1170 El algoritmo es simple, tanto teóricamente como para implementar.
1173 Tiene la desventaja de que puede llegar a usar muchos archivos temporales
1174 y todos abiertos al mismo tiempo, pero considerando que el sistema operativo
1175 en el que se utiliza suele manejar bien grandes cantidades de archivos
1176 no es una desventaja importante.
1179 Al usar un buffer intermedio, se puede controlar muy bien la cantidad de
1180 memoria que utiliza y experimentar con distintos valores para analizar
1184 Necesita sólo la misma cantidad de espacio libre en disco que la cantidad
1185 de espacio que ocupa el archivo a ordenar.
1186 Todos los métodos analizados necesitaban igual o más cantidad de espacio
1190 El buffer ordenado se implementó con un árbol binario debido a que tiene
1191 una buena relación entre velocidad de búsqueda y facilidad de implementación.
1192 Al ser el principal determinante de la velocidad los accesos a disco no
1193 se creyó necesario buscar una alternativa más rápida para mantener el buffer
1194 ordenado en memoria, ya que no cambiaría de forma notable el tiempo total
1196 Otras posibilidades hubieran sido cargar todo el buffer en memoria y ordenarlo
1197 posteriormente (dependiendo del algoritmo de ordenamiento a utilizar puede
1198 ser más o menos rápido que el árbol y más o menos complicado de implementar)
1199 o hacer una búsqueda secuencial sobre un buffer desordenado (es más fácil
1200 de implementar pero claramente más lento).
1201 Una posible ventaja notable de leer el buffer primero y luego ordenarlo
1202 en memoria es que se necesita un sólo acceso al disco para llenar el buffer,
1203 mientras que al obtener uno a uno los valores puede generar muchos accesos
1205 Esto no debería ser muy notable ya que las funciones de acceso a archivos
1206 de la biblioteca estándar de C poseen un buffer interno, por lo que los
1207 accesos a disco probablemente sea muy poco aún cuando se obtienen uno a
1214 Luego de terminar con todo los requerimientos del TP, nos pusimos a tratar
1215 de realizar las comparaciones entre los distintos índices y los tipos de
1217 En un primer momento se trato de hacerse midiendo tiempos, pero al no poseer
1218 ANSI C funciones de tiempo con suficiente precisión no se pudo obtener
1219 ninguna conclusión relevante.
1222 Fue entonces que decidimos hacer una comparativa basada en el uso del programa
1223 modificando los parametros de carga.
1224 Lo que hicimos fue para cada archivo (articulos y facturas) probar utilizar
1225 la clave primaria con los tres tipos de arboles y luego navegar por los
1226 menúes del programa realizando operaciones de consultas, busquedas, etc
1227 a fin de ver si se notaba diferencia.
1228 Este útimo análisis no es del todo objetivo, pero nos dio pié para realizar
1229 una charla por IRC donde discutimos nuestra experiencia con cada tipo de
1233 La conclusión a que llegamos es que para el archivo de artículos, que es
1234 de tipo maestro (pues es un archivo que tiene pocas altas y muchas consultas)
1235 sería preferible utilizar un índice primario de tipo B+ ya que nos permite
1236 acceder de manera ordenada de 2 maneras, mediante el índice y tener un
1237 acceso secuencial (ideal para hacer un reporte por ejemplo), y dado que
1238 los artículos permanecerán ordenados los reportes saldrán de la misma manera.
1241 En cambio para las facturas sería mejor tener un índice B o B*, ya que es
1242 un archivo de transacciónes, donde se suponen que las altas son mayores
1243 que las consultas (esperamos poder vender mucho!).
1244 La ventaja del B y B* es que la inserción es más simple, requiere de un
1245 menor movimiento de registros a la hora de agregar, ya que no es necesario
1246 que en el archivo de datos estos queden ordenados, pudiendo quedar en cualquier
1247 orden fisicamente, aún estando en el mismo bloque.
1248 Esta forma de organizarla obviamente no es para nada útil si necesitaramos
1249 por alguna razón acceder secuencialmente por clave primaria, ya que deberiamos
1250 estar saltando por todo el archivo para poder hacerlo.
1253 No hemos encontrado muchas razones para decidirnos por B o B*, y que nuestra
1254 implementación es la misma salvando por el insertar, que en el caso del
1255 B* hace todas las rotaciones posibles antes de hacer un split, cosa que
1256 puede beneficiar ya que la creación de nodos es menor, pero para las cantidades
1257 de datos manejados no vemos que influya mucho.