git/list[1] front-page[2] threads[3] people[4] search[5] about
 

'setup_work_tree()' considered harmful

From
Linus Torvalds <torvalds@linux-foundation.org>
Date
Jun 17, 2008, 00:45 UTC
Message-ID
<alpine.LFD.1.10.0806161723081.2949@woody.linux-foundation.org>
[ Dscho cc'd because I think he is the primary culprits for this thing, I 
  think. Commit e90fdc39b6903502192b2dd11e5503cea721a1ad in particular, 
  methinks. ]

So because I was looking at system call traces for object creation (due to those "rename to the final resting place" patches I did that got merged recently), I've noticed that some commands have *much* worse system call patterns than others.

In particular, doing a "git add ." will use absolute pathnames for all git files, while a "git diff" will not. And this is quite noticeable - the absolute pathnames are not just longer, they have more path components in them. Making them a lot slower to look up and use.

Of course, "a lot" depends on things a bit, but it really is noticeable.

To test, I created a kernel tree (no .git), and did a "git init" followed by a "git add .". Here's the timings for current git head:

	[torvalds@woody kernel]$ time git add .
	real    0m8.377s
	user    0m6.556s
	sys     0m1.656s

After that, I fixed "write_loose_object()" to not unnecessarily try to open a git object file, because every single caller has already done a "has_sha1_file(sha1)" or "has_loose_object(sha1)" check before calling that function, so trying to open it again is just pointless.

As a result, git add sped up a tiny bit:
	[torvalds@woody kernel]$ time ~/git/git-add .
	real    0m8.341s
	user    0m6.588s
	sys     0m1.548s

but I'll admit that it's not exactly noticeable. Half a percentage point is not a big deal.

Then I created a hack that just made "setup_work_tree()" a no-op (because it does all that crazy stuff that forces GIT_DIR to be an absolute path etc). As a result I got:

	[torvalds@woody kernel]$ time ~/git/git-add .
	real    0m7.849s
	user    0m6.420s
	sys     0m1.296s
ie now we're talking about a 5%+ performance difference.

Of course, this is all for the hot-cache case, and it wouldn't be noticeable for a cold-cache case, but it really can be a real performance issue. And no, it's not that "setup_work_tree()" itself is expensive, but depending on how GIT_DIR is set up, you get very different system call patterns.

Here's what current git does for one file (sound/usb/usbmixer_maps.c):
	lstat("sound/usb/usbmixer_maps.c", {st_mode=S_IFREG|0664, st_size=10230, ...}) = 0
	open("sound/usb/usbmixer_maps.c", O_RDONLY) = 4
	mmap(NULL, 10230, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7fa247228000
	close(4)                                = 0
	access("/home/torvalds/kernel/.git/objects/d7/55be0ad8115da5c1296211678f81a7774277d5", F_OK) = -1 ENOENT (No such file or directory)
	open("/home/torvalds/kernel/.git/objects/d7/55be0ad8115da5c1296211678f81a7774277d5", O_RDONLY) = -1 ENOENT (No such file or directory)
	open("/home/torvalds/kernel/.git/objects/d7/tmp_obj_55be0ad8115da5c12962116_vDqXRl", O_RDWR|O_CREAT|O_EXCL, 0600) = 4
	write(4, "x\1\315ZmS\333H\22\276\257\370W\364\361\t\210\1K\206$\300.\273\6\314\306\273@(l\'"..., 3402) = 3402
	fchmod(4, 0444)                         = 0
	close(4)                                = 0
	link("/home/torvalds/kernel/.git/objects/d7/tmp_obj_55be0ad8115da5c12962116_vDqXRl", "/home/torvalds/kernel/.git/objects/d7/55be0ad8115da5c1296211678f81a7774277d5") = 0
	unlink("/home/torvalds/kernel/.git/objects/d7/tmp_obj_55be0ad8115da5c12962116_vDqXRl") = 0
	munmap(0x7fa247228000, 10230)           = 0
and here is the fixed version:
	lstat("sound/usb/usbmixer_maps.c", {st_mode=S_IFREG|0664, st_size=10230, ...}) = 0
	open("sound/usb/usbmixer_maps.c", O_RDONLY) = 4
	mmap(NULL, 10230, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7f54c231a000
	close(4)                                = 0
	access(".git/objects/d7/55be0ad8115da5c1296211678f81a7774277d5", F_OK) = -1 ENOENT (No such file or directory)
	open(".git/objects/d7/tmp_obj_55be0ad8115da5c12962116_fKy6px", O_RDWR|O_CREAT|O_EXCL, 0600) = 4
	write(4, "x\1\315ZmS\333H\22\276\257\370W\364\361\t\210\1K\206$\300.\273\6\314\306\273@(l\'"..., 3402) = 3402
	fchmod(4, 0444)                         = 0
	close(4)                                = 0
	link(".git/objects/d7/tmp_obj_55be0ad8115da5c12962116_fKy6px", ".git/objects/d7/55be0ad8115da5c1296211678f81a7774277d5") = 0
	unlink(".git/objects/d7/tmp_obj_55be0ad8115da5c12962116_fKy6px") = 0
	munmap(0x7f54c231a000, 10230)           = 0

ie note how it does one unnecessary "open()" less, but more importantly, notice the difference between "/home/torvalds/kernel/.git/*" and ".git/*", and realize that that second difference was the much more noticeable one.

And no, obviously the right fix is not to just comment out all of "setup_work_tree()" (it will break stuff that depends on GIT_WORKTREE), but I did that as a minimal example of showing what the bad effect of that function is.

In general, I think we've gone in the wrong direction with a lot of the "make_absolute_path" stuff. See above. 5% performance loss is not good.

		Linus
Next: Linus Torvalds
Message 1 of 5 in “'setup_work_tree()' considered harmful”
  1. Linus TorvaldsJun 17, 2008
  2. Linus TorvaldsJun 17, 2008
  3. Johannes SchindelinJun 17, 2008
  4. Mike HommeyJun 18, 2008
  5. Linus TorvaldsJun 18, 2008

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.