"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
add LRU cache for delta data to got-read-pack
To:
gameoftrees@openbsd.org
Date:
Sun, 10 Nov 2019 15:37:54 +0200

Download raw body.

Thread
got-read-pack spends a lot of time decompressing the same delta data
over and over. This diff adds an LRU cache to make it hit the disk
and zlib less often. The effect on performance is quite pronounced.

Before:
$ time got blame sys/kern/kern_tc.c > /dev/null
    0m55.65s real     0m34.61s user     0m24.69s system
$ time got blame sys/kern/kern_tc.c > /dev/null
    0m53.54s real     0m33.43s user     0m23.62s system
$ time got blame sys/kern/kern_tc.c > /dev/null
    0m52.93s real     0m32.59s user     0m23.87s system

After:

$ time got blame sys/kern/kern_tc.c > /dev/null
    0m31.92s real     0m29.60s user     0m06.87s system
$ time got blame sys/kern/kern_tc.c > /dev/null
    0m31.53s real     0m29.13s user     0m06.86s system
$ time got blame sys/kern/kern_tc.c > /dev/null
    0m31.49s real     0m29.57s user     0m06.56s system

30 seconds still isn't great but is better than 50.

ok?

diff refs/heads/master refs/heads/deltacache
blob - 4310fc7e0269fccc28e40d464196abeb11e6d25f
blob + 709948a09b358cf01d39d50454f4f9c08fad9c28
--- got/Makefile
+++ got/Makefile
@@ -8,7 +8,7 @@ SRCS=		got.c blame.c commit_graph.c delta.c diff.c \
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c \
 		inflate.c buf.c rcsutil.c diff3.c lockfile.c \
