Restructure HPackParserTable to not use an inline table, and use a std::vector for its ring buffer instead. (#29394)

* Restructure HPackParserTable to not use an inline table, and use a std::vector for its ring buffer instead.

Before this change, it uses 6.2KiB by default, 2/3 of the size of grpc_chttp2_transport. https://screenshot.googleplex.com/6qdcybty2oCsGjG

After this change, it uses 120 bytes https://screenshot.googleplex.com/4xGWKmZZz68VE4F

This class uses up substantial amounts of memory, as it is allocated on a per-stream basis. This should reduce the size of grpc_chttp2_stream substantially

It should cause a significant memory reduction, as the HPack extension table should almost never be fully populated in terms of number of entries, especially since common headers already exist in a static table and entries are highly likely to take more memory than the absolute minimum.

* formatting

* delete unused method

* move MememtoRingBuffer

* reformat
pull/29521/head
dpcollins-google 3 years ago committed by GitHub
parent e8404698d5
commit 6d480e9d34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 81
      src/core/ext/transport/chttp2/transport/hpack_parser_table.cc
  2. 53
      src/core/ext/transport/chttp2/transport/hpack_parser_table.h

@ -20,8 +20,9 @@
#include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h" #include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h"
#include <assert.h> #include <cassert>
#include <string.h> #include <cstddef>
#include <cstring>
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
@ -38,29 +39,55 @@ extern grpc_core::TraceFlag grpc_http_trace;
namespace grpc_core { namespace grpc_core {
HPackTable::HPackTable() : static_metadata_(GetStaticMementos()) {} void HPackTable::MementoRingBuffer::Put(Memento m) {
GPR_ASSERT(num_entries_ < max_entries_);
if (entries_.size() < max_entries_) {
++num_entries_;
return entries_.push_back(std::move(m));
}
size_t index = (first_entry_ + num_entries_) % max_entries_;
entries_[index] = std::move(m);
++num_entries_;
}
HPackTable::~HPackTable() = default; auto HPackTable::MementoRingBuffer::PopOne() -> Memento {
GPR_ASSERT(num_entries_ > 0);
size_t index = first_entry_ % max_entries_;
++first_entry_;
--num_entries_;
return std::move(entries_[index]);
}
/* Evict one element from the table */ auto HPackTable::MementoRingBuffer::Lookup(uint32_t index) const
void HPackTable::EvictOne() { -> const Memento* {
auto first_entry = std::move(entries_[first_entry_]); if (index >= num_entries_) return nullptr;
GPR_ASSERT(first_entry.transport_size() <= mem_used_); uint32_t offset = (num_entries_ - 1u - index + first_entry_) % max_entries_;
mem_used_ -= first_entry.transport_size(); return &entries_[offset];
first_entry_ = ((first_entry_ + 1) % entries_.size());
num_entries_--;
} }
void HPackTable::Rebuild(uint32_t new_cap) { void HPackTable::MementoRingBuffer::Rebuild(uint32_t max_entries) {
EntriesVec entries; if (max_entries == max_entries_) return;
entries.resize(new_cap); std::vector<Memento> entries;
entries.reserve(num_entries_);
for (size_t i = 0; i < num_entries_; i++) { for (size_t i = 0; i < num_entries_; i++) {
entries[i] = std::move(entries_[(first_entry_ + i) % entries_.size()]); entries.push_back(
std::move(entries_[(first_entry_ + i) % entries_.size()]));
} }
first_entry_ = 0; first_entry_ = 0;
entries_.swap(entries); entries_.swap(entries);
} }
HPackTable::HPackTable() : static_metadata_(GetStaticMementos()) {}
HPackTable::~HPackTable() = default;
/* Evict one element from the table */
void HPackTable::EvictOne() {
auto first_entry = entries_.PopOne();
GPR_ASSERT(first_entry.transport_size() <= mem_used_);
mem_used_ -= first_entry.transport_size();
}
void HPackTable::SetMaxBytes(uint32_t max_bytes) { void HPackTable::SetMaxBytes(uint32_t max_bytes) {
if (max_bytes_ == max_bytes) { if (max_bytes_ == max_bytes) {
return; return;
@ -90,18 +117,9 @@ grpc_error_handle HPackTable::SetCurrentTableSize(uint32_t bytes) {
EvictOne(); EvictOne();
} }
current_table_bytes_ = bytes; current_table_bytes_ = bytes;
max_entries_ = hpack_constants::EntriesForBytes(bytes); uint32_t new_cap = std::max(hpack_constants::EntriesForBytes(bytes),
if (max_entries_ > entries_.size()) { hpack_constants::kInitialTableEntries);
Rebuild(max_entries_); entries_.Rebuild(new_cap);
} else if (max_entries_ < entries_.size() / 3) {
// TODO(ctiller): move to resource quota system, only shrink under memory
// pressure
uint32_t new_cap =
std::max(max_entries_, static_cast<uint32_t>(kInlineEntries));
if (new_cap != entries_.size()) {
Rebuild(new_cap);
}
}
return GRPC_ERROR_NONE; return GRPC_ERROR_NONE;
} }
@ -122,7 +140,7 @@ grpc_error_handle HPackTable::Add(Memento md) {
// attempt to add an entry larger than the entire table causes // attempt to add an entry larger than the entire table causes
// the table to be emptied of all existing entries, and results in an // the table to be emptied of all existing entries, and results in an
// empty table. // empty table.
while (num_entries_) { while (entries_.num_entries()) {
EvictOne(); EvictOne();
} }
return GRPC_ERROR_NONE; return GRPC_ERROR_NONE;
@ -136,10 +154,7 @@ grpc_error_handle HPackTable::Add(Memento md) {
// copy the finalized entry in // copy the finalized entry in
mem_used_ += md.transport_size(); mem_used_ += md.transport_size();
entries_[(first_entry_ + num_entries_) % entries_.size()] = std::move(md); entries_.Put(std::move(md));
// update accounting values
num_entries_++;
return GRPC_ERROR_NONE; return GRPC_ERROR_NONE;
} }
@ -225,7 +240,7 @@ GPR_ATTRIBUTE_NOINLINE HPackTable::Memento MakeMemento(size_t i) {
} // namespace } // namespace
const HPackTable::StaticMementos& HPackTable::GetStaticMementos() { auto HPackTable::GetStaticMementos() -> const StaticMementos& {
static const StaticMementos* const static_mementos = new StaticMementos(); static const StaticMementos* const static_mementos = new StaticMementos();
return *static_mementos; return *static_mementos;
} }

@ -63,7 +63,7 @@ class HPackTable {
grpc_error_handle Add(Memento md) GRPC_MUST_USE_RESULT; grpc_error_handle Add(Memento md) GRPC_MUST_USE_RESULT;
// Current entry count in the table. // Current entry count in the table.
uint32_t num_entries() const { return num_entries_; } uint32_t num_entries() const { return entries_.num_entries(); }
private: private:
struct StaticMementos { struct StaticMementos {
@ -72,28 +72,46 @@ class HPackTable {
}; };
static const StaticMementos& GetStaticMementos() GPR_ATTRIBUTE_NOINLINE; static const StaticMementos& GetStaticMementos() GPR_ATTRIBUTE_NOINLINE;
enum { kInlineEntries = hpack_constants::kInitialTableEntries }; class MementoRingBuffer {
using EntriesVec = absl::InlinedVector<Memento, kInlineEntries>; public:
// Rebuild this buffer with a new max_entries_ size.
void Rebuild(uint32_t max_entries);
// Put a new memento.
// REQUIRES: num_entries < max_entries
void Put(Memento m);
// Pop the oldest memento.
// REQUIRES: num_entries > 0
Memento PopOne();
// Lookup the entry at index, or return nullptr if none exists.
const Memento* Lookup(uint32_t index) const;
uint32_t max_entries() const { return max_entries_; }
uint32_t num_entries() const { return num_entries_; }
private:
// The index of the first entry in the buffer. May be greater than
// max_entries_, in which case a wraparound has occurred.
uint32_t first_entry_ = 0;
// How many entries are in the table.
uint32_t num_entries_ = 0;
// Maximum number of entries we could possibly fit in the table, given
// defined overheads.
uint32_t max_entries_ = hpack_constants::kInitialTableEntries;
std::vector<Memento> entries_;
};
const Memento* LookupDynamic(uint32_t index) const { const Memento* LookupDynamic(uint32_t index) const {
// Not static - find the value in the list of valid entries // Not static - find the value in the list of valid entries
const uint32_t tbl_index = index - (hpack_constants::kLastStaticEntry + 1); const uint32_t tbl_index = index - (hpack_constants::kLastStaticEntry + 1);
if (tbl_index < num_entries_) { return entries_.Lookup(tbl_index);
uint32_t offset =
(num_entries_ - 1u - tbl_index + first_entry_) % entries_.size();
return &entries_[offset];
}
// Invalid entry: return error
return nullptr;
} }
void EvictOne(); void EvictOne();
void Rebuild(uint32_t new_cap);
// The first used entry in ents.
uint32_t first_entry_ = 0;
// How many entries are in the table.
uint32_t num_entries_ = 0;
// The amount of memory used by the table, according to the hpack algorithm // The amount of memory used by the table, according to the hpack algorithm
uint32_t mem_used_ = 0; uint32_t mem_used_ = 0;
// The max memory allowed to be used by the table, according to the hpack // The max memory allowed to be used by the table, according to the hpack
@ -101,11 +119,8 @@ class HPackTable {
uint32_t max_bytes_ = hpack_constants::kInitialTableSize; uint32_t max_bytes_ = hpack_constants::kInitialTableSize;
// The currently agreed size of the table, according to the hpack algorithm. // The currently agreed size of the table, according to the hpack algorithm.
uint32_t current_table_bytes_ = hpack_constants::kInitialTableSize; uint32_t current_table_bytes_ = hpack_constants::kInitialTableSize;
// Maximum number of entries we could possibly fit in the table, given defined
// overheads.
uint32_t max_entries_ = hpack_constants::kInitialTableEntries;
// HPack table entries // HPack table entries
EntriesVec entries_{hpack_constants::kInitialTableEntries}; MementoRingBuffer entries_;
// Mementos for static data // Mementos for static data
const StaticMementos& static_metadata_; const StaticMementos& static_metadata_;
}; };

Loading…
Cancel
Save