Skip to content

Commit 6196ef5

Browse files
authored
Some optimizations for constant blocks (#132456)
This change introduces several optimizations for constant blocks: 1. When reading keyword fields that are index-sorted and the query range is large enough, we can return a constant block of BytesRef values to enable downstream optimizations. 2. Enable shortcuts for BytesRefBlockHash and BytesRefLongBlockHash when handling constant blocks. These optimizations are quick wins for time-series aggregations. 3. Enable shortcuts for CountAggregator and ValuesAggregator for constant blocks
1 parent 05a741b commit 6196ef5

File tree

9 files changed

+308
-43
lines changed

9 files changed

+308
-43
lines changed

docs/changelog/132456.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 132456
2+
summary: Some optimizations for constant blocks
3+
area: ES|QL
4+
type: enhancement
5+
issues: []

x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/BytesRefBlockHash.java

Lines changed: 16 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountGroupingAggregatorFunction.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,13 @@ private void addRawInput(int positionOffset, IntBigArrayBlock groups, Block valu
154154
* This method is called for count all.
155155
*/
156156
private void addRawInput(IntVector groups) {
157-
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
158-
int groupId = groups.getInt(groupPosition);
159-
state.increment(groupId, 1);
157+
if (groups.isConstant()) {
158+
state.increment(groups.getInt(0), groups.getPositionCount());
159+
} else {
160+
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
161+
int groupId = groups.getInt(groupPosition);
162+
state.increment(groupId, 1);
163+
}
160164
}
161165
}
162166

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/ValuesBytesRefAggregators.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,21 @@ static void addOrdinalInputVector(
178178
IntVector ordinalIds,
179179
IntVector hashIds
180180
) {
181-
for (int p = 0; p < groupIds.getPositionCount(); p++) {
182-
int groupId = groupIds.getInt(p);
183-
int ord = ordinalIds.getInt(p + positionOffset);
184-
state.addValueOrdinal(groupId, hashIds.getInt(ord));
181+
if (groupIds.isConstant() && hashIds.isConstant()) {
182+
state.addValueOrdinal(groupIds.getInt(0), hashIds.getInt(0));
183+
return;
184+
}
185+
int lastGroup = groupIds.getInt(0);
186+
int lastOrd = ordinalIds.getInt(positionOffset);
187+
state.addValueOrdinal(lastGroup, hashIds.getInt(lastOrd));
188+
for (int p = 1; p < groupIds.getPositionCount(); p++) {
189+
final int nextGroup = groupIds.getInt(p);
190+
final int nextOrd = ordinalIds.getInt(p + positionOffset);
191+
if (nextGroup != lastGroup || nextOrd != lastOrd) {
192+
lastGroup = nextGroup;
193+
lastOrd = nextOrd;
194+
state.addValueOrdinal(lastGroup, hashIds.getInt(lastOrd));
195+
}
185196
}
186197
}
187198

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BytesRefLongBlockHash.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,40 @@ public void add(IntBlock bytesHashes, LongBlock longsBlock, GroupingAggregatorFu
100100
public IntVector add(IntVector bytesHashes, LongVector longsVector) {
101101
int positions = bytesHashes.getPositionCount();
102102
final int[] ords = new int[positions];
103-
for (int i = 0; i < positions; i++) {
104-
ords[i] = Math.toIntExact(hashOrdToGroup(finalHash.add(bytesHashes.getInt(i), longsVector.getLong(i))));
103+
int lastByte = bytesHashes.getInt(0);
104+
long lastLong = longsVector.getLong(0);
105+
ords[0] = Math.toIntExact(hashOrdToGroup(finalHash.add(lastByte, lastLong)));
106+
boolean constant = true;
107+
if (bytesHashes.isConstant()) {
108+
for (int i = 1; i < positions; i++) {
109+
final long nextLong = longsVector.getLong(i);
110+
if (nextLong == lastLong) {
111+
ords[i] = ords[i - 1];
112+
} else {
113+
ords[i] = Math.toIntExact(hashOrdToGroup(finalHash.add(lastByte, nextLong)));
114+
lastLong = nextLong;
115+
constant = false;
116+
}
117+
}
118+
} else {
119+
for (int i = 1; i < positions; i++) {
120+
final int nextByte = bytesHashes.getInt(i);
121+
final long nextLong = longsVector.getLong(i);
122+
if (nextByte == lastByte && nextLong == lastLong) {
123+
ords[i] = ords[i - 1];
124+
} else {
125+
ords[i] = Math.toIntExact(hashOrdToGroup(finalHash.add(nextByte, nextLong)));
126+
lastByte = nextByte;
127+
lastLong = nextLong;
128+
constant = false;
129+
}
130+
}
131+
}
132+
if (constant) {
133+
return blockFactory.newConstantIntVector(ords[0], positions);
134+
} else {
135+
return blockFactory.newIntArrayVector(ords, positions);
105136
}
106-
return blockFactory.newIntArrayVector(ords, positions);
107137
}
108138

109139
@Override

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/X-BlockHash.java.st

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ final class $Type$BlockHash extends BlockHash {
109109
*/
110110
IntVector add($Type$Vector vector) {
111111
$if(BytesRef)$
112+
if (vector.isConstant()) {
113+
BytesRef v = vector.getBytesRef(0, new BytesRef());
114+
int groupId = Math.toIntExact(hashOrdToGroupNullReserved(hash.add(v)));
115+
return blockFactory.newConstantIntVector(groupId, vector.getPositionCount());
116+
}
112117
var ordinals = vector.asOrdinals();
113118
if (ordinals != null) {
114119
return addOrdinalsVector(ordinals);
@@ -168,15 +173,18 @@ $endif$
168173
$if(BytesRef)$
169174
private IntVector addOrdinalsVector(OrdinalBytesRefVector inputBlock) {
170175
IntVector inputOrds = inputBlock.getOrdinalsVector();
171-
try (
172-
var builder = blockFactory.newIntVectorBuilder(inputOrds.getPositionCount());
173-
var hashOrds = add(inputBlock.getDictionaryVector())
174-
) {
175-
for (int p = 0; p < inputOrds.getPositionCount(); p++) {
176-
int ord = hashOrds.getInt(inputOrds.getInt(p));
177-
builder.appendInt(ord);
176+
try (var hashOrds = add(inputBlock.getDictionaryVector())) {
177+
if (inputOrds.isConstant()) {
178+
int ord = hashOrds.getInt(inputOrds.getInt(0));
179+
return blockFactory.newConstantIntVector(ord, inputOrds.getPositionCount());
180+
}
181+
try (var builder = blockFactory.newIntVectorBuilder(inputOrds.getPositionCount())) {
182+
for (int p = 0; p < inputOrds.getPositionCount(); p++) {
183+
int ord = hashOrds.getInt(inputOrds.getInt(p));
184+
builder.appendInt(ord);
185+
}
186+
return builder.build();
178187
}
179-
return builder.build();
180188
}
181189
}
182190

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/read/SingletonOrdinalsBuilder.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.compute.data.BytesRefBlock;
1616
import org.elasticsearch.compute.data.BytesRefVector;
1717
import org.elasticsearch.compute.data.IntBlock;
18+
import org.elasticsearch.compute.data.IntVector;
1819
import org.elasticsearch.compute.data.OrdinalBytesRefBlock;
1920
import org.elasticsearch.compute.operator.BreakingBytesRefBuilder;
2021
import org.elasticsearch.core.Releasable;
@@ -64,6 +65,39 @@ public SingletonOrdinalsBuilder endPositionEntry() {
6465
throw new UnsupportedOperationException("should only have one value per doc");
6566
}
6667

68+
private BytesRefBlock tryBuildConstantBlock() {
69+
if (minOrd != maxOrd) {
70+
return null;
71+
}
72+
for (int ord : ords) {
73+
if (ord == -1) {
74+
return null;
75+
}
76+
}
77+
final BytesRef v;
78+
try {
79+
v = BytesRef.deepCopyOf(docValues.lookupOrd(minOrd));
80+
} catch (IOException e) {
81+
throw new UncheckedIOException("failed to lookup ordinals", e);
82+
}
83+
BytesRefVector bytes = null;
84+
IntVector ordinals = null;
85+
boolean success = false;
86+
try {
87+
bytes = blockFactory.newConstantBytesRefVector(v, 1);
88+
ordinals = blockFactory.newConstantIntVector(0, ords.length);
89+
// Ideally, we would return a ConstantBytesRefVector, but we return an ordinal constant block instead
90+
// to ensure ordinal optimizations are applied when constant optimization is not available.
91+
final var result = new OrdinalBytesRefBlock(ordinals.asBlock(), bytes);
92+
success = true;
93+
return result;
94+
} finally {
95+
if (success == false) {
96+
Releasables.close(bytes, ordinals);
97+
}
98+
}
99+
}
100+
67101
BytesRefBlock buildOrdinal() {
68102
int valueCount = maxOrd - minOrd + 1;
69103
long breakerSize = ordsSize(valueCount);
@@ -172,6 +206,10 @@ public long estimatedBytes() {
172206

173207
@Override
174208
public BytesRefBlock build() {
209+
var constantBlock = tryBuildConstantBlock();
210+
if (constantBlock != null) {
211+
return constantBlock;
212+
}
175213
return shouldBuildOrdinalsBlock() ? buildOrdinal() : buildRegularBlock();
176214
}
177215

x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTests.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,4 +1715,76 @@ private BlockHash buildBlockHash(int emitBatchSize, Block... values) {
17151715
? new PackedValuesBlockHash(specs, blockFactory, emitBatchSize)
17161716
: BlockHash.build(specs, blockFactory, emitBatchSize, true);
17171717
}
1718+
1719+
public void testConstant() {
1720+
try (
1721+
var hash = new BytesRefLongBlockHash(blockFactory, 0, 1, false, randomIntBetween(1, 1000));
1722+
var bytesHash = new BytesRefBlockHash(0, blockFactory)
1723+
) {
1724+
int iters = between(1, 20);
1725+
for (int i = 0; i < iters; i++) {
1726+
final BytesRefBlock bytes;
1727+
final LongBlock longs;
1728+
final boolean constantInput = randomBoolean();
1729+
final int positions = randomIntBetween(1, 100);
1730+
if (constantInput) {
1731+
bytes = blockFactory.newConstantBytesRefBlockWith(new BytesRef(randomAlphaOfLength(10)), positions);
1732+
if (randomBoolean()) {
1733+
try (IntVector hashIds = bytesHash.add(bytes.asVector())) {
1734+
assertTrue(hashIds.isConstant());
1735+
}
1736+
}
1737+
if (randomBoolean()) {
1738+
longs = blockFactory.newConstantLongBlockWith(randomNonNegativeLong(), positions);
1739+
} else {
1740+
long value = randomNonNegativeLong();
1741+
try (var builder = blockFactory.newLongVectorFixedBuilder(positions)) {
1742+
for (int p = 0; p < positions; p++) {
1743+
builder.appendLong(value);
1744+
}
1745+
longs = builder.build().asBlock();
1746+
}
1747+
}
1748+
} else {
1749+
try (var builder = blockFactory.newBytesRefBlockBuilder(positions)) {
1750+
for (int p = 0; p < positions; p++) {
1751+
builder.appendBytesRef(new BytesRef(randomAlphaOfLength(10)));
1752+
}
1753+
bytes = builder.build();
1754+
}
1755+
try (var builder = blockFactory.newLongVectorFixedBuilder(positions)) {
1756+
for (int p = 0; p < positions; p++) {
1757+
builder.appendLong(randomNonNegativeLong());
1758+
}
1759+
longs = builder.build().asBlock();
1760+
}
1761+
}
1762+
try (Page page = new Page(bytes, longs)) {
1763+
hash.add(page, new GroupingAggregatorFunction.AddInput() {
1764+
@Override
1765+
public void add(int positionOffset, IntArrayBlock groupIds) {
1766+
fail("should not call IntArrayBlock");
1767+
}
1768+
1769+
@Override
1770+
public void add(int positionOffset, IntBigArrayBlock groupIds) {
1771+
fail("should not call IntBigArrayBlock");
1772+
}
1773+
1774+
@Override
1775+
public void add(int positionOffset, IntVector groupIds) {
1776+
if (constantInput) {
1777+
assertTrue(groupIds.isConstant());
1778+
}
1779+
}
1780+
1781+
@Override
1782+
public void close() {
1783+
1784+
}
1785+
});
1786+
}
1787+
}
1788+
}
1789+
}
17181790
}

0 commit comments

Comments
 (0)