Am 14.08.2018 um 03:58 schrieb Jeff King: > Your suggestion can be implemented using khash (my patch below). > >> Before: >> Benchmark #1: ./git-cat-file --batch-all-objects --buffer --unordered --batch-check='%(objectname)' >> >> Time (mean ± σ): 269.5 ms ± 26.7 ms [User: 247.7 ms, System: 21.4 ms] >> >> Range (min … max): 240.3 ms … 339.3 ms >> >> After: >> Benchmark #1: ./git-cat-file --batch-all-objects --buffer --unordered --batch-check='%(objectname)' >> >> Time (mean ± σ): 224.2 ms ± 18.2 ms [User: 201.7 ms, System: 22.1 ms] >> >> Range (min … max): 205.0 ms … 259.0 ms > > Yeah. My best-of-five dropped from 300ms to 247ms. That 300 was using > the memory pool, though khash's deletion strategy isn't all that > different (the wasted memory hangs around until the next hash resize, > but if you're evenly dropping and adding, you likely won't need to > resize). With your khash patch: Benchmark #1: ./git-cat-file --batch-all-objects --buffer --unordered --batch-check='%(objectname)' Time (mean ± σ): 159.1 ms ± 20.5 ms [User: 140.3 ms, System: 18.5 ms] Range (min … max): 140.0 ms … 214.0 ms So it seems worth it. > Anyway, here's the khash patch for reference. > > diff --git a/fetch-pack.c b/fetch-pack.c > index 5714bcbddd..5a86b10a5e 100644 > --- a/fetch-pack.c > +++ b/fetch-pack.c > @@ -534,7 +534,7 @@ static int tip_oids_contain(struct oidset *tip_oids, > * add to "newlist" between calls, the additions will always be for > * oids that are already in the set. > */ > - if (!tip_oids->map.map.tablesize) { > + if (!tip_oids->map) { > add_refs_to_oidset(tip_oids, unmatched); > add_refs_to_oidset(tip_oids, newlist); > } The caller shouldn't look at the private parts of the implementation like this. tablesize is the allocation count, which becomes non-zero if at least one entry was added. tip_oids is only inserted into, never deleted from, so a count or size function could be used instead as a cleaner interface. (In a separate patch..) > diff --git a/oidset.c b/oidset.c > index 454c54f933..2964b43b2d 100644 > --- a/oidset.c > +++ b/oidset.c > @@ -3,38 +3,44 @@ > > int oidset_contains(const struct oidset *set, const struct object_id *oid) > { > - if (!set->map.map.tablesize) > + khiter_t pos; > + > + if (!set->map) > return 0; > - return !!oidmap_get(&set->map, oid); > + > + pos = kh_get_oid(set->map, *oid); > + return pos < kh_end(set->map); > } > > int oidset_insert(struct oidset *set, const struct object_id *oid) > { > - struct oidmap_entry *entry; > + int hash_ret; > > - if (!set->map.map.tablesize) > - oidmap_init(&set->map, 0); > - else if (oidset_contains(set, oid)) > - return 1; > + if (!set->map) > + set->map = kh_init_oid(); > > - entry = xmalloc(sizeof(*entry)); > - oidcpy(&entry->oid, oid); > - > - oidmap_put(&set->map, entry); > - return 0; > + kh_put_oid(set->map, *oid, &hash_ret); > + return !hash_ret; > } So initialization is deferred to the first insert, and the empty set can be represented in two ways -- map == NULL and map->size == 0. I wondered about the performance impact of all those NULL checks at insert and lookup and converted it to stack storage, with a dirty hand-rolled oidset_clear() implementation. It wasn't any faster. > > int oidset_remove(struct oidset *set, const struct object_id *oid) > { > - struct oidmap_entry *entry; > + khiter_t pos; > > - entry = oidmap_remove(&set->map, oid); > - free(entry); > + if (!set->map) > + return 0; > + > + pos = kh_get_oid(set->map, *oid); > + if (pos < kh_end(set->map)) { > + kh_del_oid(set->map, pos); > + return 1; > + } > > - return (entry != NULL); > + return 0; > } > > void oidset_clear(struct oidset *set) > { > - oidmap_free(&set->map, 1); > + kh_destroy_oid(set->map); > + set->map = NULL; > } > diff --git a/oidset.h b/oidset.h > index 40ec5f87fe..4c4c5a42fe 100644 > --- a/oidset.h > +++ b/oidset.h > @@ -2,6 +2,7 @@ > #define OIDSET_H > > #include "oidmap.h" This can go. > +#include "khash.h" > > /** > * This API is similar to sha1-array, in that it maintains a set of object ids > @@ -15,19 +16,34 @@ > * table overhead. > */ > > +static inline unsigned int oid_hash(const struct object_id oid) > +{ > + unsigned int hash; > + memcpy(&hash, oid.hash, sizeof(hash)); > + return hash; > +} > + > +static inline int oid_equal(const struct object_id a, > + const struct object_id b) > +{ > + return !oidcmp(&a, &b); > +} Look, it's oideq() from that other series in disguise! :) > + > +KHASH_INIT(oid, struct object_id, int, 0, oid_hash, oid_equal) Note to self: The 0 is for kh_is_map, which means that no values are kept and the given value type (int) doesn't matter. > + > + > /** > * A single oidset; should be zero-initialized (or use OIDSET_INIT). > */ > struct oidset { > - struct oidmap map; > + kh_oid_t *map; > }; > > -#define OIDSET_INIT { OIDMAP_INIT } > - > +#define OIDSET_INIT { NULL } > > static inline void oidset_init(struct oidset *set, size_t initial_size) > { > - oidmap_init(&set->map, initial_size); > + set->map = NULL; > } This ignores initial_size, which is misleading. We should probably call kh_resize_oid() here and move the function to oidset.c. Or get rid of the second parameter.. > > /** > @@ -58,19 +74,25 @@ int oidset_remove(struct oidset *set, const struct object_id *oid); > void oidset_clear(struct oidset *set); > > struct oidset_iter { > - struct oidmap_iter m_iter; > + kh_oid_t *map; > + khiter_t iter; > }; > > static inline void oidset_iter_init(struct oidset *set, > struct oidset_iter *iter) > { > - oidmap_iter_init(&set->map, &iter->m_iter); > + iter->map = set->map; > + iter->iter = kh_begin(iter->map); > } This is fine even if map == NULL, because kh_begin() can handle any parameter value, as it ignores them... > > static inline struct object_id *oidset_iter_next(struct oidset_iter *iter) > { > - struct oidmap_entry *e = oidmap_iter_next(&iter->m_iter); > - return e ? &e->oid : NULL; > + for (; iter->iter != kh_end(iter->map); iter->iter++) { > + if (!kh_exist(iter->map, iter->iter)) > + continue; > + return &kh_key(iter->map, iter->iter); > + } > + return NULL; > } ... but kh_end() dereferences map, so iterating a fresh oidset will segfault here. > > static inline struct object_id *oidset_iter_first(struct oidset *set, >