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

Re: [PATCH] use skip_prefix() to avoid more magic numbers

From
René Scharfe <l.s.r@web.de>
Date
Oct 9, 2014, 20:06 UTC
Message-ID
<5436EAB5.2070809@web.de>
In-Reply-To
<xmqqd2a3g2mf.fsf@gitster.dls.corp.google.com>
Am 07.10.2014 um 20:23 schrieb Junio C Hamano:
Show 35 quoted lines
> René Scharfe <l.s.r@web.de> writes:
> 
>> @@ -335,20 +337,18 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
>>   	static struct {
>>   		int kind;
>>   		const char *prefix;
>> -		int pfxlen;
>>   	} ref_kind[] = {
>> -		{ REF_LOCAL_BRANCH, "refs/heads/", 11 },
>> -		{ REF_REMOTE_BRANCH, "refs/remotes/", 13 },
>> +		{ REF_LOCAL_BRANCH, "refs/heads/" },
>> +		{ REF_REMOTE_BRANCH, "refs/remotes/" },
>>   	};
>>   
>>   	/* Detect kind */
>>   	for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
>>   		prefix = ref_kind[i].prefix;
>> -		if (strncmp(refname, prefix, ref_kind[i].pfxlen))
>> -			continue;
>> -		kind = ref_kind[i].kind;
>> -		refname += ref_kind[i].pfxlen;
>> -		break;
>> +		if (skip_prefix(refname, prefix, &refname)) {
>> +			kind = ref_kind[i].kind;
>> +			break;
>> +		}
> 
> This certainly makes it easier to read.
> 
> I suspect that the original was done as a (possibly premature)
> optimization to avoid having to do strlen(3) on a variable that
> points at constant strings for each and every ref we iterate with
> for_each_rawref(), and it is somewhat sad to see it lost because
> skip_prefix() assumes that the caller never knows the length of the
> prefix, though.

I didn't think much about the performance implications. skip_prefix() doesn't call strlen(3), though. Your reply made me curious. The synthetic test program below can be used to call the old and the new code numerous times. I called it like this:

    for a in strncmp skip_prefix
    do
        for b in refs/heads/x refs/remotes/y refs/of/the/third/kind
        do
            time ./test-prefix $a $b
        done
    done
And got the following results:
100000000x strncmp for refs/heads/x, which is a local branch

real 0m2.423s user 0m2.420s sys 0m0.000s 100000000x strncmp for refs/remotes/y, which is a remote branch

real 0m4.331s user 0m4.328s sys 0m0.000s 100000000x strncmp for refs/of/the/third/kind, which is no branch

real 0m3.878s user 0m3.872s sys 0m0.000s 100000000x skip_prefix for refs/heads/x, which is a local branch

real 0m0.891s user 0m0.888s sys 0m0.000s 100000000x skip_prefix for refs/remotes/y, which is a remote branch

real 0m1.345s user 0m1.340s sys 0m0.000s 100000000x skip_prefix for refs/of/the/third/kind, which is no branch

real 0m1.080s user 0m1.076s sys 0m0.000s

The code handles millions of ref strings per second before and after the change, and with the change it's faster. I hope the results are reproducible and make it easier to say goodbye to pfxlen. :)

René
---
 .gitignore    |  1 +
 Makefile      |  1 +
 test-prefix.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 89 insertions(+)
 create mode 100644 test-prefix.c
diff --git a/.gitignore b/.gitignore
index 5bfb234..8416c5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -193,6 +193,7 @@
 /test-mktemp
 /test-parse-options
 /test-path-utils
+/test-prefix
 /test-prio-queue
 /test-read-cache
 /test-regex