-		deflate.c object_create.c
+		deflate.c object_create.c delta_cache.c
 MAN =		${PROG}.1 got-worktree.5 git-repository.5
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
blob - 42f628d13edc1ddbaa6844b6156d41a9438d9e4b
blob + 9abb0d4dc56c7c09ebf32461a9eb717f79cbca05
--- lib/delta.c
+++ lib/delta.c
@@ -40,7 +40,7 @@
 
 struct got_delta *
 got_delta_open(off_t offset, size_t tslen, int type, size_t size,
-    off_t data_offset, uint8_t *delta_buf, size_t delta_len)
+    off_t data_offset)
 {
 	struct got_delta *delta;
 
@@ -53,8 +53,6 @@ got_delta_open(off_t offset, size_t tslen, int type, s
 	delta->tslen = tslen;
 	delta->size = size;
 	delta->data_offset = data_offset;
-	delta->delta_buf = delta_buf;
-	delta->delta_len = delta_len;
 	return delta;
 }
 
blob - /dev/null
blob + b9d4f89656dd56b278729ad6c522d9c453d476da (mode 644)
--- /dev/null
+++ lib/delta_cache.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2019 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <sha1.h>
+#include <stdio.h>
+#include <zlib.h>
+#include <limits.h>
+#include <time.h>
+
+#include "got_object.h"
+#include "got_error.h"
+
+#include "got_lib_delta.h"
+#include "got_lib_inflate.h"
+#include "got_lib_object.h"
+#include "got_lib_delta_cache.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
+#endif
+
+struct got_delta_cache_element {
+	TAILQ_ENTRY(got_delta_cache_element) entry;
+	off_t delta_data_offset;
+	uint8_t *delta_data;
+	size_t delta_len;
+};
+
+TAILQ_HEAD(got_delta_cache_head, got_delta_cache_element);
+
+struct got_delta_cache {
+	struct got_delta_cache_head entries;
+	int nelem;
+	int maxelem;
+	size_t maxelemsize;
+};
+
+struct got_delta_cache *
+got_delta_cache_alloc(int maxelem, size_t maxelemsize)
+{
+	struct got_delta_cache *cache;
+
+	cache = calloc(1, sizeof(*cache));
+	if (cache == NULL)
+		return NULL;
+
+	TAILQ_INIT(&cache->entries);
+	cache->maxelem = maxelem;
+	cache->maxelemsize = maxelemsize;
+	return cache;
+}
+
+void
+got_delta_cache_free(struct got_delta_cache *cache)
+{
+	struct got_delta_cache_element *entry;
+
+	while (!TAILQ_EMPTY(&cache->entries)) {
+		entry = TAILQ_FIRST(&cache->entries);
+		TAILQ_REMOVE(&cache->entries, entry, entry);
+		free(entry->delta_data);
+		free(entry);
+	}
+	free(cache);
+}
+
+static void
+remove_least_used_element(struct got_delta_cache *cache)
+{
+	struct got_delta_cache_element *entry;
+
+	if (cache->nelem == 0)
+		return;
+
+	entry = TAILQ_LAST(&cache->entries, got_delta_cache_head);
+	TAILQ_REMOVE(&cache->entries, entry, entry);
+	free(entry->delta_data);
+	free(entry);
+	cache->nelem--;
+}
+
+
+const struct got_error *
+got_delta_cache_add(struct got_delta_cache *cache,
+    off_t delta_data_offset, uint8_t *delta_data, size_t delta_len)
+{
+	struct got_delta_cache_element *entry;
+
+	if (delta_len > cache->maxelemsize)
+		return got_error(GOT_ERR_NO_SPACE);
+
+	if (cache->nelem >= cache->maxelem)
+		remove_least_used_element(cache);
+
+	entry = calloc(1, sizeof(*entry));
+	if (entry == NULL)
+		return got_error_from_errno("calloc");
+
+	entry->delta_data_offset = delta_data_offset;
+	entry->delta_data = delta_data;
+	entry->delta_len = delta_len;
+
+	TAILQ_INSERT_HEAD(&cache->entries, entry, entry);
+	cache->nelem++;
+	return NULL;
+}
+
+void
+got_delta_cache_get(uint8_t **delta_data, size_t *delta_len,
+    struct got_delta_cache *cache, off_t delta_data_offset)
+{
+	struct got_delta_cache_element *entry;
+
+	*delta_data = NULL;
+	*delta_len = 0;
+	TAILQ_FOREACH(entry, &cache->entries, entry) {
+		if (entry->delta_data_offset != delta_data_offset)
+			continue;
+		if (entry != TAILQ_FIRST(&cache->entries)) {
+			TAILQ_REMOVE(&cache->entries, entry, entry);
+			TAILQ_INSERT_HEAD(&cache->entries, entry, entry);
+		}
+		*delta_data = entry->delta_data;
+		*delta_len = entry->delta_len;
+		return;
+	}
+}
blob - 8b79413858bc4609d31a7e5b7910f8419f709365
blob + b04e243f7e28e091a99469856203d981d23691d5
--- lib/got_lib_delta.h
+++ lib/got_lib_delta.h
@@ -21,8 +21,6 @@ struct got_delta {
 	int type;
 	size_t size;
 	off_t data_offset;
-	uint8_t *delta_buf;
-	size_t delta_len;
 };
 
 struct got_delta_chain {
@@ -32,8 +30,7 @@ struct got_delta_chain {
 
 #define GOT_DELTA_CHAIN_RECURSION_MAX	500
 
-struct got_delta *got_delta_open(off_t, size_t, int, size_t, off_t,
-    uint8_t *, size_t);
+struct got_delta *got_delta_open(off_t, size_t, int, size_t, off_t);
 const struct got_error *got_delta_chain_get_base_type(int *,
     struct got_delta_chain *);
 const struct got_error *got_delta_get_sizes(uint64_t *, uint64_t *,
blob - /dev/null
blob + 39cb23dc018390d1de373fde1dc990faa76c21e2 (mode 644)
--- /dev/null
+++ lib/got_lib_delta_cache.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2019 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct got_delta_cache;
+
+struct got_delta_cache *got_delta_cache_alloc(int, size_t);
+void got_delta_cache_free(struct got_delta_cache *);
+
+const struct got_error *got_delta_cache_add(struct got_delta_cache *, off_t,
+    uint8_t *, size_t);
+void got_delta_cache_get(uint8_t **, size_t *, struct got_delta_cache *, off_t);
blob - 654dfa86d4de464fcdc3b68b5b8469f50f476a4c
blob + f1f994a6a41a9c16744f389369f3692b13da72e4
--- lib/got_lib_pack.h
+++ lib/got_lib_pack.h
@@ -21,6 +21,7 @@ struct got_pack {
 	uint8_t *map;
 	size_t filesize;
 	struct got_privsep_child *privsep_child;
+	struct got_delta_cache *delta_cache;
 };
 
 const struct got_error *got_pack_stop_privsep_child(struct got_pack *);
@@ -168,7 +169,7 @@ const struct got_error *got_packidx_match_id_str_prefi
 const struct got_error *got_packfile_open_object(struct got_object **,
     struct got_pack *, struct got_packidx *, int, struct got_object_id *);
 const struct got_error *got_pack_get_max_delta_object_size(uint64_t *,
-    struct got_object *);
+    struct got_object *, struct got_pack *);
 const struct got_error *got_packfile_extract_object(struct got_pack *,
     struct got_object *, FILE *, FILE *, FILE *);
 const struct got_error *got_packfile_extract_object_to_mem(uint8_t **, size_t *,
blob - 6d876216ae17f82d4f9dee9e675e1577abd93a08
blob + c67ff751c1fbd694ef012e6ec8f54283f24acdf3
--- lib/object_cache.c
+++ lib/object_cache.c
@@ -84,9 +84,9 @@ get_size_obj(struct got_object *obj)
 		return size;
 
 	SIMPLEQ_FOREACH(delta, &obj->deltas.entries, entry) {
-		if (SIZE_MAX - (sizeof(*delta) + delta->delta_len) < size)
+		if (SIZE_MAX - sizeof(*delta) < size)
 			return SIZE_MAX;
-		size += sizeof(*delta) + delta->delta_len;
+		size += sizeof(*delta);
 	}
 
 	return size;
blob - a037ceb7fcaca7f9358cecf93fb644ff7f35ccc9
blob + 9a37c6f1d7c6414382484dbde632c0fb6bb7591e
--- lib/object_parse.c
+++ lib/object_parse.c
@@ -113,7 +113,6 @@ got_object_close(struct got_object *obj)
 		while (!SIMPLEQ_EMPTY(&obj->deltas.entries)) {
 			delta = SIMPLEQ_FIRST(&obj->deltas.entries);
 			SIMPLEQ_REMOVE_HEAD(&obj->deltas.entries, entry);
-			free(delta->delta_buf);
 			free(delta);
 		}
 	}
blob - fb5f114d8a5ce54615d80a1f1a73b8ab0dc65a9a
blob + eef27b82b6eeb8a714c5fedd79b33b04f6e8e5f5
--- lib/pack.c
+++ lib/pack.c
@@ -39,6 +39,7 @@
 
 #include "got_lib_sha1.h"
 #include "got_lib_delta.h"
+#include "got_lib_delta_cache.h"
 #include "got_lib_inflate.h"
 #include "got_lib_object.h"
 #include "got_lib_object_parse.h"
@@ -546,6 +547,10 @@ got_pack_close(struct got_pack *pack)
 	free(pack->path_packfile);
 	pack->path_packfile = NULL;
 	pack->filesize = 0;
+	if (pack->delta_cache) {
+		got_delta_cache_free(pack->delta_cache);
+		pack->delta_cache = NULL;
+	}
 
 	return err;
 }
@@ -707,15 +712,32 @@ resolve_delta_chain(struct got_delta_chain *, struct g
     struct got_pack *, off_t, size_t, int, size_t, unsigned int);
 
 static const struct got_error *
+read_delta_data(uint8_t **delta_buf, size_t *delta_len,
+    size_t delta_data_offset, struct got_pack *pack)
+{
+	const struct got_error *err = NULL;
+
+	if (pack->map) {
+		if (delta_data_offset >= pack->filesize)
+			return got_error(GOT_ERR_PACK_OFFSET);
+		err = got_inflate_to_mem_mmap(delta_buf, delta_len, pack->map,
+		    delta_data_offset, pack->filesize - delta_data_offset);
+	} else {
+		if (lseek(pack->fd, delta_data_offset, SEEK_SET) == -1)
+			return got_error_from_errno("lseek");
+		err = got_inflate_to_mem_fd(delta_buf, delta_len, pack->fd);
+	}
+	return err;
+}
+
+static const struct got_error *
 add_delta(struct got_delta_chain *deltas, off_t delta_offset, size_t tslen,
-    int delta_type, size_t delta_size, size_t delta_data_offset,
-    uint8_t *delta_buf, size_t delta_len)
+    int delta_type, size_t delta_size, size_t delta_data_offset)
 {
 	struct got_delta *delta;
 
 	delta = got_delta_open(delta_offset, tslen, delta_type, delta_size,
-	    delta_data_offset, delta_buf,
-	    delta_len);
+	    delta_data_offset);
 	if (delta == NULL)
 		return got_error_from_errno("got_delta_open");
 	/* delta is freed in got_object_close() */
@@ -736,8 +758,7 @@ resolve_offset_delta(struct got_delta_chain *deltas,
 	uint64_t base_size;
 	size_t base_tslen;
 	off_t delta_data_offset;
-	uint8_t *delta_buf;
-	size_t delta_len, consumed;
+	size_t consumed;
 
 	err = parse_offset_delta(&base_offset, &consumed, pack,
 	    delta_offset, tslen);
@@ -754,21 +775,8 @@ resolve_offset_delta(struct got_delta_chain *deltas,
 			return got_error_from_errno("lseek");
 	}
 
-	if (pack->map) {
-		size_t mapoff = (size_t)delta_data_offset;
-		err = got_inflate_to_mem_mmap(&delta_buf, &delta_len, pack->map,
-		    mapoff, pack->filesize - mapoff);
-		if (err)
-			return err;
-	} else {
-
-		err = got_inflate_to_mem_fd(&delta_buf, &delta_len, pack->fd);
-		if (err)
-			return err;
-	}
-
 	err = add_delta(deltas, delta_offset, tslen, delta_type, delta_size,
-	    delta_data_offset, delta_buf, delta_len);
+	    delta_data_offset);
 	if (err)
 		return err;
 
@@ -801,28 +809,27 @@ resolve_ref_delta(struct got_delta_chain *deltas, stru
 	uint8_t *delta_buf;
 	size_t delta_len;
 
-	if (delta_offset >= pack->filesize)
+	if (delta_offset + tslen >= pack->filesize)
 		return got_error(GOT_ERR_PACK_OFFSET);
-	delta_data_offset = delta_offset + tslen;
-	if (delta_data_offset >= pack->filesize)
-		return got_error(GOT_ERR_PACK_OFFSET);
 
 	if (pack->map == NULL) {
-		delta_data_offset = lseek(pack->fd, 0, SEEK_CUR);
-		if (delta_data_offset == -1)
-			return got_error_from_errno("lseek");
 	}
 
 	if (pack->map) {
-		size_t mapoff = (size_t)delta_data_offset;
+		size_t mapoff = delta_offset + tslen;
 		memcpy(&id, pack->map + mapoff, sizeof(id));
 		mapoff += sizeof(id);
+		delta_data_offset = (off_t)mapoff;
 		err = got_inflate_to_mem_mmap(&delta_buf, &delta_len, pack->map,
 		    mapoff, pack->filesize - mapoff);
 		if (err)
 			return err;
 	} else {
-		ssize_t n = read(pack->fd, &id, sizeof(id));
+		ssize_t n;
+		delta_data_offset = lseek(pack->fd, 0, SEEK_CUR);
+		if (delta_data_offset == -1)
+			return got_error_from_errno("lseek");
+		n = read(pack->fd, &id, sizeof(id));
 		if (n < 0)
 			return got_error_from_errno("read");
 		if (n != sizeof(id))
@@ -833,7 +840,7 @@ resolve_ref_delta(struct got_delta_chain *deltas, stru
 	}
 
 	err = add_delta(deltas, delta_offset, tslen, delta_type, delta_size,
-	    delta_data_offset, delta_buf, delta_len);
+	    delta_data_offset);
 	if (err)
 		return err;
 
@@ -875,7 +882,7 @@ resolve_delta_chain(struct got_delta_chain *deltas, st
 	case GOT_OBJ_TYPE_TAG:
 		/* Plain types are the final delta base. Recursion ends. */
 		err = add_delta(deltas, delta_offset, tslen, delta_type,
-		    delta_size, 0, NULL, 0);
+		    delta_size, 0);
 		break;
 	case GOT_OBJ_TYPE_OFFSET_DELTA:
 		err = resolve_offset_delta(deltas, packidx, pack,
@@ -980,7 +987,8 @@ got_packfile_open_object(struct got_object **obj, stru
 }
 
 static const struct got_error *
-get_delta_chain_max_size(uint64_t *max_size, struct got_delta_chain *deltas)
+get_delta_chain_max_size(uint64_t *max_size, struct got_delta_chain *deltas,
+    struct got_pack *pack)
 {
 	struct got_delta *delta;
 	uint64_t base_size = 0, result_size = 0;
@@ -993,8 +1001,31 @@ get_delta_chain_max_size(uint64_t *max_size, struct go
 		    delta->type != GOT_OBJ_TYPE_BLOB &&
 		    delta->type != GOT_OBJ_TYPE_TAG) {
 			const struct got_error *err;
+			uint8_t *delta_buf;
+			size_t delta_len;
+			int cached = 1;
+
+			got_delta_cache_get(&delta_buf, &delta_len,
+			    pack->delta_cache, delta->data_offset);
+			if (delta_buf == NULL) {
+				cached = 0;
+				err = read_delta_data(&delta_buf, &delta_len,
+				    delta->data_offset, pack);
+				if (err)
+					return err;
+				err = got_delta_cache_add(pack->delta_cache,
+				    delta->data_offset, delta_buf, delta_len);
+				if (err == NULL)
+					cached = 1;
+				else if (err->code != GOT_ERR_NO_SPACE) {
+					free(delta_buf);
+					return err;
+				}
+			}
 			err = got_delta_get_sizes(&base_size, &result_size,
-			    delta->delta_buf, delta->delta_len);
+			    delta_buf, delta_len);
+			if (!cached)
+				free(delta_buf);
 			if (err)
 				return err;
 		} else
@@ -1009,12 +1040,13 @@ get_delta_chain_max_size(uint64_t *max_size, struct go
 }
 
 const struct got_error *
-got_pack_get_max_delta_object_size(uint64_t *size, struct got_object *obj)
+got_pack_get_max_delta_object_size(uint64_t *size, struct got_object *obj,
+    struct got_pack *pack)
 {
 	if ((obj->flags & GOT_OBJ_FLAG_DELTIFIED) == 0)
 		return got_error(GOT_ERR_OBJ_TYPE);
 
-	return get_delta_chain_max_size(size, &obj->deltas);
+	return get_delta_chain_max_size(size, &obj->deltas, pack);
 }
 
 static const struct got_error *
@@ -1023,8 +1055,8 @@ dump_delta_chain_to_file(size_t *result_size, struct g
 {
 	const struct got_error *err = NULL;
 	struct got_delta *delta;
-	uint8_t *base_buf = NULL, *accum_buf = NULL;
-	size_t base_bufsz = 0, accum_size = 0;
+	uint8_t *base_buf = NULL, *accum_buf = NULL, *delta_buf;
+	size_t base_bufsz = 0, accum_size = 0, delta_len;
 	uint64_t max_size;
 	int n = 0;
 
@@ -1034,7 +1066,7 @@ dump_delta_chain_to_file(size_t *result_size, struct g
 		return got_error(GOT_ERR_BAD_DELTA_CHAIN);
 
 	/* We process small enough files entirely in memory for speed. */
-	err = get_delta_chain_max_size(&max_size, deltas);
+	err = get_delta_chain_max_size(&max_size, deltas, pack);
 	if (err)
 		return err;
 	if (max_size < GOT_DELTA_RESULT_SIZE_CACHED_MAX) {
@@ -1047,6 +1079,7 @@ dump_delta_chain_to_file(size_t *result_size, struct g
 
 	/* Deltas are ordered in ascending order. */
 	SIMPLEQ_FOREACH(delta, &deltas->entries, entry) {
+		int cached = 1;
 		if (n == 0) {
 			size_t mapoff;
 			off_t delta_data_offset;
@@ -1099,18 +1132,37 @@ dump_delta_chain_to_file(size_t *result_size, struct g
 			continue;
 		}
 
+		got_delta_cache_get(&delta_buf, &delta_len,
+		    pack->delta_cache, delta->data_offset);
+		if (delta_buf == NULL) {
+			cached = 0;
+			err = read_delta_data(&delta_buf, &delta_len,
+			    delta->data_offset, pack);
+			if (err)
+				goto done;
+			err = got_delta_cache_add(pack->delta_cache,
+			    delta->data_offset, delta_buf, delta_len);
+			if (err == NULL)
+				cached = 1;
+			else if (err->code != GOT_ERR_NO_SPACE) {
+				free(delta_buf);
+				goto done;
+			}
+		}
 		if (base_buf) {
 			err = got_delta_apply_in_mem(base_buf, base_bufsz,
-			    delta->delta_buf, delta->delta_len, accum_buf,
+			    delta_buf, delta_len, accum_buf,
 			    &accum_size, max_size);
 			n++;
 		} else {
-			err = got_delta_apply(base_file, delta->delta_buf,
-			    delta->delta_len,
+			err = got_delta_apply(base_file, delta_buf,
+			    delta_len,
 			    /* Final delta application writes to output file. */
 			    ++n < deltas->nentries ? accum_file : outfile,
 			    &accum_size);
 		}
+		if (!cached)
+			free(delta_buf);
 		if (err)
 			goto done;
 
@@ -1167,8 +1219,8 @@ dump_delta_chain_to_mem(uint8_t **outbuf, size_t *outl
 {
 	const struct got_error *err = NULL;
 	struct got_delta *delta;
-	uint8_t *base_buf = NULL, *accum_buf = NULL;
-	size_t base_bufsz = 0, accum_size = 0;
+	uint8_t *base_buf = NULL, *accum_buf = NULL, *delta_buf;
+	size_t base_bufsz = 0, accum_size = 0, delta_len;
 	uint64_t max_size;
 	int n = 0;
 
@@ -1178,7 +1230,7 @@ dump_delta_chain_to_mem(uint8_t **outbuf, size_t *outl
 	if (SIMPLEQ_EMPTY(&deltas->entries))
 		return got_error(GOT_ERR_BAD_DELTA_CHAIN);
 
-	err = get_delta_chain_max_size(&max_size, deltas);
+	err = get_delta_chain_max_size(&max_size, deltas, pack);
 	if (err)
 		return err;
 	accum_buf = malloc(max_size);
@@ -1187,6 +1239,7 @@ dump_delta_chain_to_mem(uint8_t **outbuf, size_t *outl
 
 	/* Deltas are ordered in ascending order. */
 	SIMPLEQ_FOREACH(delta, &deltas->entries, entry) {
+		int cached = 1;
 		if (n == 0) {
 			size_t delta_data_offset;
 
@@ -1224,9 +1277,28 @@ dump_delta_chain_to_mem(uint8_t **outbuf, size_t *outl
 			continue;
 		}
 
+		got_delta_cache_get(&delta_buf, &delta_len,
+		    pack->delta_cache, delta->data_offset);
+		if (delta_buf == NULL) {
+			cached = 0;
+			err = read_delta_data(&delta_buf, &delta_len,
+			    delta->data_offset, pack);
+			if (err)
+				goto done;
+			err = got_delta_cache_add(pack->delta_cache,
+			    delta->data_offset, delta_buf, delta_len);
+			if (err == NULL)
+				cached = 1;
+			else if (err->code != GOT_ERR_NO_SPACE) {
+				free(delta_buf);
+				goto done;
+			}
+		}
 		err = got_delta_apply_in_mem(base_buf, base_bufsz,
-		    delta->delta_buf, delta->delta_len, accum_buf,
+		    delta_buf, delta_len, accum_buf,
 		    &accum_size, max_size);
+		if (!cached)
+			free(delta_buf);
 		n++;
 		if (err)
 			goto done;
blob - e50b725ba52b655d85bb730cc8fae281a9751215
blob + 5fe2b7efcfad5831f504354f84e46082989a0d5f
--- libexec/got-read-pack/Makefile
+++ libexec/got-read-pack/Makefile
@@ -5,7 +5,7 @@
 PROG=		got-read-pack
 SRCS=		got-read-pack.c delta.c error.c inflate.c object_cache.c \
 		object_idset.c object_parse.c opentemp.c pack.c path.c \
-		privsep.c sha1.c
+		privsep.c sha1.c delta_cache.c
 
 CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
 LDADD = -lutil -lz
blob - faa1ca0fe5d71391f0f5fe9cb3b683725741ca18
blob + d8c2e0f8683a4492064ae0e7be3d35a1c6b485f0
--- libexec/got-read-pack/got-read-pack.c
+++ libexec/got-read-pack/got-read-pack.c
@@ -35,6 +35,7 @@
 #include "got_object.h"
 
 #include "got_lib_delta.h"
+#include "got_lib_delta_cache.h"
 #include "got_lib_object.h"
 #include "got_lib_object_cache.h"
 #include "got_lib_object_parse.h"
@@ -289,7 +290,7 @@ blob_request(struct imsg *imsg, struct imsgbuf *ibuf, 
 		goto done;
 
 	if (obj->flags & GOT_OBJ_FLAG_DELTIFIED) {
-		err = got_pack_get_max_delta_object_size(&blob_size, obj);
+		err = got_pack_get_max_delta_object_size(&blob_size, obj, pack);
 		if (err)
 			goto done;
 	} else
@@ -491,6 +492,13 @@ receive_pack(struct got_pack **packp, struct imsgbuf *
 	pack->path_packfile = strdup(ipack.path_packfile);
 	if (pack->path_packfile == NULL) {
 		err = got_error_from_errno("strdup");
+		goto done;
+	}
+
+	pack->delta_cache = got_delta_cache_alloc(100,
+	    GOT_DELTA_RESULT_SIZE_CACHED_MAX);
+	if (pack->delta_cache == NULL) {
+		err = got_error_from_errno("got_delta_cache_alloc");
 		goto done;
 	}
 
blob - 531c090468f03b4cc3cec7ddbb0b6cd2c7df89a4
blob + aafde1a4c7fe645f39e2b0acb46faa64767369c7
--- tog/Makefile
+++ tog/Makefile
@@ -8,7 +8,7 @@ SRCS=		tog.c blame.c commit_graph.c delta.c diff.c \
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c \
 		utf8.c inflate.c buf.c rcsutil.c diff3.c \
-		lockfile.c deflate.c object_create.c
+		lockfile.c deflate.c object_create.c delta_cache.c
 MAN =		${PROG}.1
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib