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

``

`-

`

201

``

`-

`

202

``

`-

`

203

``

`-

`

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

`