Make MaxScoreAccumulator use primitive long instead Object return (#13866)

An object return inside hot code like this is needlessly wasteful.
Escape analysis doesn't catch this one and we end up allocating many GB
of throwaway objects during benchmark runs. We might as well use two
utility methods and accumulate the raw value.
This commit is contained in:
Armin Braun 2024-10-08 12:50:16 +02:00
parent 22638ec8a2
commit 7c6237a912
6 changed files with 57 additions and 84 deletions

View File

@ -35,8 +35,8 @@ final class MaxScoreAccumulator {
} }
/** /**
* Return the max encoded DocAndScore in a way that is consistent with {@link * Return the max encoded docId and score found in the two longs, following the encoding in {@link
* DocAndScore#compareTo}. * #accumulate}.
*/ */
private static long maxEncode(long v1, long v2) { private static long maxEncode(long v1, long v2) {
float score1 = Float.intBitsToFloat((int) (v1 >> 32)); float score1 = Float.intBitsToFloat((int) (v1 >> 32));
@ -57,26 +57,15 @@ final class MaxScoreAccumulator {
acc.accumulate(encode); acc.accumulate(encode);
} }
DocAndScore get() { public static float toScore(long value) {
long value = acc.get(); return Float.intBitsToFloat((int) (value >> 32));
if (value == Long.MIN_VALUE) {
return null;
}
float score = Float.intBitsToFloat((int) (value >> 32));
int docId = (int) value;
return new DocAndScore(docId, score);
} }
record DocAndScore(int docId, float score) implements Comparable<DocAndScore> { public static int docId(long value) {
return (int) value;
}
@Override long getRaw() {
public int compareTo(DocAndScore o) { return acc.get();
int cmp = Float.compare(score, o.score);
if (cmp == 0) {
// tie-break on doc id, lower id has the priority
return Integer.compare(o.docId, docId);
}
return cmp;
}
} }
} }

View File

