Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java (revision 1669328) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java (working copy) @@ -349,9 +349,19 @@ } private String readString(int offset) { + if (StringCache.ENABLED) { + String s = StringCache.getString(this, offset); + if (s != null) { + return s; + } + s = loadString(offset); + StringCache.addString(this, offset, s); + return s; + } String string = strings.get(offset); if (string == null) { string = loadString(offset); + StringCache.addString(this, offset, string); strings.putIfAbsent(offset, string); // only keep the first copy } return string; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/StringCache.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/StringCache.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/StringCache.java (working copy) @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.oak.plugins.segment; + +public class StringCache { + + public static final boolean ENABLED = Boolean.getBoolean("oak.SegmentStringCache"); + + /** + * The number of entries in the cache. Must be a power of 2. + */ + private static final int CACHE_SIZE = 16 * 1024; + + /** + * The maximum number of characters in string that are cached. + */ + private static final int MAX_STRING_SIZE = 128; + + private static final StringCacheEntry[] CACHE = new StringCacheEntry[CACHE_SIZE]; + + public static String getString(Segment segment, int offset) { + int index = getIndex(segment, offset); + StringCacheEntry e = CACHE[index]; + if (e != null && e.matches(segment, offset)) { + return e.string; + } + return null; + } + + public static void addString(Segment segment, int offset, String string) { + if (string.length() > MAX_STRING_SIZE) { + return; + } + int index = segment.getSegmentId().hashCode() ^ offset; + CACHE[index] = new StringCacheEntry(segment, offset, string); + } + + private static int getIndex(Segment segment, int offset) { + int hash = segment.getSegmentId().hashCode() ^ offset; + return hash & (CACHE_SIZE - 1); + } + + static class StringCacheEntry { + private final long msb, lsb; + private final int offset; + final String string; + + StringCacheEntry(Segment segment, int offset, String string) { + SegmentId id = segment.getSegmentId(); + this.msb = id.getMostSignificantBits(); + this.lsb = id.getLeastSignificantBits(); + this.offset = offset; + this.string = string; + } + + boolean matches(Segment segment, int offset) { + if (this.offset != offset) { + return false; + } + SegmentId id = segment.getSegmentId(); + return id.getMostSignificantBits() == msb && id.getLeastSignificantBits() == lsb; + } + + } + +}