# UNDF: UNDF-2026-000000795 # UNDF: (leave blank) # darktable-0001: dt_map_location_update_images g_list_find O(N*M) image diff # # In src/common/map_locations.c, dt_map_location_update_images() computes the # symmetric difference between old images (imgs) and new images (new_imgs) by # calling g_list_find() inside two sequential for-loops. Each g_list_find() is # O(M) on a GList, making each loop O(N*M). For a location covering N=1000 # geotagged images with M=1000 new images, this is O(N*M) = 1,000,000 # pointer comparisons instead of O(N+M) with a hash set. # # Similarly, dt_map_location_update_locations() does the same pattern with # old_tags vs tags lists: two loops with g_list_find() inside each. # # Fix: build a GHashTable from one list, then iterate the other with O(1) # hash lookups. # # Severity: MEDIUM — triggered on every map location update; scales with # collection size. --- a/src/common/map_locations.c +++ b/src/common/map_locations.c @@ -535,14 +535,22 @@ // clean up locations which are not valid anymore + GHashTable *tag_set = g_hash_table_new(g_direct_hash, g_direct_equal); + for(GList *tag = (GList *)tags; tag; tag = g_list_next(tag)) + g_hash_table_add(tag_set, tag->data); + for(GList *tag = old_tags; tag; tag = g_list_next(tag)) { - if(!g_list_find((GList *)tags, tag->data)) + if(!g_hash_table_contains(tag_set, tag->data)) { dt_tag_detach(GPOINTER_TO_INT(tag->data), imgid, FALSE, FALSE); } } + g_hash_table_destroy(tag_set); // add new locations + GHashTable *old_set = g_hash_table_new(g_direct_hash, g_direct_equal); + for(GList *tag = old_tags; tag; tag = g_list_next(tag)) + g_hash_table_add(old_set, tag->data); + for(GList *tag = (GList *)tags; tag; tag = g_list_next(tag)) { - if(!g_list_find(old_tags, tag->data)) + if(!g_hash_table_contains(old_set, tag->data)) { dt_tag_attach(GPOINTER_TO_INT(tag->data), imgid, FALSE, FALSE); } } + g_hash_table_destroy(old_set); g_list_free(old_tags); } @@ -558,14 +566,22 @@ gboolean res = FALSE; // detach images which are not in location anymore + GHashTable *new_set = g_hash_table_new(g_direct_hash, g_direct_equal); + for(GList *img = new_imgs; img; img = g_list_next(img)) + g_hash_table_add(new_set, img->data); + for(GList *img = imgs; img; img = g_list_next(img)) { - if(!g_list_find(new_imgs, img->data)) + if(!g_hash_table_contains(new_set, img->data)) { dt_tag_detach(ld->id, GPOINTER_TO_INT(img->data), FALSE, FALSE); res = TRUE; } } + g_hash_table_destroy(new_set); // add new images to location + GHashTable *old_img_set = g_hash_table_new(g_direct_hash, g_direct_equal); + for(GList *img = imgs; img; img = g_list_next(img)) + g_hash_table_add(old_img_set, img->data); + for(GList *img = new_imgs; img; img = g_list_next(img)) { - if(!g_list_find(imgs, img->data)) + if(!g_hash_table_contains(old_img_set, img->data)) { dt_tag_attach(ld->id, GPOINTER_TO_INT(img->data), FALSE, FALSE); res = TRUE; } } + g_hash_table_destroy(old_img_set); g_list_free(new_imgs);