Improve PatchSet.Id.fromRef performance and avoid double-parsing

If5a39210 optimized PatchSet.isRef() to improve the performance of
VisibleRefFilter during ref advertisement on large repositories.
However, typical large Gerrit repositories are dominated by patch set
refs, in which case the relatively slower PatchSet.Id.fromRef() would
be called after each call to isRef().

Teach fromRef() to return null for invalid refs, and switch to a
character-by-character implementation like the former isRef()
implementation, which also avoids any allocations until returning.

Add small tests for various ref formats; the new parser is somewhat
more strict.

On a test project with 50,000 changes (50,004 refs), this reduces
average ls-remote time on my MacBook Air by about 30%, 57653us to
40480us (3 warmup runs then average of 10).

Leave isRef(), now implemented as "fromRef() != null". This may be
marginally less efficient than before, since it involves allocating a
PatchSet.Id. This makes the code more maintainable; when writing
tests, I found several inconsistencies between the old
isRef()/fromRef() implementations. Rather than fix these, I stuck
with one implementation. In any case, remaining isRef() callers
are not called on every ref in the repo in a tight loop, so the
performance impact should be minimal.

Change-Id: I9dfb074a7c2312512960fd070aef323c33f80dbd
This commit is contained in:
Dave Borowitz
2014-07-31 13:56:42 -07:00
committed by Yacob Yonas
parent 2148bf294d
commit 50678c33f2
6 changed files with 148 additions and 34 deletions

View File

@@ -0,0 +1,69 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.reviewdb.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class PatchSetTest {
@Test
public void parseRefNames() {
assertRef(1, 1, "refs/changes/01/1/1");
assertRef(1234, 56, "refs/changes/34/1234/56");
// Not even close.
assertNotRef(null);
assertNotRef("");
assertNotRef("01/1/1");
assertNotRef("HEAD");
assertNotRef("refs/tags/v1");
// Invalid characters.
assertNotRef("refs/changes/0x/1/1");
assertNotRef("refs/changes/01/x/1");
assertNotRef("refs/changes/01/1/x");
// Truncations.
assertNotRef("refs/changes/");
assertNotRef("refs/changes/1");
assertNotRef("refs/changes/01");
assertNotRef("refs/changes/01/");
assertNotRef("refs/changes/01/1/");
assertNotRef("refs/changes/01/1/1/");
assertNotRef("refs/changes/01//1/1");
// Leading zeroes.
assertNotRef("refs/changes/01/01/1");
assertNotRef("refs/changes/01/1/01");
// Mismatched last 2 digits.
assertNotRef("refs/changes/35/1234/56");
}
private static void assertRef(int changeId, int psId, String refName) {
assertTrue(PatchSet.isRef(refName));
assertEquals(new PatchSet.Id(new Change.Id(changeId), psId),
PatchSet.Id.fromRef(refName));
}
private static void assertNotRef(String refName) {
assertFalse(PatchSet.isRef(refName));
assertNull(PatchSet.Id.fromRef(refName));
}
}