]> git.ipfire.org Git - thirdparty/git.git/commitdiff
reftable/record: decode keys in place
authorPatrick Steinhardt <ps@pks.im>
Mon, 4 Mar 2024 10:49:31 +0000 (11:49 +0100)
committerJunio C Hamano <gitster@pobox.com>
Mon, 4 Mar 2024 18:19:49 +0000 (10:19 -0800)
When reading a record from a block, we need to decode the record's key.
As reftable keys are prefix-compressed, meaning they reuse a prefix from
the preceding record's key, this is a bit more involved than just having
to copy the relevant bytes: we need to figure out the prefix and suffix
lengths, copy the prefix from the preceding record and finally copy the
suffix from the current record.

This is done by passing three buffers to `reftable_decode_key()`: one
buffer that holds the result, one buffer that holds the last key, and
one buffer that points to the current record. The final key is then
assembled by calling `strbuf_add()` twice to copy over the prefix and
suffix.

Performing two memory copies is inefficient though. And we can indeed do
better by decoding keys in place. Instead of providing two buffers, the
caller may only call a single buffer that is already pre-populated with
the last key. Like this, we only have to call `strbuf_setlen()` to trim
the record to its prefix and then `strbuf_add()` to add the suffix.

This refactoring leads to a noticeable performance bump when iterating
over 1 million refs:

  Benchmark 1: show-ref: single matching ref (revision = HEAD~)
    Time (mean ± σ):     112.2 ms ±   3.9 ms    [User: 109.3 ms, System: 2.8 ms]
    Range (min … max):   109.2 ms … 149.6 ms    1000 runs

  Benchmark 2: show-ref: single matching ref (revision = HEAD)
    Time (mean ± σ):     106.0 ms ±   3.5 ms    [User: 103.2 ms, System: 2.7 ms]
    Range (min … max):   103.2 ms … 133.7 ms    1000 runs

  Summary
    show-ref: single matching ref (revision = HEAD) ran
      1.06 ± 0.05 times faster than show-ref: single matching ref (revision = HEAD~)

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
reftable/block.c
reftable/block.h
reftable/record.c
reftable/record.h
reftable/record_test.c

index 72eb73b3806d623010c0e224f4f8ce77d555ce43..ad9074dba6121e9e40265f73494b233d99fa74d4 100644 (file)
@@ -291,9 +291,8 @@ static int restart_key_less(size_t idx, void *args)
        /* the restart key is verbatim in the block, so this could avoid the
           alloc for decoding the key */
        struct strbuf rkey = STRBUF_INIT;
-       struct strbuf last_key = STRBUF_INIT;
        uint8_t unused_extra;