diff --git a/Makefile b/Makefile
index f34a2d4..c09b59e 100644
--- a/Makefile
+++ b/Makefile
@@ -561,6 +561,7 @@ TEST_PROGRAMS_NEED_X += test-mergesort
 TEST_PROGRAMS_NEED_X += test-mktemp
 TEST_PROGRAMS_NEED_X += test-parse-options
 TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-prefix
 TEST_PROGRAMS_NEED_X += test-prio-queue
 TEST_PROGRAMS_NEED_X += test-read-cache
 TEST_PROGRAMS_NEED_X += test-regex
diff --git a/test-prefix.c b/test-prefix.c
new file mode 100644
index 0000000..ddc63af
--- /dev/null
+++ b/test-prefix.c
@@ -0,0 +1,87 @@
+#include "cache.h"
+
+#define ROUNDS 100000000
+
+#define REF_LOCAL_BRANCH    0x01
+#define REF_REMOTE_BRANCH   0x02
+
+static int test_skip_prefix(const char *refname)
+{
+	int kind, i;
+	const char *prefix;
+	static struct {
+		int kind;
+		const char *prefix;
+	} ref_kind[] = {
+		{ REF_LOCAL_BRANCH, "refs/heads/" },
+		{ REF_REMOTE_BRANCH, "refs/remotes/" },
+	};
+
+	for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+		prefix = ref_kind[i].prefix;
+		if (skip_prefix(refname, prefix, &refname)) {
+			kind = ref_kind[i].kind;
+			break;
+		}
+	}
+	if (ARRAY_SIZE(ref_kind) <= i)
+		return 0;
+	return kind;
+}
+
+static int test_strncmp(const char *refname)
+{
+	int kind, i;
+	const char *prefix;
+	static struct {
+		int kind;
+		const char *prefix;
+		int pfxlen;
+	} ref_kind[] = {
+		{ REF_LOCAL_BRANCH, "refs/heads/", 11 },
+		{ REF_REMOTE_BRANCH, "refs/remotes/", 13 },
+	};
+
+	for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+		prefix = ref_kind[i].prefix;
+		if (strncmp(refname, prefix, ref_kind[i].pfxlen))
+			continue;
+		kind = ref_kind[i].kind;
+		refname += ref_kind[i].pfxlen;
+		break;
+	}
+	if (ARRAY_SIZE(ref_kind) <= i)
+		return 0;
+	return kind;
+}
+
+int main(int argc, char **argv)
+{
+	if (argc == 3) {
+		int (*fn)(const char *) = NULL;
+		printf("%dx %s for %s, which is ", ROUNDS, argv[1], argv[2]);
+		if (!strcmp(argv[1], "skip_prefix"))
+			fn = test_skip_prefix;
+		if (!strcmp(argv[1], "strncmp"))
+			fn = test_strncmp;
+		if (fn) {
+			int i, kind = 0;
+			for (i = 0; i < ROUNDS; i++)
+				kind |= fn(argv[2]);
+			switch (kind) {
+			case 0:
+				puts("no branch");
+				break;
+			case REF_LOCAL_BRANCH:
+				puts("a local branch");
+				break;
+			case REF_REMOTE_BRANCH:
+				puts("a remote branch");
+				break;
+			default:
+				puts("invalid");
+			}
+		}
+	}
+	return 0;
+}
-- 
2.1.2
Previous: Junio C HamanoNext: Junio C Hamano
Message 12 of 13 in “use skip_prefix() to avoid more magic numbers”
  1. use skip_prefix() to avoid more magic numbersRené Scharfe, Oct 4, 2014
  2. Jonathan NiederOct 5, 2014
  3. Jeff KingOct 6, 2014
  4. Junio C HamanoOct 7, 2014
  5. Jeff KingOct 7, 2014
  6. Jeff KingOct 7, 2014
  7. Junio C HamanoOct 7, 2014
  8. Junio C HamanoOct 7, 2014
  9. Jeff KingOct 7, 2014
  10. Jeff KingOct 6, 2014
  11. Junio C HamanoOct 7, 2014
  12. René ScharfeOct 9, 2014
  13. Junio C HamanoOct 9, 2014

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.