You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by da...@apache.org on 2020/04/02 08:04:15 UTC

[lucene-solr] 01/01: SOLR-14365: CollapsingQParser - Avoiding always allocate int[] and float[] with size equals to number of unique values (WIP)

This is an automated email from the ASF dual-hosted git repository.

datcm pushed a commit to branch jira/SOLR-14365
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit 0d82a9f5cdac67c9de92c979e016c1c6b1f8dcf4
Author: Cao Manh Dat <da...@apache.org>
AuthorDate: Thu Apr 2 15:03:44 2020 +0700

    SOLR-14365: CollapsingQParser - Avoiding always allocate int[] and float[] with size equals to number of unique values (WIP)
---
 .../solr/search/CollapsingQParserPlugin.java       | 335 ++++++++++-----------
 .../apache/solr/util/numeric/FloatConsumer.java    |  23 ++
 .../solr/util/numeric/IntFloatArrayBasedMap.java   |  69 +++++
 .../apache/solr/util/numeric/IntFloatHashMap.java  |  44 +++
 .../org/apache/solr/util/numeric/IntFloatMap.java  |  26 ++
 .../solr/util/numeric/IntIntArrayBasedMap.java     |  81 +++++
 .../apache/solr/util/numeric/IntIntHashMap.java    |  60 ++++
 .../org/apache/solr/util/numeric/IntIntMap.java    |  29 ++
 .../solr/util/numeric/IntLongArrayBasedMap.java    |  76 +++++
 .../apache/solr/util/numeric/IntLongHashMap.java   |  51 ++++
 .../org/apache/solr/util/numeric/IntLongMap.java   |  28 ++
 .../solr/util/numeric/PrimitiveMapFactory.java     |  62 ++++
 .../org/apache/solr/util/numeric/package-info.java |  23 ++
 13 files changed, 730 insertions(+), 177 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
index 690f806..c6ea397 100644
--- a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
@@ -17,6 +17,7 @@
 package org.apache.solr.search;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -33,6 +34,7 @@ import com.carrotsearch.hppc.IntLongHashMap;
 import com.carrotsearch.hppc.cursors.IntIntCursor;
 import com.carrotsearch.hppc.cursors.IntLongCursor;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.spi.LoggerRegistry;
 import org.apache.lucene.codecs.DocValuesProducer;
 import org.apache.lucene.index.DocValues;
 import org.apache.lucene.index.DocValuesType;
@@ -78,6 +80,12 @@ import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.StrField;
 import org.apache.solr.uninverting.UninvertingReader;
+import org.apache.solr.util.numeric.IntFloatMap;
+import org.apache.solr.util.numeric.IntIntMap;
+import org.apache.solr.util.numeric.IntLongMap;
+import org.apache.solr.util.numeric.PrimitiveMapFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static org.apache.solr.common.params.CommonParams.SORT;
 
