From f291e8bfefbfd1663f98abe456febfa034e858c7 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 16 Jun 2014 20:21:48 +0200 Subject: [PATCH] reuse common user data maps in VFS to save memory --- .../vfs/newvfs/impl/UserDataInterner.java | 43 ++++++++++++++++ .../vfs/newvfs/impl/VirtualDirectoryImpl.java | 2 +- .../vfs/newvfs/impl/VirtualFileImpl.java | 2 +- .../util/keyFMap/ArrayBackedFMap.java | 2 +- .../com/intellij/util/keyFMap/EmptyFMap.java | 2 +- .../intellij/util/keyFMap/OneElementFMap.java | 50 ++++++++++++++----- .../util/keyFMap/PairElementsFMap.java | 23 ++++----- 7 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java new file mode 100644 index 000000000000..f8da08d33198 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 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.util.ConcurrencyUtil; +import com.intellij.util.containers.ConcurrentWeakHashMap; +import com.intellij.util.keyFMap.KeyFMap; +import com.intellij.util.keyFMap.OneElementFMap; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.Charset; + +/** + * @author peter + */ +class UserDataInterner { + private static final ConcurrentWeakHashMap ourCache = new ConcurrentWeakHashMap(); + + static KeyFMap internUserData(@NotNull KeyFMap map) { + if (map instanceof OneElementFMap && shouldIntern((OneElementFMap)map)) { + return ConcurrencyUtil.cacheOrGet(ourCache, (OneElementFMap)map, (OneElementFMap)map); + } + return map; + } + + private static boolean shouldIntern(OneElementFMap map) { + Object value = map.getValue(); + return value instanceof Enum || value instanceof Boolean || value instanceof Charset; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java index f80900164821..47ef3345885a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java @@ -536,7 +536,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { @Override protected boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) { - return myData.changeUserMap(oldMap, newMap); + return myData.changeUserMap(oldMap, UserDataInterner.internUserData(newMap)); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java index 841390c7213a..5175a5875dbf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java @@ -142,7 +142,7 @@ public class VirtualFileImpl extends VirtualFileSystemEntry { @Override protected boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) { - return mySegment.changeUserMap(Math.abs(getId()), oldMap, newMap); + return mySegment.changeUserMap(Math.abs(getId()), oldMap, UserDataInterner.internUserData(newMap)); } } diff --git a/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java b/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java index 0f408fa8dd8a..0e3ef835b21b 100644 --- a/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java @@ -78,7 +78,7 @@ class ArrayBackedFMap implements KeyFMap { if (oldSize == 3) { int i1 = (2-i)/2; int i2 = 3 - (i+2)/2; - return new PairElementsFMap(keys[i1], values[i1], keys[i2], values[i2]); + return new PairElementsFMap(Key.getKeyByIndex(keys[i1]), values[i1], Key.getKeyByIndex(keys[i2]), values[i2]); } int newSize = oldSize - 1; int[] newKeys = new int[newSize]; diff --git a/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java b/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java index d682bcb4dd46..a51068c18abd 100644 --- a/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java @@ -25,7 +25,7 @@ class EmptyFMap implements KeyFMap { @NotNull @Override public KeyFMap plus(@NotNull Key key, @NotNull V value) { - return new OneElementFMap(key.hashCode(), value); + return new OneElementFMap(key, value); } @NotNull diff --git a/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java b/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java index 76488c68c979..d57d6903fd08 100644 --- a/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java @@ -18,45 +18,69 @@ package com.intellij.util.keyFMap; import com.intellij.openapi.util.Key; import org.jetbrains.annotations.NotNull; -class OneElementFMap implements KeyFMap { - private final int myKeyCode; +public class OneElementFMap implements KeyFMap { + private final Key myKey; private final V myValue; - OneElementFMap(int keyCode, @NotNull V value) { - myKeyCode = keyCode; + public OneElementFMap(Key key, @NotNull V value) { + myKey = key; myValue = value; } @NotNull @Override public KeyFMap plus(@NotNull Key key, @NotNull V value) { - int keyCode = key.hashCode(); - if (myKeyCode == keyCode) return new OneElementFMap(keyCode, value); - return new PairElementsFMap(myKeyCode, myValue, keyCode, value); + if (myKey == key) return new OneElementFMap(key, value); + return new PairElementsFMap(myKey, myValue, key, value); } @NotNull @Override public KeyFMap minus(@NotNull Key key) { - if (key.hashCode() == myKeyCode) { - return KeyFMap.EMPTY_MAP; - } - return this; + return key == myKey ? KeyFMap.EMPTY_MAP : this; } @Override public V get(@NotNull Key key) { //noinspection unchecked - return myKeyCode == key.hashCode() ? (V)myValue : null; + return myKey == key ? (V)myValue : null; } @Override public String toString() { - return "<"+Key.getKeyByIndex(myKeyCode) + " -> " + myValue+">"; + return "<" + myKey + " -> " + myValue+">"; } @Override public boolean isEmpty() { return false; } + + public Key getKey() { + return myKey; + } + + public V getValue() { + return myValue; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OneElementFMap)) return false; + + OneElementFMap map = (OneElementFMap)o; + + if (!myKey.equals(map.myKey)) return false; + if (!myValue.equals(map.myValue)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myKey.hashCode(); + result = 31 * result + myValue.hashCode(); + return result; + } } diff --git a/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java b/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java index 35ea8b957e36..800032e50456 100644 --- a/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java @@ -19,12 +19,12 @@ import com.intellij.openapi.util.Key; import org.jetbrains.annotations.NotNull; class PairElementsFMap implements KeyFMap { - private final int key1; - private final int key2; + private final Key key1; + private final Key key2; private final Object value1; private final Object value2; - PairElementsFMap(int key1, @NotNull Object value1, int key2, @NotNull Object value2) { + PairElementsFMap(Key key1, @NotNull Object value1, Key key2, @NotNull Object value2) { this.key1 = key1; this.value1 = value1; this.key2 = key2; @@ -35,31 +35,28 @@ class PairElementsFMap implements KeyFMap { @NotNull @Override public KeyFMap plus(@NotNull Key key, @NotNull V value) { - int keyCode = key.hashCode(); - if (keyCode == key1) return new PairElementsFMap(keyCode, value, key2, value2); - if (keyCode == key2) return new PairElementsFMap(keyCode, value, key1, value1); - return new ArrayBackedFMap(new int[]{key1, key2, keyCode}, new Object[]{value1, value2, value}); + if (key == key1) return new PairElementsFMap(key, value, key2, value2); + if (key == key2) return new PairElementsFMap(key, value, key1, value1); + return new ArrayBackedFMap(new int[]{key1.hashCode(), key2.hashCode(), key.hashCode()}, new Object[]{value1, value2, value}); } @NotNull @Override public KeyFMap minus(@NotNull Key key) { - int keyCode = key.hashCode(); - if (keyCode == key1) return new OneElementFMap(key2, value2); - if (keyCode == key2) return new OneElementFMap(key1, value1); + if (key == key1) return new OneElementFMap(key2, value2); + if (key == key2) return new OneElementFMap(key1, value1); return this; } @Override public V get(@NotNull Key key) { - int keyCode = key.hashCode(); //noinspection unchecked - return keyCode == key1 ? (V)value1 : keyCode == key2 ? (V)value2 : null; + return key == key1 ? (V)value1 : key == key2 ? (V)value2 : null; } @Override public String toString() { - return "Pair: ("+ Key.getKeyByIndex(key1) + " -> " + value1+"; "+Key.getKeyByIndex(key2) + " -> " + value2 + ")"; + return "Pair: (" + key1 + " -> " + value1 + "; " + key2 + " -> " + value2 + ")"; } @Override