From 3a57ea79abb43deb8bf4647e719f13a16ffb7886 Mon Sep 17 00:00:00 2001 From: Patrick Koenemann Date: Wed, 5 Apr 2017 11:19:53 +0200 Subject: [PATCH] Bug 514708 - Performance improvement indexer by caching Strings. Change-Id: If07961701bd568f674918c484cad16699bfa1cdf Signed-off-by: Patrick Koenemann --- .../cdt/internal/core/pdom/db/Database.java | 63 +++++++++++++++++-- .../cdt/internal/core/pdom/db/LongString.java | 10 +++ .../internal/core/pdom/db/ShortString.java | 12 +++- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java index 8177c6b4b94..5c473cc974d 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java @@ -18,11 +18,16 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; import java.nio.ByteBuffer; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.core.runtime.CoreException; @@ -110,6 +115,22 @@ public class Database { private long cacheHits; private long cacheMisses; + /** Soft reference wrapper to keep track of the record for disposed strings. */ + private static class SoftStringRef extends SoftReference { + private final long record; + public SoftStringRef(IString referent, ReferenceQueue q) { + super(referent, q); + record = referent.getRecord(); + } + public long getRecord() { + return record; + } + } + + // a cache for strings which is used for btree lookups; soft refs ensure garbage collection + private final Map> stringCache = new HashMap<>(); + private final ReferenceQueue stringDisposal = new ReferenceQueue<>(); + /** * Construct a new Database object, creating a backing file if necessary. * @param location the local file path for the database @@ -243,6 +264,8 @@ public class Database { createNewChunks((int) setasideChunks); flush(); } + // clear cache for strings which are used for btree searches + clearStringCache(); } private void removeChunksFromCache() { @@ -463,6 +486,7 @@ public class Database { } addBlock(chunk, blocksize, block); freed += blocksize; + stringCache.remove(offset); // also remove record from string cache (if it exists) } public void putByte(long offset, byte value) throws CoreException { @@ -564,9 +588,9 @@ public class Database { } if (bytelen > ShortString.MAX_BYTE_LENGTH) { - return new LongString(this, chars, useBytes); + return addStringToCache(new LongString(this, chars, useBytes)); } else { - return new ShortString(this, chars, useBytes); + return addStringToCache(new ShortString(this, chars, useBytes)); } } @@ -579,12 +603,33 @@ public class Database { } public IString getString(long offset) throws CoreException { + final Reference cachedStringReference = stringCache.get(offset); + if (cachedStringReference != null) { + final IString cachedString = cachedStringReference.get(); + if (cachedString != null) { + return cachedString; // string already cached, no need to re-retrieve it :-) + } + } final int l = getInt(offset); int bytelen= l < 0 ? -l : 2 * l; if (bytelen > ShortString.MAX_BYTE_LENGTH) { - return new LongString(this, offset); + return addStringToCache(new LongString(this, offset)); } - return new ShortString(this, offset); + return addStringToCache(new ShortString(this, offset)); + } + + private IString addStringToCache(IString string) { + // add string to cache + stringCache.put(string.getRecord(), new SoftStringRef(string, stringDisposal)); + // also remove keys from cache list upon garbage collection + if (stringDisposal != null) { + Reference disposedRef = stringDisposal.poll(); + while (disposedRef instanceof SoftStringRef) { + stringCache.remove(((SoftStringRef) disposedRef).getRecord()); + disposedRef = stringDisposal.poll(); + } + } + return string; } /** @@ -629,6 +674,7 @@ public class Database { } catch (IOException e) { throw new CoreException(new DBStatus(e)); } + clearStringCache(); } /** @@ -731,6 +777,15 @@ public class Database { // Also handles header chunk. flushAndUnlockChunks(dirtyChunks, true); + + // And clear string cache + clearStringCache(); + } + + private void clearStringCache() { + stringCache.clear(); + while (stringDisposal.poll() != null) { + } } private void flushAndUnlockChunks(final ArrayList dirtyChunks, boolean isComplete) throws CoreException { diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/LongString.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/LongString.java index 996d5ee93af..596fbc92b94 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/LongString.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/LongString.java @@ -27,6 +27,9 @@ public class LongString implements IString { private final long record; private int hash; + // this string is immutable, so we can cache the actual char array + private char[] cachedChars; + // Additional fields of first record. private static final int LENGTH = 0; // Must be first to match ShortString. private static final int NEXT1 = 4; @@ -89,6 +92,9 @@ public class LongString implements IString { } else { chunk.putChars(nextRecord + CHARSN, chars, start, remaining); } + + // There is currently no need to store char[] in cachedChars because all + // callers are currently only interested in the record. } @Override @@ -98,6 +104,9 @@ public class LongString implements IString { @Override public char[] getChars() throws CoreException { + if (cachedChars != null) { + return cachedChars; // no need to re-retrieve array if it is already cached + } int length = db.getInt(record + LENGTH); final boolean useBytes = length < 0; int numChars1 = NUM_CHARS1; @@ -135,6 +144,7 @@ public class LongString implements IString { start += partLen; p= p + NEXTN; } + cachedChars = chars; // cache the array return chars; } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/ShortString.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/ShortString.java index f8d7626a1bf..45778bcd4eb 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/ShortString.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/ShortString.java @@ -25,7 +25,10 @@ public class ShortString implements IString { private final Database db; private final long record; private int hash; - + + // this string is immutable, so we can cache the actual char array + private char[] cachedChars; + private static final int LENGTH = 0; private static final int CHARS = 4; @@ -49,6 +52,9 @@ public class ShortString implements IString { } else { chunk.putChars(p, chars, 0, n); } + + // There is currently no need to store char[] in cachedChars because all + // callers are currently only interested in the record. } @Override @@ -63,6 +69,9 @@ public class ShortString implements IString { @Override public char[] getChars() throws CoreException { + if (cachedChars != null) { + return cachedChars; // no need to re-retrieve array if it is already cached + } final Chunk chunk = db.getChunk(record); final int l = chunk.getInt(record + LENGTH); final int length = Math.abs(l); @@ -72,6 +81,7 @@ public class ShortString implements IString { } else { chunk.getChars(record + CHARS, chars, 0, length); } + cachedChars = chars; // cache the array return chars; }