Bug 569069 - [performance] Optimize HashtableOfPackage
Replace HashtableOfPackage with HashMap
HashtableOfPackage is a hotspot during compilation.
get() time is reduced from ~5ns to ~3ns for empty maps
get() time is reduced from ~14ns to less then ~12ns for non empty maps
at the cost of an additional Object allocation.
The CharArrayMap grants a smooth transition between empty map
performance and large map performance for >5 elements.
Bug: 569069
Change-Id: I45df9ee3118093e64a68aa3a547bd1a7764699db
Signed-off-by: jkubitz <jkubitz-eclipse@gmx.de>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.core/+/177529
Tested-by: JDT Bot <jdt-bot@eclipse.org>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/org.eclipse.jdt.compiler.apt/META-INF/MANIFEST.MF b/org.eclipse.jdt.compiler.apt/META-INF/MANIFEST.MF
index aea1867..cfc3b91 100644
--- a/org.eclipse.jdt.compiler.apt/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.compiler.apt/META-INF/MANIFEST.MF
@@ -5,7 +5,7 @@
Bundle-Version: 1.3.1400.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-Vendor: %providerName
-Fragment-Host: org.eclipse.jdt.core;bundle-version="[3.5.0,4.0.0)"
+Fragment-Host: org.eclipse.jdt.core;bundle-version="[3.26.100,4.0.0)"
Bundle-Localization: compiler_apt_fragment
Export-Package: org.eclipse.jdt.internal.compiler.apt.dispatch;x-friends:="org.eclipse.jdt.apt.pluggable.core",
org.eclipse.jdt.internal.compiler.apt.model;x-friends:="org.eclipse.jdt.apt.pluggable.core",
diff --git a/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF
index 2f4b668..b56847c 100644
--- a/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF
@@ -6,6 +6,7 @@
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Export-Package: org.eclipse.jdt.core.tests.compiler,
+ org.eclipse.jdt.core.tests.compiler.map,
org.eclipse.jdt.core.tests.compiler.parser,
org.eclipse.jdt.core.tests.compiler.regression,
org.eclipse.jdt.core.tests.dom,
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/map/CharArrayMapperTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/map/CharArrayMapperTest.java
new file mode 100644
index 0000000..d977b19
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/map/CharArrayMapperTest.java
@@ -0,0 +1,263 @@
+/*******************************************************************************
+ * Copyright (c) 2021 jkubitz and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * jkubitz - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.compiler.map;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import org.eclipse.jdt.core.tests.junit.extension.TestCase;
+import org.eclipse.jdt.internal.compiler.util.CharArrayHashMap;
+import org.eclipse.jdt.internal.compiler.util.CharArrayMap;
+import org.eclipse.jdt.internal.compiler.util.CharArrayMapper;
+import org.eclipse.jdt.internal.compiler.util.CharDelegateMap;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class CharArrayMapperTest extends TestCase {
+ public CharArrayMapperTest(String testName) {
+ super(testName);
+ }
+ public static Test suite() {
+
+ TestSuite suite = new TestSuite(CharArrayMapperTest.class.getPackageName());
+ suite.addTest(new TestSuite(CharArrayMapperTest.class));
+ return suite;
+ }
+ void testPutNew(CharArrayMapper<String> map, String keyString) {
+ int oldSize = map.size();
+ String value = "_" + keyString + "_";
+ String previous = map.put(keyString.toCharArray(), value);
+ int newSize = map.size();
+ assertEquals("testPutNew1(" + keyString + ")", null, previous);
+ String vinside = map.get(keyString.toCharArray());
+ assertEquals("testPutNew2(" + keyString + ")", value, vinside);
+ assertEquals("testPutNew3(" + keyString + ")", oldSize+1, newSize);
+ assertEquals("testPutNew4(" + keyString + ")", map.keys().size(), newSize);
+ assertEquals("testPutNew5(" + keyString + ")", map.values().size(), newSize);
+ }
+
+ void testPutExisting(CharArrayMapper<String> map, String keyString) {
+ int oldSize = map.size();
+ String value = "_new_" + keyString + "_";
+ String previous = map.get(keyString.toCharArray());
+ String returned = map.put(keyString.toCharArray(), value);
+ int newSize = map.size();
+ assertEquals("testPutExisting1(" + keyString + ")", previous, returned);
+ assertEquals("testPutNew3(" + keyString + ")", oldSize, newSize);
+ assertEquals("testPutNew4(" + keyString + ")", map.keys().size(), oldSize);
+ assertEquals("testPutNew5(" + keyString + ")", map.values().size(), oldSize);
+ map.put(keyString.toCharArray(), previous); // restore
+ }
+
+ void testGetExisting(CharArrayMapper<String> map, String keyString) {
+ String value = "_" + keyString + "_";
+ String vinside = map.get(keyString.toCharArray());
+ assertEquals("testGetExisting1(" + keyString + ")", value, vinside);
+ assertEquals("testGetExisting2(" + keyString + ")", true, map.values().contains(value));
+ assertEquals("testGetExisting3(" + keyString + ")", true,
+ map.keys().stream().anyMatch(k -> Arrays.equals(k, keyString.toCharArray())));
+ }
+
+ void testGetNonExisting(CharArrayMapper<String> map, String keyString) {
+ String value = "_" + keyString + "_";
+ String vinside = map.get(keyString.toCharArray());
+ assertEquals("testGetNonExisting1(" + keyString + ")", null, vinside);
+ assertEquals("testGetNonExisting1(" + keyString + ")", false, map.values().contains(value));
+ assertEquals("testGetNonExisting1(" + keyString + ")", false,
+ map.keys().stream().anyMatch(k -> Arrays.equals(k, keyString.toCharArray())));
+ }
+
+
+ void testIntList(CharArrayMapper<String> map) {
+ assertEquals("empty", Collections.emptyList(), map.keys());
+ int N = 100;
+ for (int i = 0; i < N; i++) {
+ testPutNew(map, "" + i);
+ testPutExisting(map, "" + i);
+ testGetExisting(map, "" + i);
+ }
+ for (int i = 0; i < N; i++) {
+ testGetExisting(map, "" + i);
+ }
+ for (int i = N + 1; i < N * 2; i++) {
+ testGetNonExisting(map, "" + i);
+ }
+ }
+
+ void testColliding(CharArrayMapper<String> map) {
+ assertEquals("empty", Collections.emptyList(), map.keys());
+ for (int[] collision : hashCollisions) {
+ int h1 = Arrays.hashCode(("" + collision[0]).toCharArray());
+ int h2 = Arrays.hashCode(("" + collision[1]).toCharArray());
+ assertEquals("collision(" + collision[0] + "<->" + collision[1] + ")", h1, h2);
+ for (int i : collision) {
+ testPutNew(map, "" + i);
+ testPutExisting(map, "" + i);
+ testGetExisting(map, "" + i);
+ }
+ }
+ for (int[] collision : hashCollisions) {
+ for (int i : collision) {
+ testGetExisting(map, "" + i);
+ }
+ }
+ int N = 100;
+ for (int i = N + 1; i < N * 2; i++) {
+ testGetNonExisting(map, "" + i);
+ }
+ }
+
+ public void testCharArrayHashMap() {
+ testIntList(new CharArrayHashMap<>(4));
+ testColliding(new CharArrayHashMap<>(4));
+ }
+
+ public void testCharArrayMap() {
+ testIntList(new CharArrayMap<>());
+ testColliding(new CharArrayMap<>());
+ }
+
+ public void testCharDelegateMap() {
+ testIntList(new CharDelegateMap<>());
+ testColliding(new CharDelegateMap<>());
+ }
+
+ static int[][] hashCollisions = { { 17510, 37760009 }, { 17520, 37760019 }, { 17530, 37760029 },
+ { 17540, 37760039 }, { 17550, 37760049 }, { 17560, 37760059 }, { 17570, 37760069 }, { 17580, 37760079 },
+ { 17590, 37760089 }, { 17610, 37760109 }, { 17620, 37760119 }, { 17630, 37760129 }, { 17640, 37760139 },
+ { 17650, 37760149 }, { 17660, 37760159 }, { 17670, 37760169 }, { 17680, 37760179 }, { 17690, 37760189 },
+ { 17710, 37760209 }, { 17720, 37760219 }, { 17730, 37760229 }, { 17740, 37760239 }, { 17750, 37760249 },
+ { 17760, 37760259 }, { 17770, 37760269 }, { 17780, 37760279 }, { 17790, 37760289 }, { 17810, 37760309 },
+ { 17820, 37760319 }, { 17830, 37760329 }, { 17840, 37760339 }, { 17850, 37760349 }, { 17860, 37760359 },
+ { 17870, 37760369 }, { 17880, 37760379 }, { 17890, 37760389 }, { 17910, 37760409 }, { 17920, 37760419 },
+ { 17930, 37760429 }, { 17940, 37760439 }, { 17950, 37760449 }, { 17960, 37760459 }, { 17970, 37760469 },
+ { 17980, 37760479 }, { 17990, 37760489 }, { 18510, 37761009 }, { 18520, 37761019 }, { 18530, 37761029 },
+ { 18540, 37761039 }, { 18550, 37761049 }, { 18560, 37761059 }, { 18570, 37761069 }, { 18580, 37761079 },
+ { 18590, 37761089 }, { 18610, 37761109 }, { 18620, 37761119 }, { 18630, 37761129 }, { 18640, 37761139 },
+ { 18650, 37761149 }, { 18660, 37761159 }, { 18670, 37761169 }, { 18680, 37761179 }, { 18690, 37761189 },
+ { 18710, 37761209 }, { 18720, 37761219 }, { 18730, 37761229 }, { 18740, 37761239 }, { 18750, 37761249 },
+ { 18760, 37761259 }, { 18770, 37761269 }, { 18780, 37761279 }, { 18790, 37761289 }, { 18810, 37761309 },
+ { 18820, 37761319 }, { 18830, 37761329 }, { 18840, 37761339 }, { 18850, 37761349 }, { 18860, 37761359 },
+ { 18870, 37761369 }, { 18880, 37761379 }, { 18890, 37761389 }, { 18910, 37761409 }, { 18920, 37761419 },
+ { 18930, 37761429 }, { 18940, 37761439 }, { 18950, 37761449 }, { 18960, 37761459 }, { 18970, 37761469 },
+ { 18980, 37761479 }, { 18990, 37761489 }, { 19510, 37762009 }, { 19520, 37762019 }, { 19530, 37762029 },
+ { 19540, 37762039 }, { 19550, 37762049 }, { 19560, 37762059 }, { 19570, 37762069 }, { 19580, 37762079 },
+ { 19590, 37762089 }, { 19610, 37762109 }, { 19620, 37762119 }, { 19630, 37762129 }, { 19640, 37762139 },
+ { 19650, 37762149 }, { 19660, 37762159 }, { 19670, 37762169 }, { 19680, 37762179 }, { 19690, 37762189 },
+ { 19710, 37762209 }, { 19720, 37762219 }, { 19730, 37762229 }, { 19740, 37762239 }, { 19750, 37762249 },
+ { 19760, 37762259 }, { 19770, 37762269 }, { 19780, 37762279 }, { 19790, 37762289 }, { 19810, 37762309 },
+ { 19820, 37762319 }, { 19830, 37762329 }, { 19840, 37762339 }, { 19850, 37762349 }, { 19860, 37762359 },
+ { 19870, 37762369 }, { 19880, 37762379 }, { 19890, 37762389 }, { 19910, 37762409 }, { 19920, 37762419 },
+ { 19930, 37762429 }, { 19940, 37762439 }, { 19950, 37762449 }, { 19960, 37762459 }, { 19970, 37762469 },
+ { 19980, 37762479 }, { 19990, 37762489 }, { 27510, 37770009 }, { 27520, 37770019 }, { 27530, 37770029 },
+ { 27540, 37770039 }, { 27550, 37770049 }, { 27560, 37770059 }, { 27570, 37770069 }, { 27580, 37770079 },
+ { 27590, 37770089 }, { 27610, 37770109 }, { 27620, 37770119 }, { 27630, 37770129 }, { 27640, 37770139 },
+ { 27650, 37770149 }, { 27660, 37770159 }, { 27670, 37770169 }, { 27680, 37770179 }, { 27690, 37770189 },
+ { 27710, 37770209 }, { 27720, 37770219 }, { 27730, 37770229 }, { 27740, 37770239 }, { 27750, 37770249 },
+ { 27760, 37770259 }, { 27770, 37770269 }, { 27780, 37770279 }, { 27790, 37770289 }, { 27810, 37770309 },
+ { 27820, 37770319 }, { 27830, 37770329 }, { 27840, 37770339 }, { 27850, 37770349 }, { 27860, 37770359 },
+ { 27870, 37770369 }, { 27880, 37770379 }, { 27890, 37770389 }, { 27910, 37770409 }, { 27920, 37770419 },
+ { 27930, 37770429 }, { 27940, 37770439 }, { 27950, 37770449 }, { 27960, 37770459 }, { 27970, 37770469 },
+ { 27980, 37770479 }, { 27990, 37770489 }, { 28510, 37771009 }, { 28520, 37771019 }, { 28530, 37771029 },
+ { 28540, 37771039 }, { 28550, 37771049 }, { 28560, 37771059 }, { 28570, 37771069 }, { 28580, 37771079 },
+ { 28590, 37771089 }, { 28610, 37771109 }, { 28620, 37771119 }, { 28630, 37771129 }, { 28640, 37771139 },
+ { 28650, 37771149 }, { 28660, 37771159 }, { 28670, 37771169 }, { 28680, 37771179 }, { 28690, 37771189 },
+ { 28710, 37771209 }, { 28720, 37771219 }, { 28730, 37771229 }, { 28740, 37771239 }, { 28750, 37771249 },
+ { 28760, 37771259 }, { 28770, 37771269 }, { 28780, 37771279 }, { 28790, 37771289 }, { 28810, 37771309 },
+ { 28820, 37771319 }, { 28830, 37771329 }, { 28840, 37771339 }, { 28850, 37771349 }, { 28860, 37771359 },
+ { 28870, 37771369 }, { 28880, 37771379 }, { 28890, 37771389 }, { 28910, 37771409 }, { 28920, 37771419 },
+ { 28930, 37771429 }, { 28940, 37771439 }, { 28950, 37771449 }, { 28960, 37771459 }, { 28970, 37771469 },
+ { 28980, 37771479 }, { 28990, 37771489 }, { 29510, 37772009 }, { 29520, 37772019 }, { 29530, 37772029 },
+ { 29540, 37772039 }, { 29550, 37772049 }, { 29560, 37772059 }, { 29570, 37772069 }, { 29580, 37772079 },
+ { 29590, 37772089 }, { 29610, 37772109 }, { 29620, 37772119 }, { 29630, 37772129 }, { 29640, 37772139 },
+ { 29650, 37772149 }, { 29660, 37772159 }, { 29670, 37772169 }, { 29680, 37772179 }, { 29690, 37772189 },
+ { 29710, 37772209 }, { 29720, 37772219 }, { 29730, 37772229 }, { 29740, 37772239 }, { 29750, 37772249 },
+ { 29760, 37772259 }, { 29770, 37772269 }, { 29780, 37772279 }, { 29790, 37772289 }, { 29810, 37772309 },
+ { 29820, 37772319 }, { 29830, 37772329 }, { 29840, 37772339 }, { 29850, 37772349 }, { 29860, 37772359 },
+ { 29870, 37772369 }, { 29880, 37772379 }, { 29890, 37772389 }, { 29910, 37772409 }, { 29920, 37772419 },
+ { 29930, 37772429 }, { 29940, 37772439 }, { 29950, 37772449 }, { 29960, 37772459 }, { 29970, 37772469 },
+ { 29980, 37772479 }, { 29990, 37772489 }, { 37510, 37780009 }, { 37520, 37780019 }, { 37530, 37780029 },
+ { 37540, 37780039 }, { 37550, 37780049 }, { 37560, 37780059 }, { 37570, 37780069 }, { 37580, 37780079 },
+ { 37590, 37780089 }, { 37610, 37780109 }, { 37620, 37780119 }, { 37630, 37780129 }, { 37640, 37780139 },
+ { 37650, 37780149 }, { 37660, 37780159 }, { 37670, 37780169 }, { 37680, 37780179 }, { 37690, 37780189 },
+ { 37710, 37780209 }, { 37720, 37780219 }, { 37730, 37780229 }, { 37740, 37780239 }, { 37750, 37780249 }, { 37760, 37780259 }, { 37770, 37780269 }, { 37780, 37780279 }, { 37790, 37780289 }, { 37810, 37780309 },
+ { 37820, 37780319 }, { 37830, 37780329 }, { 37840, 37780339 }, { 37850, 37780349 }, { 37860, 37780359 },
+ { 37870, 37780369 }, { 37880, 37780379 }, { 37890, 37780389 }, { 37910, 37780409 }, { 37920, 37780419 },
+ { 37930, 37780429 }, { 37940, 37780439 }, { 37950, 37780449 }, { 37960, 37780459 }, { 37970, 37780469 },
+ { 37980, 37780479 }, { 37990, 37780489 }, { 38510, 37781009 }, { 38520, 37781019 }, { 38530, 37781029 },
+ { 38540, 37781039 }, { 38550, 37781049 }, { 38560, 37781059 }, { 38570, 37781069 }, { 38580, 37781079 },
+ { 38590, 37781089 }, { 38610, 37781109 }, { 38620, 37781119 }, { 38630, 37781129 }, { 38640, 37781139 },
+ { 38650, 37781149 }, { 38660, 37781159 }, { 38670, 37781169 }, { 38680, 37781179 }, { 38690, 37781189 },
+ { 38710, 37781209 }, { 38720, 37781219 }, { 38730, 37781229 }, { 38740, 37781239 }, { 38750, 37781249 },
+ { 38760, 37781259 }, { 38770, 37781269 }, { 38780, 37781279 }, { 38790, 37781289 }, { 38810, 37781309 },
+ { 38820, 37781319 }, { 38830, 37781329 }, { 38840, 37781339 }, { 38850, 37781349 }, { 38860, 37781359 },
+ { 38870, 37781369 }, { 38880, 37781379 }, { 38890, 37781389 }, { 38910, 37781409 }, { 38920, 37781419 },
+ { 38930, 37781429 }, { 38940, 37781439 }, { 38950, 37781449 }, { 38960, 37781459 }, { 38970, 37781469 },
+ { 38980, 37781479 }, { 38990, 37781489 }, { 39510, 37782009 }, { 39520, 37782019 }, { 39530, 37782029 },
+ { 39540, 37782039 }, { 39550, 37782049 }, { 39560, 37782059 }, { 39570, 37782069 }, { 39580, 37782079 },
+ { 39590, 37782089 }, { 39610, 37782109 }, { 39620, 37782119 }, { 39630, 37782129 }, { 39640, 37782139 },
+ { 39650, 37782149 }, { 39660, 37782159 }, { 39670, 37782169 }, { 39680, 37782179 }, { 39690, 37782189 },
+ { 39710, 37782209 }, { 39720, 37782219 }, { 39730, 37782229 }, { 39740, 37782239 }, { 39750, 37782249 },
+ { 39760, 37782259 }, { 39770, 37782269 }, { 39780, 37782279 }, { 39790, 37782289 }, { 39810, 37782309 },
+ { 39820, 37782319 }, { 39830, 37782329 }, { 39840, 37782339 }, { 39850, 37782349 }, { 39860, 37782359 },
+ { 39870, 37782369 }, { 39880, 37782379 }, { 39890, 37782389 }, { 39910, 37782409 }, { 39920, 37782419 },
+ { 39930, 37782429 }, { 39940, 37782439 }, { 39950, 37782449 }, { 39960, 37782459 }, { 39970, 37782469 },
+ { 39980, 37782479 }, { 39990, 37782489 }, { 47510, 37790009 }, { 47520, 37790019 }, { 47530, 37790029 },
+ { 47540, 37790039 }, { 47550, 37790049 }, { 47560, 37790059 }, { 47570, 37790069 }, { 47580, 37790079 },
+ { 47590, 37790089 }, { 47610, 37790109 }, { 47620, 37790119 }, { 47630, 37790129 }, { 47640, 37790139 },
+ { 47650, 37790149 }, { 47660, 37790159 }, { 47670, 37790169 }, { 47680, 37790179 }, { 47690, 37790189 },
+ { 47710, 37790209 }, { 47720, 37790219 }, { 47730, 37790229 }, { 47740, 37790239 }, { 47750, 37790249 },
+ { 47760, 37790259 }, { 47770, 37790269 }, { 47780, 37790279 }, { 47790, 37790289 }, { 47810, 37790309 },
+ { 47820, 37790319 }, { 47830, 37790329 }, { 47840, 37790339 }, { 47850, 37790349 }, { 47860, 37790359 },
+ { 47870, 37790369 }, { 47880, 37790379 }, { 47890, 37790389 }, { 47910, 37790409 }, { 47920, 37790419 },
+ { 47930, 37790429 }, { 47940, 37790439 }, { 47950, 37790449 }, { 47960, 37790459 }, { 47970, 37790469 },
+ { 47980, 37790479 }, { 47990, 37790489 }, { 48510, 37791009 }, { 48520, 37791019 }, { 48530, 37791029 },
+ { 48540, 37791039 }, { 48550, 37791049 }, { 48560, 37791059 }, { 48570, 37791069 }, { 48580, 37791079 },
+ { 48590, 37791089 }, { 48610, 37791109 }, { 48620, 37791119 }, { 48630, 37791129 }, { 48640, 37791139 },
+ { 48650, 37791149 }, { 48660, 37791159 }, { 48670, 37791169 }, { 48680, 37791179 }, { 48690, 37791189 },
+ { 48710, 37791209 }, { 48720, 37791219 }, { 48730, 37791229 }, { 48740, 37791239 }, { 48750, 37791249 },
+ { 48760, 37791259 }, { 48770, 37791269 }, { 48780, 37791279 }, { 48790, 37791289 }, { 48810, 37791309 },
+ { 48820, 37791319 }, { 48830, 37791329 }, { 48840, 37791339 }, { 48850, 37791349 }, { 48860, 37791359 },
+ { 48870, 37791369 }, { 48880, 37791379 }, { 48890, 37791389 }, { 48910, 37791409 }, { 48920, 37791419 },
+ { 48930, 37791429 }, { 48940, 37791439 }, { 48950, 37791449 }, { 48960, 37791459 }, { 48970, 37791469 },
+ { 48980, 37791479 }, { 48990, 37791489 }, { 49510, 37792009 }, { 49520, 37792019 }, { 49530, 37792029 },
+ { 49540, 37792039 }, { 49550, 37792049 }, { 49560, 37792059 }, { 49570, 37792069 }, { 49580, 37792079 },
+ { 49590, 37792089 }, { 49610, 37792109 }, { 49620, 37792119 }, { 49630, 37792129 }, { 49640, 37792139 },
+ { 49650, 37792149 }, { 49660, 37792159 }, { 49670, 37792169 }, { 49680, 37792179 }, { 49690, 37792189 },
+ { 49710, 37792209 }, { 49720, 37792219 }, { 49730, 37792229 }, { 49740, 37792239 }, { 49750, 37792249 },
+ { 49760, 37792259 }, { 49770, 37792269 }, { 49780, 37792279 }, { 49790, 37792289 }, { 49810, 37792309 },
+ { 49820, 37792319 }, { 49830, 37792329 }, { 49840, 37792339 }, { 49850, 37792349 }, { 49860, 37792359 },
+ { 49870, 37792369 }, { 49880, 37792379 }, { 49890, 37792389 }, { 49910, 37792409 }, { 49920, 37792419 },
+ { 49930, 37792429 }, { 49940, 37792439 }, { 49950, 37792449 }, { 49960, 37792459 }, { 49970, 37792469 },
+ { 49980, 37792479 }, { 49990, 37792489 } };
+
+ /* calculate hashCollisions used above: */
+ public static void main(String[] args) {
+ int N = 100000000;
+ HashMap<Integer, List<Integer>> hashCollisions_ = new HashMap<>();
+ for (int i = 0; i < N; i++) {
+ char[] key = ("" + i).toCharArray();
+ Integer hashCode = Arrays.hashCode(key);
+ List<Integer> l = hashCollisions_.computeIfAbsent(hashCode, h -> new ArrayList<>());
+ l.add(i);
+ if (l.size() > 1) {
+ System.out.println(l.size() + " collissions:" + hashCode + "->" + l);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
index ddc31e0..9ec473b 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
@@ -18,6 +18,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import org.eclipse.jdt.core.tests.compiler.map.CharArrayMapperTest;
import org.eclipse.jdt.core.tests.junit.extension.TestCase;
import junit.framework.Test;
@@ -224,6 +225,8 @@
NullAnnotationModelTests9.class,
JavaModelManagerTests.class,
+
+ CharArrayMapperTest.class,
};
Class[] deprecatedClasses = getDeprecatedJDOMTestClasses();
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
index 7b7dd32..d88fc79 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
@@ -85,7 +85,7 @@
public ModuleBinding module;
public PlainPackageBinding defaultPackage;
/** All visible toplevel packages, i.e. observable packages associated with modules read by the current module. */
- HashtableOfPackage knownPackages;
+ HashtableOfPackage<PackageBinding> knownPackages;
private int lastCompletedUnitIndex = -1; // ROOT_ONLY
private int lastUnitIndex = -1; // ROOT_ONLY
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArray.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArray.java
new file mode 100644
index 0000000..f6ac05e
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArray.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2021 jkubitz and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Gunnar Wagenknecht, jkubitz - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.util;
+
+import java.util.Arrays;
+
+/**
+ * Wrapper around char arrays that can be used as a key in a Map or Set.
+ * <p>
+ * The {@link #hashCode()} and {@link #equals(Object)} method will work with the underlying array using
+ * <code>Arrays.hashCode</code> and <code>Arrays.equals</code>.
+ * </p>
+ */
+final class CharArray implements Comparable<CharArray> {
+ private final char[] key;
+
+ public CharArray(char[] key) {
+ this.key = key;
+ }
+
+ @Override
+ public int compareTo(CharArray o) {
+ return compare(this.key, o.key);
+ }
+
+ public char[] getKey() {
+ return this.key;
+ }
+
+ // Can be replaced with Arrays.compare once JDT core moves to BREE >= 9
+ private static int compare(char[] a, char[] b) {
+ if (a == b) {
+ return 0;
+ }
+ if (a == null || b == null) {
+ return a == null ? -1 : 1;
+ }
+ int shortestLenght = Math.min(a.length, b.length);
+ for (int i = 0; i < shortestLenght; i++) {
+ if (a[i] != b[i]) {
+ return Character.compare(a[i], b[i]);
+ }
+ }
+
+ return a.length - b.length;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CharArray)) {
+ return false;
+ }
+ CharArray other = (CharArray) obj;
+ return Arrays.equals(this.key, other.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(this.key);
+ }
+
+ /**
+ * @return <code>Arrays.toString</code> of the underlying array
+ */
+ @Override
+ public String toString() {
+ return Arrays.toString(this.key);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayHashMap.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayHashMap.java
new file mode 100644
index 0000000..da07e0e
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayHashMap.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2021 jkubitz and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * jkubitz - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.stream.Collectors;
+
+public final class CharArrayHashMap<V> implements CharArrayMapper<V>, Serializable {
+ private static final long serialVersionUID = -4247853285180184851L;
+
+ // Constructing the intermediate CharArray is little overhead on each get().
+ // Would be slightly better to have a HashMap specialization to char[] keys.
+ // However this implementation is only used for large maps where algorithmic overhead dominates.
+ private final HashMap<CharArray, V> map;
+
+ public CharArrayHashMap(int initialCapacity) {
+ this.map = new HashMap<>(initialCapacity);
+ }
+
+ @Override
+ public Collection<V> values() {
+ return new ArrayList<>(this.map.values());
+ }
+
+ @Override
+ public Collection<char[]> keys() {
+ return this.map.keySet().stream().map(s -> s.getKey()).collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean containsKey(char[] key) {
+ return this.map.containsKey(new CharArray(key));
+ }
+
+ @Override
+ public V get(char[] key) {
+ return this.map.get(new CharArray(key));
+ }
+
+ @Override
+ public V put(char[] key, V value) {
+ return this.map.put(new CharArray(key), value);
+ }
+
+ @Override
+ public int size() {
+ return this.map.size();
+ }
+
+ @Override
+ public String toString() {
+ return CharArrayMapper.toString(this);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayMap.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayMap.java
new file mode 100644
index 0000000..1b196fc
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayMap.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2021 jkubitz and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * jkubitz - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * This map avoids hashing. This is suitable for few elements or long char arrays because it uses vectorized equals.
+ * Vectorized equals is several times faster then calculating hashCode in a loop. This class is not thread safe and
+ * callers are responsible for thread safety.
+ *
+ * @author jkubitz
+ */
+public final class CharArrayMap<P> implements CharArrayMapper<P> {
+ private char[] keyTable[];
+ private P valueTable[];
+
+ /**
+ * The number of key-value mappings contained in this map.
+ */
+ private int size;
+
+ public CharArrayMap() {
+ this(0); // usually not very large
+ }
+
+ public CharArrayMap(int estimatedSize) {
+ int capacity = estimatedSize > 0 ? estimatedSize : 0;
+ this.size = 0;
+ this.keyTable = new char[capacity][];
+ @SuppressWarnings("unchecked")
+ P[] x = (P[]) new Object[capacity];
+ this.valueTable = x;
+ }
+
+ @Override
+ public Collection<P> values() {
+ return Arrays.stream(this.valueTable).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
+ @Override
+ public Collection<char[]> keys() {
+ return Arrays.stream(this.keyTable).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean containsKey(char[] key) {
+ for (int i = 0; i < this.size; i++) {
+ if (Arrays.equals(this.keyTable[i], key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public P get(char[] key) {
+ for (int i = 0; i < this.size; i++) {
+ if (Arrays.equals(this.keyTable[i], key)) {
+ return this.valueTable[i];
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public P put(char[] key, P value) {
+ int i = 0;
+ for (; i < this.size; i++) {
+ if (Arrays.equals(this.keyTable[i], key)) {
+ P previous = this.valueTable[i];
+ this.valueTable[i] = value;
+ return previous;
+ }
+ }
+
+ if (i >= this.keyTable.length) {
+ grow();
+ }
+ this.keyTable[i] = key;
+ this.valueTable[i] = value;
+ this.size++;
+ // assumes the threshold is never equal to the size of the table
+ return null;
+ }
+
+ void transferTo(CharArrayMapper<P> bigMap) {
+ for (int i = 0; i < this.size; i++) {
+ if (this.keyTable[i] != null) {
+ bigMap.put(this.keyTable[i], this.valueTable[i]);
+ }
+ }
+ }
+
+ private void grow() {
+ int capacity = this.keyTable.length > 1 ? this.keyTable.length : 1;
+ int newCapacity = capacity * 2;
+ this.keyTable = Arrays.copyOfRange(this.keyTable, 0, newCapacity);
+ this.valueTable = Arrays.copyOfRange(this.valueTable, 0, newCapacity);
+ }
+
+ @Override
+ public int size() {
+ return this.size;
+ }
+
+ @Override
+ public String toString() {
+ return CharArrayMapper.toString(this);
+ }
+}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayMapper.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayMapper.java
new file mode 100644
index 0000000..e1d50af
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharArrayMapper.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2021 jkubitz and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * jkubitz - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.util;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+public interface CharArrayMapper<V> extends Cloneable {
+
+ public boolean containsKey(char[] key);
+
+ public V get(char[] key);
+
+ /** @return the previous value **/
+ public V put(char[] key, V value);
+
+ /** @return the number of keys **/
+ public int size();
+
+ /**
+ * Returns a copied collection of values.
+ *
+ * @return all values in undefined order. The order is not guaranteed to be stable.
+ **/
+ public Collection<V> values();
+
+ /**
+ * Returns a copied collection of keys.
+ *
+ * @return all keys in undefined order. The order is not guaranteed to be stable.
+ **/
+ public Collection<char[]> keys();
+
+ public static <V> String toString(CharArrayMapper<V> map) {
+ return map.keys().stream().map(k -> new String(k) + "->" + map.get(k)) //$NON-NLS-1$
+ .collect(Collectors.joining("\n")); //$NON-NLS-1$
+ }
+
+}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharDelegateMap.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharDelegateMap.java
new file mode 100644
index 0000000..d84a9a0
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CharDelegateMap.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) 2021 jkubitz and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * jkubitz - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.util;
+
+import java.util.Collection;
+
+/**
+ * This map uses a specialized implementation for few elements. Because compiler lookups are typically small maps.
+ *
+ * @author jkubitz
+ */
+public class CharDelegateMap<P> implements CharArrayMapper<P> {
+ // Threshold was found during micro benchmarks. Its not very sensitive though.
+ // Note the exact value would also depenend on the average key length since
+ // bigDelegate hashes the whole key while smallDelegate only compares the chars until mismatch!
+ private static final int SMAL_BIG_THRESHOLD = 5;
+
+ // Instead of having a single CharArrayMapper<P> delegate
+ // it showed to be a little bit faster to use two delegates to avoid polymorphic calls.
+ // Only one of both will be not null:
+ CharArrayMap<P> smallDelegate; // for few elements - avoiding hashing
+ CharArrayHashMap<P> bigDelegate; // for many elements
+
+ public CharDelegateMap() {
+ this(0); // usually not very large
+ }
+
+ private CharArrayMapper<P> getDelegate() {
+ return this.smallDelegate == null ? this.bigDelegate : this.smallDelegate;
+ }
+
+ public CharDelegateMap(int estimatedSize) {
+ if (estimatedSize > SMAL_BIG_THRESHOLD) {
+ this.bigDelegate = new CharArrayHashMap<>(estimatedSize);
+ } else {
+ this.smallDelegate = new CharArrayMap<>(estimatedSize);
+ }
+ }
+
+ @Override
+ public Collection<P> values() {
+ return getDelegate().values();
+ }
+
+ @Override
+ public boolean containsKey(char[] key) {
+ return getDelegate().containsKey(key);
+ }
+
+ @Override
+ public P get(char[] key) {
+ if (this.smallDelegate != null) {
+ return this.smallDelegate.get(key);
+ }
+ return this.bigDelegate.get(key);
+ }
+
+ @Override
+ public P put(char[] key, P value) {
+ if (this.smallDelegate != null) {
+ P v = this.smallDelegate.put(key, value);
+ if (this.smallDelegate.size() > SMAL_BIG_THRESHOLD) {
+ toBigMap();
+ }
+ return v;
+ } else {
+ return this.bigDelegate.put(key, value);
+ }
+ }
+
+ private void toBigMap() {
+ this.bigDelegate = new CharArrayHashMap<>(this.smallDelegate.size());
+ this.smallDelegate.transferTo(this.bigDelegate);
+ this.smallDelegate = null;
+ }
+
+ @Override
+ public int size() {
+ return getDelegate().size();
+ }
+
+ @Override
+ public String toString() {
+ return CharArrayMapper.toString(this);
+ }
+
+ @Override
+ public Collection<char[]> keys() {
+ return getDelegate().keys();
+ }
+}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/HashtableOfPackage.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/HashtableOfPackage.java
index abdfce1..32e7e8b 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/HashtableOfPackage.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/HashtableOfPackage.java
@@ -13,110 +13,22 @@
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.util;
-import java.util.Arrays;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
-public final class HashtableOfPackage<P extends PackageBinding> {
- // to avoid using Enumerations, walk the individual tables skipping nulls
- public char[] keyTable[];
- private PackageBinding valueTable[];
-
- public int elementSize; // number of elements in the table
- int threshold;
-public HashtableOfPackage() {
- this(3); // usually not very large
-}
-public HashtableOfPackage(int size) {
- this.elementSize = 0;
- this.threshold = size; // size represents the expected number of elements
- int extraRoom = (int) (size * 1.75f);
- if (this.threshold == extraRoom)
- extraRoom++;
- this.keyTable = new char[extraRoom][];
- this.valueTable = new PackageBinding[extraRoom];
-}
-public Iterable<P> values() {
- return Arrays.stream(this.valueTable)
- .filter(Objects::nonNull)
- .map(p -> { @SuppressWarnings("unchecked") P theP = (P)p; return theP; })
- .collect(Collectors.toList());
-}
-public boolean containsKey(char[] key) {
- int length = this.keyTable.length,
- index = CharOperation.hashCode(key) % length;
- int keyLength = key.length;
- char[] currentKey;
- while ((currentKey = this.keyTable[index]) != null) {
- if (currentKey.length == keyLength && CharOperation.equals(currentKey, key))
- return true;
- if (++index == length) {
- index = 0;
- }
+public final class HashtableOfPackage<P extends PackageBinding> extends CharDelegateMap<P> {
+ public HashtableOfPackage() {
+ // usually not very large:
+ this(0); // Size 0 maps have faster get() because no hashing needed at all.
+ // CAUTION THIS VALUES DEPEND ON THE WORKSPACE:
+ // Statistics from a (!) large workspace:
+ // ~100 times more get() then put() operations.
+ // ~50% of all maps have 0 elements when finalized.
+ // average size: 3 elements
+ // average size of non empty: 5 elements
+ // average key length: 8 chars
}
- return false;
-}
-public P get(char[] key) {
- int length = this.keyTable.length,
- index = CharOperation.hashCode(key) % length;
- int keyLength = key.length;
- char[] currentKey;
- while ((currentKey = this.keyTable[index]) != null) {
- if (currentKey.length == keyLength && CharOperation.equals(currentKey, key)) {
- @SuppressWarnings("unchecked")
- P p = (P) this.valueTable[index];
- return p;
- }
- if (++index == length) {
- index = 0;
- }
- }
- return null;
-}
-public PackageBinding put(char[] key, PackageBinding value) {
- int length = this.keyTable.length,
- index = CharOperation.hashCode(key) % length;
- int keyLength = key.length;
- char[] currentKey;
- while ((currentKey = this.keyTable[index]) != null) {
- if (currentKey.length == keyLength && CharOperation.equals(currentKey, key))
- return this.valueTable[index] = value;
- if (++index == length) {
- index = 0;
- }
- }
- this.keyTable[index] = key;
- this.valueTable[index] = value;
- // assumes the threshold is never equal to the size of the table
- if (++this.elementSize > this.threshold)
- rehash();
- return value;
-}
-private void rehash() {
- HashtableOfPackage<P> newHashtable = new HashtableOfPackage<P>(this.elementSize * 2); // double the number of expected elements
- char[] currentKey;
- for (int i = this.keyTable.length; --i >= 0;)
- if ((currentKey = this.keyTable[i]) != null)
- newHashtable.put(currentKey, this.valueTable[i]);
-
- this.keyTable = newHashtable.keyTable;
- this.valueTable = newHashtable.valueTable;
- this.threshold = newHashtable.threshold;
-}
-public int size() {
- return this.elementSize;
-}
-@Override
-public String toString() {
- String s = ""; //$NON-NLS-1$
- PackageBinding pkg;
- for (int i = 0, length = this.valueTable.length; i < length; i++)
- if ((pkg = this.valueTable[i]) != null)
- s += pkg.toString() + "\n"; //$NON-NLS-1$
- return s;
-}
+ public HashtableOfPackage(int size) {
+ super(size);
+ }
}