Extract HPACK Encoder Hash Map to a separate file (#26973)

* comments

* Extract hash map from hpack encoder

* Automated change: Fix sanity tests

* x

* x

* Automated change: Fix sanity tests

Co-authored-by: ctiller <ctiller@users.noreply.github.com>
pull/27110/head
Craig Tiller 3 years ago committed by GitHub
parent 0001d6873d
commit 152b79144c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      BUILD
  2. 36
      CMakeLists.txt
  3. 12
      build_autogenerated.yaml
  4. 2
      gRPC-C++.podspec
  5. 2
      gRPC-Core.podspec
  6. 1
      grpc.gemspec
  7. 1
      package.xml
  8. 187
      src/core/ext/transport/chttp2/transport/hpack_encoder.cc
  9. 97
      src/core/ext/transport/chttp2/transport/hpack_encoder.h
  10. 107
      src/core/ext/transport/chttp2/transport/hpack_encoder_index.h
  11. 13
      test/core/transport/chttp2/BUILD
  12. 61
      test/core/transport/chttp2/hpack_encoder_index_test.cc
  13. 1
      tools/doxygen/Doxyfile.c++.internal
  14. 1
      tools/doxygen/Doxyfile.core.internal
  15. 24
      tools/run_tests/generated/tests.json

15
BUILD

@ -2580,6 +2580,20 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "hpack_encoder_index",
hdrs = [
"src/core/ext/transport/chttp2/transport/hpack_encoder_index.h",
],
external_deps = [
"absl/types:optional",
],
language = "c++",
deps = [
"gpr_platform",
],
)
grpc_cc_library(
name = "grpc_transport_chttp2",
srcs = [
@ -2647,6 +2661,7 @@ grpc_cc_library(
"grpc_http_filters",
"grpc_trace",
"grpc_transport_chttp2_alpn",
"hpack_encoder_index",
"match",
"popularity_count",
],

36
CMakeLists.txt generated

@ -884,6 +884,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx head_of_line_blocking_bad_client_test)
add_dependencies(buildtests_cxx headers_bad_client_test)
add_dependencies(buildtests_cxx health_service_end2end_test)
add_dependencies(buildtests_cxx hpack_encoder_index_test)
add_dependencies(buildtests_cxx http2_client)
add_dependencies(buildtests_cxx hybrid_end2end_test)
add_dependencies(buildtests_cxx init_test)
@ -11850,6 +11851,41 @@ target_link_libraries(health_service_end2end_test
)
endif()
if(gRPC_BUILD_TESTS)
add_executable(hpack_encoder_index_test
test/core/transport/chttp2/hpack_encoder_index_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(hpack_encoder_index_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(hpack_encoder_index_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
absl::optional
)
endif()
if(gRPC_BUILD_TESTS)

@ -502,6 +502,7 @@ libs:
- src/core/ext/transport/chttp2/transport/frame_settings.h
- src/core/ext/transport/chttp2/transport/frame_window_update.h
- src/core/ext/transport/chttp2/transport/hpack_encoder.h
- src/core/ext/transport/chttp2/transport/hpack_encoder_index.h
- src/core/ext/transport/chttp2/transport/hpack_parser.h
- src/core/ext/transport/chttp2/transport/hpack_table.h
- src/core/ext/transport/chttp2/transport/http2_settings.h
@ -1734,6 +1735,7 @@ libs:
- src/core/ext/transport/chttp2/transport/frame_settings.h
- src/core/ext/transport/chttp2/transport/frame_window_update.h
- src/core/ext/transport/chttp2/transport/hpack_encoder.h
- src/core/ext/transport/chttp2/transport/hpack_encoder_index.h
- src/core/ext/transport/chttp2/transport/hpack_parser.h
- src/core/ext/transport/chttp2/transport/hpack_table.h
- src/core/ext/transport/chttp2/transport/http2_settings.h
@ -5596,6 +5598,16 @@ targets:
- test/cpp/end2end/test_service_impl.cc
deps:
- grpc++_test_util
- name: hpack_encoder_index_test
gtest: true
build: test
language: c++
headers:
- src/core/ext/transport/chttp2/transport/hpack_encoder_index.h
src:
- test/core/transport/chttp2/hpack_encoder_index_test.cc
deps:
- absl/types:optional
- name: hpack_parser_fuzzer_test
build: fuzzer
language: c++

2
gRPC-C++.podspec generated

@ -283,6 +283,7 @@ Pod::Spec.new do |s|
'src/core/ext/transport/chttp2/transport/frame_settings.h',
'src/core/ext/transport/chttp2/transport/frame_window_update.h',
'src/core/ext/transport/chttp2/transport/hpack_encoder.h',
'src/core/ext/transport/chttp2/transport/hpack_encoder_index.h',
'src/core/ext/transport/chttp2/transport/hpack_parser.h',
'src/core/ext/transport/chttp2/transport/hpack_table.h',
'src/core/ext/transport/chttp2/transport/http2_settings.h',
@ -945,6 +946,7 @@ Pod::Spec.new do |s|
'src/core/ext/transport/chttp2/transport/frame_settings.h',
'src/core/ext/transport/chttp2/transport/frame_window_update.h',
'src/core/ext/transport/chttp2/transport/hpack_encoder.h',
'src/core/ext/transport/chttp2/transport/hpack_encoder_index.h',
'src/core/ext/transport/chttp2/transport/hpack_parser.h',
'src/core/ext/transport/chttp2/transport/hpack_table.h',
'src/core/ext/transport/chttp2/transport/http2_settings.h',

2
gRPC-Core.podspec generated

@ -375,6 +375,7 @@ Pod::Spec.new do |s|
'src/core/ext/transport/chttp2/transport/frame_window_update.h',
'src/core/ext/transport/chttp2/transport/hpack_encoder.cc',
'src/core/ext/transport/chttp2/transport/hpack_encoder.h',
'src/core/ext/transport/chttp2/transport/hpack_encoder_index.h',
'src/core/ext/transport/chttp2/transport/hpack_parser.cc',
'src/core/ext/transport/chttp2/transport/hpack_parser.h',
'src/core/ext/transport/chttp2/transport/hpack_table.cc',
@ -1532,6 +1533,7 @@ Pod::Spec.new do |s|
'src/core/ext/transport/chttp2/transport/frame_settings.h',
'src/core/ext/transport/chttp2/transport/frame_window_update.h',
'src/core/ext/transport/chttp2/transport/hpack_encoder.h',
'src/core/ext/transport/chttp2/transport/hpack_encoder_index.h',
'src/core/ext/transport/chttp2/transport/hpack_parser.h',
'src/core/ext/transport/chttp2/transport/hpack_table.h',
'src/core/ext/transport/chttp2/transport/http2_settings.h',

1
grpc.gemspec generated

@ -288,6 +288,7 @@ Gem::Specification.new do |s|
s.files += %w( src/core/ext/transport/chttp2/transport/frame_window_update.h )
s.files += %w( src/core/ext/transport/chttp2/transport/hpack_encoder.cc )
s.files += %w( src/core/ext/transport/chttp2/transport/hpack_encoder.h )
s.files += %w( src/core/ext/transport/chttp2/transport/hpack_encoder_index.h )
s.files += %w( src/core/ext/transport/chttp2/transport/hpack_parser.cc )
s.files += %w( src/core/ext/transport/chttp2/transport/hpack_parser.h )
s.files += %w( src/core/ext/transport/chttp2/transport/hpack_table.cc )

1
package.xml generated

@ -268,6 +268,7 @@
<file baseinstalldir="/" name="src/core/ext/transport/chttp2/transport/frame_window_update.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/transport/chttp2/transport/hpack_encoder.cc" role="src" />
<file baseinstalldir="/" name="src/core/ext/transport/chttp2/transport/hpack_encoder.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/transport/chttp2/transport/hpack_encoder_index.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/transport/chttp2/transport/hpack_parser.cc" role="src" />
<file baseinstalldir="/" name="src/core/ext/transport/chttp2/transport/hpack_parser.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/transport/chttp2/transport/hpack_table.cc" role="src" />

@ -69,156 +69,6 @@ namespace {
constexpr size_t kMaxDecoderSpaceUsage = 512;
constexpr size_t kDataFrameHeaderSize = 9;
/* The hpack index we encode over the wire. Meaningful to the hpack encoder and
parser on the remote end as well as HTTP2. *Not* the same as
HpackEncoderSlotHash, which is only meaningful to the hpack encoder
implementation (HpackEncoderSlotHash is used for the hashtable implementation
when mapping from metadata to HpackEncoderIndex. */
typedef uint32_t HpackEncoderIndex;
/* Internal-table bookkeeping (*not* the hpack index). */
typedef uint32_t HpackEncoderSlotHash;
struct SliceRefComparator {
typedef grpc_slice_refcount* Type;
static grpc_slice_refcount* Null() { return nullptr; }
static bool IsNull(const grpc_slice_refcount* sref) {
return sref == nullptr;
}
static bool Equals(const grpc_slice_refcount* s1,
const grpc_slice_refcount* s2) {
return s1 == s2;
}
static void Ref(grpc_slice_refcount* sref) {
GPR_DEBUG_ASSERT(sref != nullptr);
sref->Ref();
}
static void Unref(grpc_slice_refcount* sref) {
GPR_DEBUG_ASSERT(sref != nullptr);
sref->Unref();
}
};
struct MetadataComparator {
typedef grpc_mdelem Type;
static grpc_mdelem Null() { return {0}; }
static bool IsNull(const grpc_mdelem md) { return md.payload == 0; }
static bool Equals(const grpc_mdelem md1, const grpc_mdelem md2) {
return md1.payload == md2.payload;
}
static void Ref(grpc_mdelem md) {
GPR_DEBUG_ASSERT(md.payload != 0);
GRPC_MDELEM_REF(md);
}
static void Unref(grpc_mdelem md) {
GPR_DEBUG_ASSERT(md.payload != 0);
GRPC_MDELEM_UNREF(md);
}
};
/* Index table management */
template <typename Hashtable>
static HpackEncoderIndex HpackIndex(const Hashtable* hashtable,
HpackEncoderSlotHash hash_index) {
return hashtable[hash_index].index;
}
template <typename ValueType, typename Hashtable>
static const ValueType& GetEntry(const Hashtable* hashtable,
HpackEncoderSlotHash hash_index) {
return hashtable[hash_index].value;
}
template <typename Cmp, typename Hashtable>
static bool TableEmptyAt(const Hashtable* hashtable,
HpackEncoderSlotHash hash_index) {
return Cmp::Equals(hashtable[hash_index].value, Cmp::Null());
}
template <typename Cmp, typename Hashtable, typename ValueType>
static bool Matches(const Hashtable* hashtable, const ValueType& value,
HpackEncoderSlotHash hash_index) {
return Cmp::Equals(value, hashtable[hash_index].value);
}
template <typename Hashtable>
static void UpdateIndex(Hashtable* hashtable, HpackEncoderSlotHash hash_index,
HpackEncoderIndex hpack_index) {
hashtable[hash_index].index = hpack_index;
}
template <typename Hashtable, typename ValueType>
static void SetIndex(Hashtable* hashtable, HpackEncoderSlotHash hash_index,
const ValueType& value, HpackEncoderIndex hpack_index) {
hashtable[hash_index].value = value;
UpdateIndex(hashtable, hash_index, hpack_index);
}
template <typename Cmp, typename Hashtable, typename ValueType>
static bool GetMatchingIndex(Hashtable* hashtable, const ValueType& value,
uint32_t value_hash, HpackEncoderIndex* index) {
const HpackEncoderSlotHash cuckoo_first = HASH_FRAGMENT_2(value_hash);
if (Matches<Cmp>(hashtable, value, cuckoo_first)) {
*index = HpackIndex(hashtable, cuckoo_first);
return true;
}
#if GRPC_HPACK_ENCODER_USE_CUCKOO_HASH
const HpackEncoderSlotHash cuckoo_second = HASH_FRAGMENT_3(value_hash);
if (Matches<Cmp>(hashtable, value, cuckoo_second)) {
*index = HpackIndex(hashtable, cuckoo_second);
return true;
}
#endif
return false;
}
template <typename Cmp, typename Hashtable, typename ValueType>
static ValueType ReplaceOlderIndex(Hashtable* hashtable, const ValueType& value,
HpackEncoderSlotHash hash_index_a,
HpackEncoderSlotHash hash_index_b,
HpackEncoderIndex new_index) {
const HpackEncoderIndex hpack_idx_a = hashtable[hash_index_a].index;
const HpackEncoderIndex hpack_idx_b = hashtable[hash_index_b].index;
const HpackEncoderSlotHash id =
hpack_idx_a < hpack_idx_b ? hash_index_a : hash_index_b;
ValueType old = GetEntry<typename Cmp::Type>(hashtable, id);
SetIndex(hashtable, id, value, new_index);
return old;
}
template <typename Cmp, typename Hashtable, typename ValueType>
static void UpdateAddOrEvict(Hashtable hashtable, const ValueType& value,
uint32_t value_hash, HpackEncoderIndex new_index) {
const HpackEncoderSlotHash cuckoo_first = HASH_FRAGMENT_2(value_hash);
if (Matches<Cmp>(hashtable, value, cuckoo_first)) {
UpdateIndex(hashtable, cuckoo_first, new_index);
return;
}
if (TableEmptyAt<Cmp>(hashtable, cuckoo_first)) {
Cmp::Ref(value);
SetIndex(hashtable, cuckoo_first, value, new_index);
return;
}
#if GRPC_HPACK_ENCODER_USE_CUCKOO_HASH
const HpackEncoderSlotHash cuckoo_second = HASH_FRAGMENT_3(value_hash);
if (Matches<Cmp>(hashtable, value, cuckoo_second)) {
UpdateIndex(hashtable, cuckoo_second, new_index);
return;
}
Cmp::Ref(value);
if (TableEmptyAt<Cmp>(hashtable, cuckoo_second)) {
SetIndex(hashtable, cuckoo_second, value, new_index);
return;
}
Cmp::Unref(ReplaceOlderIndex<Cmp>(hashtable, value, cuckoo_first,
cuckoo_second, new_index));
#else
ValueType old = GetEntry<typename Cmp::Type>(hashtable, cuckoo_first);
SetIndex(hashtable, cuckoo_first, value, new_index);
Cmp::Unref(old);
#endif
}
} /* namespace */
struct framer_state {
@ -396,8 +246,8 @@ static uint32_t prepare_space_for_new_elem(grpc_chttp2_hpack_compressor* c,
static void AddKeyWithIndex(grpc_chttp2_hpack_compressor* c,
grpc_slice_refcount* key_ref, uint32_t new_index,
uint32_t key_hash) {
UpdateAddOrEvict<SliceRefComparator>(c->key_table.entries, key_ref, key_hash,
new_index);
c->key_table->Insert(
grpc_chttp2_hpack_compressor::KeySliceRef(key_ref, key_hash), new_index);
}
/* add an element to the decoder table */
@ -405,8 +255,8 @@ static void AddElemWithIndex(grpc_chttp2_hpack_compressor* c, grpc_mdelem elem,
uint32_t new_index, uint32_t elem_hash,
uint32_t key_hash) {
GPR_DEBUG_ASSERT(GRPC_MDELEM_IS_INTERNED(elem));
UpdateAddOrEvict<MetadataComparator>(c->elem_table.entries, elem, elem_hash,
new_index);
c->elem_table->Insert(grpc_chttp2_hpack_compressor::KeyElem(elem, elem_hash),
new_index);
AddKeyWithIndex(c, GRPC_MDKEY(elem).refcount, new_index, key_hash);
}
@ -613,11 +463,10 @@ static EmitIndexedStatus maybe_emit_indexed(grpc_chttp2_hpack_compressor* c,
bool can_add_to_hashtable =
c->filter_elems.AddElement(HASH_FRAGMENT_1(elem_hash));
/* is this elem currently in the decoders table? */
HpackEncoderIndex indices_key;
if (GetMatchingIndex<MetadataComparator>(c->elem_table.entries, elem,
elem_hash, &indices_key) &&
indices_key > c->tail_remote_index) {
emit_indexed(c, dynidx(c, indices_key), st);
auto indices_key = c->elem_table->Lookup(
grpc_chttp2_hpack_compressor::KeyElem(elem, elem_hash));
if (indices_key.has_value() && *indices_key > c->tail_remote_index) {
emit_indexed(c, dynidx(c, *indices_key), st);
return EmitIndexedStatus(elem_hash, true, false);
}
/* Didn't hit either cuckoo index, so no emit. */
@ -681,11 +530,10 @@ static void hpack_enc(grpc_chttp2_hpack_compressor* c, grpc_mdelem elem,
const uint32_t elem_hash = ret.elem_hash;
/* no hits for the elem... maybe there's a key? */
const uint32_t key_hash = elem_key.refcount->Hash(elem_key);
HpackEncoderIndex indices_key;
if (GetMatchingIndex<SliceRefComparator>(
c->key_table.entries, elem_key.refcount, key_hash, &indices_key) &&
indices_key > c->tail_remote_index) {
emit_maybe_add(c, elem, st, indices_key, should_add_elem,
auto indices_key = c->key_table->Lookup(
grpc_chttp2_hpack_compressor::KeySliceRef(elem_key.refcount, key_hash));
if (indices_key.has_value() && indices_key > c->tail_remote_index) {
emit_maybe_add(c, elem, st, *indices_key, should_add_elem,
decoder_space_usage, elem_hash, key_hash);
return;
}
@ -728,17 +576,14 @@ void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor* c) {
c->max_usable_size = GRPC_CHTTP2_HPACKC_INITIAL_TABLE_SIZE;
const size_t alloc_size = sizeof(*c->table_elem_size) * c->cap_table_elems;
c->table_elem_size = static_cast<uint16_t*>(gpr_malloc(alloc_size));
c->elem_table.Init();
c->key_table.Init();
memset(c->table_elem_size, 0, alloc_size);
}
void grpc_chttp2_hpack_compressor_destroy(grpc_chttp2_hpack_compressor* c) {
for (int i = 0; i < GRPC_CHTTP2_HPACKC_NUM_VALUES; i++) {
auto* const key = GetEntry<grpc_slice_refcount*>(c->key_table.entries, i);
if (key != nullptr) {
key->Unref();
}
GRPC_MDELEM_UNREF(GetEntry<grpc_mdelem>(c->elem_table.entries, i));
}
c->elem_table.Destroy();
c->key_table.Destroy();
gpr_free(c->table_elem_size);
}

@ -24,6 +24,7 @@
#include <grpc/slice.h>
#include <grpc/slice_buffer.h>
#include "src/core/ext/transport/chttp2/transport/frame.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder_index.h"
#include "src/core/ext/transport/chttp2/transport/popularity_count.h"
#include "src/core/lib/transport/metadata.h"
#include "src/core/lib/transport/metadata_batch.h"
@ -62,25 +63,87 @@ struct grpc_chttp2_hpack_compressor {
been seen. When that count reaches max (255), all values are halved. */
grpc_core::PopularityCount<GRPC_CHTTP2_HPACKC_NUM_VALUES> filter_elems;
class KeyElem {
public:
class Stored {
public:
Stored() : elem_(GRPC_MDNULL) {}
explicit Stored(grpc_mdelem elem) : elem_(GRPC_MDELEM_REF(elem)) {}
Stored(const Stored& other) : elem_(GRPC_MDELEM_REF(other.elem_)) {}
Stored& operator=(Stored other) {
std::swap(elem_, other.elem_);
return *this;
}
~Stored() { GRPC_MDELEM_UNREF(elem_); }
const grpc_mdelem& elem() const { return elem_; }
bool operator==(const Stored& other) const noexcept {
return elem_.payload == other.elem_.payload;
}
private:
grpc_mdelem elem_;
};
KeyElem(grpc_mdelem elem, uint32_t hash) : elem_(elem), hash_(hash) {}
KeyElem(const KeyElem&);
KeyElem& operator=(const KeyElem&);
uint32_t hash() const {
// TODO(ctiller): unify this with what's in the cc file when we move this
// code to c++
return hash_ >> 6;
}
Stored stored() const { return Stored(elem_); }
bool operator==(const Stored& stored) const noexcept {
return elem_.payload == stored.elem().payload;
}
private:
grpc_mdelem elem_;
uint32_t hash_;
};
class KeySliceRef {
public:
using Stored = grpc_core::RefCountedPtr<grpc_slice_refcount>;
KeySliceRef(grpc_slice_refcount* ref, uint32_t hash)
: ref_(ref), hash_(hash) {}
KeySliceRef(const KeySliceRef&) = delete;
KeySliceRef& operator=(const KeySliceRef&) = delete;
uint32_t hash() const {
// TODO(ctiller): unify this with what's in the cc file when we move this
// code to c++
return hash_ >> 6;
}
Stored stored() const {
ref_->Ref();
return Stored(ref_);
}
bool operator==(const Stored& stored) const noexcept {
return ref_ == stored.get();
}
private:
grpc_slice_refcount* ref_;
uint32_t hash_;
};
/* entry tables for keys & elems: these tables track values that have been
seen and *may* be in the decompressor table */
struct {
struct {
grpc_mdelem value;
uint32_t index;
} entries[GRPC_CHTTP2_HPACKC_NUM_VALUES];
} elem_table; /* Metadata table management */
struct {
struct {
/* Only store the slice refcount - we do not need the byte buffer or
length of the slice since we only need to store a mapping between the
identity of the slice and the corresponding HPACK index. Since the
slice *must* be static or interned, the refcount is sufficient to
establish identity. */
grpc_slice_refcount* value;
uint32_t index;
} entries[GRPC_CHTTP2_HPACKC_NUM_VALUES];
} key_table; /* Key table management */
grpc_core::ManualConstructor<
grpc_core::HPackEncoderIndex<KeyElem, GRPC_CHTTP2_HPACKC_NUM_VALUES>>
elem_table;
grpc_core::ManualConstructor<
grpc_core::HPackEncoderIndex<KeySliceRef, GRPC_CHTTP2_HPACKC_NUM_VALUES>>
key_table;
};
void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor* c);

@ -0,0 +1,107 @@
// Copyright 2021 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_INDEX_H
#define GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_INDEX_H
#include <grpc/impl/codegen/port_platform.h>
#include "absl/types/optional.h"
namespace grpc_core {
// A fixed size mapping of a key to a chronologically ordered index
template <typename Key, size_t kNumEntries>
class HPackEncoderIndex {
public:
using Index = uint32_t;
HPackEncoderIndex() : entries_{} {}
// If key exists in the table, update it to a new index.
// If it does not and there is an empty slot, add it to the index.
// Finally, if it does not and there is no empty slot, evict the oldest
// conflicting member.
void Insert(const Key& key, Index new_index) {
auto* const cuckoo_first = first_slot(key);
if (cuckoo_first->UpdateOrAdd(key, new_index)) return;
auto* const cuckoo_second = second_slot(key);
if (cuckoo_second->UpdateOrAdd(key, new_index)) return;
auto* const clobber = Older(cuckoo_first, cuckoo_second);
clobber->key = key.stored();
clobber->index = new_index;
}
// Lookup key and return its index, or return empty if it's not in this table.
absl::optional<Index> Lookup(const Key& key) {
auto* const cuckoo_first = first_slot(key);
if (key == cuckoo_first->key) return cuckoo_first->index;
auto* const cuckoo_second = second_slot(key);
if (key == cuckoo_second->key) return cuckoo_second->index;
return {};
}
private:
using StoredKey = typename Key::Stored;
// One entry in the index
struct Entry {
Entry() : key{}, index{} {};
StoredKey key;
Index index;
// Update this entry if it matches key, otherwise if it's empty add it.
// If neither happens, return false.
bool UpdateOrAdd(const Key& new_key, Index new_index) {
if (new_key == key) {
index = new_index;
return true;
} else if (key == StoredKey()) {
key = new_key.stored();
index = new_index;
return true;
} else {
return false;
}
}
};
static Entry* Older(Entry* a, Entry* b) {
if (a->index < b->index) {
return a;
} else {
return b;
}
}
// Return the first slot in which key could be stored.
Entry* first_slot(const Key& key) {
return &entries_[key.hash() % kNumEntries];
}
// Return the second slot in which key could be stored.
Entry* second_slot(const Key& key) {
return &entries_[(key.hash() / kNumEntries) % kNumEntries];
}
// Fixed size entry map.
// We store each key/value pair in two slots based on it's hash value.
// They can be evicted individually.
Entry entries_[kNumEntries];
};
} // namespace grpc_core
#endif // GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_INDEX_H

@ -223,3 +223,16 @@ grpc_cc_test(
"//test/core/util:grpc_suppressions",
],
)
grpc_cc_test(
name = "hpack_encoder_index_test",
srcs = ["hpack_encoder_index_test.cc"],
external_deps = [
"gtest",
],
deps = [
"//:gpr_platform",
"//:hpack_encoder_index",
"//test/core/util:grpc_suppressions",
],
)

@ -0,0 +1,61 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "include/grpc/impl/codegen/port_platform.h"
#include <gtest/gtest.h>
#include <random>
#include <unordered_map>
#include "src/core/ext/transport/chttp2/transport/hpack_encoder_index.h"
namespace grpc_core {
namespace testing {
struct TestKey {
using Stored = uint32_t;
uint32_t value;
uint32_t stored() const { return value; }
uint32_t hash() const { return value; }
bool operator==(uint32_t other) const { return other == value; }
};
TEST(HPackEncoderIndexTest, SetAndGet) {
HPackEncoderIndex<TestKey, 64> index;
std::default_random_engine rng;
std::unordered_map<uint32_t, uint32_t> last_index;
for (uint32_t i = 0; i < 10000; i++) {
uint32_t key = rng();
index.Insert({key}, i);
EXPECT_EQ(index.Lookup({key}), i);
last_index[key] = i;
}
for (auto p : last_index) {
auto r = index.Lookup({p.first});
if (r.has_value()) {
EXPECT_EQ(*r, p.second);
}
}
}
} // namespace testing
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

@ -1225,6 +1225,7 @@ src/core/ext/transport/chttp2/transport/frame_window_update.cc \
src/core/ext/transport/chttp2/transport/frame_window_update.h \
src/core/ext/transport/chttp2/transport/hpack_encoder.cc \
src/core/ext/transport/chttp2/transport/hpack_encoder.h \
src/core/ext/transport/chttp2/transport/hpack_encoder_index.h \
src/core/ext/transport/chttp2/transport/hpack_parser.cc \
src/core/ext/transport/chttp2/transport/hpack_parser.h \
src/core/ext/transport/chttp2/transport/hpack_table.cc \

@ -1060,6 +1060,7 @@ src/core/ext/transport/chttp2/transport/frame_window_update.cc \
src/core/ext/transport/chttp2/transport/frame_window_update.h \
src/core/ext/transport/chttp2/transport/hpack_encoder.cc \
src/core/ext/transport/chttp2/transport/hpack_encoder.h \
src/core/ext/transport/chttp2/transport/hpack_encoder_index.h \
src/core/ext/transport/chttp2/transport/hpack_parser.cc \
src/core/ext/transport/chttp2/transport/hpack_parser.h \
src/core/ext/transport/chttp2/transport/hpack_table.cc \

@ -5035,6 +5035,30 @@
],
"uses_polling": true
},
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "hpack_encoder_index_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": true
},
{
"args": [],
"benchmark": false,

Loading…
Cancel
Save