/* * Copyright 2000-2015 JetBrains s.r.o. * * 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.intellij.openapi.vfs.newvfs.impl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.text.StringUtil; import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.fixtures.IdeaTestFixture; import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; import com.intellij.util.containers.ContainerUtil; import gnu.trove.TIntObjectHashMap; import gnu.trove.TLongArrayList; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import javax.swing.*; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** * @author peter */ public class FileNameCacheMicroBenchmark { public static void main(String[] args) throws Exception { //noinspection SSBasedInspection SwingUtilities.invokeAndWait(() -> { try { IdeaTestFixture fixture = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder(LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR).getFixture(); fixture.setUp(); long start = System.currentTimeMillis(); runTest(200, "All names in cache"); runTest(50000, "Cache almost overflows"); runTest(120000, "Cache certain overflow"); long elapsed = System.currentTimeMillis() - start; System.out.println("Total elapsed: " + elapsed/1000.0 +"s"); fixture.tearDown(); } catch (Exception e) { throw new RuntimeException(e); } }); System.exit(0); } private static final TestIteration LONG_RANDOM_ACCESS = new TestIteration() { @Override public void doTest(int threadNumber, int[] ids, Random threadRandom, int queryCount) { final int blackHole = threadRandom.nextInt(); for (int j = 0; j < queryCount; j++) { final CharSequence name = FileNameCache.getVFileName(ids[threadRandom.nextInt(ids.length)]); if (blackHole == name.hashCode() && blackHole+1 == name.hashCode()) { failure(); } } } @Override public String toString() { return "random access"; } }; private static final TestIteration LONG_RANDOM_ACCESS_WITH_GET_PATH = new TestIteration() { @Override public void doTest(int threadNumber, int[] ids, Random threadRandom, int queryCount) { final int blackHole = threadRandom.nextInt(); for (int j = 0; j < queryCount; j++) { final int hash = getPath(threadRandom.nextInt(ids.length), ids); if (blackHole == hash) { failure(); } } } @Override public String toString() { return "random access + getPath"; } }; private static final TestIteration LINEAR_SCAN_AND_RANDOM_ACCESS_WITH_GET_PATH = new TestIteration() { @Override public void doTest(int threadNumber, int[] ids, Random threadRandom, int queryCount) { if (threadNumber % 2 == 1) { // linear scan for every second_case final int blackHole = threadRandom.nextInt(); int currentId = 0; for(int j = 0; j < queryCount; ++j) { final int hash = getPath(currentId++, ids); if (currentId == ids.length) currentId = 0; if (blackHole == hash) { failure(); } } return; } LONG_RANDOM_ACCESS_WITH_GET_PATH.doTest(threadNumber, ids, threadRandom, queryCount); } @Override public String toString() { return "linear scan + random access + getPath"; } }; private static void runTest(int nameCount, String name) throws InterruptedException, ExecutionException { System.out.println("----- " + name + " ------ name count: "+nameCount); TIntObjectHashMap<CharSequence> map = generateNames(nameCount); final int[] ids = map.keys(); checkNames(map, ids); warmUp(ids); measureAverageTime(ids, 1, LONG_RANDOM_ACCESS); measureAverageTime(ids, 4, LONG_RANDOM_ACCESS); measureAverageTime(ids, 1, LONG_RANDOM_ACCESS_WITH_GET_PATH); measureAverageTime(ids, 4, LONG_RANDOM_ACCESS_WITH_GET_PATH); measureAverageTime(ids, 1, LINEAR_SCAN_AND_RANDOM_ACCESS_WITH_GET_PATH); measureAverageTime(ids, 4, LINEAR_SCAN_AND_RANDOM_ACCESS_WITH_GET_PATH); } private static boolean warmedUp; private static void warmUp(int[] ids) throws InterruptedException, ExecutionException { if (warmedUp) return; System.out.println("Warming up"); for (int i = 0; i < 200000; i++) { runThreads(ids, 2, 1000, LONG_RANDOM_ACCESS); } Thread.sleep(10000); System.out.println("Warmup complete"); warmedUp = true; } private static int getPath(int id, int[] ids) { int result = 0; while (id > 0) { result += FileNameCache.getVFileName(ids[id]).hashCode(); id /= 10; } return result; } private static void measureAverageTime(int[] ids, int threadCount, TestIteration iteration) throws InterruptedException, ExecutionException { System.out.println("Running "+threadCount+" threads, using "+iteration); TLongArrayList times = new TLongArrayList(); for (int i = 0; i < 10; i++) { long time = runThreads(ids, threadCount, 2000000/*0*/, iteration); System.out.println(time); times.add(time); } times.sort(); long median = times.get(times.size() / 2); System.out.println("Median for " + threadCount + " threads: " + median+"ms"); System.out.println(); } private abstract static class TestIteration { abstract void doTest(int threadNumber, int[] ids, Random threadRandom, int queryCount); static void failure() { System.out.println("Failure"); assert false; } } private static long runThreads(final int[] ids, int threadCount, final int queryCount, final TestIteration testIteration) throws InterruptedException, ExecutionException { long start = System.currentTimeMillis(); List<Future<?>> futures = ContainerUtil.newArrayList(); Random seedRandom = new Random(); for (int i = 0; i < threadCount; i++) { final Random threadRandom = new Random(seedRandom.nextInt()); final int finalI = i; futures.add(ApplicationManager.getApplication().executeOnPooledThread( () -> testIteration.doTest(finalI, ids, threadRandom, queryCount))); } for (Future<?> future : futures) { future.get(); } return System.currentTimeMillis() - start; } private static void checkNames(TIntObjectHashMap<CharSequence> map, int[] ids) { for (int id : ids) { Assert.assertEquals(map.get(id), FileNameCache.getVFileName(id).toString()); } } @NotNull private static TIntObjectHashMap<CharSequence> generateNames(int nameCount) { Random random = new Random(); TIntObjectHashMap<CharSequence> map = new TIntObjectHashMap<>(); for (int i = 0; i < nameCount; i++) { String name = "some_name_" + random.nextInt() + StringUtil.repeat("a", random.nextInt(10)); int id = FileNameCache.storeName(name); map.put(id, name); } return map; } }