From 9bac501c8a63e7910f0e2b6aa343963e0f567597 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Fri, 16 Oct 2026 12:43:37 +0200 Subject: [PATCH 1/2] xdu.c: Use hash table to find children in addtree Scanning the sibling list made tree building quadratic in the number of entries per directory. --- xdu.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 15 deletions(-) diff --git a/xdu.c b/xdu.c index f2234b2..b78d08b 100644 --- a/xdu.c +++ b/xdu.c @@ -49,6 +49,7 @@ #define _GNU_SOURCE #include "version.h" +#include #include #include #include @@ -131,11 +132,80 @@ struct node { struct node* peer; /* siblings */ struct node* child; /* list of children if !NULL */ struct node* parent; /* backpointer to parent */ + struct node* hnext; /* next in hash chain */ } top; struct node* topp = ⊤ #define NODE_NULL ((struct node*)0) long nnodes = 0; +/* + * Hash table to find a child by (parent, name) in constant time + */ +static struct node** htab; +static size_t hsize; /* 1 << hbits */ +static int hbits; +static size_t hcount; + +/* + * Hash the name with 64-bit FNV-1a (RFC 9923), mix in the parent pointer + * and reduce to a bucket index of the given width with Fibonacci hashing + * (Knuth, TAOCP Vol. 3, 6.4: multiply by 2^64/phi and keep the top bits). + */ +static size_t hash(struct node* parent, char* name, int bits) +{ + uint64_t h = 0xcbf29ce484222325ULL; + + while (*name) + h = (h ^ (unsigned char)*name++) * 0x100000001b3ULL; + h ^= (uintptr_t)parent; + return (size_t)((h * 0x9e3779b97f4a7c15ULL) >> (64 - bits)); +} + +static void hinsert(struct node* np) +{ + size_t i; + + if (hcount >= hsize) { + int nbits = hbits ? hbits + 1 : 10; + size_t nsize = (size_t)1 << nbits; + struct node** ntab = calloc(nsize, sizeof(struct node*)); + struct node *p, *next; + + if (ntab == NULL) { + fprintf(stderr, "xdu: out of memory\n"); + exit(1); + } + for (i = 0; i < hsize; i++) { + for (p = htab[i]; p != NODE_NULL; p = next) { + size_t j = hash(p->parent, p->name, nbits); + next = p->hnext; + p->hnext = ntab[j]; + ntab[j] = p; + } + } + free(htab); + htab = ntab; + hsize = nsize; + hbits = nbits; + } + i = hash(np->parent, np->name, hbits); + np->hnext = htab[i]; + htab[i] = np; + hcount++; +} + +static struct node* hlookup(struct node* parent, char* name) +{ + struct node* np; + + if (hsize == 0) + return NODE_NULL; + for (np = htab[hash(parent, name, hbits)]; np != NODE_NULL; np = np->hnext) + if (np->parent == parent && strcmp(np->name, name) == 0) + return np; + return NODE_NULL; +} + /* * create a new node with the given name and size info */ @@ -427,26 +497,25 @@ static void addtree(struct node* top, char* path[], long long size) */ /* - * check all children for a match + * check children for a match */ - for (np = top->child; np != NULL; np = np->peer) { - if (strcmp(path[0], np->name) == 0) { - /* - * name matches - */ - if (path[1] == NULL) { - /* - * end of the chain, save size - */ - np->size = size; - return; - } + np = hlookup(top, path[0]); + if (np != NODE_NULL) { + /* + * name matches + */ + if (path[1] == NULL) { /* - * recurse + * end of the chain, save size */ - addtree(np, &path[1], size); + np->size = size; return; } + /* + * recurse + */ + addtree(np, &path[1], size); + return; } /* * no child matched, add a new child @@ -455,6 +524,7 @@ static void addtree(struct node* top, char* path[], long long size) np->parent = top; np->peer = top->child; top->child=np; + hinsert(np); if (path[1] == NULL) { /* From f061bdbc3d6f05fb549fe46c5f64cb596a4dc6e5 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Fri, 16 Oct 2026 12:44:20 +0200 Subject: [PATCH 2/2] xdu.c: Sort child lists with merge sort The selection-style list sort was quadratic in the number of entries per directory. --- xdu.c | 76 ++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/xdu.c b/xdu.c index b78d08b..55bbc37 100644 --- a/xdu.c +++ b/xdu.c @@ -557,13 +557,55 @@ void dumptree(struct node* np, int level) } } +/* + * merge sort a list of peers, return the new head + */ +static struct node* sortlist(struct node* list, int order) +{ + struct node *slow, *fast, *right; + struct node* head; + struct node** tail; + + if (list == NODE_NULL || list->peer == NODE_NULL) + return list; + + /* + * split the list in half + */ + slow = list; + fast = list->peer; + while (fast != NODE_NULL && fast->peer != NODE_NULL) { + slow = slow->peer; + fast = fast->peer->peer; + } + right = slow->peer; + slow->peer = NODE_NULL; + + list = sortlist(list, order); + right = sortlist(right, order); + + /* + * merge both halves + */ + tail = &head; + while (list != NODE_NULL && right != NODE_NULL) { + if (compare(list, right, order) <= 0) { + *tail = list; + list = list->peer; + } else { + *tail = right; + right = right->peer; + } + tail = &(*tail)->peer; + } + *tail = list != NODE_NULL ? list : right; + + return head; +} + static void sorttree(struct node* np, int order) { struct node* subnp; - struct node *np0, - *np1, - *np2, - *np3; /* * sort the trees of each of this nodes children @@ -574,31 +616,7 @@ static void sorttree(struct node* np, int order) /* * then put the given nodes children in order */ - np0 = np; /* np0 points to node before np1 */ - for (np1 = np->child; np1 != NODE_NULL; np1 = np1->peer) { - np2 = np1; /* np2 points to node before np3 */ - for (np3 = np1->peer; np3 != NODE_NULL; np3 = np3->peer) { - if (compare(np3, np1, order) < 0) { - /* - * swap links - */ - if (np0 == np) - np0->child = np3; - else - np0->peer = np3; - np2->peer = np3->peer; - np3->peer = np1; - - /* - * adjust pointers - */ - np1 = np3; - np3 = np2; - } - np2 = np3; - } - np0 = np1; - } + np->child = sortlist(np->child, order); } /*