diff --git a/Readme.md b/Readme.md index 4c5c8e5e4801d1563127e718304744a1ff1a3b16..a70804f952475d580b19b2dc343e5769673c2f44 100644 --- a/Readme.md +++ b/Readme.md @@ -29,6 +29,9 @@ La courbe est beaucoup moins fluide. Elle monte par paliers, car le dictionnaire La MPHF est clairement plus avantageuse en termes de mémoire, surtout quand on travaille avec de très grands ensembles de k-mers, comme c’est souvent le cas en bioinformatique. Le dictionnaire, lui, reste pratique et facile à utiliser, mais il devient vite inefficace quand on monte en volume. Bonus : faites varier `nb_niveaux` et `gamma`, voyez quelle influence ils peuvent avoir. +On constate qu'augmenter gamma ou nb_niveaux ne change pas la taille finale de la structure (elle reste fixe car on stocke une paire par k-mer), +mais cela ralentit la construction de la MPHF. +Autrement dit, ces paramètres font travailler le code un peu plus pour mieux gérer les collisions, ce qui allonge le temps de calcul sans affecter la taille finale. # Partie 2 - Analyse de performance de dictionnaires en Python @@ -75,3 +78,17 @@ Initialiser dans la fonction `experiment_load_factor` des listes `insertion_time - Un histogramme des fréquences des temps d'insertions discrétisés (code fourni, remplacer la liste vide par la bonne entrée) 10. Commentez vos résultats. + + -Pour le graphique du temps d'insertion en fonction du facteur de charge ,on constate que l’insertion dans un dictionnaire Python demeure rapide même lorsque le facteur de charge est proche de 1.0. +Les sauts traduisent les réallocations internes et la gestion mémoire dynamique de Python, qui peuvent se produire à des moments différents d’une exécution à l’autre + +-Pour le graphique du nombre de réallocations de mémoire en fonction du facteur de charge , on constate que plus on insère d’éléments (donc plus le facteur de charge est élevé), + plus le dictionnaire Python se réalloue en mémoire, jusqu’à atteindre un plateau autour de 12 réallocations. + Python effectue ces réallocations par paliers, ce qui explique pourquoi le nombre total de «resizes» reste finalement assez limité malgré un grand nombre d’insertions. + +-Pour le graphique de la taille de mémoire occupée en fonction du nombre d'éléments ,On voit que la mémoire occupée par le dictionnaire augmente à mesure que le nombre d’éléments insérés croît, + avec des sauts plus ou moins marqués qui correspondent aux réallocations internes. Au-delà d’un certain seuil (environ 6000 éléments ici), la taille mémoire se stabilise, car le dictionnaire a suffisamment grandi pour accueillir la totalité des clés. + +-On constate sur l'histogramme des fréquences des temps d'insertions discrétisés que La majorité des insertions se font en un temps quasi nul , tandis qu’une poignée d’insertions plus longues apparaissent dans la seconde barre, +probablement lors des réallocations. +Cela souligne qu’en Python, la plupart des insertions sont très rapides, mais certaines opérations de réallocation peuvent entraîner un temps d’insertion plus élevé. \ No newline at end of file diff --git a/tp_2_miso_dict.py b/tp_2_miso_dict.py index 1f72420370fb6188331755af6e992d0552199960..5408d2926af36fd86929b9a0033b44e7aacf4f63 100644 --- a/tp_2_miso_dict.py +++ b/tp_2_miso_dict.py @@ -1,4 +1,5 @@ from re import search +from typing import List import matplotlib.pyplot as plt import numpy as np @@ -10,100 +11,126 @@ import sys ###### PARTIE 2 ###### def experiment_load_factor(load_factors): - """ - Etude l'inmpact du facteur de charge sur les performances d'un dictionnaire Python. - Mesure les temps d'insertion, de recherche et de suppression pour différents facteurs de charge. - Returns: - insetion_times: liste contenant les temps d'insertion (en secondes) pour chaque facteur de charge. - search_times: Liste contenant les temps de recherchde (en secondes) pour chaque facteur de charge. - deletion_times: Liste contenant les temps de suppression (en secondes) pour chaque facteur de charge - """ - # 1- Initialisation des listes pour stocker les résultats - insertion_times = [] # Temps d'insertion pour chaque facteur de charge - search_times = [] # Temps de recherche pour chaque facteur de charge - deletion_times = [] # Temps de suppression pour chaque facteur de charge - - # 2. Boucle sur chaque facteur de charge donné en entrée - for load_factor in load_factors: - # Création d'un dictionnaire vide au départ - d = {} - - # Calcul de la taille du dictionnaire en fonction du facteur de charge - size = int(10000 * load_factor) - - # Mesure du temps d'insertion - start_time = time.time() - for i in range(size): - key = str(i) - value = i - d[key] = value # Insertion de la clé et de la valeur - insertion_time = time.time() - start_time - insertion_times.append(insertion_time) - - # Mesure du temps de recherche - start_time = time.time() - for i in range(size): - key = str(i) - _ = d.get(key) # Recherche de la clé - search_time = time.time() - start_time - search_times.append(search_time) - - # Réinitialisation du dictionnaire pour la suppression - d = {str(i): i for i in range(size)} - - # Mesure du temps de suppression - start_time = time.time() - for i in range(size): - key = str(i) - del d[key] # Suppression de la clé - deletion_time = time.time() - start_time - deletion_times.append(deletion_time) - - # Retour des résultats - return insertion_times, search_times, deletion_times + """ + Etude l'inmpact du facteur de charge sur les performances d'un dictionnaire Python. + Mesure les temps d'insertion, de recherche et de suppression pour différents facteurs de charge. + Returns: + insetion_times: liste contenant les temps d'insertion (en secondes) pour chaque facteur de charge. + search_times: Liste contenant les temps de recherchde (en secondes) pour chaque facteur de charge. + deletion_times: Liste contenant les temps de suppression (en secondes) pour chaque facteur de charge + """ + insertion_times = [] # Pour stocker le temps total d'insertion + num_resizes = [] # Pour stocker le nombre de réallocations + sizes = [] # Pour stocker la taille mémoire finale + for lf in load_factors: + # Crée un dictionnaire vide + d = {} + + # Nombre d'éléments à insérer pour ce facteur de charge + num_elements = int(10000 * lf) + + # Variables pour suivre la taille courante et détecter une réallocation + last_size = sys.getsizeof(d) # taille mémoire initiale + resize_count = 0 + + # Mesure du temps d'insertion (global) + start_time = time.time() + + for i in range(num_elements): + d[i] = i + + # Après chaque insertion, vérifie si la taille mémoire a augmenté + current_size = sys.getsizeof(d) + if current_size > last_size: + resize_count += 1 + last_size = current_size + + end_time = time.time() + total_insertion_time = end_time - start_time + + # Stockage des résultats pour ce facteur de charge + insertion_times.append(total_insertion_time) + num_resizes.append(resize_count) + sizes.append(sys.getsizeof(d)) + + return insertion_times, num_resizes, sizes def experiment_longest(): - """ - TODO: que fait cette fonction - """ - d = {} - insertion_times = [] - - for i in range(10000): - key = str(i) - value = i - start_time = time.time() - d[key] = value - insertion_time = time.time() - start_time - insertion_times.append(insertion_time) - frequencies = np.histogram(insertion_times)[0] - return frequencies + """ + Effectue 10 000 insertions successives dans un dict, en mesurant + le temps d'insertion pour chacune. + Retourne l'histogramme (fréquences) de ces temps d'insertion, + discrétisés en bacs (bins). + """ + d = {} + insertion_times = [] + + for i in range(10000): + key = str(i) + value = i + start_time = time.time() + d[key] = value + insertion_time = time.time() - start_time + insertion_times.append(insertion_time) + frequencies = np.histogram(insertion_times)[0] + return frequencies def visualisation(load_factors, insertion_times, num_resizes, sizes, frequencies): - """ - Visualisation des résultats - """ - # Temps d'insertion en fonction du facteur de charge - - # Nombre de réallocations de mémoire en fonction du facteur de charge - - # Taille de mémoire occupée en fonction du nombre d'éléments - - # Deuxième étude - f = list() - plt.figure(figsize=(10, 6)) - plt.bar(range(len(f)), f) - plt.xlabel('Temps d\'insertion (s)') - plt.ylabel('Fréquence') - plt.title('Histogramme des fréquences des temps d\'insertions') - plt.yscale('log') - xticks = np.logspace(-6, 1, 3) - xtick_labels = [f'{x:.1e}' for x in xticks] - plt.xticks(xticks, xtick_labels) - plt.savefig('histogramme.png') + """ + Visualisation des résultats + """ + # Temps d'insertion en fonction du facteur de charge + plt.figure(figsize=(6, 4)) + plt.plot(load_factors, insertion_times, marker='o') + plt.xlabel('Facteur de charge (load_factor)') + plt.ylabel('Temps total d\'insertion (secondes)') + plt.title('Temps d\'insertion en fonction du facteur de charge') + plt.grid(True) + plt.tight_layout() + plt.savefig('Tps_insertion.facteur_de_charge.png') + plt.close() + + # Nombre de réallocations de mémoire en fonction du facteur de charge + plt.figure(figsize=(6, 4)) + plt.plot(load_factors, num_resizes, marker='o') + plt.xlabel('Facteur de charge (load_factor)') + plt.ylabel('Nombre de réallocations (resizes)') + plt.title('Nombre de réallocations de mémoire en fonction du facteur de charge') + plt.grid(True) + plt.tight_layout() + plt.savefig('nb_reallocation.facteur_de_charge.png') + plt.close() + + # Taille de mémoire occupée en fonction du nombre d'éléments + # Le nombre d'éléments insérés = 10000 * load_factor + nb_elements = [int(10000 * lf) for lf in load_factors] + plt.figure(figsize=(6, 4)) + plt.plot(nb_elements, sizes, marker='o') + plt.xlabel('Nombre d\'éléments insérés') + plt.ylabel('Taille mémoire du dict (octets)') + plt.title('Taille de mémoire occupée en fonction du nombre d\'éléments') + plt.grid(True) + plt.tight_layout() + plt.savefig('courbe_memoire.nombre_delement.png') + plt.close() + + # Deuxième étude + + f = frequencies + plt.figure(figsize=(10, 6)) + plt.bar(range(len(f)), f) + plt.xlabel('Temps d\'insertion (s)') + plt.ylabel('Fréquence') + plt.title('Histogramme des fréquences des temps d\'insertions') + plt.yscale('log') + xticks = np.logspace(-6, 1, 3) + xtick_labels = [f'{x:.1e}' for x in xticks] + plt.xticks(xticks, xtick_labels) + plt.savefig('histogramme.png') + load_factors = [0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] insertion_times, num_resizes, sizes = experiment_load_factor(load_factors) diff --git a/tp_2_miso_mphf.py b/tp_2_miso_mphf.py index d99923180359b9fdf5ded6feda7f8a76a7a81f03..cd2f8feff246a8da109440687ae0acb6d07ff334 100644 --- a/tp_2_miso_mphf.py +++ b/tp_2_miso_mphf.py @@ -200,4 +200,27 @@ def compare_taille(n_max, fichier_sortie): # dé-commenter quand vous êtes prêts, expliquer les résultats compare_taille(10000,"mphf.png") +#bonus +def experiment_mphf_variation(set_kmer, n, gamma_values, niveaux_values): + """ + Compare l'influence de gamma et nb_niveaux sur la taille et/ou le temps de construction + de la MPHF pour un ensemble set_kmer. + """ + + + for gamma in gamma_values: + for nb_niveaux in niveaux_values: + start = time.time() + mphf = construction_mphf(set_kmer, n, gamma=gamma, nb_niveaux=nb_niveaux) + end = time.time() + + size_mphf = sys.getsizeof(mphf) + print(f"gamma={gamma}, nb_niveaux={nb_niveaux} | " + f"Taille MPHF: {size_mphf} octets | " + f"Temps construction: {end - start:.5f} s") + +set_kmer = generer_kmers(1000, 21) # Par exemple, 1000 k-mers de taille 21 +experiment_mphf_variation(set_kmer, n=1000, + gamma_values=[1,2,3,4], + niveaux_values=[1,2,3])