diff options
Diffstat (limited to 'org.eclipse.jgit.benchmarks/src')
8 files changed, 1237 insertions, 0 deletions
diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/CreateFileSnapshotBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/CreateFileSnapshotBenchmark.java new file mode 100644 index 0000000000..913ca5a9e8 --- /dev/null +++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/CreateFileSnapshotBenchmark.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.benchmarks; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.internal.storage.file.FileSnapshot; +import org.eclipse.jgit.util.FileUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +public class CreateFileSnapshotBenchmark { + + Path path; + + Path testDir; + + @Setup + public void setupBenchmark() throws IOException { + testDir = Files.createTempDirectory("dir"); + path = testDir.resolve("toSnapshot"); + Files.createFile(path); + } + + @TearDown + public void teardown() throws IOException { + FileUtils.delete(testDir.toFile(), FileUtils.RECURSIVE | FileUtils.RETRY); + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS) + public Path testCreateFile() throws IOException { + return Files.createTempFile(testDir, "create", ""); + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public FileSnapshot testCreateFileSnapshot() { + return FileSnapshot.save(path.toFile()); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(CreateFileSnapshotBenchmark.class.getSimpleName()) + .forks(1).jvmArgs("-ea").build(); + new Runner(opt).run(); + } +} diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/FileMoveBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/FileMoveBenchmark.java new file mode 100644 index 0000000000..2ec5f1f19a --- /dev/null +++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/FileMoveBenchmark.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2020, Matthias Sohn <matthias.sohn@sap.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.benchmarks; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.util.FileUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +public class FileMoveBenchmark { + int i; + + Path testDir; + + Path targetDir; + + @Setup + public void setupBenchmark() throws IOException { + testDir = Files.createTempDirectory("dir"); + targetDir = testDir.resolve("target"); + Files.createDirectory(targetDir); + } + + @TearDown + public void teardown() throws IOException { + FileUtils.delete(testDir.toFile(), + FileUtils.RECURSIVE | FileUtils.RETRY); + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + @Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 5000, timeUnit = TimeUnit.MILLISECONDS) + public Path moveFileToExistingDir() throws IOException { + i++; + Path tmp = testDir.resolve("tmp" + i++); + Files.createFile(tmp); + Path targetDirectory = targetDir; + Path targetFile = targetDirectory.resolve("tmp" + i); + try { + return Files.move(tmp, targetFile, StandardCopyOption.ATOMIC_MOVE); + } catch (NoSuchFileException e) { + Files.createDirectory(targetDirectory); + return Files.move(tmp, targetFile, StandardCopyOption.ATOMIC_MOVE); + } + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + @Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 5000, timeUnit = TimeUnit.MILLISECONDS) + public Path moveFileToExistingDirExists() throws IOException { + Path tmp = testDir.resolve("tmp" + i++); + Files.createFile(tmp); + Path targetDirectory = targetDir; + Path targetFile = targetDir.resolve("tmp" + i); + if (!targetDirectory.toFile().exists()) { + Files.createDirectory(targetDirectory); + } + return Files.move(tmp, targetFile, StandardCopyOption.ATOMIC_MOVE); + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + @Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 5000, timeUnit = TimeUnit.MILLISECONDS) + public Path moveFileToMissingDir() throws IOException { + i++; + Path tmp = testDir.resolve("tmp" + i); + Files.createFile(tmp); + Path targetDirectory = testDir.resolve("target" + i); + Path targetFile = targetDirectory.resolve("tmp" + i); + try { + return Files.move(tmp, targetFile, StandardCopyOption.ATOMIC_MOVE); + } catch (NoSuchFileException e) { + Files.createDirectory(targetDirectory); + return Files.move(tmp, targetFile, StandardCopyOption.ATOMIC_MOVE); + } + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + @Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 5000, timeUnit = TimeUnit.MILLISECONDS) + public Path moveFileToMissingDirExists() throws IOException { + i++; + Path tmp = testDir.resolve("tmp" + i); + Files.createFile(tmp); + Path targetDirectory = testDir.resolve("target" + i); + Path targetFile = targetDirectory.resolve("tmp" + i); + if (!targetDirectory.toFile().exists()) { + Files.createDirectory(targetDirectory); + } + return Files.move(tmp, targetFile, StandardCopyOption.ATOMIC_MOVE); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(FileMoveBenchmark.class + .getSimpleName()) + .forks(1).jvmArgs("-ea").build(); + new Runner(opt).run(); + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/GetRefsBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/GetRefsBenchmark.java new file mode 100644 index 0000000000..44e862e7c8 --- /dev/null +++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/GetRefsBenchmark.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2021, Luca Milanesio <luca.milanesio@gmail.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.benchmarks; + +import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.internal.storage.file.FileReftableDatabase; +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig.TrustStat; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryCache; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; +import org.junit.Assume; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +public class GetRefsBenchmark { + + ThreadLocalRandom branchIndex = ThreadLocalRandom.current(); + + @State(Scope.Benchmark) + public static class BenchmarkState { + + @Param({ "true", "false" }) + boolean useRefTable; + + @Param({ "true", "false" }) + boolean autoRefresh; + + @Param({ "100", "1000", "10000", "100000" }) + int numBranches; + + @Param({ "ALWAYS", "AFTER_OPEN", "NEVER" }) + TrustStat trustStat; + + List<String> branches = new ArrayList<>(numBranches); + + Path testDir; + + Repository repo; + + @Setup + @SuppressWarnings("boxing") + public void setupBenchmark() throws IOException, GitAPIException { + // if we use RefDirectory skip autoRefresh = false + Assume.assumeTrue(useRefTable || autoRefresh); + + String firstBranch = "firstbranch"; + testDir = Files.createDirectory(Paths.get("testrepos")); + String repoName = "branches-" + numBranches + "-trustStat-" + + trustStat + "-" + refDatabaseType(); + Path workDir = testDir.resolve(repoName); + Path repoPath = workDir.resolve(".git"); + Git git = Git.init().setDirectory(workDir.toFile()).call(); + RevCommit firstCommit = git.commit().setMessage("First commit") + .call(); + git.branchCreate().setName(firstBranch).call(); + + StoredConfig cfg = git.getRepository().getConfig(); + if (useRefTable) { + ((FileRepository) git.getRepository()).convertRefStorage( + ConfigConstants.CONFIG_REF_STORAGE_REFTABLE, false, + false); + FileReftableDatabase refdb = (FileReftableDatabase) git + .getRepository().getRefDatabase(); + refdb.setAutoRefresh(autoRefresh); + } else { + cfg.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_TRUST_STAT, + trustStat); + } + cfg.setInt(ConfigConstants.CONFIG_RECEIVE_SECTION, null, + "maxCommandBytes", Integer.MAX_VALUE); + cfg.save(); + + repo = RepositoryCache.open(RepositoryCache.FileKey + .lenient(repoPath.toFile(), FS.DETECTED)); + + System.out.println("Preparing test"); + System.out.println("- repository: \t\t" + repoPath); + System.out.println("- refDatabase: \t\t" + refDatabaseType()); + System.out.println("- autoRefresh: \t\t" + autoRefresh); + System.out.println("- trustStat: \t" + trustStat); + System.out.println("- branches: \t\t" + numBranches); + + BatchRefUpdate u = repo.getRefDatabase().newBatchUpdate(); + + branches = IntStream.range(0, numBranches) + .mapToObj(i -> "branch/" + i % 100 + "/" + i) + .collect(Collectors.toList()); + for (String branch : branches) { + u.addCommand(new ReceiveCommand(ObjectId.zeroId(), + firstCommit.toObjectId(), Constants.R_HEADS + branch, + CREATE)); + } + + System.out.println(); + System.out.print( + String.format("Creating %d branches ... ", numBranches)); + + try (RevWalk rw = new RevWalk(repo)) { + u.execute(rw, new TextProgressMonitor()); + } + System.out.println("DONE"); + } + + private String refDatabaseType() { + return useRefTable ? "reftable" : "refdir"; + } + + @TearDown + public void teardown() throws IOException { + repo.close(); + FileUtils.delete(testDir.toFile(), + FileUtils.RECURSIVE | FileUtils.RETRY); + } + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + @Warmup(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(2) + public void testGetExactRef(Blackhole blackhole, BenchmarkState state) + throws IOException { + String branchName = state.branches + .get(branchIndex.nextInt(state.numBranches)); + blackhole.consume(state.repo.exactRef(branchName)); + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + @Warmup(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(2) + public void testGetRefsByPrefix(Blackhole blackhole, BenchmarkState state) + throws IOException { + String branchPrefix = "refs/heads/branch/" + branchIndex.nextInt(100) + + "/"; + blackhole.consume( + state.repo.getRefDatabase().getRefsByPrefix(branchPrefix)); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(GetRefsBenchmark.class.getSimpleName()) + // .addProfiler(StackProfiler.class) + // .addProfiler(GCProfiler.class) + .forks(1).jvmArgs("-ea").build(); + new Runner(opt).run(); + } +} diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/LookupFileStoreBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/LookupFileStoreBenchmark.java new file mode 100644 index 0000000000..e9c9ef36a0 --- /dev/null +++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/LookupFileStoreBenchmark.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.benchmarks; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.util.FileUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +public class LookupFileStoreBenchmark { + + Path path; + + @Setup + public void setupBenchmark() throws IOException { + path = Files.createTempFile("test", "x"); + } + + @TearDown + public void teardown() throws IOException { + FileUtils.delete(path.toFile(), FileUtils.RETRY); + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public FileStore testLookupFileStore() throws IOException { + FileStore fs = Files.getFileStore(path); + return fs; + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(LookupFileStoreBenchmark.class.getSimpleName()) + .forks(1).jvmArgs("-ea").build(); + new Runner(opt).run(); + } +} diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/RawTextBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/RawTextBenchmark.java new file mode 100644 index 0000000000..19297ebebb --- /dev/null +++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/RawTextBenchmark.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.benchmarks; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +import static org.eclipse.jgit.diff.RawText.getBufferSize; +import static org.eclipse.jgit.diff.RawText.isBinary; +import static org.eclipse.jgit.diff.RawText.isCrLfText; + +@State(Scope.Thread) +public class RawTextBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + + @Param({"1", "2", "3", "4", "5", "6"}) + int testIndex; + + @Param({"false", "true"}) + boolean complete; + + byte[] bytes; + + @Setup + public void setupBenchmark() { + switch (testIndex) { + case 1: { + byte[] tmpBytes = "a".repeat(102400).getBytes(); + bytes = tmpBytes; + break; + } + case 2: { + byte[] tmpBytes = "a".repeat(102400).getBytes(); + byte[] tmpBytes2 = new byte[tmpBytes.length + 1]; + System.arraycopy(tmpBytes, 0, tmpBytes2, 0, tmpBytes.length); + tmpBytes2[500] = '\0'; + tmpBytes2[tmpBytes.length] = '\0'; + bytes = tmpBytes2; + break; + } + case 3: { + byte[] tmpBytes = "a".repeat(102400).getBytes(); + byte[] tmpBytes2 = new byte[tmpBytes.length + 1]; + System.arraycopy(tmpBytes, 0, tmpBytes2, 0, tmpBytes.length); + tmpBytes2[500] = '\r'; + tmpBytes2[tmpBytes.length] = '\r'; + bytes = tmpBytes2; + break; + } + case 4: { + byte[] tmpBytes = "a".repeat(102400).getBytes(); + byte[] tmpBytes2 = new byte[tmpBytes.length + 1]; + System.arraycopy(tmpBytes, 0, tmpBytes2, 0, tmpBytes.length); + tmpBytes2[499] = '\r'; + tmpBytes2[500] = '\n'; + tmpBytes2[tmpBytes.length - 1] = '\r'; + tmpBytes2[tmpBytes.length] = '\n'; + bytes = tmpBytes2; + break; + } + case 5: { + byte[] tmpBytes = "a".repeat(102400).getBytes(); + tmpBytes[0] = '\0'; + bytes = tmpBytes; + break; + } + case 6: { + byte[] tmpBytes = "a".repeat(102400).getBytes(); + tmpBytes[0] = '\r'; + bytes = tmpBytes; + break; + } + default: + } + } + + @TearDown + public void teardown() { + } + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(1) + public void testIsCrLfTextOld(Blackhole blackhole, BenchmarkState state) { + blackhole.consume( + isCrLfTextOld( + state.bytes, + state.bytes.length, + state.complete + ) + ); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(1) + public void testIsCrLfTextNewCandidate1(Blackhole blackhole, BenchmarkState state) { + blackhole.consume( + isCrLfTextNewCandidate1( + state.bytes, + state.bytes.length, + state.complete + ) + ); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(1) + public void testIsCrLfTextNewCandidate2(Blackhole blackhole, BenchmarkState state) { + blackhole.consume( + isCrLfTextNewCandidate2( + state.bytes, + state.bytes.length, + state.complete + ) + ); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(1) + public void testIsCrLfTextNewCandidate3(Blackhole blackhole, BenchmarkState state) { + blackhole.consume( + isCrLfTextNewCandidate3( + state.bytes, + state.bytes.length, + state.complete + ) + ); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(1) + public void testIsCrLfTextNew(Blackhole blackhole, BenchmarkState state) { + blackhole.consume( + isCrLfText( + state.bytes, + state.bytes.length, + state.complete + ) + ); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(1) + public void testIsBinaryOld(Blackhole blackhole, BenchmarkState state) { + blackhole.consume( + isBinaryOld( + state.bytes, + state.bytes.length, + state.complete + ) + ); + } + + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(1) + public void testIsBinaryNew(Blackhole blackhole, BenchmarkState state) { + blackhole.consume( + isBinary( + state.bytes, + state.bytes.length, + state.complete + ) + ); + } + + + /** + * Determine heuristically whether a byte array represents binary (as + * opposed to text) content. + * + * @param raw + * the raw file content. + * @param length + * number of bytes in {@code raw} to evaluate. This should be + * {@code raw.length} unless {@code raw} was over-allocated by + * the caller. + * @param complete + * whether {@code raw} contains the whole data + * @return true if raw is likely to be a binary file, false otherwise + * @since 6.0 + */ + public static boolean isBinaryOld(byte[] raw, int length, boolean complete) { + // Similar heuristic as C Git. Differences: + // - limited buffer size; may be only the beginning of a large blob + // - no counting of printable vs. non-printable bytes < 0x20 and 0x7F + int maxLength = getBufferSize(); + boolean isComplete = complete; + if (length > maxLength) { + // We restrict the length in all cases to getBufferSize() to get + // predictable behavior. Sometimes we load streams, and sometimes we + // have the full data in memory. With streams, we never look at more + // than the first getBufferSize() bytes. If we looked at more when + // we have the full data, different code paths in JGit might come to + // different conclusions. + length = maxLength; + isComplete = false; + } + byte last = 'x'; // Just something inconspicuous. + for (int ptr = 0; ptr < length; ptr++) { + byte curr = raw[ptr]; + if (isBinary(curr, last)) { + return true; + } + last = curr; + } + if (isComplete) { + // Buffer contains everything... + return last == '\r'; // ... so this must be a lone CR + } + return false; + } + + /** + * Determine heuristically whether a byte array represents text content + * using CR-LF as line separator. + * + * @param raw the raw file content. + * @param length number of bytes in {@code raw} to evaluate. + * @param complete whether {@code raw} contains the whole data + * @return {@code true} if raw is likely to be CR-LF delimited text, + * {@code false} otherwise + * @since 6.0 + */ + public static boolean isCrLfTextOld(byte[] raw, int length, boolean complete) { + boolean has_crlf = false; + byte last = 'x'; // Just something inconspicuous + for (int ptr = 0; ptr < length; ptr++) { + byte curr = raw[ptr]; + if (isBinary(curr, last)) { + return false; + } + if (curr == '\n' && last == '\r') { + has_crlf = true; + } + last = curr; + } + if (last == '\r') { + if (complete) { + // Lone CR: it's binary after all. + return false; + } + // Tough call. If the next byte, which we don't have, would be a + // '\n', it'd be a CR-LF text, otherwise it'd be binary. Just decide + // based on what we already scanned; it wasn't binary until now. + } + return has_crlf; + } + + /** + * Determine heuristically whether a byte array represents text content + * using CR-LF as line separator. + * + * @param raw + * the raw file content. + * @param length + * number of bytes in {@code raw} to evaluate. + * @return {@code true} if raw is likely to be CR-LF delimited text, + * {@code false} otherwise + * @param complete + * whether {@code raw} contains the whole data + * @since 6.0 + */ + public static boolean isCrLfTextNewCandidate1(byte[] raw, int length, boolean complete) { + boolean has_crlf = false; + + // first detect empty + if (length <= 0) { + return false; + } + + // next detect '\0' + for (int reversePtr = length - 1; reversePtr >= 0; --reversePtr) { + if (raw[reversePtr] == '\0') { + return false; + } + } + + // if '\r' be last, then if complete then return non-crlf + if (raw[length - 1] == '\r' && complete) { + return false; + } + + for (int ptr = 0; ptr < length - 1; ptr++) { + byte curr = raw[ptr]; + if (curr == '\r') { + byte next = raw[ptr + 1]; + if (next != '\n') { + return false; + } + // else + // we have crlf here + has_crlf = true; + // as next is '\n', it can never be '\r', just skip it from next check + ++ptr; + } + } + + return has_crlf; + } + + /** + * Determine heuristically whether a byte array represents text content + * using CR-LF as line separator. + * + * @param raw + * the raw file content. + * @param length + * number of bytes in {@code raw} to evaluate. + * @return {@code true} if raw is likely to be CR-LF delimited text, + * {@code false} otherwise + * @param complete + * whether {@code raw} contains the whole data + * @since 6.0 + */ + public static boolean isCrLfTextNewCandidate2(byte[] raw, int length, boolean complete) { + boolean has_crlf = false; + + // first detect empty + if (length <= 0) { + return false; + } + + // if '\r' be last, then if complete then return non-crlf + byte last = raw[length - 1]; + if (last == '\0' || last == '\r' && complete) { + return false; + } + + for (int ptr = 0; ptr < length - 1; ptr++) { + byte b = raw[ptr]; + switch (b) { + case '\0': + return false; + case '\r': { + ++ptr; + b = raw[ptr]; + if (b != '\n') { + return false; + } + // else + // we have crlf here + has_crlf = true; + // as next is '\n', it can never be '\r', just skip it from next check + break; + } + default: + // do nothing; + break; + } + } + + return has_crlf; + } + + /** + * Determine heuristically whether a byte array represents text content + * using CR-LF as line separator. + * + * @param raw + * the raw file content. + * @param length + * number of bytes in {@code raw} to evaluate. + * @return {@code true} if raw is likely to be CR-LF delimited text, + * {@code false} otherwise + * @param complete + * whether {@code raw} contains the whole data + * @since 6.0 + */ + public static boolean isCrLfTextNewCandidate3(byte[] raw, int length, boolean complete) { + boolean has_crlf = false; + + int ptr = -1; + byte current; + while (ptr < length - 2) { + current = raw[++ptr]; + if ('\0' == current || '\r' == current && (raw[++ptr] != '\n' || !(has_crlf = true))) { + return false; + } + } + + if (ptr == length - 2) { + // if '\r' be last, then if isComplete then return binary + current = raw[++ptr]; + if('\0' == current || '\r' == current && complete){ + return false; + } + } + + return has_crlf; + } + + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(RawTextBenchmark.class.getSimpleName()) + .forks(1).jvmArgs("-ea").build(); + new Runner(opt).run(); + } +} diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/SHA1Benchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/SHA1Benchmark.java new file mode 100644 index 0000000000..a2b59339bb --- /dev/null +++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/SHA1Benchmark.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.benchmarks; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.junit.MockSystemReader; +import org.eclipse.jgit.util.SystemReader; +import org.eclipse.jgit.util.sha1.SHA1; +import org.eclipse.jgit.util.sha1.SHA1.Sha1Implementation; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +public class SHA1Benchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + + @Param({ "1", "2", "4", "8", "16", "32", "64" }) + int size; + + @Param({ "false", "true" }) + boolean detectCollision; + + @Param({ "java", "jdkNative" }) + String impl; + + private SecureRandom rnd; + + byte[] content; + + @Setup + public void setupBenchmark() { + SystemReader.setInstance(new MockSystemReader()); + if (impl.equalsIgnoreCase(Sha1Implementation.JDKNATIVE.name())) { + System.setProperty("org.eclipse.jgit.util.sha1.implementation", + Sha1Implementation.JDKNATIVE.name()); + } + content = new byte[size * 1024]; + try { + rnd = SecureRandom.getInstanceStrong(); + } catch (NoSuchAlgorithmException e) { + // ignore + } + rnd.nextBytes(content); + } + + @TearDown + public void teardown() { + SystemReader.setInstance(null); + rnd = null; + } + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + @Warmup(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(1) + public void testSHA1(Blackhole blackhole, BenchmarkState state) { + SHA1 hash = SHA1.newInstance(); + hash.setDetectCollision(state.detectCollision); + hash.update(state.content); + blackhole.consume(hash.digest()); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(SHA1Benchmark.class.getSimpleName()) + .forks(1).jvmArgs("-ea").build(); + new Runner(opt).run(); + } +} diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/SimpleLruCacheBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/SimpleLruCacheBenchmark.java new file mode 100644 index 0000000000..caefd75d9b --- /dev/null +++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/SimpleLruCacheBenchmark.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.benchmarks; + +import java.security.SecureRandom; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.util.SimpleLruCache; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Group; +import org.openjdk.jmh.annotations.GroupThreads; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Benchmark) +public class SimpleLruCacheBenchmark { + + SecureRandom rnd = new SecureRandom(); + + private volatile SimpleLruCache<String, String> cache = new SimpleLruCache<>( + 100, 0.2f); + + private volatile int i; + + @Setup + public void setupBenchmark() { + i = rnd.nextInt(250); + } + + @TearDown + public void teardown() { + cache = null; + } + + @Benchmark + @Group("readwrite") + @GroupThreads(1) + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public SimpleLruCache<String, String> testCacheWrite() { + cache.put("k" + i, "v" + i); + return cache; + } + + @Benchmark + @Group("readwrite") + @GroupThreads(1) + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public SimpleLruCache<String, String> testCacheRead() { + cache.get("k" + i); + return cache; + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(SimpleLruCacheBenchmark.class.getSimpleName()) + .forks(1).jvmArgs("-ea").build(); + new Runner(opt).run(); + } +} diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/revwalk/DateRevQueueBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/revwalk/DateRevQueueBenchmark.java new file mode 100644 index 0000000000..71075a8b4c --- /dev/null +++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/revwalk/DateRevQueueBenchmark.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2023, GerritForge Ltd + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.revwalk; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FileUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +public class DateRevQueueBenchmark { + + ThreadLocalRandom commitsIndex = ThreadLocalRandom.current(); + + @State(Scope.Benchmark) + public static class BenchmarkState { + + @Param({ "5", "10", "50", "100", "500", "1000", "5000", "10000", + "50000", "100000", "500000" }) + int numCommits; + + @Param({ "true", "false" }) + boolean usePriorityQueue; + + int low, count; + + RevCommit[] commits = new RevCommit[numCommits]; + + private Path testDir; + + private TestRepository<Repository> repoUtil; + + DateRevQueue queue; + + @Setup + public void setupBenchmark() throws Exception { + testDir = Files.createTempDirectory("testrepos"); + String repoName = "commits-" + numCommits + "-usePriorityQueue-" + + usePriorityQueue; + Path workDir = testDir.resolve(repoName); + Git git = Git.init().setDirectory(workDir.toFile()).call(); + repoUtil = new TestRepository<>(git.getRepository()); + + RevCommit parent = repoUtil.commit().create(); + commits = new RevCommit[numCommits]; + commits[0] = parent; + for (int i = 1; i < numCommits; i++) { + parent = repoUtil.parseBody(repoUtil.commit(i, parent)); + commits[i] = parent; + if (i % 10000 == 0) { + System.out.println(" " + i + " done"); + } + } + + if (usePriorityQueue) { + queue = new DateRevPriorityQueue(false); + } else { + queue = new DateRevQueue(false); + } + + low = 9 * numCommits / 10; + ThreadLocalRandom random = ThreadLocalRandom.current(); + // add 90% * numCommits commits, benchmark adding commits from + // 90-100% + for (int i = 0; i < low; i++) { + RevCommit commit = commits[random.nextInt(numCommits)]; + queue.add(commit); + ++count; + } + } + + @TearDown(Level.Invocation) + public void check() { + // if queue is full remove 10% of its entries + if (++count == numCommits) { + do { + queue.next(); + } while (--count > low); + } + } + + @TearDown + public void teardown() throws IOException { + repoUtil.close(); + FileUtils.delete(testDir.toFile(), + FileUtils.RECURSIVE | FileUtils.RETRY); + } + } + + @Benchmark + @BenchmarkMode({ Mode.AverageTime }) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Warmup(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 10, time = 10, timeUnit = TimeUnit.SECONDS) + public void testDataRevQueue(BenchmarkState state) throws Exception { + RevCommit commit = state.commits[commitsIndex + .nextInt(state.numCommits)]; + state.queue.add(commit); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(DateRevQueueBenchmark.class.getSimpleName()).forks(1) + .jvmArgs("-ea").build(); + new Runner(opt).run(); + } + +} |