From f4f8e76a5d4c0953996d464ca450e2e7f932cc8b Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Thu, 7 Sep 2017 18:00:19 -0400 Subject: [PATCH] git/odb/pack: prevent unnecessary runtime.memmove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running 'go tool pprof' in CPU mode while Git LFS is running a migration against a large repository shows that 4.73% percent of the CPU time is spent in a function called 'runtime.memmove'. This is the function called when append()-ing to a slice causes the slice to grow, and memory occupied by the existing slice must be moved into a different contiguous group. ``` $ go tool pprof $(which git-lfs) git-lfs-1504820145.pprof dot File: git-lfs Type: cpu Time: Sep 7, 2017 at 5:35pm (EDT) Duration: 35.17s, Total samples = 30.42s (86.49%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) list runtime\.memmove Total: 30.42s ROUTINE ======================== runtime.memmove in ... 1.44s 1.44s (flat, cum) 4.73% of Total . . 30:// void runtime·memmove(void*, void*, uintptr) . . 31:TEXT runtime·memmove(SB), NOSPLIT, $0-24 . . 32: ... ``` The implementation of 'runtime.memmove' is [fairly complex][1] and takes a relatively long amount of time to execute. Unfortunately, the 'patch' function in package 'git/odb/pack' is one of the smaller contributors: ``` (pprof) tree runtime\.memmove ----------------------------------------------------------+------------- 0.01s 100% | git/odb/pack.patch 0 0% 4.73% 0.01s 0.033% | runtime.growslice 0.01s 100% | runtime.gcAssistAlloc ----------------------------------------------------------+------------- ``` That said, the delta instructions do hint at the size of the patched result of applying 'delta' to 'base', which we can use to eagerly allocate a contiguous block of memory for. [1]: https://github.com/golang/go/blob/go1.9/src/runtime/memmove_amd64.s --- git/odb/pack/chain_delta.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/odb/pack/chain_delta.go b/git/odb/pack/chain_delta.go index 4445d81f..daf240d1 100644 --- a/git/odb/pack/chain_delta.go +++ b/git/odb/pack/chain_delta.go @@ -52,13 +52,13 @@ func patch(base, delta []byte) ([]byte, error) { return nil, errors.New("git/odb/pack: invalid delta data") } - var dest []byte - // The remainder of the delta header contains the destination size, and // moves the "pos" offset to the correct position to begin the set of // delta instructions. destSize, pos := patchDeltaHeader(delta, pos) + dest := make([]byte, 0, destSize) + for pos < len(delta) { c := int(delta[pos]) pos += 1