#include "tree.h"
#include "delta.h"
#include "pack.h"
+#include "pack-revindex.h"
#include "csum-file.h"
#include "tree-walk.h"
#include "diff.h"
#include "progress.h"
#ifdef THREADED_DELTA_SEARCH
+#include "thread-utils.h"
#include <pthread.h>
#endif
static const char *base_name;
static int progress = 1;
static int window = 10;
-static uint32_t pack_size_limit;
+static uint32_t pack_size_limit, pack_size_limit_cfg;
static int depth = 50;
static int delta_search_threads = 1;
static int pack_to_stdout;
static int *object_ix;
static int object_ix_hashsz;
-/*
- * Pack index for existing packs give us easy access to the offsets into
- * corresponding pack file where each object's data starts, but the entries
- * do not store the size of the compressed representation (uncompressed
- * size is easily available by examining the pack entry header). It is
- * also rather expensive to find the sha1 for an object given its offset.
- *
- * We build a hashtable of existing packs (pack_revindex), and keep reverse
- * index here -- pack index file is sorted by object name mapping to offset;
- * this pack_revindex[].revindex array is a list of offset/index_nr pairs
- * ordered by offset, so if you know the offset of an object, next offset
- * is where its packed representation ends and the index_nr can be used to
- * get the object sha1 from the main index.
- */
-struct revindex_entry {
- off_t offset;
- unsigned int nr;
-};
-struct pack_revindex {
- struct packed_git *p;
- struct revindex_entry *revindex;
-};
-static struct pack_revindex *pack_revindex;
-static int pack_revindex_hashsz;
-
/*
* stats
*/
static uint32_t written, written_delta;
static uint32_t reused, reused_delta;
-static int pack_revindex_ix(struct packed_git *p)
-{
- unsigned long ui = (unsigned long)p;
- int i;
-
- ui = ui ^ (ui >> 16); /* defeat structure alignment */
- i = (int)(ui % pack_revindex_hashsz);
- while (pack_revindex[i].p) {
- if (pack_revindex[i].p == p)
- return i;
- if (++i == pack_revindex_hashsz)
- i = 0;
- }
- return -1 - i;
-}
-
-static void prepare_pack_ix(void)
-{
- int num;
- struct packed_git *p;
- for (num = 0, p = packed_git; p; p = p->next)
- num++;
- if (!num)
- return;
- pack_revindex_hashsz = num * 11;
- pack_revindex = xcalloc(sizeof(*pack_revindex), pack_revindex_hashsz);
- for (p = packed_git; p; p = p->next) {
- num = pack_revindex_ix(p);
- num = - 1 - num;
- pack_revindex[num].p = p;
- }
- /* revindex elements are lazily initialized */
-}
-
-static int cmp_offset(const void *a_, const void *b_)
-{
- const struct revindex_entry *a = a_;
- const struct revindex_entry *b = b_;
- return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0;
-}
-
-/*
- * Ordered list of offsets of objects in the pack.
- */
-static void prepare_pack_revindex(struct pack_revindex *rix)
-{
- struct packed_git *p = rix->p;
- int num_ent = p->num_objects;
- int i;
- const char *index = p->index_data;
-
- rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
- index += 4 * 256;
-
- if (p->index_version > 1) {
- const uint32_t *off_32 =
- (uint32_t *)(index + 8 + p->num_objects * (20 + 4));
- const uint32_t *off_64 = off_32 + p->num_objects;
- for (i = 0; i < num_ent; i++) {
- uint32_t off = ntohl(*off_32++);
- if (!(off & 0x80000000)) {
- rix->revindex[i].offset = off;
- } else {
- rix->revindex[i].offset =
- ((uint64_t)ntohl(*off_64++)) << 32;
- rix->revindex[i].offset |=
- ntohl(*off_64++);
- }
- rix->revindex[i].nr = i;
- }
- } else {
- for (i = 0; i < num_ent; i++) {
- uint32_t hl = *((uint32_t *)(index + 24 * i));
- rix->revindex[i].offset = ntohl(hl);
- rix->revindex[i].nr = i;
- }
- }
-
- /* This knows the pack format -- the 20-byte trailer
- * follows immediately after the last object data.
- */
- rix->revindex[num_ent].offset = p->pack_size - 20;
- rix->revindex[num_ent].nr = -1;
- qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset);
-}
-
-static struct revindex_entry * find_packed_object(struct packed_git *p,
- off_t ofs)
-{
- int num;
- int lo, hi;
- struct pack_revindex *rix;
- struct revindex_entry *revindex;
- num = pack_revindex_ix(p);
- if (num < 0)
- die("internal error: pack revindex uninitialized");
- rix = &pack_revindex[num];
- if (!rix->revindex)
- prepare_pack_revindex(rix);
- revindex = rix->revindex;
- lo = 0;
- hi = p->num_objects + 1;
- do {
- int mi = (lo + hi) / 2;
- if (revindex[mi].offset == ofs) {
- return revindex + mi;
- }
- else if (ofs < revindex[mi].offset)
- hi = mi;
- else
- lo = mi + 1;
- } while (lo < hi);
- die("internal error: pack revindex corrupt");
-}
-
-static const unsigned char *find_packed_object_name(struct packed_git *p,
- off_t ofs)
-{
- struct revindex_entry *entry = find_packed_object(p, ofs);
- return nth_packed_object_sha1(p, entry->nr);
-}
static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
{
/* nothing */;
deflateEnd(&stream);
datalen = stream.total_out;
- deflateEnd(&stream);
+
/*
* The object header is a byte of 'type' followed by zero or
* more bytes of length.
}
hdrlen = encode_header(obj_type, entry->size, header);
offset = entry->in_pack_offset;
- revidx = find_packed_object(p, offset);
+ revidx = find_pack_revindex(p, offset);
datalen = revidx[1].offset - offset;
if (!pack_to_stdout && p->index_version > 1 &&
check_pack_crc(p, &w_curs, offset, datalen, revidx->nr))
die("delta base offset out of bound for %s",
sha1_to_hex(entry->idx.sha1));
ofs = entry->in_pack_offset - ofs;
- if (!no_reuse_delta && !entry->preferred_base)
- base_ref = find_packed_object_name(p, ofs);
+ if (!no_reuse_delta && !entry->preferred_base) {
+ struct revindex_entry *revidx;
+ revidx = find_pack_revindex(p, ofs);
+ base_ref = nth_packed_object_sha1(p, revidx->nr);
+ }
entry->in_pack_header_size = used + used_0;
break;
}
sorted_by_offset[i] = objects + i;
qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
- prepare_pack_ix();
+ init_pack_revindex();
+
for (i = 0; i < nr_objects; i++)
check_object(sorted_by_offset[i]);
+
free(sorted_by_offset);
}
* accounting lock. Compiler will optimize the strangeness
* away when THREADED_DELTA_SEARCH is not defined.
*/
- if (trg_entry->delta_data)
- free(trg_entry->delta_data);
+ free(trg_entry->delta_data);
cache_lock();
if (trg_entry->delta_data) {
delta_cache_size -= trg_entry->delta_size;
return freed_mem;
}
-static void find_deltas(struct object_entry **list, unsigned list_size,
+static void find_deltas(struct object_entry **list, unsigned *list_size,
int window, int depth, unsigned *processed)
{
- uint32_t i = 0, idx = 0, count = 0;
+ uint32_t i, idx = 0, count = 0;
unsigned int array_size = window * sizeof(struct unpacked);
struct unpacked *array;
unsigned long mem_usage = 0;
array = xmalloc(array_size);
memset(array, 0, array_size);
- do {
- struct object_entry *entry = list[i++];
+ for (;;) {
+ struct object_entry *entry = *list++;
struct unpacked *n = array + idx;
int j, max_depth, best_base = -1;
+ progress_lock();
+ if (!*list_size) {
+ progress_unlock();
+ break;
+ }
+ (*list_size)--;
+ if (!entry->preferred_base) {
+ (*processed)++;
+ display_progress(progress_state, *processed);
+ }
+ progress_unlock();
+
mem_usage -= free_unpacked(n);
n->entry = entry;
if (entry->preferred_base)
goto next;
- progress_lock();
- (*processed)++;
- display_progress(progress_state, *processed);
- progress_unlock();
-
/*
* If the current object is at pack edge, take the depth the
* objects that depend on the current object into account
count++;
if (idx >= window)
idx = 0;
- } while (i < list_size);
+ }
for (i = 0; i < window; ++i) {
free_delta_index(array[i].index);
#ifdef THREADED_DELTA_SEARCH
+/*
+ * The main thread waits on the condition that (at least) one of the workers
+ * has stopped working (which is indicated in the .working member of
+ * struct thread_params).
+ * When a work thread has completed its work, it sets .working to 0 and
+ * signals the main thread and waits on the condition that .data_ready
+ * becomes 1.
+ */
+
struct thread_params {
pthread_t thread;
struct object_entry **list;
unsigned list_size;
+ unsigned remaining;
int window;
int depth;
+ int working;
+ int data_ready;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
unsigned *processed;
};
-static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER;
-static struct thread_params *data_requester;
+static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER;
static void *threaded_find_deltas(void *arg)
{
struct thread_params *me = arg;
- for (;;) {
- pthread_mutex_lock(&data_request);
- data_requester = me;
- pthread_mutex_unlock(&data_provider);
- pthread_mutex_lock(&data_ready);
- pthread_mutex_unlock(&data_request);
+ while (me->remaining) {
+ find_deltas(me->list, &me->remaining,
+ me->window, me->depth, me->processed);
- if (!me->list_size)
- return NULL;
+ progress_lock();
+ me->working = 0;
+ pthread_cond_signal(&progress_cond);
+ progress_unlock();
- find_deltas(me->list, me->list_size,
- me->window, me->depth, me->processed);
+ /*
+ * We must not set ->data_ready before we wait on the
+ * condition because the main thread may have set it to 1
+ * before we get here. In order to be sure that new
+ * work is available if we see 1 in ->data_ready, it
+ * was initialized to 0 before this thread was spawned
+ * and we reset it to 0 right away.
+ */
+ pthread_mutex_lock(&me->mutex);
+ while (!me->data_ready)
+ pthread_cond_wait(&me->cond, &me->mutex);
+ me->data_ready = 0;
+ pthread_mutex_unlock(&me->mutex);
}
+ /* leave ->working 1 so that this doesn't get more work assigned */
+ return NULL;
}
static void ll_find_deltas(struct object_entry **list, unsigned list_size,
int window, int depth, unsigned *processed)
{
- struct thread_params *target, p[delta_search_threads];
- int i, ret;
- unsigned chunk_size;
+ struct thread_params p[delta_search_threads];
+ int i, ret, active_threads = 0;
if (delta_search_threads <= 1) {
- find_deltas(list, list_size, window, depth, processed);
+ find_deltas(list, &list_size, window, depth, processed);
return;
}
- pthread_mutex_lock(&data_provider);
- pthread_mutex_lock(&data_ready);
-
+ /* Partition the work amongst work threads. */
for (i = 0; i < delta_search_threads; i++) {
+ unsigned sub_size = list_size / (delta_search_threads - i);
+
p[i].window = window;
p[i].depth = depth;
p[i].processed = processed;
+ p[i].working = 1;
+ p[i].data_ready = 0;
+
+ /* try to split chunks on "path" boundaries */
+ while (sub_size && sub_size < list_size &&
+ list[sub_size]->hash &&
+ list[sub_size]->hash == list[sub_size-1]->hash)
+ sub_size++;
+
+ p[i].list = list;
+ p[i].list_size = sub_size;
+ p[i].remaining = sub_size;
+
+ list += sub_size;
+ list_size -= sub_size;
+ }
+
+ /* Start work threads. */
+ for (i = 0; i < delta_search_threads; i++) {
+ if (!p[i].list_size)
+ continue;
+ pthread_mutex_init(&p[i].mutex, NULL);
+ pthread_cond_init(&p[i].cond, NULL);
ret = pthread_create(&p[i].thread, NULL,
threaded_find_deltas, &p[i]);
if (ret)
die("unable to create thread: %s", strerror(ret));
+ active_threads++;
}
- /* this should be auto-tuned somehow */
- chunk_size = window * 1000;
+ /*
+ * Now let's wait for work completion. Each time a thread is done
+ * with its work, we steal half of the remaining work from the
+ * thread with the largest number of unprocessed objects and give
+ * it to that newly idle thread. This ensure good load balancing
+ * until the remaining object list segments are simply too short
+ * to be worth splitting anymore.
+ */
+ while (active_threads) {
+ struct thread_params *target = NULL;
+ struct thread_params *victim = NULL;
+ unsigned sub_size = 0;
- do {
- unsigned sublist_size = chunk_size;
- if (sublist_size > list_size)
- sublist_size = list_size;
+ progress_lock();
+ for (;;) {
+ for (i = 0; !target && i < delta_search_threads; i++)
+ if (!p[i].working)
+ target = &p[i];
+ if (target)
+ break;
+ pthread_cond_wait(&progress_cond, &progress_mutex);
+ }
- /* try to split chunks on "path" boundaries */
- while (sublist_size < list_size && list[sublist_size]->hash &&
- list[sublist_size]->hash == list[sublist_size-1]->hash)
- sublist_size++;
-
- pthread_mutex_lock(&data_provider);
- target = data_requester;
- target->list = list;
- target->list_size = sublist_size;
- pthread_mutex_unlock(&data_ready);
-
- list += sublist_size;
- list_size -= sublist_size;
- if (!sublist_size) {
+ for (i = 0; i < delta_search_threads; i++)
+ if (p[i].remaining > 2*window &&
+ (!victim || victim->remaining < p[i].remaining))
+ victim = &p[i];
+ if (victim) {
+ sub_size = victim->remaining / 2;
+ list = victim->list + victim->list_size - sub_size;
+ while (sub_size && list[0]->hash &&
+ list[0]->hash == list[-1]->hash) {
+ list++;
+ sub_size--;
+ }
+ if (!sub_size) {
+ /*
+ * It is possible for some "paths" to have
+ * so many objects that no hash boundary
+ * might be found. Let's just steal the
+ * exact half in that case.
+ */
+ sub_size = victim->remaining / 2;
+ list -= sub_size;
+ }
+ target->list = list;
+ victim->list_size -= sub_size;
+ victim->remaining -= sub_size;
+ }
+ target->list_size = sub_size;
+ target->remaining = sub_size;
+ target->working = 1;
+ progress_unlock();
+
+ pthread_mutex_lock(&target->mutex);
+ target->data_ready = 1;
+ pthread_cond_signal(&target->cond);
+ pthread_mutex_unlock(&target->mutex);
+
+ if (!sub_size) {
pthread_join(target->thread, NULL);
- i--;
+ pthread_cond_destroy(&target->cond);
+ pthread_mutex_destroy(&target->mutex);
+ active_threads--;
}
- } while (i);
+ }
}
#else
-#define ll_find_deltas find_deltas
+#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p)
#endif
static void prepare_pack(int window, int depth)
}
if (!strcmp(k, "pack.threads")) {
delta_search_threads = git_config_int(k, v);
- if (delta_search_threads < 1)
+ if (delta_search_threads < 0)
die("invalid number of threads specified (%d)",
delta_search_threads);
#ifndef THREADED_DELTA_SEARCH
- if (delta_search_threads > 1)
+ if (delta_search_threads != 1)
warning("no threads support, ignoring %s", k);
#endif
return 0;
die("bad pack.indexversion=%d", pack_idx_default_version);
return 0;
}
+ if (!strcmp(k, "pack.packsizelimit")) {
+ pack_size_limit_cfg = git_config_ulong(k, v);
+ return 0;
+ }
return git_default_config(k, v);
}
init_revisions(&revs, NULL);
save_commit_buffer = 0;
- track_object_refs = 0;
setup_revisions(ac, av, &revs, NULL);
while (fgets(line, sizeof(line), stdin) != NULL) {
int len = strlen(line);
- if (line[len - 1] == '\n')
+ if (len && line[len - 1] == '\n')
line[--len] = 0;
if (!len)
break;
die("bad revision '%s'", line);
}
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
mark_edges_uninteresting(revs.commits, &revs, show_edge);
traverse_commit_list(&revs, show_commit, show_object);
}
if (!prefixcmp(arg, "--max-pack-size=")) {
char *end;
+ pack_size_limit_cfg = 0;
pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024;
if (!arg[16] || *end)
usage(pack_usage);
if (!prefixcmp(arg, "--threads=")) {
char *end;
delta_search_threads = strtoul(arg+10, &end, 0);
- if (!arg[10] || *end || delta_search_threads < 1)
+ if (!arg[10] || *end || delta_search_threads < 0)
usage(pack_usage);
#ifndef THREADED_DELTA_SEARCH
- if (delta_search_threads > 1)
+ if (delta_search_threads != 1)
warning("no threads support, "
"ignoring %s", arg);
#endif
if (pack_to_stdout != !base_name)
usage(pack_usage);
+ if (!pack_to_stdout && !pack_size_limit)
+ pack_size_limit = pack_size_limit_cfg;
+
if (pack_to_stdout && pack_size_limit)
die("--max-pack-size cannot be used to build a pack for transfer.");
if (!pack_to_stdout && thin)
die("--thin cannot be used to build an indexable pack.");
+#ifdef THREADED_DELTA_SEARCH
+ if (!delta_search_threads) /* --threads=0 means autodetect */
+ delta_search_threads = online_cpus();
+#endif
+
prepare_packed_git();
if (progress)