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:14 UTC

[lucene-solr] branch jira/SOLR-14365 created (now 0d82a9f)

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

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


      at 0d82a9f  SOLR-14365: CollapsingQParser - Avoiding always allocate int[] and float[] with size equals to number of unique values (WIP)

This branch includes the following new commits:

     new 0d82a9f  SOLR-14365: CollapsingQParser - Avoiding always allocate int[] and float[] with size equals to number of unique values (WIP)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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

Posted by da...@apache.org.
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;
+