-       int n = reftable_decode_key(&rkey, &unused_extra, last_key, in);
+       int n = reftable_decode_key(&rkey, &unused_extra, in);
        int result;
        if (n < 0) {
                a->error = 1;
@@ -326,35 +325,34 @@ int block_iter_next(struct block_iter *it, struct reftable_record *rec)
        if (it->next_off >= it->br->block_len)
                return 1;
 
-       n = reftable_decode_key(&it->key, &extra, it->last_key, in);
+       n = reftable_decode_key(&it->last_key, &extra, in);
        if (n < 0)
                return -1;
-
-       if (!it->key.len)
+       if (!it->last_key.len)
                return REFTABLE_FORMAT_ERROR;
 
        string_view_consume(&in, n);
-       n = reftable_record_decode(rec, it->key, extra, in, it->br->hash_size);
+       n = reftable_record_decode(rec, it->last_key, extra, in, it->br->hash_size);
        if (n < 0)
                return -1;
        string_view_consume(&in, n);
 
-       strbuf_swap(&it->last_key, &it->key);
        it->next_off += start.len - in.len;
        return 0;
 }
 
 int block_reader_first_key(struct block_reader *br, struct strbuf *key)
 {
-       struct strbuf empty = STRBUF_INIT;
-       int off = br->header_off + 4;
+       int off = br->header_off + 4, n;
        struct string_view in = {
                .buf = br->block.data + off,
                .len = br->block_len - off,
        };
-
        uint8_t extra = 0;
-       int n = reftable_decode_key(key, &extra, empty, in);
+
+       strbuf_reset(key);
+
+       n = reftable_decode_key(key, &extra, in);
        if (n < 0)
                return n;
        if (!key->len)
@@ -371,7 +369,6 @@ int block_iter_seek(struct block_iter *it, struct strbuf *want)
 void block_iter_close(struct block_iter *it)
 {
        strbuf_release(&it->last_key);
-       strbuf_release(&it->key);
 }
 
 int block_reader_seek(struct block_reader *br, struct block_iter *it,
@@ -408,8 +405,8 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
                if (err < 0)
                        goto done;
 
-               reftable_record_key(&rec, &it->key);
-               if (err > 0 || strbuf_cmp(&it->key, want) >= 0) {
+               reftable_record_key(&rec, &it->last_key);
+               if (err > 0 || strbuf_cmp(&it->last_key, want) >= 0) {
                        err = 0;
                        goto done;
                }
index 17481e6331979cc31972ee3dad576e9c594a1769..51699af233d823fafe681f3d7cbb20cf61b88d8a 100644 (file)
@@ -84,12 +84,10 @@ struct block_iter {
 
        /* key for last entry we read. */
        struct strbuf last_key;
-       struct strbuf key;
 };
 
 #define BLOCK_ITER_INIT { \
        .last_key = STRBUF_INIT, \
-       .key = STRBUF_INIT, \
 }
 
 /* initializes a block reader. */
index 12a44f70e55c1458b73cec819ad9cb930a2d5bd0..b9c6eee88a212d0b84288c9619dbc97915973e12 100644 (file)
@@ -159,20 +159,19 @@ int reftable_encode_key(int *restart, struct string_view dest,
        return start.len - dest.len;
 }
 
-int reftable_decode_key(struct strbuf *key, uint8_t *extra,
-                       struct strbuf last_key, struct string_view in)
+int reftable_decode_key(struct strbuf *last_key, uint8_t *extra,
+                       struct string_view in)
 {
        int start_len = in.len;
        uint64_t prefix_len = 0;
        uint64_t suffix_len = 0;
-       int n = get_var_int(&prefix_len, &in);
+       int n;
+
+       n = get_var_int(&prefix_len, &in);
        if (n < 0)
                return -1;
        string_view_consume(&in, n);
 
-       if (prefix_len > last_key.len)
-               return -1;
-
        n = get_var_int(&suffix_len, &in);
        if (n <= 0)
                return -1;
@@ -181,12 +180,12 @@ int reftable_decode_key(struct strbuf *key, uint8_t *extra,
        *extra = (uint8_t)(suffix_len & 0x7);
        suffix_len >>= 3;
 
-       if (in.len < suffix_len)
+       if (in.len < suffix_len ||
+           prefix_len > last_key->len)
                return -1;
 
-       strbuf_reset(key);
-       strbuf_add(key, last_key.buf, prefix_len);
-       strbuf_add(key, in.buf, suffix_len);
+       strbuf_setlen(last_key, prefix_len);
+       strbuf_add(last_key, in.buf, suffix_len);
        string_view_consume(&in, suffix_len);
 
        return start_len - in.len;
index a05e2be17971d5e26f27871159cda293aa2640ad..91c9c6ebfdf94b40df156252427e6a1f392a2516 100644 (file)
@@ -81,9 +81,12 @@ int reftable_encode_key(int *is_restart, struct string_view dest,
                        struct strbuf prev_key, struct strbuf key,
                        uint8_t extra);
 
-/* Decode into `key` and `extra` from `in` */
-int reftable_decode_key(struct strbuf *key, uint8_t *extra,
-                       struct strbuf last_key, struct string_view in);
+/*
+ * Decode into `last_key` and `extra` from `in`. `last_key` is expected to
+ * contain the decoded key of the preceding record, if any.
+ */
+int reftable_decode_key(struct strbuf *last_key, uint8_t *extra,
+                       struct string_view in);
 
 /* reftable_index_record are used internally to speed up lookups. */
 struct reftable_index_record {
index a86cff552661ba0f00be32200bf8b0c800a9fa87..89209894d84395ce9ae476ba461fea04100c05a6 100644 (file)
@@ -295,7 +295,8 @@ static void test_key_roundtrip(void)
        EXPECT(!restart);
        EXPECT(n > 0);
 
-       m = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);
+       strbuf_addstr(&roundtrip, "refs/heads/master");
+       m = reftable_decode_key(&roundtrip, &rt_extra, dest);
        EXPECT(n == m);
        EXPECT(0 == strbuf_cmp(&key, &roundtrip));
        EXPECT(rt_extra == extra);