ref_array: keep track of whether references are sorted

Keep track of how many entries at the beginning of a ref_array are already sorted. In sort_ref_array(), return early if the the array is already sorted (i.e., if no new references has been appended to the end of the list since the last call to sort_ref_array()). Sort ref_arrays only when needed, namely in search_ref_array() and in do_for_each_ref(). However, never call sort_ref_array() on the extra_refs, because extra_refs can contain multiple entries with the same name and because sort_ref_array() not only sorts, but de-dups its contents. This change is currently not useful, because entries are not added to ref_arrays after they are created. But in a moment they will be... Implementation note: we could store a binary "sorted" value instead of an integer, but storing the number of sorted entries leaves the way open for a couple of possible future optimizations: * In sort_ref_array(), sort *only* the unsorted entries, then merge them with the sorted entries. This should be faster if most of the entries are already sorted. * Teach search_ref_array() to do a binary search of any sorted entries, and if unsuccessful do a linear search of any unsorted entries. This would avoid the need to sort the list every time that search_ref_array() is called, and (given some intelligence about how often to sort) could significantly improve the speed in certain hypothetical usage patterns. Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Haggerty committed Jan 17, 2012 at 06:50 UTC e6ed3ca651fac702f9d9a9ef64a14c7efadf7365
1 file changed +26 -7
refs.c
+26 -7
@@ -17,6 +17,15 @@ struct ref_entry {
17
18 struct ref_array {
19 int nr, alloc;
20 +
21 + /*
22 + * Entries with index 0 <= i < sorted are sorted by name. New
23 + * entries are appended to the list unsorted, and are sorted
24 + * only when required; thus we avoid the need to sort the list
25 + * after the addition of every reference.
26 + */
27 + int sorted;
28 +
29 struct ref_entry **refs;
30 };
31
@@ -105,12 +114,18 @@ static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2
114 }
115 }
116
117 +/*
118 + * Sort the entries in array (if they are not already sorted).
119 + */
120 static void sort_ref_array(struct ref_array *array)
121 {
122 int i, j;
123
112 - /* Nothing to sort unless there are at least two entries */
113 - if (array->nr < 2)
124 + /*
125 + * This check also prevents passing a zero-length array to qsort(),
126 + * which is a problem on some platforms.
127 + */
128 + if (array->sorted == array->nr)
129 return;
130
131 qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
@@ -124,7 +139,7 @@ static void sort_ref_array(struct ref_array *array)
139 }
140 array->refs[++i] = array->refs[j];
141 }
127 - array->nr = i + 1;
142 + array->sorted = array->nr = i + 1;
143 }
144
145 static struct ref_entry *search_ref_array(struct ref_array *array, const char *refname)
@@ -137,7 +152,7 @@ static struct ref_entry *search_ref_array(struct ref_array *array, const char *r
152
153 if (!array->nr)
154 return NULL;
140 -
155 + sort_ref_array(array);
156 len = strlen(refname) + 1;
157 e = xmalloc(sizeof(struct ref_entry) + len);
158 memcpy(e->name, refname, len);
@@ -168,6 +183,10 @@ static struct ref_cache {
183
184 static struct ref_entry *current_ref;
185
186 +/*
187 + * Never call sort_ref_array() on the extra_refs, because it is
188 + * allowed to contain entries with duplicate names.
189 + */
190 static struct ref_array extra_refs;
191
192 static void clear_ref_array(struct ref_array *array)
@@ -176,7 +195,7 @@ static void clear_ref_array(struct ref_array *array)
195 for (i = 0; i < array->nr; i++)
196 free(array->refs[i]);
197 free(array->refs);
179 - array->nr = array->alloc = 0;
198 + array->sorted = array->nr = array->alloc = 0;
199 array->refs = NULL;
200 }
201
@@ -268,7 +287,6 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
287 !get_sha1_hex(refline + 1, sha1))
288 hashcpy(last->peeled, sha1);
289 }
271 - sort_ref_array(array);
290 }
291
292 void add_extra_ref(const char *refname, const unsigned char *sha1, int flag)
@@ -404,7 +422,6 @@ static struct ref_array *get_loose_refs(struct ref_cache *refs)
422 {
423 if (!refs->did_loose) {
424 get_ref_dir(refs, "refs", &refs->loose);
407 - sort_ref_array(&refs->loose);
425 refs->did_loose = 1;
426 }
427 return &refs->loose;
@@ -720,6 +737,8 @@ static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn
737 for (i = 0; i < extra->nr; i++)
738 retval = do_one_ref(base, fn, trim, flags, cb_data, extra->refs[i]);
739
740 + sort_ref_array(packed);
741 + sort_ref_array(loose);
742 while (p < packed->nr && l < loose->nr) {
743 struct ref_entry *entry;
744 int cmp = strcmp(packed->refs[p]->name, loose->refs[l]->name);