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
* DocAndScore#compareTo}.
* Return the max encoded docId and score found in the two longs, following the encoding in {@link
* #accumulate}.
*/
private static long maxEncode(long v1, long v2) {
float score1 = Float.intBitsToFloat((int) (v1 >> 32));
@ -57,26 +57,15 @@ final class MaxScoreAccumulator {
acc.accumulate(encode);
}
DocAndScore get() {
long value = acc.get();
if (value == Long.MIN_VALUE) {
return null;
}
float score = Float.intBitsToFloat((int) (value >> 32));
int docId = (int) value;
return new DocAndScore(docId, score);
public static float toScore(long value) {
return Float.intBitsToFloat((int) (value >> 32));
}
record DocAndScore(int docId, float score) implements Comparable<DocAndScore> {
public static int docId(long value) {
return (int) value;
}
@Override
public int compareTo(DocAndScore o) {
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;
}
long getRaw() {
return acc.get();
}
}

View File

@ -24,7 +24,6 @@ import java.util.Objects;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.search.FieldValueHitQueue.Entry;
import org.apache.lucene.search.MaxScoreAccumulator.DocAndScore;
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
// if the local queue is not full because the threshold
// is reached.
DocAndScore maxMinScore = minScoreAcc.get();
if (maxMinScore != null && maxMinScore.score() > minCompetitiveScore) {
scorer.setMinCompetitiveScore(maxMinScore.score());
minCompetitiveScore = maxMinScore.score();
long maxMinScore = minScoreAcc.getRaw();
float score;
if (maxMinScore != Long.MIN_VALUE
&& (score = MaxScoreAccumulator.toScore(maxMinScore)) > minCompetitiveScore) {
scorer.setMinCompetitiveScore(score);
minCompetitiveScore = score;
totalHitsRelation = TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO;
}
}

View File

@ -18,7 +18,6 @@ package org.apache.lucene.search;
import java.io.IOException;
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
@ -226,13 +225,13 @@ public abstract class TopScoreDocCollector extends TopDocsCollector<ScoreDoc> {
protected void updateGlobalMinCompetitiveScore(Scorable scorer) throws IOException {
assert minScoreAcc != null;
DocAndScore maxMinScore = minScoreAcc.get();
if (maxMinScore != null) {
long maxMinScore = minScoreAcc.getRaw();
if (maxMinScore != Long.MIN_VALUE) {
// 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
// smaller than the ids in the current leaf
float score =
docBase >= maxMinScore.docId() ? Math.nextUp(maxMinScore.score()) : maxMinScore.score();
float score = MaxScoreAccumulator.toScore(maxMinScore);
score = docBase >= MaxScoreAccumulator.docId(maxMinScore) ? Math.nextUp(score) : score;
if (score > minCompetitiveScore) {
assert hitsThresholdChecker.isThresholdReached();
scorer.setMinCompetitiveScore(score);

View File

@ -23,44 +23,28 @@ public class TestMaxScoreAccumulator extends LuceneTestCase {
public void testSimple() {
MaxScoreAccumulator acc = new MaxScoreAccumulator();
acc.accumulate(0, 0f);
assertEquals(0f, acc.get().score(), 0);
assertEquals(0, acc.get().docId(), 0);
assertEquals(0f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(0, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(10, 0f);
assertEquals(0f, acc.get().score(), 0);
assertEquals(0, acc.get().docId(), 0);
assertEquals(0f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(0, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(100, 1000f);
assertEquals(1000f, acc.get().score(), 0);
assertEquals(100, acc.get().docId(), 0);
assertEquals(1000f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(100, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(1000, 5f);
assertEquals(1000f, acc.get().score(), 0);
assertEquals(100, acc.get().docId(), 0);
assertEquals(1000f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(100, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(99, 1000f);
assertEquals(1000f, acc.get().score(), 0);
assertEquals(99, acc.get().docId(), 0);
assertEquals(1000f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(99, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(1000, 1001f);
assertEquals(1001f, acc.get().score(), 0);
assertEquals(1000, acc.get().docId(), 0);
assertEquals(1001f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(1000, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(10, 1001f);
assertEquals(1001f, acc.get().score(), 0);
assertEquals(10, acc.get().docId(), 0);
assertEquals(1001f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(10, MaxScoreAccumulator.docId(acc.getRaw()), 0);
acc.accumulate(100, 1001f);
assertEquals(1001f, acc.get().score(), 0);
assertEquals(10, acc.get().docId(), 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());
assertEquals(1001f, MaxScoreAccumulator.toScore(acc.getRaw()), 0);
assertEquals(10, MaxScoreAccumulator.docId(acc.getRaw()), 0);
}
}

View File

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

View File

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