@ -24,7 +24,6 @@ import java.util.Objects;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.ReaderUtil; import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.search.FieldValueHitQueue.Entry; import org.apache.lucene.search.FieldValueHitQueue.Entry;
import org.apache.lucene.search.MaxScoreAccumulator.DocAndScore;
import org.apache.lucene.search.TotalHits.Relation; import org.apache.lucene.search.TotalHits.Relation;
/** /**
@ -366,10 +365,12 @@ public abstract class TopFieldCollector extends TopDocsCollector<Entry> {
// we can start checking the global maximum score even // we can start checking the global maximum score even
// if the local queue is not full because the threshold // if the local queue is not full because the threshold
// is reached. // is reached.
DocAndScore maxMinScore = minScoreAcc.get(); long maxMinScore = minScoreAcc.getRaw();
if (maxMinScore != null && maxMinScore.score() > minCompetitiveScore) { float score;
scorer.setMinCompetitiveScore(maxMinScore.score()); if (maxMinScore != Long.MIN_VALUE
minCompetitiveScore = maxMinScore.score(); && (score = MaxScoreAccumulator.toScore(maxMinScore)) > minCompetitiveScore) {
scorer.setMinCompetitiveScore(score);
minCompetitiveScore = score;
totalHitsRelation = TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO; totalHitsRelation = TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO;
} }
} }

View File

@ -18,7 +18,6 @@ package org.apache.lucene.search;
import java.io.IOException; import java.io.IOException;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.MaxScoreAccumulator.DocAndScore;
/** /**
* A {@link Collector} implementation that collects the top-scoring hits, returning them as a {@link * A {@link Collector} implementation that collects the top-scoring hits, returning them as a {@link
@ -226,13 +225,13 @@ public abstract class TopScoreDocCollector extends TopDocsCollector<ScoreDoc> {
protected void updateGlobalMinCompetitiveScore(Scorable scorer) throws IOException { protected void updateGlobalMinCompetitiveScore(Scorable scorer) throws IOException {
assert minScoreAcc != null; assert minScoreAcc != null;
DocAndScore maxMinScore = minScoreAcc.get(); long maxMinScore = minScoreAcc.getRaw();
if (maxMinScore != null) { if (maxMinScore != Long.MIN_VALUE) {
// since we tie-break on doc id and collect in doc id order we can require // since we tie-break on doc id and collect in doc id order we can require
// the next float if the global minimum score is set on a document id that is // the next float if the global minimum score is set on a document id that is
// smaller than the ids in the current leaf // smaller than the ids in the current leaf
float score = float score = MaxScoreAccumulator.toScore(maxMinScore);
docBase >= maxMinScore.docId() ? Math.nextUp(maxMinScore.score()) : maxMinScore.score(); score = docBase >= MaxScoreAccumulator.docId(maxMinScore) ? Math.nextUp(score) : score;
if (score > minCompetitiveScore) { if (score > minCompetitiveScore) {
assert hitsThresholdChecker.isThresholdReached(); assert hitsThresholdChecker.isThresholdReached();
scorer.setMinCompetitiveScore(score); scorer.setMinCompetitiveScore(score);

View File

@ -23,44 +23,28 @@ public class TestMaxScoreAccumulator extends LuceneTestCase {
public void testSimple() { public void testSimple() {
MaxScoreAccumulator acc = new MaxScoreAccumulator(); MaxScoreAccumulator acc = new MaxScoreAccumulator();
acc.accumulate(0, 0f); acc.accumulate(0, 0f);
assertEquals(0f, acc.get().score(), 0); assertEquals(0f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(0, acc.get().docId(), 0); assertEquals(0, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(10, 0f); acc.accumulate(10, 0f);
assertEquals(0f, acc.get().score(), 0); assertEquals(0f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(0, acc.get().docId(), 0); assertEquals(0, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(100, 1000f); acc.accumulate(100, 1000f);
assertEquals(1000f, acc.get().score(), 0); assertEquals(1000f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(100, acc.get().docId(), 0); assertEquals(100, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(1000, 5f); acc.accumulate(1000, 5f);
assertEquals(1000f, acc.get().score(), 0); assertEquals(1000f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(100, acc.get().docId(), 0); assertEquals(100, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(99, 1000f); acc.accumulate(99, 1000f);
assertEquals(1000f, acc.get().score(), 0); assertEquals(1000f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(99, acc.get().docId(), 0); assertEquals(99, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(1000, 1001f); acc.accumulate(1000, 1001f);
assertEquals(1001f, acc.get().score(), 0); assertEquals(1001f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(1000, acc.get().docId(), 0); assertEquals(1000, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(10, 1001f); acc.accumulate(10, 1001f);
assertEquals(1001f, acc.get().score(), 0); assertEquals(1001f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(10, acc.get().docId(), 0); assertEquals(10, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(100, 1001f); acc.accumulate(100, 1001f);
assertEquals(1001f, acc.get().score(), 0); assertEquals(1001f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(10, acc.get().docId(), 0); assertEquals(10, MaxScoreAccumulator.docId(acc.getRaw()), 0);
}
public void testRandom() {
MaxScoreAccumulator acc = new MaxScoreAccumulator();
int numDocs = atLeast(100);
int maxDocs = atLeast(10000);
MaxScoreAccumulator.DocAndScore max = new MaxScoreAccumulator.DocAndScore(-1, -1);
for (int i = 0; i < numDocs; i++) {
MaxScoreAccumulator.DocAndScore res =
new MaxScoreAccumulator.DocAndScore(random().nextInt(maxDocs), random().nextFloat());
acc.accumulate(res.docId(), res.score());
if (res.compareTo(max) > 0) {
max = res;
}
}
assertEquals(max, acc.get());
} }
} }

View File

@ -519,47 +519,47 @@ public class TestTopDocsCollector extends LuceneTestCase {
scorer.score = 3; scorer.score = 3;
leafCollector.collect(0); leafCollector.collect(0);
assertNull(minValueChecker.get()); assertEquals(Long.MIN_VALUE, minValueChecker.getRaw());
assertNull(scorer.minCompetitiveScore); assertNull(scorer.minCompetitiveScore);
scorer2.score = 6; scorer2.score = 6;
leafCollector2.collect(0); leafCollector2.collect(0);
assertNull(minValueChecker.get()); assertEquals(Long.MIN_VALUE, minValueChecker.getRaw());
assertNull(scorer2.minCompetitiveScore); assertNull(scorer2.minCompetitiveScore);
scorer.score = 2; scorer.score = 2;
leafCollector.collect(1); leafCollector.collect(1);
assertEquals(2f, minValueChecker.get().score(), 0f); assertEquals(2f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(Math.nextUp(2f), scorer.minCompetitiveScore, 0f); assertEquals(Math.nextUp(2f), scorer.minCompetitiveScore, 0f);
assertNull(scorer2.minCompetitiveScore); assertNull(scorer2.minCompetitiveScore);
scorer2.score = 9; scorer2.score = 9;
leafCollector2.collect(1); leafCollector2.collect(1);
assertEquals(6f, minValueChecker.get().score(), 0f); assertEquals(6f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(Math.nextUp(2f), scorer.minCompetitiveScore, 0f); assertEquals(Math.nextUp(2f), scorer.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(6f), scorer2.minCompetitiveScore, 0f); assertEquals(Math.nextUp(6f), scorer2.minCompetitiveScore, 0f);
scorer2.score = 7; scorer2.score = 7;
leafCollector2.collect(2); leafCollector2.collect(2);
assertEquals(minValueChecker.get().score(), 7f, 0f); assertEquals(MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 7f, 0f);
assertEquals(Math.nextUp(2f), scorer.minCompetitiveScore, 0f); assertEquals(Math.nextUp(2f), scorer.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f); assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f);
scorer2.score = 1; scorer2.score = 1;
leafCollector2.collect(3); leafCollector2.collect(3);
assertEquals(minValueChecker.get().score(), 7f, 0f); assertEquals(MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 7f, 0f);
assertEquals(Math.nextUp(2f), scorer.minCompetitiveScore, 0f); assertEquals(Math.nextUp(2f), scorer.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f); assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f);
scorer.score = 10; scorer.score = 10;
leafCollector.collect(2); leafCollector.collect(2);
assertEquals(minValueChecker.get().score(), 7f, 0f); assertEquals(MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 7f, 0f);
assertEquals(7f, scorer.minCompetitiveScore, 0f); assertEquals(7f, scorer.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f); assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f);
scorer.score = 11; scorer.score = 11;
leafCollector.collect(3); leafCollector.collect(3);
assertEquals(minValueChecker.get().score(), 10, 0f); assertEquals(MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 10, 0f);
assertEquals(Math.nextUp(10f), scorer.minCompetitiveScore, 0f); assertEquals(Math.nextUp(10f), scorer.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f); assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f);
@ -571,19 +571,19 @@ public class TestTopDocsCollector extends LuceneTestCase {
scorer3.score = 1f; scorer3.score = 1f;
leafCollector3.collect(0); leafCollector3.collect(0);
assertEquals(10f, minValueChecker.get().score(), 0f); assertEquals(10f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(Math.nextUp(10f), scorer3.minCompetitiveScore, 0f); assertEquals(Math.nextUp(10f), scorer3.minCompetitiveScore, 0f);
scorer.score = 11; scorer.score = 11;
leafCollector.collect(4); leafCollector.collect(4);
assertEquals(11f, minValueChecker.get().score(), 0f); assertEquals(11f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(Math.nextUp(11f), scorer.minCompetitiveScore, 0f); assertEquals(Math.nextUp(11f), scorer.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f); assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(10f), scorer3.minCompetitiveScore, 0f); assertEquals(Math.nextUp(10f), scorer3.minCompetitiveScore, 0f);
scorer3.score = 2f; scorer3.score = 2f;
leafCollector3.collect(1); leafCollector3.collect(1);
assertEquals(minValueChecker.get().score(), 11f, 0f); assertEquals(MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 11f, 0f);
assertEquals(Math.nextUp(11f), scorer.minCompetitiveScore, 0f); assertEquals(Math.nextUp(11f), scorer.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f); assertEquals(Math.nextUp(7f), scorer2.minCompetitiveScore, 0f);
assertEquals(Math.nextUp(11f), scorer3.minCompetitiveScore, 0f); assertEquals(Math.nextUp(11f), scorer3.minCompetitiveScore, 0f);

View File

@ -577,47 +577,47 @@ public class TestTopFieldCollector extends LuceneTestCase {
scorer.score = 3; scorer.score = 3;
leafCollector.collect(0); leafCollector.collect(0);
assertNull(minValueChecker.get()); assertEquals(Long.MIN_VALUE, minValueChecker.getRaw());
assertNull(scorer.minCompetitiveScore); assertNull(scorer.minCompetitiveScore);
scorer2.score = 6; scorer2.score = 6;
leafCollector2.collect(0); leafCollector2.collect(0);
assertNull(minValueChecker.get()); assertEquals(Long.MIN_VALUE, minValueChecker.getRaw());
assertNull(scorer2.minCompetitiveScore); assertNull(scorer2.minCompetitiveScore);
scorer.score = 2; scorer.score = 2;
leafCollector.collect(1); leafCollector.collect(1);
assertEquals(2f, minValueChecker.get().score(), 0f); assertEquals(2f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(2f, scorer.minCompetitiveScore, 0f); assertEquals(2f, scorer.minCompetitiveScore, 0f);
assertNull(scorer2.minCompetitiveScore); assertNull(scorer2.minCompetitiveScore);
scorer2.score = 9; scorer2.score = 9;
leafCollector2.collect(1); leafCollector2.collect(1);
assertEquals(6f, minValueChecker.get().score(), 0f); assertEquals(6f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(2f, scorer.minCompetitiveScore, 0f); assertEquals(2f, scorer.minCompetitiveScore, 0f);
assertEquals(6f, scorer2.minCompetitiveScore, 0f); assertEquals(6f, scorer2.minCompetitiveScore, 0f);
scorer2.score = 7; scorer2.score = 7;
leafCollector2.collect(2); leafCollector2.collect(2);
assertEquals(7f, minValueChecker.get().score(), 0f); assertEquals(7f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(2f, scorer.minCompetitiveScore, 0f); assertEquals(2f, scorer.minCompetitiveScore, 0f);
assertEquals(7f, scorer2.minCompetitiveScore, 0f); assertEquals(7f, scorer2.minCompetitiveScore, 0f);
scorer2.score = 1; scorer2.score = 1;
leafCollector2.collect(3); leafCollector2.collect(3);
assertEquals(7f, minValueChecker.get().score(), 0f); assertEquals(7f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(2f, scorer.minCompetitiveScore, 0f); assertEquals(2f, scorer.minCompetitiveScore, 0f);
assertEquals(7f, scorer2.minCompetitiveScore, 0f); assertEquals(7f, scorer2.minCompetitiveScore, 0f);
scorer.score = 10; scorer.score = 10;
leafCollector.collect(2); leafCollector.collect(2);
assertEquals(7f, minValueChecker.get().score(), 0f); assertEquals(7f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(7f, scorer.minCompetitiveScore, 0f); assertEquals(7f, scorer.minCompetitiveScore, 0f);
assertEquals(7f, scorer2.minCompetitiveScore, 0f); assertEquals(7f, scorer2.minCompetitiveScore, 0f);
scorer.score = 11; scorer.score = 11;
leafCollector.collect(3); leafCollector.collect(3);
assertEquals(10f, minValueChecker.get().score(), 0f); assertEquals(10f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(10f, scorer.minCompetitiveScore, 0f); assertEquals(10f, scorer.minCompetitiveScore, 0f);
assertEquals(7f, scorer2.minCompetitiveScore, 0f); assertEquals(7f, scorer2.minCompetitiveScore, 0f);
@ -629,19 +629,19 @@ public class TestTopFieldCollector extends LuceneTestCase {
scorer3.score = 1f; scorer3.score = 1f;
leafCollector3.collect(0); leafCollector3.collect(0);
assertEquals(10f, minValueChecker.get().score(), 0f); assertEquals(10f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(10f, scorer3.minCompetitiveScore, 0f); assertEquals(10f, scorer3.minCompetitiveScore, 0f);
scorer.score = 11; scorer.score = 11;
leafCollector.collect(4); leafCollector.collect(4);
assertEquals(11f, minValueChecker.get().score(), 0f); assertEquals(11f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(11f, scorer.minCompetitiveScore, 0f); assertEquals(11f, scorer.minCompetitiveScore, 0f);
assertEquals(7f, scorer2.minCompetitiveScore, 0f); assertEquals(7f, scorer2.minCompetitiveScore, 0f);
assertEquals(10f, scorer3.minCompetitiveScore, 0f); assertEquals(10f, scorer3.minCompetitiveScore, 0f);
scorer3.score = 2f; scorer3.score = 2f;
leafCollector3.collect(1); leafCollector3.collect(1);
assertEquals(11f, minValueChecker.get().score(), 0f); assertEquals(11f, MaxScoreAccumulator.toScore(minValueChecker.getRaw()), 0f);
assertEquals(11f, scorer.minCompetitiveScore, 0f); assertEquals(11f, scorer.minCompetitiveScore, 0f);
assertEquals(7f, scorer2.minCompetitiveScore, 0f); assertEquals(7f, scorer2.minCompetitiveScore, 0f);
assertEquals(11f, scorer3.minCompetitiveScore, 0f); assertEquals(11f, scorer3.minCompetitiveScore, 0f);