Optimize HpackStaticTable by using a perfect hash function (#12713) · netty/netty@2ed95c9 (original) (raw)
`@@ -31,8 +31,8 @@
`
31
31
` */
`
32
32
`package io.netty.handler.codec.http2;
`
33
33
``
34
``
`-
import io.netty.handler.codec.UnsupportedValueConverter;
`
35
34
`import io.netty.util.AsciiString;
`
``
35
`+
import io.netty.util.internal.PlatformDependent;
`
36
36
``
37
37
`import java.util.Arrays;
`
38
38
`import java.util.List;
`
`@@ -117,9 +117,53 @@ private static HpackHeaderField newHeaderField(String name, String value) {
`
117
117
`return new HpackHeaderField(AsciiString.cached(name), AsciiString.cached(value));
`
118
118
` }
`
119
119
``
120
``
`-
private static final CharSequenceMap STATIC_INDEX_BY_NAME = createMap();
`
``
120
`+
// The table size and bit shift are chosen so that each hash bucket contains a single header name.
`
``
121
`+
private static final int HEADER_NAMES_TABLE_SIZE = 1 << 9;
`
121
122
``
122
``
`-
private static final int MAX_SAME_NAME_FIELD_INDEX = maxSameNameFieldIndex();
`
``
123
`+
private static final int HEADER_NAMES_TABLE_SHIFT = PlatformDependent.BIG_ENDIAN_NATIVE_ORDER ? 22 : 18;
`
``
124
+
``
125
`+
// A table mapping header names to their associated indexes.
`
``
126
`+
private static final HeaderNameIndex[] HEADER_NAMES = new HeaderNameIndex[HEADER_NAMES_TABLE_SIZE];
`
``
127
`+
static {
`
``
128
`+
// Iterate through the static table in reverse order to
`
``
129
`+
// save the smallest index for a given name in the table.
`
``
130
`+
for (int index = STATIC_TABLE.size(); index > 0; index--) {
`
``
131
`+
HpackHeaderField entry = getEntry(index);
`
``
132
`+
int bucket = headerNameBucket(entry.name);
`
``
133
`+
HeaderNameIndex tableEntry = HEADER_NAMES[bucket];
`
``
134
`+
if (tableEntry != null && !equalsVariableTime(tableEntry.name, entry.name)) {
`
``
135
`+
// Can happen if AsciiString.hashCode changes
`
``
136
`+
throw new IllegalStateException("Hash bucket collision between " +
`
``
137
`+
tableEntry.name + " and " + entry.name);
`
``
138
`+
}
`
``
139
`+
HEADER_NAMES[bucket] = new HeaderNameIndex(entry.name, index, entry.value.length() == 0);
`
``
140
`+
}
`
``
141
`+
}
`
``
142
+
``
143
`+
// The table size and bit shift are chosen so that each hash bucket contains a single header.
`
``
144
`+
private static final int HEADERS_WITH_NON_EMPTY_VALUES_TABLE_SIZE = 1 << 6;
`
``
145
+
``
146
`+
private static final int HEADERS_WITH_NON_EMPTY_VALUES_TABLE_SHIFT =
`
``
147
`+
PlatformDependent.BIG_ENDIAN_NATIVE_ORDER ? 0 : 6;
`
``
148
+
``
149
`+
// A table mapping headers with non-empty values to their associated indexes.
`
``
150
`+
private static final HeaderIndex[] HEADERS_WITH_NON_EMPTY_VALUES =
`
``
151
`+
new HeaderIndex[HEADERS_WITH_NON_EMPTY_VALUES_TABLE_SIZE];
`
``
152
`+
static {
`
``
153
`+
for (int index = STATIC_TABLE.size(); index > 0; index--) {
`
``
154
`+
HpackHeaderField entry = getEntry(index);
`
``
155
`+
if (entry.value.length() > 0) {
`
``
156
`+
int bucket = headerBucket(entry.value);
`
``
157
`+
HeaderIndex tableEntry = HEADERS_WITH_NON_EMPTY_VALUES[bucket];
`
``
158
`+
if (tableEntry != null) {
`
``
159
`+
// Can happen if AsciiString.hashCode changes
`
``
160
`+
throw new IllegalStateException("Hash bucket collision between " +
`
``
161
`+
tableEntry.value + " and " + entry.value);
`
``
162
`+
}
`
``
163
`+
HEADERS_WITH_NON_EMPTY_VALUES[bucket] = new HeaderIndex(entry.name, entry.value, index);
`
``
164
`+
}
`
``
165
`+
}
`
``
166
`+
}
`
123
167
``
124
168
`/**
`
125
169
` * The number of header fields in the static table.
`
`@@ -138,82 +182,73 @@ static HpackHeaderField getEntry(int index) {
`
138
182
` * -1 if the header field name is not in the static table.
`
139
183
` */
`
140
184
`static int getIndex(CharSequence name) {
`
141
``
`-
Integer index = STATIC_INDEX_BY_NAME.get(name);
`
142
``
`-
if (index == null) {
`
143
``
`-
return NOT_FOUND;
`
144
``
`-
}
`
145
``
`-
return index;
`
``
185
`+
HeaderNameIndex entry = getEntry(name);
`
``
186
`+
return entry == null ? NOT_FOUND : entry.index;
`
146
187
` }
`
147
188
``
148
189
`/**
`
149
190
` * Returns the index value for the given header field in the static table. Returns -1 if the
`
150
191
` * header field is not in the static table.
`
151
192
` */
`
152
193
`static int getIndexInsensitive(CharSequence name, CharSequence value) {
`
153
``
`-
int index = getIndex(name);
`
154
``
`-
if (index == NOT_FOUND) {
`
``
194
`+
if (value.length() == 0) {
`
``
195
`+
HeaderNameIndex entry = getEntry(name);
`
``
196
`+
return entry == null || !entry.emptyValue ? NOT_FOUND : entry.index;
`
``
197
`+
}
`
``
198
`+
int bucket = headerBucket(value);
`
``
199
`+
HeaderIndex header = HEADERS_WITH_NON_EMPTY_VALUES[bucket];
`
``
200
`+
if (header == null) {
`
155
201
`return NOT_FOUND;
`
156
202
` }
`
157
``
-
158
``
`-
// Compare values for the first name match
`
159
``
`-
HpackHeaderField entry = getEntry(index);
`
160
``
`-
if (equalsVariableTime(value, entry.value)) {
`
161
``
`-
return index;
`
``
203
`+
if (equalsVariableTime(header.name, name) && equalsVariableTime(header.value, value)) {
`
``
204
`+
return header.index;
`
162
205
` }
`
``
206
`+
return NOT_FOUND;
`
``
207
`+
}
`
163
208
``
164
``
`-
// Note this assumes all entries for a given header field are sequential.
`
165
``
`-
index++;
`
166
``
`-
while (index <= MAX_SAME_NAME_FIELD_INDEX) {
`
167
``
`-
entry = getEntry(index);
`
168
``
`-
if (!equalsVariableTime(name, entry.name)) {
`
169
``
`-
// As far as fields with the same name are placed in the table sequentially
`
170
``
`-
// and INDEX_BY_NAME returns index of the fist position, - it's safe to
`
171
``
`-
// exit immediately.
`
172
``
`-
return NOT_FOUND;
`
173
``
`-
}
`
174
``
`-
if (equalsVariableTime(value, entry.value)) {
`
175
``
`-
return index;
`
176
``
`-
}
`
177
``
`-
index++;
`
``
209
`+
private static HeaderNameIndex getEntry(CharSequence name) {
`
``
210
`+
int bucket = headerNameBucket(name);
`
``
211
`+
HeaderNameIndex entry = HEADER_NAMES[bucket];
`
``
212
`+
if (entry == null) {
`
``
213
`+
return null;
`
178
214
` }
`
``
215
`+
return equalsVariableTime(entry.name, name) ? entry : null;
`
``
216
`+
}
`
179
217
``
180
``
`-
return NOT_FOUND;
`
``
218
`+
private static int headerNameBucket(CharSequence name) {
`
``
219
`+
return bucket(name, HEADER_NAMES_TABLE_SHIFT, HEADER_NAMES_TABLE_SIZE - 1);
`
181
220
` }
`
182
221
``
183
``
`-
// create a map CharSequenceMap header name to index value to allow quick lookup
`
184
``
`-
private static CharSequenceMap createMap() {
`
185
``
`-
int length = STATIC_TABLE.size();
`
186
``
`-
@SuppressWarnings("unchecked")
`
187
``
`-
CharSequenceMap ret = new CharSequenceMap(true,
`
188
``
`-
UnsupportedValueConverter.instance(), length);
`
189
``
`-
// Iterate through the static table in reverse order to
`
190
``
`-
// save the smallest index for a given name in the map.
`
191
``
`-
for (int index = length; index > 0; index--) {
`
192
``
`-
HpackHeaderField entry = getEntry(index);
`
193
``
`-
CharSequence name = entry.name;
`
194
``
`-
ret.set(name, index);
`
``
222
`+
private static int headerBucket(CharSequence value) {
`
``
223
`+
return bucket(value, HEADERS_WITH_NON_EMPTY_VALUES_TABLE_SHIFT, HEADERS_WITH_NON_EMPTY_VALUES_TABLE_SIZE - 1);
`
``
224
`+
}
`
``
225
+
``
226
`+
private static int bucket(CharSequence s, int shift, int mask) {
`
``
227
`+
return (AsciiString.hashCode(s) >> shift) & mask;
`
``
228
`+
}
`
``
229
+
``
230
`+
private static final class HeaderNameIndex {
`
``
231
`+
final CharSequence name;
`
``
232
`+
final int index;
`
``
233
`+
final boolean emptyValue;
`
``
234
+
``
235
`+
HeaderNameIndex(CharSequence name, int index, boolean emptyValue) {
`
``
236
`+
this.name = name;
`
``
237
`+
this.index = index;
`
``
238
`+
this.emptyValue = emptyValue;
`
195
239
` }
`
196
``
`-
return ret;
`
197
240
` }
`
198
241
``
199
``
`-
/**
`
200
``
`-
- Returns the last position in the array that contains multiple
`
201
``
`-
- fields with the same name. Starting from this position, all
`
202
``
`-
- names are unique. Similar to {@link #getIndexInsensitive(CharSequence, CharSequence)} method
`
203
``
`-
- assumes all entries for a given header field are sequential
`
204
``
`-
*/
`
205
``
`-
private static int maxSameNameFieldIndex() {
`
206
``
`-
final int length = STATIC_TABLE.size();
`
207
``
`-
HpackHeaderField cursor = getEntry(length);
`
208
``
`-
for (int index = length - 1; index > 0; index--) {
`
209
``
`-
HpackHeaderField entry = getEntry(index);
`
210
``
`-
if (equalsVariableTime(entry.name, cursor.name)) {
`
211
``
`-
return index + 1;
`
212
``
`-
} else {
`
213
``
`-
cursor = entry;
`
214
``
`-
}
`
``
242
`+
private static final class HeaderIndex {
`
``
243
`+
final CharSequence name;
`
``
244
`+
final CharSequence value;
`
``
245
`+
final int index;
`
``
246
+
``
247
`+
HeaderIndex(CharSequence name, CharSequence value, int index) {
`
``
248
`+
this.name = name;
`
``
249
`+
this.value = value;
`
``
250
`+
this.index = index;
`
215
251
` }
`
216
``
`-
return length;
`
217
252
` }
`
218
253
``
219
254
`// singleton
`