| package org.apache.lucene.facet; |
| |
| /* |
| * 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. |
| */ |
| |
| import java.io.IOException; |
| import java.util.Map; |
| import java.util.WeakHashMap; |
| |
| import org.apache.lucene.codecs.DocValuesFormat; |
| import org.apache.lucene.index.AtomicReaderContext; |
| import org.apache.lucene.index.BinaryDocValues; |
| import org.apache.lucene.util.ArrayUtil; |
| import org.apache.lucene.util.IntsRef; |
| import org.apache.lucene.util.RamUsageEstimator; |
| |
| /** |
| * A per-segment cache of documents' facet ordinals. Every |
| * {@link CachedOrds} holds the ordinals in a raw {@code |
| * int[]}, and therefore consumes as much RAM as the total |
| * number of ordinals found in the segment, but saves the |
| * CPU cost of decoding ordinals during facet counting. |
| * |
| * <p> |
| * <b>NOTE:</b> every {@link CachedOrds} is limited to 2.1B |
| * total ordinals. If that is a limitation for you then |
| * consider limiting the segment size to fewer documents, or |
| * use an alternative cache which pages through the category |
| * ordinals. |
| * |
| * <p> |
| * <b>NOTE:</b> when using this cache, it is advised to use |
| * a {@link DocValuesFormat} that does not cache the data in |
| * memory, at least for the category lists fields, or |
| * otherwise you'll be doing double-caching. |
| * |
| * <p> |
| * <b>NOTE:</b> create one instance of this and re-use it |
| * for all facet implementations (the cache is per-instance, |
| * not static). |
| */ |
| public class CachedOrdinalsReader extends OrdinalsReader { |
| |
| private final OrdinalsReader source; |
| |
| private final Map<Object,CachedOrds> ordsCache = new WeakHashMap<Object,CachedOrds>(); |
| |
| /** Sole constructor. */ |
| public CachedOrdinalsReader(OrdinalsReader source) { |
| this.source = source; |
| } |
| |
| private synchronized CachedOrds getCachedOrds(AtomicReaderContext context) throws IOException { |
| Object cacheKey = context.reader().getCoreCacheKey(); |
| CachedOrds ords = ordsCache.get(cacheKey); |
| if (ords == null) { |
| ords = new CachedOrds(source.getReader(context), context.reader().maxDoc()); |
| ordsCache.put(cacheKey, ords); |
| } |
| |
| return ords; |
| } |
| |
| @Override |
| public String getIndexFieldName() { |
| return source.getIndexFieldName(); |
| } |
| |
| @Override |
| public OrdinalsSegmentReader getReader(AtomicReaderContext context) throws IOException { |
| final CachedOrds cachedOrds = getCachedOrds(context); |
| return new OrdinalsSegmentReader() { |
| @Override |
| public void get(int docID, IntsRef ordinals) { |
| ordinals.ints = cachedOrds.ordinals; |
| ordinals.offset = cachedOrds.offsets[docID]; |
| ordinals.length = cachedOrds.offsets[docID+1] - ordinals.offset; |
| } |
| }; |
| } |
| |
| /** Holds the cached ordinals in two paralel {@code int[]} arrays. */ |
| public static final class CachedOrds { |
| |
| /** Index into {@link #ordinals} for each document. */ |
| public final int[] offsets; |
| |
| /** Holds ords for all docs. */ |
| public final int[] ordinals; |
| |
| /** |
| * Creates a new {@link CachedOrds} from the {@link BinaryDocValues}. |
| * Assumes that the {@link BinaryDocValues} is not {@code null}. |
| */ |
| public CachedOrds(OrdinalsSegmentReader source, int maxDoc) throws IOException { |
| offsets = new int[maxDoc + 1]; |
| int[] ords = new int[maxDoc]; // let's assume one ordinal per-document as an initial size |
| |
| // this aggregator is limited to Integer.MAX_VALUE total ordinals. |
| long totOrds = 0; |
| final IntsRef values = new IntsRef(32); |
| for (int docID = 0; docID < maxDoc; docID++) { |
| offsets[docID] = (int) totOrds; |
| source.get(docID, values); |
| long nextLength = totOrds + values.length; |
| if (nextLength > ords.length) { |
| if (nextLength > ArrayUtil.MAX_ARRAY_LENGTH) { |
| throw new IllegalStateException("too many ordinals (>= " + nextLength + ") to cache"); |
| } |
| ords = ArrayUtil.grow(ords, (int) nextLength); |
| } |
| System.arraycopy(values.ints, 0, ords, (int) totOrds, values.length); |
| totOrds = nextLength; |
| } |
| offsets[maxDoc] = (int) totOrds; |
| |
| // if ords array is bigger by more than 10% of what we really need, shrink it |
| if ((double) totOrds / ords.length < 0.9) { |
| this.ordinals = new int[(int) totOrds]; |
| System.arraycopy(ords, 0, this.ordinals, 0, (int) totOrds); |
| } else { |
| this.ordinals = ords; |
| } |
| } |
| } |
| |
| /** How many bytes is this cache using? */ |
| public synchronized long ramBytesUsed() { |
| long bytes = 0; |
| for(CachedOrds ords : ordsCache.values()) { |
| bytes += RamUsageEstimator.sizeOf(ords); |
| } |
| |
| return bytes; |
| } |
| } |