@@ -118,6 +126,7 @@ import static org.apache.solr.common.params.CommonParams.SORT;
 
 public class CollapsingQParserPlugin extends QParserPlugin {
 
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   public static final String NAME = "collapse";
   public static final String NULL_COLLAPSE = "collapse";
   public static final String NULL_IGNORE = "ignore";
@@ -510,8 +519,8 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     private SortedDocValues segmentValues;
     private LongValues segmentOrdinalMap;
     private MultiDocValues.MultiSortedDocValues multiSortedDocValues;
-    private int[] ords;
-    private float[] scores;
+    private IntIntMap ords;
+    private IntFloatMap scores;
     private int maxDoc;
     private int nullPolicy;
     private float nullScore = -Float.MAX_VALUE;
@@ -524,6 +533,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public OrdScoreCollector(int maxDoc,
                              int segments,
+                             PrimitiveMapFactory mapFactory,
                              DocValuesProducer collapseValuesProducer,
                              int nullPolicy,
                              IntIntHashMap boostDocsMap,
@@ -544,10 +554,8 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         this.multiSortedDocValues = (MultiDocValues.MultiSortedDocValues)collapseValues;
         this.ordinalMap = multiSortedDocValues.mapping;
       }
-      this.ords = new int[valueCount];
-      Arrays.fill(this.ords, -1);
-      this.scores = new float[valueCount];
-      Arrays.fill(this.scores, -Float.MAX_VALUE);
+      this.ords = mapFactory.newIntIntMap(valueCount, -1);
+      this.scores = mapFactory.newIntFloatMap(valueCount, -Float.MAX_VALUE);
       this.nullPolicy = nullPolicy;
       if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
         nullScores = new FloatArrayList();
@@ -613,9 +621,9 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
       if(ord > -1) {
         float score = scorer.score();
-        if(score > scores[ord]) {
-          ords[ord] = globalDoc;
-          scores[ord] = score;
+        if(score > scores.get(ord)) {
+          ords.set(ord, globalDoc);
+          scores.set(ord, score);
         }
       } else if(nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
         float score = scorer.score();
@@ -646,7 +654,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
           int ord = this.boostOrds.get(i);
           if(ord > -1) {
             //Remove any group heads that are in the same groups as boosted documents.
-            ords[ord] = -1;
+            ords.remove(ord);
           }
           //Add the boosted docs to the collapsedSet
           this.collapsedSet.set(boostDocs.get(i));
@@ -655,12 +663,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       }
 
       //Build the sorted DocSet of group heads.
-      for(int i=0; i<ords.length; i++) {
-        int doc = ords[i];
-        if(doc > -1) {
-          collapsedSet.set(doc);
-        }
-      }
+      ords.forEachValue(doc -> collapsedSet.set(doc));
 
       int currentContext = 0;
       int currentDocBase = 0;
@@ -715,7 +718,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         }
 
         if(ord > -1) {
-          dummy.score = scores[ord];
+          dummy.score = scores.get(ord);
         } else if(boosts && mergeBoost.boost(docId)) {
           //Ignore so it doesn't mess up the null scoring.
         } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -957,6 +960,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public OrdFieldValueCollector(int maxDoc,
                                   int segments,
+                                  PrimitiveMapFactory mapFactory,
                                   DocValuesProducer collapseValuesProducer,
                                   int nullPolicy,
                                   GroupHeadSelector groupHeadSelector,
@@ -987,9 +991,9 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       this.needsScores4Collapsing = needsScores4Collapsing;
       this.needsScores = needsScores;
       if (null != sortSpec) {
-        this.collapseStrategy = new OrdSortSpecStrategy(maxDoc, nullPolicy, new int[valueCount], groupHeadSelector, this.needsScores4Collapsing, this.needsScores, boostDocs, sortSpec, searcher, collapseValues);
+        this.collapseStrategy = new OrdSortSpecStrategy(maxDoc, nullPolicy, valueCount, mapFactory, groupHeadSelector, this.needsScores4Collapsing, this.needsScores, boostDocs, sortSpec, searcher, collapseValues);
       } else if (funcQuery != null) {
-        this.collapseStrategy =  new OrdValueSourceStrategy(maxDoc, nullPolicy, new int[valueCount], groupHeadSelector, this.needsScores4Collapsing, this.needsScores, boostDocs, funcQuery, searcher, collapseValues);
+        this.collapseStrategy =  new OrdValueSourceStrategy(maxDoc, nullPolicy, valueCount, mapFactory, groupHeadSelector, this.needsScores4Collapsing, this.needsScores, boostDocs, funcQuery, searcher, collapseValues);
       } else {
         NumberType numType = fieldType.getNumberType();
         if (null == numType) {
@@ -997,15 +1001,15 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         }
         switch (numType) {
           case INTEGER: {
-            this.collapseStrategy = new OrdIntStrategy(maxDoc, nullPolicy, new int[valueCount], groupHeadSelector, this.needsScores, boostDocs, collapseValues);
+            this.collapseStrategy = new OrdIntStrategy(maxDoc, nullPolicy, valueCount, mapFactory, groupHeadSelector, this.needsScores, boostDocs, collapseValues);
             break;
           }
           case FLOAT: {
-            this.collapseStrategy = new OrdFloatStrategy(maxDoc, nullPolicy, new int[valueCount], groupHeadSelector, this.needsScores, boostDocs, collapseValues);
+            this.collapseStrategy = new OrdFloatStrategy(maxDoc, nullPolicy, valueCount, mapFactory, groupHeadSelector, this.needsScores, boostDocs, collapseValues);
             break;
           }
           case LONG: {
-            this.collapseStrategy =  new OrdLongStrategy(maxDoc, nullPolicy, new int[valueCount], groupHeadSelector, this.needsScores, boostDocs, collapseValues);
+            this.collapseStrategy =  new OrdLongStrategy(maxDoc, nullPolicy, valueCount, mapFactory, groupHeadSelector, this.needsScores, boostDocs, collapseValues);
             break;
           }
           default: {
@@ -1075,7 +1079,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       DocIdSetIterator it = new BitSetIterator(collapseStrategy.getCollapsedSet(), 0); // cost is not useful here
       int globalDoc = -1;
       int nullScoreIndex = 0;
-      float[] scores = collapseStrategy.getScores();
+      IntFloatMap scores = collapseStrategy.getScores();
       FloatArrayList nullScores = collapseStrategy.getNullScores();
       float nullScore = collapseStrategy.getNullScore();
 
@@ -1112,7 +1116,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
           }
 
           if(ord > -1) {
-            dummy.score = scores[ord];
+            dummy.score = scores.get(ord);
           } else if (mergeBoost != null && mergeBoost.boost(globalDoc)) {
             //It's an elevated doc so no score is needed
             dummy.score = 0F;
@@ -1151,6 +1155,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public IntFieldValueCollector(int maxDoc,
                                   int size,
+                                  PrimitiveMapFactory mapFactory,
                                   int segments,
                                   int nullValue,
                                   int nullPolicy,
@@ -1178,19 +1183,19 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       this.needsScores4Collapsing = needsScores4Collapsing;
       this.needsScores = needsScores;
       if (null != sortSpec) {
-        this.collapseStrategy = new IntSortSpecStrategy(maxDoc, size, collapseField, nullValue, nullPolicy, groupHeadSelector, this.needsScores4Collapsing, this.needsScores, boostDocsMap, sortSpec, searcher);
+        this.collapseStrategy = new IntSortSpecStrategy(maxDoc, size, mapFactory, collapseField, nullValue, nullPolicy, groupHeadSelector, this.needsScores4Collapsing, this.needsScores, boostDocsMap, sortSpec, searcher);
       } else if (funcQuery != null) {
-        this.collapseStrategy =  new IntValueSourceStrategy(maxDoc, size, collapseField, nullValue, nullPolicy, groupHeadSelector, this.needsScores4Collapsing, this.needsScores, boostDocsMap, funcQuery, searcher);
+        this.collapseStrategy =  new IntValueSourceStrategy(maxDoc, size, mapFactory, collapseField, nullValue, nullPolicy, groupHeadSelector, this.needsScores4Collapsing, this.needsScores, boostDocsMap, funcQuery, searcher);
       } else {
         NumberType numType = fieldType.getNumberType();
         assert null != numType; // shouldn't make it here for non-numeric types
         switch (numType) {
           case INTEGER: {
-            this.collapseStrategy = new IntIntStrategy(maxDoc, size, collapseField, nullValue, nullPolicy, groupHeadSelector, this.needsScores, boostDocsMap);
+            this.collapseStrategy = new IntIntStrategy(maxDoc, size, mapFactory, collapseField, nullValue, nullPolicy, groupHeadSelector, this.needsScores, boostDocsMap);
             break;
           }
           case FLOAT: {
-            this.collapseStrategy = new IntFloatStrategy(maxDoc, size, collapseField, nullValue, nullPolicy, groupHeadSelector, this.needsScores, boostDocsMap);
+            this.collapseStrategy = new IntFloatStrategy(maxDoc, size, mapFactory, collapseField, nullValue, nullPolicy, groupHeadSelector, this.needsScores, boostDocsMap);
             break;
           }
           default: {
@@ -1243,8 +1248,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       int globalDoc = -1;
       int nullScoreIndex = 0;
       IntIntHashMap cmap = collapseStrategy.getCollapseMap();
-      int[] docs = collapseStrategy.getDocs();
-      float[] scores = collapseStrategy.getScores();
+      IntFloatMap scores = collapseStrategy.getScores();
       FloatArrayList nullScores = collapseStrategy.getNullScores();
       MergeBoost mergeBoost = collapseStrategy.getMergeBoost();
       float nullScore = collapseStrategy.getNullScore();
@@ -1272,7 +1276,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
           
           if(collapseValue != nullValue) {
             int pointer = cmap.get(collapseValue);
-            dummy.score = scores[pointer];
+            dummy.score = scores.get(pointer);
           } else if (mergeBoost != null && mergeBoost.boost(globalDoc)) {
             //Its an elevated doc so no score is needed
             dummy.score = 0F;
@@ -1293,6 +1297,10 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     }
   }
 
+  private static PrimitiveMapFactory getMapFactory() {
+    return PrimitiveMapFactory.newHashBasedFactory();
+  }
+
   private static class CollectorFactory {
     /** @see #isNumericCollapsible */
     private final static EnumSet<NumberType> NUMERIC_COLLAPSIBLE_TYPES = EnumSet.of(NumberType.INTEGER,
@@ -1363,11 +1371,12 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       int maxDoc = searcher.maxDoc();
       int leafCount = searcher.getTopReaderContext().leaves().size();
 
+      PrimitiveMapFactory mapFactory = getMapFactory();
       if (GroupHeadSelectorType.SCORE.equals(groupHeadSelector.type)) {
         
         if (collapseFieldType instanceof StrField) {
 
-          return new OrdScoreCollector(maxDoc, leafCount, docValuesProducer, nullPolicy, boostDocs, searcher);
+          return new OrdScoreCollector(maxDoc, leafCount, mapFactory, docValuesProducer, nullPolicy, boostDocs, searcher);
 
         } else if (isNumericCollapsible(collapseFieldType)) {
 
@@ -1399,6 +1408,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
           return new OrdFieldValueCollector(maxDoc,
                                             leafCount,
+                                            mapFactory,
                                             docValuesProducer,
                                             nullPolicy,
                                             groupHeadSelector,
@@ -1429,6 +1439,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
           return new IntFieldValueCollector(maxDoc,
                                             size,
+                                            mapFactory,
                                             leafCount,
                                             nullValue,
                                             nullPolicy,
@@ -1494,11 +1505,11 @@ public class CollapsingQParserPlugin extends QParserPlugin {
    */
   private static abstract class OrdFieldValueStrategy {
     protected int nullPolicy;
-    protected int[] ords; 
+    protected IntIntMap ords;
     protected Scorable scorer;
     protected FloatArrayList nullScores;
     protected float nullScore;
-    protected float[] scores;
+    protected IntFloatMap scores;
     protected FixedBitSet collapsedSet;
     protected int nullDoc = -1;
     protected boolean needsScores;
@@ -1512,13 +1523,13 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     public abstract void setNextReader(LeafReaderContext context) throws IOException;
 
     public OrdFieldValueStrategy(int maxDoc,
-                                 int[] ords,
+                                 int numValues,
+                                 PrimitiveMapFactory mapFactory,
                                  int nullPolicy,
                                  boolean needsScores,
                                  IntIntHashMap boostDocsMap,
-                                 SortedDocValues values) {
-      this.ords = ords;
-      Arrays.fill(ords, -1);
+                                 SortedDocValues sortedDocValues) {
+      this.ords = mapFactory.newIntIntMap(numValues, -1);
       this.nullPolicy = nullPolicy;
       this.needsScores = needsScores;
       this.collapsedSet = new FixedBitSet(maxDoc);
@@ -1554,7 +1565,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         for(int i=0; i<s; i++) {
           int ord = boostOrds.get(i);
           if(ord > -1) {
-            ords[ord] = -1;
+            ords.remove(ord);
           }
           collapsedSet.set(boostDocs.get(i));
         }
@@ -1562,12 +1573,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         mergeBoost.reset();
       }
 
-      for(int i=0; i<ords.length; i++) {
-        int doc = ords[i];
-        if(doc > -1) {
-          collapsedSet.set(doc);
-        }
-      }
+      ords.forEachValue(doc -> collapsedSet.set(doc));
 
       return collapsedSet;
     }
@@ -1584,7 +1590,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       return this.nullScore;
     }
 
-    public float[] getScores() {
+    public IntFloatMap getScores() {
       return scores;
     }
   }
@@ -1598,32 +1604,32 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     private NumericDocValues minMaxValues;
     private IntCompare comp;
     private int nullVal;
-    private int[] ordVals;
+    private IntIntMap ordVals;
 
     public OrdIntStrategy(int maxDoc,
                           int nullPolicy,
-                          int[] ords,
+                          int numValues,
+                          PrimitiveMapFactory mapFactory,
                           GroupHeadSelector groupHeadSelector,
                           boolean needsScores,
                           IntIntHashMap boostDocs,
                           SortedDocValues values) throws IOException {
-      super(maxDoc, ords, nullPolicy, needsScores, boostDocs, values);
+      super(maxDoc, numValues, mapFactory, nullPolicy, needsScores, boostDocs, values);
       this.field = groupHeadSelector.selectorText;
-      this.ordVals = new int[ords.length];
 
       assert GroupHeadSelectorType.MIN_MAX.contains(groupHeadSelector.type);
       
       if (GroupHeadSelectorType.MAX.equals(groupHeadSelector.type)) {
         comp = new MaxIntComp();
-        Arrays.fill(ordVals, Integer.MIN_VALUE);
+        this.ordVals = mapFactory.newIntIntMap(numValues, Integer.MIN_VALUE);
       } else {
         comp = new MinIntComp();
-        Arrays.fill(ordVals, Integer.MAX_VALUE);
+        this.ordVals = mapFactory.newIntIntMap(numValues, Integer.MAX_VALUE);
         this.nullVal = Integer.MAX_VALUE;
       }
 
       if(needsScores) {
-        this.scores = new float[ords.length];
+        this.scores = mapFactory.newIntFloatMap(numValues, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -1650,11 +1656,11 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       }
       
       if(ord > -1) {
-        if(comp.test(currentVal, ordVals[ord])) {
-          ords[ord] = globalDoc;
-          ordVals[ord] = currentVal;
+        if(comp.test(currentVal, ordVals.get(ord))) {
+          ords.set(ord, globalDoc);
+          ordVals.set(ord, currentVal);
           if(needsScores) {
-            scores[ord] = scorer.score();
+            scores.set(ord, scorer.score());
           }
         }
       } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -1684,33 +1690,33 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     private NumericDocValues minMaxValues;
     private FloatCompare comp;
     private float nullVal;
-    private float[] ordVals;
+    private IntFloatMap ordVals;
 
     public OrdFloatStrategy(int maxDoc,
                             int nullPolicy,
-                            int[] ords,
+                            int numValues,
+                            PrimitiveMapFactory mapFactory,
                             GroupHeadSelector groupHeadSelector,
                             boolean needsScores,
                             IntIntHashMap boostDocs,
                             SortedDocValues values) throws IOException {
-      super(maxDoc, ords, nullPolicy, needsScores, boostDocs, values);
+      super(maxDoc, numValues, mapFactory, nullPolicy, needsScores, boostDocs, values);
       this.field = groupHeadSelector.selectorText;
-      this.ordVals = new float[ords.length];
-      
+
       assert GroupHeadSelectorType.MIN_MAX.contains(groupHeadSelector.type);
 
       if (GroupHeadSelectorType.MAX.equals(groupHeadSelector.type)) {
         comp = new MaxFloatComp();
-        Arrays.fill(ordVals, -Float.MAX_VALUE);
+        this.ordVals = mapFactory.newIntFloatMap(numValues, -Float.MAX_VALUE);
         this.nullVal = -Float.MAX_VALUE;
       } else {
         comp = new MinFloatComp();
-        Arrays.fill(ordVals, Float.MAX_VALUE);
+        this.ordVals = mapFactory.newIntFloatMap(numValues, Float.MAX_VALUE);
         this.nullVal = Float.MAX_VALUE;
       }
 
       if(needsScores) {
-        this.scores = new float[ords.length];
+        this.scores = mapFactory.newIntFloatMap(numValues, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -1739,11 +1745,11 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       float currentVal = Float.intBitsToFloat(currentMinMax);
 
       if(ord > -1) {
-        if(comp.test(currentVal, ordVals[ord])) {
-          ords[ord] = globalDoc;
-          ordVals[ord] = currentVal;
+        if(comp.test(currentVal, ordVals.get(ord))) {
+          ords.set(ord, globalDoc);
+          ordVals.set(ord, currentVal);
           if(needsScores) {
-            scores[ord] = scorer.score();
+            scores.set(ord, scorer.score());
           }
         }
       } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -1774,31 +1780,31 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     private NumericDocValues minMaxVals;
     private LongCompare comp;
     private long nullVal;
-    private long[] ordVals;
+    private IntLongMap ordVals;
 
     public OrdLongStrategy(int maxDoc,
                            int nullPolicy,
-                           int[] ords,
+                           int numValues,
+                           PrimitiveMapFactory mapFactory,
                            GroupHeadSelector groupHeadSelector,
                            boolean needsScores,
                            IntIntHashMap boostDocs, SortedDocValues values) throws IOException {
-      super(maxDoc, ords, nullPolicy, needsScores, boostDocs, values);
+      super(maxDoc, numValues, mapFactory, nullPolicy, needsScores, boostDocs, values);
       this.field = groupHeadSelector.selectorText;
-      this.ordVals = new long[ords.length];
 
       assert GroupHeadSelectorType.MIN_MAX.contains(groupHeadSelector.type);
       
       if (GroupHeadSelectorType.MAX.equals(groupHeadSelector.type)) {
         comp = new MaxLongComp();
-        Arrays.fill(ordVals, Long.MIN_VALUE);
+        this.ordVals = mapFactory.newIntLongMap(numValues, Long.MIN_VALUE);
       } else {
         this.nullVal = Long.MAX_VALUE;
         comp = new MinLongComp();
-        Arrays.fill(ordVals, Long.MAX_VALUE);
+        this.ordVals = mapFactory.newIntLongMap(numValues, Long.MAX_VALUE);
       }
 
       if(needsScores) {
-        this.scores = new float[ords.length];
+        this.scores = mapFactory.newIntFloatMap(numValues, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -1825,11 +1831,11 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       }
 
       if(ord > -1) {
-        if(comp.test(currentVal, ordVals[ord])) {
-          ords[ord] = globalDoc;
-          ordVals[ord] = currentVal;
+        if(comp.test(currentVal, ordVals.get(ord))) {
+          ords.set(ord, globalDoc);
+          ordVals.set(ord, currentVal);
           if(needsScores) {
-            scores[ord] = scorer.score();
+            scores.set(ord, scorer.score());
           }
         }
       } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -1859,14 +1865,15 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     private float nullVal;
     private ValueSource valueSource;
     private FunctionValues functionValues;
-    private float[] ordVals;
+    private IntFloatMap ordVals;
     private Map rcontext;
     private final CollapseScore collapseScore = new CollapseScore();
     private boolean needsScores4Collapsing;
 
     public OrdValueSourceStrategy(int maxDoc,
                                   int nullPolicy,
-                                  int[] ords,
+                                  int numValue,
+                                  PrimitiveMapFactory mapFactory,
                                   GroupHeadSelector groupHeadSelector,
                                   boolean needsScores4Collapsing,
                                   boolean needsScores,
@@ -1874,27 +1881,26 @@ public class CollapsingQParserPlugin extends QParserPlugin {
                                   FunctionQuery funcQuery,
                                   IndexSearcher searcher,
                                   SortedDocValues values) throws IOException {
-      super(maxDoc, ords, nullPolicy, needsScores, boostDocs, values);
+      super(maxDoc, numValue, mapFactory, nullPolicy, needsScores, boostDocs, values);
       this.needsScores4Collapsing = needsScores4Collapsing;
       this.valueSource = funcQuery.getValueSource();
       this.rcontext = ValueSource.newContext(searcher);
-      this.ordVals = new float[ords.length];
 
       assert GroupHeadSelectorType.MIN_MAX.contains(groupHeadSelector.type);
       
       if (GroupHeadSelectorType.MAX.equals(groupHeadSelector.type)) {
         comp = new MaxFloatComp();
-        Arrays.fill(ordVals, -Float.MAX_VALUE );
+        this.ordVals = mapFactory.newIntFloatMap(numValue, -Float.MAX_VALUE);
       } else {
         this.nullVal = Float.MAX_VALUE;
         comp = new MinFloatComp();
-        Arrays.fill(ordVals, Float.MAX_VALUE);
+        this.ordVals = mapFactory.newIntFloatMap(numValue, Float.MAX_VALUE);
       }
 
       collapseScore.setupIfNeeded(groupHeadSelector, rcontext);
 
       if(this.needsScores) {
-        this.scores = new float[ords.length];
+        this.scores = mapFactory.newIntFloatMap(numValue, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -1921,14 +1927,14 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       float currentVal = functionValues.floatVal(contextDoc);
 
       if(ord > -1) {
-        if(comp.test(currentVal, ordVals[ord])) {
-          ords[ord] = globalDoc;
-          ordVals[ord] = currentVal;
+        if(comp.test(currentVal, ordVals.get(ord))) {
+          ords.set(ord, globalDoc);
+          ordVals.set(ord, currentVal);
           if(needsScores) {
             if (!needsScores4Collapsing) {
               score = scorer.score();
             }
-            scores[ord] = score;
+            scores.set(ord, score);
           }
         }
       } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -1968,7 +1974,8 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public OrdSortSpecStrategy(int maxDoc,
                                int nullPolicy,
-                               int[] ords,
+                               int numValues,
+                               PrimitiveMapFactory mapFactory,
                                GroupHeadSelector groupHeadSelector,
                                boolean needsScores4Collapsing,
                                boolean needsScores,
@@ -1976,17 +1983,17 @@ public class CollapsingQParserPlugin extends QParserPlugin {
                                SortSpec sortSpec,
                                IndexSearcher searcher,
                                SortedDocValues values) throws IOException {
-      super(maxDoc, ords, nullPolicy, needsScores, boostDocs, values);
+      super(maxDoc, numValues, mapFactory, nullPolicy, needsScores, boostDocs, values);
       this.needsScores4Collapsing = needsScores4Collapsing;
       
       assert GroupHeadSelectorType.SORT.equals(groupHeadSelector.type);
       
       this.sort = rewriteSort(sortSpec, searcher);
       
-      this.compareState = new SortFieldsCompare(sort.getSort(), ords.length);
+      this.compareState = new SortFieldsCompare(sort.getSort(), numValues);
 
       if (this.needsScores) {
-        this.scores = new float[ords.length];
+        this.scores = mapFactory.newIntFloatMap(numValues, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -2017,25 +2024,25 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       }
 
       if (ord > -1) { // real collapseKey
-        if (-1 == ords[ord]) {
+        if (-1 == ords.get(ord)) {
           // we've never seen this ord (aka: collapseKey) before, treat it as group head for now
           compareState.setGroupValues(ord, contextDoc);
-          ords[ord] = globalDoc;
+          ords.set(ord, globalDoc);
           if (needsScores) {
             if (!needsScores4Collapsing) {
               this.score = scorer.score();
             }
-            scores[ord] = score;
+            scores.set(ord, score);
           }
         } else {
           // test this ord to see if it's a new group leader
           if (compareState.testAndSetGroupValues(ord, contextDoc)) {//TODO X
-            ords[ord] = globalDoc;
+            ords.set(ord, globalDoc);
             if (needsScores) {
               if (!needsScores4Collapsing) {
                 this.score = scorer.score();
               }
-              scores[ord] = score;
+              scores.set(ord, score);
             }
           }
         }
@@ -2085,12 +2092,12 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     protected Scorable scorer;
     protected FloatArrayList nullScores;
     protected float nullScore;
-    protected float[] scores;
+    protected IntFloatMap scores;
     protected FixedBitSet collapsedSet;
     protected int nullDoc = -1;
     protected boolean needsScores;
     protected String collapseField;
-    protected int[] docs;
+    protected IntIntMap docs;
     protected int nullValue;
     protected IntArrayList boostDocs;
     protected IntArrayList boostKeys;
@@ -2102,6 +2109,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public IntFieldValueStrategy(int maxDoc,
                                  int size,
+                                 PrimitiveMapFactory mapFactory,
                                  String collapseField,
                                  int nullValue,
                                  int nullPolicy,
@@ -2113,7 +2121,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       this.needsScores = needsScores;
       this.collapsedSet = new FixedBitSet(maxDoc);
       this.cmap = new IntIntHashMap(size);
-      this.docs = new int[size];
+      this.docs = mapFactory.newIntIntMap(size, 0);
       if(boostDocsMap != null) {
         this.boosts = true;
         this.boostDocs = new IntArrayList();
@@ -2156,7 +2164,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       while(it1.hasNext()) {
         IntIntCursor cursor = it1.next();
         int pointer = cursor.value;
-        collapsedSet.set(docs[pointer]);
+        collapsedSet.set(docs.get(pointer));
       }
 
       return collapsedSet;
@@ -2178,11 +2186,11 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       return this.nullScore;
     }
 
-    public float[] getScores() {
+    public IntFloatMap getScores() {
       return scores;
     }
 
-    public int[] getDocs() { return docs;}
+    public IntIntMap getDocs() { return docs;}
 
     public MergeBoost getMergeBoost()  {
       return this.mergeBoost;
@@ -2197,7 +2205,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     private final String field;
     private NumericDocValues minMaxVals;
-    private int[] testValues;
+    private IntIntMap testValues;
     private IntCompare comp;
     private int nullCompVal;
 
@@ -2205,6 +2213,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public IntIntStrategy(int maxDoc,
                           int size,
+                          PrimitiveMapFactory mapFactory,
                           String collapseField,
                           int nullValue,
                           int nullPolicy,
@@ -2212,9 +2221,9 @@ public class CollapsingQParserPlugin extends QParserPlugin {
                           boolean needsScores,
                           IntIntHashMap boostDocs) throws IOException {
 
-      super(maxDoc, size, collapseField, nullValue, nullPolicy, needsScores, boostDocs);
+      super(maxDoc, size, mapFactory, collapseField, nullValue, nullPolicy, needsScores, boostDocs);
       this.field = groupHeadSelector.selectorText;
-      this.testValues = new int[size];
+      this.testValues = mapFactory.newIntIntMap(size, 0);
 
       assert GroupHeadSelectorType.MIN_MAX.contains(groupHeadSelector.type);
       
@@ -2227,7 +2236,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       }
 
       if(needsScores) {
-        this.scores = new float[size];
+        this.scores = mapFactory.newIntFloatMap(size, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -2258,29 +2267,20 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         final int idx;
         if((idx = cmap.indexOf(collapseKey)) >= 0) {
           int pointer = cmap.indexGet(idx);
-          if(comp.test(currentVal, testValues[pointer])) {
-            testValues[pointer]= currentVal;
-            docs[pointer] = globalDoc;
+          if(comp.test(currentVal, testValues.get(pointer))) {
+            testValues.set(pointer, currentVal);
+            docs.set(pointer, globalDoc);
             if(needsScores) {
-              scores[pointer] = scorer.score();
+              scores.set(pointer, scorer.score());
             }
           }
         } else {
           ++index;
           cmap.put(collapseKey, index);
-          if(index == testValues.length) {
-            testValues = ArrayUtil.grow(testValues);
-            docs = ArrayUtil.grow(docs);
-            if(needsScores) {
-              scores = ArrayUtil.grow(scores);
-            }
-          }
-
-          testValues[index] = currentVal;
-          docs[index] = (globalDoc);
-
+          testValues.set(index, currentVal);
+          docs.set(index, globalDoc);
           if(needsScores) {
-            scores[index] = scorer.score();
+            scores.set(index, scorer.score());
           }
         }
       } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -2304,7 +2304,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     private final String field;
     private NumericDocValues minMaxVals;
-    private float[] testValues;
+    private IntFloatMap testValues;
     private FloatCompare comp;
     private float nullCompVal;
 
@@ -2312,6 +2312,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public IntFloatStrategy(int maxDoc,
                             int size,
+                            PrimitiveMapFactory mapFactory,
                             String collapseField,
                             int nullValue,
                             int nullPolicy,
@@ -2319,9 +2320,9 @@ public class CollapsingQParserPlugin extends QParserPlugin {
                             boolean needsScores,
                             IntIntHashMap boostDocs) throws IOException {
 
-      super(maxDoc, size, collapseField, nullValue, nullPolicy, needsScores, boostDocs);
+      super(maxDoc, size, mapFactory, collapseField, nullValue, nullPolicy, needsScores, boostDocs);
       this.field = groupHeadSelector.selectorText;
-      this.testValues = new float[size];
+      this.testValues = mapFactory.newIntFloatMap(size, 0.0f);
 
       assert GroupHeadSelectorType.MIN_MAX.contains(groupHeadSelector.type);
       
@@ -2334,7 +2335,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       }
 
       if(needsScores) {
-        this.scores = new float[size];
+        this.scores = mapFactory.newIntFloatMap(size, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -2367,28 +2368,20 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         final int idx;
         if((idx = cmap.indexOf(collapseKey)) >= 0) {
           int pointer = cmap.indexGet(idx);
-          if(comp.test(currentVal, testValues[pointer])) {
-            testValues[pointer] = currentVal;
-            docs[pointer] = globalDoc;
+          if(comp.test(currentVal, testValues.get(pointer))) {
+            testValues.set(pointer, currentVal);
+            docs.set(pointer, globalDoc);
             if(needsScores) {
-              scores[pointer] = scorer.score();
+              scores.set(pointer, scorer.score());
             }
           }
         } else {
           ++index;
           cmap.put(collapseKey, index);
-          if(index == testValues.length) {
-            testValues = ArrayUtil.grow(testValues);
-            docs = ArrayUtil.grow(docs);
-            if(needsScores) {
-              scores = ArrayUtil.grow(scores);
-            }
-          }
-
-          testValues[index] = currentVal;
-          docs[index] = globalDoc;
+          testValues.set(index, currentVal);
+          docs.set(index, globalDoc);
           if(needsScores) {
-            scores[index] = scorer.score();
+            scores.set(index, scorer.score());
           }
         }
       } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -2415,7 +2408,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
   private static class IntValueSourceStrategy extends IntFieldValueStrategy {
 
     private FloatCompare comp;
-    private float[] testValues;
+    private IntFloatMap testValues;
     private float nullCompVal;
 
     private ValueSource valueSource;
@@ -2427,6 +2420,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public IntValueSourceStrategy(int maxDoc,
                                   int size,
+                                  PrimitiveMapFactory mapFactory,
                                   String collapseField,
                                   int nullValue,
                                   int nullPolicy,
@@ -2437,10 +2431,10 @@ public class CollapsingQParserPlugin extends QParserPlugin {
                                   FunctionQuery funcQuery,
                                   IndexSearcher searcher) throws IOException {
 
-      super(maxDoc, size, collapseField, nullValue, nullPolicy, needsScores, boostDocs);
+      super(maxDoc, size, mapFactory, collapseField, nullValue, nullPolicy, needsScores, boostDocs);
 
       this.needsScores4Collapsing = needsScores4Collapsing;
-      this.testValues = new float[size];
+      this.testValues = mapFactory.newIntFloatMap(size, 0.0f);
 
       this.valueSource = funcQuery.getValueSource();
       this.rcontext = ValueSource.newContext(searcher);
@@ -2458,7 +2452,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       collapseScore.setupIfNeeded(groupHeadSelector, rcontext);
 
       if(needsScores) {
-        this.scores = new float[size];
+        this.scores = mapFactory.newIntFloatMap(size, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -2490,33 +2484,26 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         final int idx;
         if((idx = cmap.indexOf(collapseKey)) >= 0) {
           int pointer = cmap.indexGet(idx);
-          if(comp.test(currentVal, testValues[pointer])) {
-            testValues[pointer] = currentVal;
-            docs[pointer] = globalDoc;
+          if(comp.test(currentVal, testValues.get(pointer))) {
+            testValues.set(pointer, currentVal);
+            docs.set(pointer, globalDoc);
             if(needsScores){
               if (!needsScores4Collapsing) {
                 score = scorer.score();
               }
-              scores[pointer] = score;
+              scores.set(pointer, score);
             }
           }
         } else {
           ++index;
           cmap.put(collapseKey, index);
-          if(index == testValues.length) {
-            testValues = ArrayUtil.grow(testValues);
-            docs = ArrayUtil.grow(docs);
-            if(needsScores) {
-              scores = ArrayUtil.grow(scores);
-            }
-          }
-          docs[index] = globalDoc;
-          testValues[index] = currentVal;
+          docs.set(index, globalDoc);
+          testValues.set(index, currentVal);
           if(needsScores) {
             if (!needsScores4Collapsing) {
               score = scorer.score();
             }
-            scores[index] = score;
+            scores.set(index, score);
           }
         }
       } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -2558,6 +2545,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
 
     public IntSortSpecStrategy(int maxDoc,
                                int size,
+                               PrimitiveMapFactory mapFactory,
                                String collapseField,
                                int nullValue,
                                int nullPolicy,
@@ -2568,7 +2556,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
                                SortSpec sortSpec,
                                IndexSearcher searcher) throws IOException {
       
-      super(maxDoc, size, collapseField, nullValue, nullPolicy, needsScores, boostDocs);
+      super(maxDoc, size, mapFactory, collapseField, nullValue, nullPolicy, needsScores, boostDocs);
       this.needsScores4Collapsing = needsScores4Collapsing;
 
       assert GroupHeadSelectorType.SORT.equals(groupHeadSelector.type);
@@ -2578,7 +2566,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       this.compareState = new SortFieldsCompare(sort.getSort(), size);
 
       if(needsScores) {
-        this.scores = new float[size];
+        this.scores = mapFactory.newIntFloatMap(size, 0.0f);
         if(nullPolicy == CollapsingPostFilter.NULL_POLICY_EXPAND) {
           nullScores = new FloatArrayList();
         }
@@ -2616,32 +2604,25 @@ public class CollapsingQParserPlugin extends QParserPlugin {
           // we've seen this collapseKey before, test to see if it's a new group leader
           int pointer = cmap.indexGet(idx);
           if (compareState.testAndSetGroupValues(pointer, contextDoc)) {
-            docs[pointer] = globalDoc;
+            docs.set(pointer, globalDoc);
             if (needsScores) {
               if (!needsScores4Collapsing) {
                 score = scorer.score();
               }
-              scores[pointer] = score;
+              scores.set(pointer, score);
             }
           }
         } else {
           // we've never seen this collapseKey before, treat it as group head for now
           ++index;
           cmap.put(collapseKey, index);
-          if (index == docs.length) {
-            docs = ArrayUtil.grow(docs);
-            compareState.grow(docs.length);
-            if(needsScores) {
-              scores = ArrayUtil.grow(scores);
-            }
-          }
-          docs[index] = globalDoc;
+          docs.set(index, globalDoc);
           compareState.setGroupValues(index, contextDoc);
           if(needsScores) {
             if (!needsScores4Collapsing) {
               score = scorer.score();
             }
-            scores[index] = score;
+            scores.set(index, score);
           }
         }
       } else if(this.nullPolicy == CollapsingPostFilter.NULL_POLICY_COLLAPSE) {
@@ -2794,7 +2775,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
      */
     public void setGroupValues(int collapseKey, int contextDoc) throws IOException {
       assert 0 <= collapseKey : "negative collapseKey";
-      assert collapseKey < groupHeadValues.length : "collapseKey too big -- need to grow array?";
+      if (collapseKey >= groupHeadValues.length) grow(collapseKey);
       setGroupValues(getOrInitGroupHeadValues(collapseKey), contextDoc);
     }
     
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/FloatConsumer.java b/solr/core/src/java/org/apache/solr/util/numeric/FloatConsumer.java
new file mode 100644
index 0000000..05b24e9
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/FloatConsumer.java
@@ -0,0 +1,23 @@
+/*
+ * 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.solr.util.numeric;
+
+@FunctionalInterface
+public interface FloatConsumer {
+  void accept(float f);
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntFloatArrayBasedMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntFloatArrayBasedMap.java
new file mode 100644
index 0000000..592d525
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntFloatArrayBasedMap.java
@@ -0,0 +1,69 @@
+/*
+ * 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.solr.util.numeric;
+
+import java.util.Arrays;
+
+import org.apache.lucene.util.ArrayUtil;
+
+public class IntFloatArrayBasedMap implements IntFloatMap {
+
+  private int size;
+  private float[] keyValues;
+  private float emptyValue;
+
+  public IntFloatArrayBasedMap(int initialSize, float emptyValue) {
+    this.size = initialSize;
+    this.keyValues = new float[initialSize];
+    this.emptyValue = emptyValue;
+    if (emptyValue != 0) {
+      Arrays.fill(keyValues, emptyValue);
+    }
+  }
+
+  @Override
+  public void set(int key, float value) {
+    if (key >= size) {
+      keyValues = ArrayUtil.grow(keyValues);
+      if (emptyValue != 0) {
+        for (int i = size; i < keyValues.length; i++) {
+          keyValues[i] = emptyValue;
+        }
+      }
+      size = keyValues.length;
+    }
+    keyValues[key] = value;
+  }
+
+  @Override
+  public float get(int key) {
+    if (key >= size) {
+      return emptyValue;
+    }
+    return keyValues[key];
+  }
+
+  @Override
+  public void forEachValue(FloatConsumer consumer) {
+    for (float val: keyValues) {
+      if (val != emptyValue) {
+        consumer.accept(val);
+      }
+    }
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntFloatHashMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntFloatHashMap.java
new file mode 100644
index 0000000..4fbe3d6
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntFloatHashMap.java
@@ -0,0 +1,44 @@
+/*
+ * 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.solr.util.numeric;
+
+import com.carrotsearch.hppc.procedures.IntFloatProcedure;
+
+public class IntFloatHashMap implements IntFloatMap {
+  private com.carrotsearch.hppc.IntFloatHashMap hashMap;
+  private float emptyValue;
+
+  public IntFloatHashMap(int initialSize, float emptyValue) {
+    this.hashMap = new com.carrotsearch.hppc.IntFloatHashMap();
+    this.emptyValue = emptyValue;
+  }
+  @Override
+  public final void set(int key, float value) {
+    this.hashMap.put(key, value);
+  }
+
+  @Override
+  public final float get(int key) {
+    return this.hashMap.getOrDefault(key, emptyValue);
+  }
+
+  @Override
+  public void forEachValue(FloatConsumer consumer) {
+    this.hashMap.forEach((IntFloatProcedure) (key, value) -> consumer.accept(value));
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntFloatMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntFloatMap.java
new file mode 100644
index 0000000..e847e16
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntFloatMap.java
@@ -0,0 +1,26 @@
+/*
+ * 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.solr.util.numeric;
+
+import java.util.function.IntConsumer;
+
+public interface IntFloatMap {
+  void set(int key, float value);
+  float get(int key);
+  void forEachValue(FloatConsumer consumer);
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntIntArrayBasedMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntIntArrayBasedMap.java
new file mode 100644
index 0000000..bd695b7
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntIntArrayBasedMap.java
@@ -0,0 +1,81 @@
+/*
+ * 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.solr.util.numeric;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.function.IntConsumer;
+
+import org.apache.lucene.util.ArrayUtil;
+
+public class IntIntArrayBasedMap implements IntIntMap {
+
+  private int size;
+  private int[] keyValues;
+  private int emptyValue;
+
+  public IntIntArrayBasedMap(int initialSize, int emptyValue) {
+    this.size = initialSize;
+    this.keyValues = new int[initialSize];
+    this.emptyValue = emptyValue;
+    if (emptyValue != 0) {
+      Arrays.fill(keyValues, emptyValue);
+    }
+  }
+
+  @Override
+  public void set(int key, int value) {
+    if (key >= size) {
+      keyValues = ArrayUtil.grow(keyValues);
+      if (emptyValue != 0) {
+        for (int i = size; i < keyValues.length; i++) {
+          keyValues[i] = emptyValue;
+        }
+      }
+      size = keyValues.length;
+    }
+    keyValues[key] = value;
+  }
+
+  @Override
+  public int get(int key) {
+    if (key >= size) {
+      return emptyValue;
+    }
+    return keyValues[key];
+  }
+
+  @Override
+  public void forEachValue(IntConsumer consumer) {
+    for (int val: keyValues) {
+      if (val != emptyValue) {
+        consumer.accept(val);
+      }
+    }
+  }
+
+  @Override
+  public void remove(int key) {
+    if (key < size) keyValues[key] = emptyValue;
+  }
+
+  @Override
+  public int size() {
+    return keyValues.length;
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntIntHashMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntIntHashMap.java
new file mode 100644
index 0000000..d652055
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntIntHashMap.java
@@ -0,0 +1,60 @@
+/*
+ * 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.solr.util.numeric;
+
+import java.util.function.IntConsumer;
+
+import com.carrotsearch.hppc.cursors.IntCursor;
+import com.carrotsearch.hppc.procedures.IntIntProcedure;
+
+public class IntIntHashMap implements IntIntMap {
+  private com.carrotsearch.hppc.IntIntHashMap hashMap;
+  private int emptyValue;
+
+  public IntIntHashMap(int initialSize, int emptyValue) {
+    this.hashMap = new com.carrotsearch.hppc.IntIntHashMap();
+    this.emptyValue = emptyValue;
+  }
+
+  @Override
+  public final void set(int key, int value) {
+    this.hashMap.put(key, value);
+  }
+
+  @Override
+  public final int get(int key) {
+    return this.hashMap.getOrDefault(key, emptyValue);
+  }
+
+  @Override
+  public void forEachValue(IntConsumer consumer) {
+    for (IntCursor ord : hashMap.values()) {
+      consumer.accept(ord.value);
+    }
+  }
+
+  @Override
+  public void remove(int key) {
+    this.hashMap.remove(key);
+  }
+
+  @Override
+  public int size() {
+    return hashMap.size();
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntIntMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntIntMap.java
new file mode 100644
index 0000000..0366ffe
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntIntMap.java
@@ -0,0 +1,29 @@
+/*
+ * 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.solr.util.numeric;
+
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+
+public interface IntIntMap {
+  void set(int key, int value);
+  int get(int key);
+  void forEachValue(IntConsumer consumer);
+  void remove(int key);
+  int size();
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntLongArrayBasedMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntLongArrayBasedMap.java
new file mode 100644
index 0000000..155b8be
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntLongArrayBasedMap.java
@@ -0,0 +1,76 @@
+/*
+ * 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.solr.util.numeric;
+
+import java.util.Arrays;
+import java.util.function.IntConsumer;
+import java.util.function.LongConsumer;
+
+import org.apache.lucene.util.ArrayUtil;
+
+public class IntLongArrayBasedMap implements IntLongMap {
+
+  private int size;
+  private long[] keyValues;
+  private long emptyValue;
+
+  public IntLongArrayBasedMap(int initialSize, long emptyValue) {
+    this.size = initialSize;
+    this.keyValues = new long[initialSize];
+    this.emptyValue = emptyValue;
+    if (emptyValue != 0) {
+      Arrays.fill(keyValues, emptyValue);
+    }
+  }
+
+  @Override
+  public void set(int key, long value) {
+    if (key >= size) {
+      keyValues = ArrayUtil.grow(keyValues);
+      if (emptyValue != 0) {
+        for (int i = size; i < keyValues.length; i++) {
+          keyValues[i] = emptyValue;
+        }
+      }
+      size = keyValues.length;
+    }
+    keyValues[key] = value;
+  }
+
+  @Override
+  public long get(int key) {
+    if (key >= size) {
+      return emptyValue;
+    }
+    return keyValues[key];
+  }
+
+  @Override
+  public void forEachValue(LongConsumer consumer) {
+    for (long val: keyValues) {
+      if (val != emptyValue) {
+        consumer.accept(val);
+      }
+    }
+  }
+
+  @Override
+  public void remove(int key) {
+    if (key < size) keyValues[key] = emptyValue;
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntLongHashMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntLongHashMap.java
new file mode 100644
index 0000000..cbe4e21
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntLongHashMap.java
@@ -0,0 +1,51 @@
+/*
+ * 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.solr.util.numeric;
+
+import java.util.function.LongConsumer;
+
+import com.carrotsearch.hppc.procedures.IntLongProcedure;
+
+public class IntLongHashMap implements IntLongMap {
+  private com.carrotsearch.hppc.IntLongHashMap hashMap;
+  private long emptyValue;
+
+  public IntLongHashMap(int initialSize, long emptyValue) {
+    this.hashMap = new com.carrotsearch.hppc.IntLongHashMap(initialSize);
+    this.emptyValue = emptyValue;
+  }
+  @Override
+  public void set(int key, long value) {
+    this.hashMap.put(key, value);
+  }
+
+  @Override
+  public long get(int key) {
+    return this.hashMap.getOrDefault(key, emptyValue);
+  }
+
+  @Override
+  public void forEachValue(LongConsumer consumer) {
+    this.hashMap.forEach((IntLongProcedure) (key, value) -> consumer.accept(value));
+  }
+
+  @Override
+  public void remove(int key) {
+    this.hashMap.remove(key);
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/IntLongMap.java b/solr/core/src/java/org/apache/solr/util/numeric/IntLongMap.java
new file mode 100644
index 0000000..48c2828
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/IntLongMap.java
@@ -0,0 +1,28 @@
+/*
+ * 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.solr.util.numeric;
+
+import java.util.function.IntConsumer;
+import java.util.function.LongConsumer;
+
+public interface IntLongMap {
+  void set(int key, long value);
+  long get(int key);
+  void forEachValue(LongConsumer consumer);
+  void remove(int key);
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/PrimitiveMapFactory.java b/solr/core/src/java/org/apache/solr/util/numeric/PrimitiveMapFactory.java
new file mode 100644
index 0000000..81702f5
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/PrimitiveMapFactory.java
@@ -0,0 +1,62 @@
+/*
+ * 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.solr.util.numeric;
+
+public interface PrimitiveMapFactory {
+  IntIntMap newIntIntMap(int initialSize, int emptyValue);
+  IntFloatMap newIntFloatMap(int initialSize, float emptyValue);
+  IntLongMap newIntLongMap(int initialSize, long emptyValue);
+
+  static PrimitiveMapFactory newArrayBasedFactory() {
+    return new PrimitiveMapFactory() {
+      @Override
+      public IntIntMap newIntIntMap(int initialSize, int emptyValue) {
+        return new IntIntArrayBasedMap(initialSize, emptyValue);
+      }
+
+      @Override
+      public IntFloatMap newIntFloatMap(int initialSize, float emptyValue) {
+        return new IntFloatArrayBasedMap(initialSize, emptyValue);
+      }
+
+      @Override
+      public IntLongMap newIntLongMap(int initialSize, long emptyValue) {
+        return new IntLongArrayBasedMap(initialSize, emptyValue);
+      }
+    };
+  }
+
+  static PrimitiveMapFactory newHashBasedFactory() {
+    return new PrimitiveMapFactory() {
+      @Override
+      public IntIntMap newIntIntMap(int initialSize, int emptyValue) {
+        return new IntIntHashMap(4, emptyValue);
+      }
+
+      @Override
+      public IntFloatMap newIntFloatMap(int initialSize, float emptyValue) {
+        return new IntFloatHashMap(4, emptyValue);
+      }
+
+      @Override
+      public IntLongMap newIntLongMap(int initialSize, long emptyValue) {
+        return new IntLongHashMap(4, emptyValue);
+      }
+    };
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/numeric/package-info.java b/solr/core/src/java/org/apache/solr/util/numeric/package-info.java
new file mode 100644
index 0000000..baa6a58
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/numeric/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+ 
+/** 
+ * Collections of simple, high-performance classes for numeric.
+ *
+ */
+package org.apache.solr.util.numeric;
+