pack-redundant.con commit Make git-pack-redundant non-horribly slow on large sets of packs (751a71e)
   1/*
   2*
   3* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
   4*
   5* This file is licensed under the GPL v2.
   6*
   7*/
   8
   9#include "cache.h"
  10
  11static const char pack_redundant_usage[] =
  12"git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
  13
  14int load_all_packs = 0, verbose = 0, alt_odb = 0;
  15
  16struct llist_item {
  17        struct llist_item *next;
  18        char *sha1;
  19};
  20struct llist {
  21        struct llist_item *front;
  22        struct llist_item *back;
  23        size_t size;
  24} *all_objects; /* all objects which must be present in local packfiles */
  25
  26struct pack_list {
  27        struct pack_list *next;
  28        struct packed_git *pack;
  29        struct llist *unique_objects;
  30        struct llist *all_objects;
  31} *local_packs = NULL, *altodb_packs = NULL;
  32
  33struct pll {
  34        struct pll *next;
  35        struct pack_list *pl;
  36        size_t pl_size;
  37};
  38
  39inline void llist_free(struct llist *list)
  40{
  41        while((list->back = list->front)) {
  42                list->front = list->front->next;
  43                free(list->back);
  44        }
  45        free(list);
  46}
  47
  48inline void llist_init(struct llist **list)
  49{
  50        *list = xmalloc(sizeof(struct llist));
  51        (*list)->front = (*list)->back = NULL;
  52        (*list)->size = 0;
  53}
  54
  55struct llist * llist_copy(struct llist *list)
  56{
  57        struct llist *ret;
  58        struct llist_item *new, *old, *prev;
  59        
  60        llist_init(&ret);
  61
  62        if ((ret->size = list->size) == 0)
  63                return ret;
  64
  65        new = ret->front = xmalloc(sizeof(struct llist_item));
  66        new->sha1 = list->front->sha1;
  67
  68        old = list->front->next;
  69        while (old) {
  70                prev = new;
  71                new = xmalloc(sizeof(struct llist_item));
  72                prev->next = new;
  73                new->sha1 = old->sha1;
  74                old = old->next;
  75        }
  76        new->next = NULL;
  77        ret->back = new;
  78        
  79        return ret;
  80}
  81
  82inline struct llist_item * llist_insert(struct llist *list,
  83                                        struct llist_item *after, char *sha1)
  84{
  85        struct llist_item *new = xmalloc(sizeof(struct llist_item));
  86        new->sha1 = sha1;
  87        new->next = NULL;
  88
  89        if (after != NULL) {
  90                new->next = after->next;
  91                after->next = new;
  92                if (after == list->back)
  93                        list->back = new;
  94        } else {/* insert in front */
  95                if (list->size == 0)
  96                        list->back = new;
  97                else
  98                        new->next = list->front;
  99                list->front = new;
 100        }
 101        list->size++;
 102        return new;
 103}
 104
 105inline struct llist_item * llist_insert_back(struct llist *list, char *sha1)
 106{
 107        return llist_insert(list, list->back, sha1);
 108}
 109
 110inline struct llist_item * llist_insert_sorted_unique(struct llist *list,
 111                                        char *sha1, struct llist_item *hint)
 112{
 113        struct llist_item *prev = NULL, *l;
 114
 115        l = (hint == NULL) ? list->front : hint;
 116        while (l) {
 117                int cmp = memcmp(l->sha1, sha1, 20);
 118                if (cmp > 0) { /* we insert before this entry */
 119                        return llist_insert(list, prev, sha1);
 120                }
 121                if(!cmp) { /* already exists */
 122                        return l;
 123                }
 124                prev = l;
 125                l = l->next;
 126        }
 127        /* insert at the end */
 128        return llist_insert_back(list, sha1);
 129}
 130
 131/* returns a pointer to an item in front of sha1 */
 132inline struct llist_item * llist_sorted_remove(struct llist *list, char *sha1,
 133                                               struct llist_item *hint)
 134{
 135        struct llist_item *prev, *l;
 136
 137redo_from_start:
 138        l = (hint == NULL) ? list->front : hint;
 139        prev = NULL;
 140        while (l) {
 141                int cmp = memcmp(l->sha1, sha1, 20);
 142                if (cmp > 0) /* not in list, since sorted */
 143                        return prev;
 144                if(!cmp) { /* found */
 145                        if (prev == NULL) {
 146                                if (hint != NULL && hint != list->front) {
 147                                        /* we don't know the previous element */
 148                                        hint = NULL;
 149                                        goto redo_from_start;
 150                                }
 151                                list->front = l->next;
 152                        } else
 153                                prev->next = l->next;
 154                        if (l == list->back)
 155                                list->back = prev;
 156                        free(l);
 157                        list->size--;
 158                        return prev;
 159                }
 160                prev = l;
 161                l = l->next;
 162        }
 163        return prev;
 164}
 165
 166/* computes A\B */
 167void llist_sorted_difference_inplace(struct llist *A,
 168                                     struct llist *B)
 169{
 170        struct llist_item *hint, *b;
 171
 172        hint = NULL;
 173        b = B->front;
 174
 175        while (b) {
 176                hint = llist_sorted_remove(A, b->sha1, hint);
 177                b = b->next;
 178        }
 179}
 180
 181inline struct pack_list * pack_list_insert(struct pack_list **pl,
 182                                           struct pack_list *entry)
 183{
 184        struct pack_list *p = xmalloc(sizeof(struct pack_list));
 185        memcpy(p, entry, sizeof(struct pack_list));
 186        p->next = *pl;
 187        *pl = p;
 188        return p;
 189}
 190
 191inline size_t pack_list_size(struct pack_list *pl)
 192{
 193        size_t ret = 0;
 194        while(pl) {
 195                ret++;
 196                pl = pl->next;
 197        }
 198        return ret;
 199}
 200
 201struct pack_list * pack_list_difference(struct pack_list *A,
 202                                        struct pack_list *B)
 203{
 204        struct pack_list *ret, *pl;
 205
 206        if (A == NULL)
 207                return NULL;
 208
 209        pl = B;
 210        while (pl != NULL) {
 211                if (A->pack == pl->pack)
 212                        return pack_list_difference(A->next, B);
 213                pl = pl->next;
 214        }
 215        ret = xmalloc(sizeof(struct pack_list));
 216        memcpy(ret, A, sizeof(struct pack_list));
 217        ret->next = pack_list_difference(A->next, B);
 218        return ret;
 219}
 220
 221void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
 222{
 223        int p1_off, p2_off;
 224        void *p1_base, *p2_base;
 225        struct llist_item *p1_hint = NULL, *p2_hint = NULL;
 226        
 227        p1_off = p2_off = 256 * 4 + 4;
 228        p1_base = (void *)p1->pack->index_base;
 229        p2_base = (void *)p2->pack->index_base;
 230
 231        while (p1_off <= p1->pack->index_size - 3 * 20 &&
 232               p2_off <= p2->pack->index_size - 3 * 20)
 233        {
 234                int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20);
 235                /* cmp ~ p1 - p2 */
 236                if (cmp == 0) {
 237                        p1_hint = llist_sorted_remove(p1->unique_objects,
 238                                        p1_base + p1_off, p1_hint);
 239                        p2_hint = llist_sorted_remove(p2->unique_objects,
 240                                        p1_base + p1_off, p2_hint);
 241                        p1_off+=24;
 242                        p2_off+=24;
 243                        continue;
 244                }
 245                if (cmp < 0) { /* p1 has the object, p2 doesn't */
 246                        p1_off+=24;
 247                } else { /* p2 has the object, p1 doesn't */
 248                        p2_off+=24;
 249                }
 250        }
 251}
 252
 253void pll_insert(struct pll **pll, struct pll **hint_table)
 254{
 255        struct pll *prev;
 256        int i = (*pll)->pl_size - 1;
 257
 258        if (hint_table[i] == NULL) {
 259                hint_table[i--] = *pll;
 260                for (; i >= 0; --i) {
 261                        if (hint_table[i] != NULL)
 262                                break;
 263                }
 264                if (hint_table[i] == NULL) /* no elements in list */
 265                        die("Why did this happen?");
 266        }
 267
 268        prev = hint_table[i];
 269        while (prev->next && prev->next->pl_size < (*pll)->pl_size)
 270                prev = prev->next;
 271
 272        (*pll)->next = prev->next;
 273        prev->next = *pll;
 274}
 275
 276/* all the permutations have to be free()d at the same time,
 277 * since they refer to each other
 278 */
 279struct pll * get_all_permutations(struct pack_list *list)
 280{
 281        struct pll *subset, *pll, *new_pll = NULL; /*silence warning*/
 282        static struct pll **hint = NULL;
 283        if (hint == NULL)
 284                hint = xcalloc(pack_list_size(list), sizeof(struct pll *));
 285                
 286        if (list == NULL)
 287                return NULL;
 288
 289        if (list->next == NULL) {
 290                new_pll = xmalloc(sizeof(struct pll));
 291                hint[0] = new_pll;
 292                new_pll->next = NULL;
 293                new_pll->pl = list;
 294                return new_pll;
 295        }
 296
 297        pll = subset = get_all_permutations(list->next);
 298        while (pll) {
 299                if (pll->pl->pack == list->pack) {
 300                        pll = pll->next;
 301                        continue;
 302                }
 303                new_pll = xmalloc(sizeof(struct pll));
 304
 305                new_pll->pl = xmalloc(sizeof(struct pack_list));
 306                memcpy(new_pll->pl, list, sizeof(struct pack_list));
 307                new_pll->pl->next = pll->pl;
 308                new_pll->pl_size = pll->pl_size + 1;
 309                
 310                pll_insert(&new_pll, hint);
 311
 312                pll = pll->next;
 313        }
 314        /* add ourself */
 315        new_pll = xmalloc(sizeof(struct pll));
 316        new_pll->pl = xmalloc(sizeof(struct pack_list));
 317        memcpy(new_pll->pl, list, sizeof(struct pack_list));
 318        new_pll->pl->next = NULL;
 319        new_pll->pl_size = 1;
 320        pll_insert(&new_pll, hint);
 321
 322        return hint[0];
 323}
 324
 325int is_superset(struct pack_list *pl, struct llist *list)
 326{
 327        struct llist *diff;
 328
 329        diff = llist_copy(list);
 330
 331        while (pl) {
 332                llist_sorted_difference_inplace(diff,
 333                                                pl->all_objects);
 334                if (diff->size == 0) { /* we're done */
 335                        llist_free(diff);
 336                        return 1;
 337                }
 338                pl = pl->next;
 339        }
 340        llist_free(diff);
 341        return 0;
 342}
 343
 344size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
 345{
 346        size_t ret = 0;
 347        int p1_off, p2_off;
 348        void *p1_base, *p2_base;
 349
 350        p1_off = p2_off = 256 * 4 + 4;
 351        p1_base = (void *)p1->index_base;
 352        p2_base = (void *)p2->index_base;
 353
 354        while (p1_off <= p1->index_size - 3 * 20 &&
 355               p2_off <= p2->index_size - 3 * 20)
 356        {
 357                int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20);
 358                /* cmp ~ p1 - p2 */
 359                if (cmp == 0) {
 360                        ret++;
 361                        p1_off+=24;
 362                        p2_off+=24;
 363                        continue;
 364                }
 365                if (cmp < 0) { /* p1 has the object, p2 doesn't */
 366                        p1_off+=24;
 367                } else { /* p2 has the object, p1 doesn't */
 368                        p2_off+=24;
 369                }
 370        }
 371        return ret;
 372}
 373
 374/* another O(n^2) function ... */
 375size_t get_pack_redundancy(struct pack_list *pl)
 376{
 377        struct pack_list *subset;
 378
 379        if (pl == NULL)
 380                return 0;
 381
 382        size_t ret = 0;
 383        while ((subset = pl->next)) {
 384                while(subset) {
 385                        ret += sizeof_union(pl->pack, subset->pack);
 386                        subset = subset->next;
 387                }
 388                pl = pl->next;
 389        }
 390        return ret;
 391}
 392
 393inline size_t pack_set_bytecount(struct pack_list *pl)
 394{
 395        size_t ret = 0;
 396        while (pl) {
 397                ret += pl->pack->pack_size;
 398                ret += pl->pack->index_size;
 399                pl = pl->next;
 400        }
 401        return ret;
 402}
 403
 404void minimize(struct pack_list **min)
 405{
 406        struct pack_list *pl, *unique = NULL,
 407                *non_unique = NULL, *min_perm = NULL;
 408        struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
 409        struct llist *missing;
 410        size_t min_perm_size = (size_t)-1, perm_size;
 411
 412        pl = local_packs;
 413        while (pl) {
 414                if(pl->unique_objects->size)
 415                        pack_list_insert(&unique, pl);
 416                else
 417                        pack_list_insert(&non_unique, pl);
 418                pl = pl->next;
 419        }
 420        /* find out which objects are missing from the set of unique packs */
 421        missing = llist_copy(all_objects);
 422        pl = unique;
 423        while (pl) {
 424                llist_sorted_difference_inplace(missing,
 425                                                pl->all_objects);
 426                pl = pl->next;
 427        }
 428
 429        /* return if there are no objects missing from the unique set */
 430        if (missing->size == 0) {
 431                *min = unique;
 432                return;
 433        }
 434
 435        /* find the permutations which contain all missing objects */
 436        perm_all = perm = get_all_permutations(non_unique);
 437        while (perm) {
 438                if (perm_ok && perm->pl_size > perm_ok->pl_size)
 439                        break; /* ignore all larger permutations */
 440                if (is_superset(perm->pl, missing)) {
 441                        new_perm = xmalloc(sizeof(struct pll));
 442                        new_perm->pl = perm->pl;
 443                        new_perm->next = perm_ok;
 444                        perm_ok = new_perm;
 445                }
 446                perm = perm->next;
 447        }
 448        
 449        if (perm_ok == NULL)
 450                die("Internal error: No complete sets found!\n");
 451
 452        /* find the permutation with the smallest size */
 453        perm = perm_ok;
 454        while (perm) {
 455                perm_size = pack_set_bytecount(perm->pl);
 456                if (min_perm_size > perm_size) {
 457                        min_perm_size = perm_size;
 458                        min_perm = perm->pl;
 459                }
 460                perm = perm->next;
 461        }
 462        *min = min_perm;
 463        /* add the unique packs to the list */
 464        pl = unique;
 465        while(pl) {
 466                pack_list_insert(min, pl);
 467                pl = pl->next;
 468        }
 469}
 470
 471void load_all_objects()
 472{
 473        struct pack_list *pl = local_packs;
 474        struct llist_item *hint, *l;
 475        int i;
 476
 477        llist_init(&all_objects);
 478
 479        while (pl) {
 480                i = 0;
 481                hint = NULL;
 482                l = pl->all_objects->front;
 483                while (l) {
 484                        hint = llist_insert_sorted_unique(all_objects,
 485                                                          l->sha1, hint);
 486                        l = l->next;
 487                }
 488                pl = pl->next;
 489        }
 490        /* remove objects present in remote packs */
 491        pl = altodb_packs;
 492        while (pl) {
 493                llist_sorted_difference_inplace(all_objects, pl->all_objects);
 494                pl = pl->next;
 495        }
 496}
 497
 498/* this scales like O(n^2) */
 499void cmp_packs()
 500{
 501        struct pack_list *subset, *pl = local_packs;
 502
 503        while ((subset = pl)) {
 504                while((subset = subset->next))
 505                        cmp_two_packs(pl, subset);
 506                pl = pl->next;
 507        }
 508
 509        pl = altodb_packs;
 510        while (pl) {
 511                subset = local_packs;
 512                while (subset) {
 513                        llist_sorted_difference_inplace(subset->unique_objects,
 514                                                        pl->all_objects);
 515                        subset = subset->next;
 516                }
 517                pl = pl->next;
 518        }
 519}
 520
 521struct pack_list * add_pack(struct packed_git *p)
 522{
 523        struct pack_list l;
 524        size_t off;
 525        void *base;
 526
 527        l.pack = p;
 528        llist_init(&l.all_objects);
 529
 530        off = 256 * 4 + 4;
 531        base = (void *)p->index_base;
 532        while (off <= p->index_size - 3 * 20) {
 533                llist_insert_back(l.all_objects, base + off);
 534                off+=24;
 535        }
 536        /* this list will be pruned in cmp_two_packs later */
 537        l.unique_objects = llist_copy(l.all_objects);
 538        if (p->pack_local)
 539                return pack_list_insert(&local_packs, &l);
 540        else
 541                return alt_odb ? pack_list_insert(&altodb_packs, &l) : NULL;
 542}
 543
 544struct pack_list * add_pack_file(char *filename)
 545{
 546        struct packed_git *p = packed_git;
 547
 548        if (strlen(filename) < 40)
 549                die("Bad pack filename: %s\n", filename);
 550
 551        while (p) {
 552                if (strstr(p->pack_name, filename))
 553                        return add_pack(p);
 554                p = p->next;
 555        }
 556        die("Filename %s not found in packed_git\n", filename);
 557}
 558
 559void load_all()
 560{
 561        struct packed_git *p = packed_git;
 562
 563        while (p) {
 564                add_pack(p);
 565                p = p->next;
 566        }
 567}
 568
 569int main(int argc, char **argv)
 570{
 571        int i;
 572        struct pack_list *min, *red, *pl;
 573
 574        for (i = 1; i < argc; i++) {
 575                const char *arg = argv[i];
 576                if(!strcmp(arg, "--")) {
 577                        i++;
 578                        break;
 579                }
 580                if(!strcmp(arg, "--all")) {
 581                        load_all_packs = 1;
 582                        continue;
 583                }
 584                if(!strcmp(arg, "--verbose")) {
 585                        verbose = 1;
 586                        continue;
 587                }
 588                if(!strcmp(arg, "--alt-odb")) {
 589                        alt_odb = 1;
 590                        continue;
 591                }
 592                if(*arg == '-')
 593                        usage(pack_redundant_usage);
 594                else
 595                        break;
 596        }
 597
 598        prepare_packed_git();
 599
 600        if (load_all_packs)
 601                load_all();
 602        else
 603                while (*(argv + i) != NULL)
 604                        add_pack_file(*(argv + i++));
 605
 606        if (local_packs == NULL)
 607                die("Zero packs found!\n");
 608
 609        cmp_packs();
 610
 611        load_all_objects();
 612
 613        minimize(&min);
 614        if (verbose) {
 615                fprintf(stderr, "There are %lu packs available in alt-odbs.\n",
 616                        (unsigned long)pack_list_size(altodb_packs));
 617                fprintf(stderr, "The smallest (bytewise) set of packs is:\n");
 618                pl = min;
 619                while (pl) {
 620                        fprintf(stderr, "\t%s\n", pl->pack->pack_name);
 621                        pl = pl->next;
 622                }
 623                fprintf(stderr, "containing %lu duplicate objects "
 624                                "with a total size of %lukb.\n",
 625                        (unsigned long)get_pack_redundancy(min),
 626                        (unsigned long)pack_set_bytecount(min)/1024);
 627                fprintf(stderr, "A total of %lu unique objects were considered.\n",
 628                        (unsigned long)all_objects->size);
 629                fprintf(stderr, "Redundant packs (with indexes):\n");
 630        }
 631        pl = red = pack_list_difference(local_packs, min);
 632        while (pl) {
 633                printf("%s\n%s\n",
 634                       sha1_pack_index_name(pl->pack->sha1),
 635                       pl->pack->pack_name);
 636                pl = pl->next;
 637        }
 638
 639        return 0;
 640}