Skip to content

Commit ceb8840

Browse files
author
kelbon
committed
encode with cache + dyntab find
1 parent 8e847b5 commit ceb8840

4 files changed

Lines changed: 364 additions & 60 deletions

File tree

include/hpack/dynamic_table.hpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ struct dynamic_table_t {
1616

1717
private:
1818
struct key_of_entry {
19-
using type = table_entry;
20-
table_entry operator()(const entry_t& v) const noexcept;
19+
using type = std::string_view;
20+
std::string_view operator()(const entry_t&) const noexcept;
2121
};
22-
// for forward declaring entry_t
23-
using hook_type_option = bi::base_hook<bi::set_base_hook<bi::link_mode<bi::normal_link>>>;
2422

2523
// invariant: do not contain nullptrs
2624
std::vector<entry_t*> entries;
27-
bi::multiset<entry_t, bi::constant_time_size<false>, hook_type_option, bi::key_of_value<key_of_entry>> set;
25+
using entry_set_hook = bi::set_base_hook<bi::link_mode<bi::normal_link>>;
26+
// sorted by name
27+
using entry_set_t = bi::multiset<entry_t, bi::constant_time_size<false>, bi::base_hook<entry_set_hook>,
28+
bi::key_of_value<key_of_entry>>;
29+
30+
entry_set_t set;
2831
// in bytes
2932
// invariant: <= _max_size
3033
size_type _current_size = 0;
@@ -49,7 +52,9 @@ struct dynamic_table_t {
4952
Insertion Point Dropping Point
5053
*/
5154
public:
52-
dynamic_table_t() = default;
55+
// 4096 - default size by protocol
56+
dynamic_table_t() : dynamic_table_t(4096) {
57+
}
5358
// `user_protocol_max_size` and `max_size()` both initialized to `max_size`
5459
explicit dynamic_table_t(size_type max_size,
5560
std::pmr::memory_resource* m = std::pmr::get_default_resource()) noexcept;
@@ -71,6 +76,12 @@ struct dynamic_table_t {
7176

7277
dynamic_table_t& operator=(dynamic_table_t&& other) noexcept;
7378

79+
void swap(dynamic_table_t&) noexcept;
80+
81+
friend void swap(dynamic_table_t& a, dynamic_table_t& b) noexcept {
82+
return a.swap(b);
83+
}
84+
7485
~dynamic_table_t();
7586

7687
// returns index of added pair, 0 if cannot add
@@ -97,11 +108,15 @@ struct dynamic_table_t {
97108
return entries.size() + static_table_t::first_unused_index - 1;
98109
}
99110

100-
find_result_t find(std::string_view name, std::string_view value) noexcept;
101-
find_result_t find(index_type name, std::string_view value) noexcept;
111+
// searches both static and dynamic table
112+
find_result_t find(std::string_view name, std::string_view value) const noexcept;
113+
// searches both static and dynamic table
114+
// precondition: name <= current_max_index()
115+
find_result_t find(index_type name, std::string_view value) const noexcept;
102116

103-
// precondition: first_unused_index <= index <= current_max_index()
117+
// precondition: 0 < index <= current_max_index()
104118
// Note: returned value may be invalidated on next .add_entry()
119+
// searches both in static and dynamic tables
105120
table_entry get_entry(index_type index) const noexcept;
106121

107122
void reset() noexcept;

include/hpack/encoder.hpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "hpack/strings.hpp"
55
#include "hpack/integers.hpp"
66

7+
#include <charconv>
8+
79
namespace hpack {
810

911
struct encoder {
@@ -37,6 +39,11 @@ struct encoder {
3739

3840
// only name indexed
3941
// precondition: header_index present in static or dynamic table
42+
//
43+
// Note: will encode to the cached header, but calling this function again will not result in more efficient
44+
// encoding.
45+
// Instead, it will send requests to cache the header and this will result in evicting from dynamic table.
46+
// Its likely you want to use encode_with_cache instead
4047
template <bool Huffman = false, Out O>
4148
O encode_header_and_cache(index_type header_index, std::string_view value, O _out) {
4249
assert(header_index <= dyntab.current_max_index() && header_index != 0);
@@ -51,6 +58,11 @@ struct encoder {
5158

5259
// indexes value for future use
5360
// 'out_index' contains index of 'name' + 'value' pair after encode
61+
//
62+
// Note: will encode to the cached header, but calling this function again will not result in more efficient
63+
// encoding.
64+
// Instead, it will send requests to cache the header and this will result in evicting from dynamic table.
65+
// Its likely you want to use encode_with_cache instead
5466
template <bool Huffman = false, Out O>
5567
O encode_header_and_cache(std::string_view name, std::string_view value, O _out) {
5668
/*
@@ -76,6 +88,34 @@ struct encoder {
7688
return noexport::unadapt<O>(encode_string<Huffman>(value, out));
7789
}
7890

91+
// encodes header like 'encode_header_and_cache', but uses created cache, so next calls much more efficient
92+
// than first call
93+
//
94+
// Note: does not use static_table. In this case its better to use `encode_header_fully_indexed`
95+
template <bool Huffman = false, Out O>
96+
O encode_with_cache(std::string_view name, std::string_view value, O out) {
97+
find_result_t r = dyntab.find(name, value);
98+
if (r.value_indexed) [[likely]] {
99+
// its likely, because only first call will be not cached
100+
return encode_header_fully_indexed(r.header_name_index, out);
101+
}
102+
return encode_header_and_cache<Huffman>(name, value, out);
103+
}
104+
105+
// encodes header like 'encode_header_and_cache', but uses created cache, so next calls much more efficient
106+
// than first call
107+
//
108+
// Note: does not use static_table. In this case its better to use `encode_header_fully_indexed`
109+
template <bool Huffman = false, Out O>
110+
O encode_with_cache(index_type name, std::string_view value, O out) {
111+
find_result_t r = dyntab.find(name, value);
112+
if (r.value_indexed) [[likely]] {
113+
// its likely, because only first call will be not cached
114+
return encode_header_fully_indexed(r.header_name_index, out);
115+
}
116+
return encode_header_and_cache<Huffman>(name, value, out);
117+
}
118+
79119
template <bool Huffman = false, Out O>
80120
O encode_header_without_indexing(index_type name, std::string_view value, O _out) {
81121
/*
@@ -251,6 +291,35 @@ struct encoder {
251291
dyntab.update_size(new_size);
252292
return it;
253293
}
294+
295+
// encodes :status pseudoheader for server
296+
// precondition:
297+
template <Out O>
298+
O encode_status(int status, O out) {
299+
using enum static_table_t::values;
300+
switch (status) {
301+
case 200:
302+
return encode_header_fully_indexed(status_200, out);
303+
case 204:
304+
return encode_header_fully_indexed(status_204, out);
305+
case 206:
306+
return encode_header_fully_indexed(status_206, out);
307+
case 304:
308+
return encode_header_fully_indexed(status_304, out);
309+
case 400:
310+
return encode_header_fully_indexed(status_400, out);
311+
case 404:
312+
return encode_header_fully_indexed(status_404, out);
313+
case 500:
314+
return encode_header_fully_indexed(status_500, out);
315+
default:
316+
char data[32];
317+
auto [ptr, ec] = std::to_chars(data, data + 32, status);
318+
assert(ec == std::errc{});
319+
// its likely, that server will send this status again, so cache it
320+
return encode_with_cache(status_200, std::string_view(+data, ptr), out);
321+
}
322+
}
254323
};
255324

256325
} // namespace hpack

src/dynamic_table.cpp

Lines changed: 37 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
#include <utility>
55
#include <cstring> // memcpy
66

7-
namespace bi = boost::intrusive;
8-
97
namespace hpack {
108

11-
struct dynamic_table_t::entry_t : bi::set_base_hook<bi::link_mode<bi::normal_link>> {
9+
struct dynamic_table_t::entry_t : entry_set_hook {
1210
const size_type name_end;
1311
const size_type value_end;
1412
const size_t _insert_c;
@@ -46,6 +44,10 @@ struct dynamic_table_t::entry_t : bi::set_base_hook<bi::link_mode<bi::normal_lin
4644
}
4745
};
4846

47+
std::string_view dynamic_table_t::key_of_entry::operator()(const dynamic_table_t::entry_t& v) const noexcept {
48+
return v.name();
49+
}
50+
4951
// precondition: 'e' now in entries
5052
index_type dynamic_table_t::indexof(const dynamic_table_t::entry_t& e) const noexcept {
5153
return static_table_t::first_unused_index + (_insert_count - e._insert_c);
@@ -61,10 +63,6 @@ static size_type entry_size(const dynamic_table_t::entry_t& entry) noexcept {
6163
return entry.value_end + 32;
6264
}
6365

64-
table_entry dynamic_table_t::key_of_entry::operator()(const dynamic_table_t::entry_t& v) const noexcept {
65-
return {v.name(), v.value()};
66-
}
67-
6866
dynamic_table_t::dynamic_table_t(size_type max_size, std::pmr::memory_resource* m) noexcept
6967
: _current_size(0),
7068
_max_size(max_size),
@@ -73,25 +71,23 @@ dynamic_table_t::dynamic_table_t(size_type max_size, std::pmr::memory_resource*
7371
_resource(m ? m : std::pmr::get_default_resource()) {
7472
}
7573

76-
dynamic_table_t::dynamic_table_t(dynamic_table_t&& other) noexcept
77-
: entries(std::move(other.entries)),
78-
set(std::move(other.set)),
79-
_current_size(std::exchange(other._current_size, 0)),
80-
_max_size(std::exchange(other._max_size, 0)),
81-
_insert_count(std::exchange(other._insert_count, 0)),
82-
_resource(std::exchange(other._resource, std::pmr::get_default_resource())) {
74+
void dynamic_table_t::swap(dynamic_table_t& other) noexcept {
75+
using std::swap;
76+
swap(entries, other.entries);
77+
swap(set, other.set);
78+
swap(_current_size, other._current_size);
79+
swap(_max_size, other._max_size);
80+
swap(_user_protocol_max_size, other._user_protocol_max_size);
81+
swap(_insert_count, other._insert_count);
82+
swap(_resource, other._resource);
83+
}
84+
85+
dynamic_table_t::dynamic_table_t(dynamic_table_t&& other) noexcept {
86+
swap(other);
8387
}
8488

8589
dynamic_table_t& dynamic_table_t::operator=(dynamic_table_t&& other) noexcept {
86-
if (this == &other) [[unlikely]]
87-
return *this;
88-
reset();
89-
entries = std::move(other.entries);
90-
set = std::move(other.set);
91-
_current_size = std::exchange(other._current_size, 0);
92-
_max_size = std::exchange(other._max_size, 0);
93-
_insert_count = std::exchange(other._insert_count, 0);
94-
_resource = std::exchange(other._resource, std::pmr::get_default_resource());
90+
swap(other);
9591
return *this;
9692
}
9793

@@ -129,38 +125,26 @@ void dynamic_table_t::update_size(size_type new_max_size) {
129125
_max_size = new_max_size;
130126
}
131127

132-
find_result_t dynamic_table_t::find(std::string_view name, std::string_view value) noexcept {
128+
find_result_t dynamic_table_t::find(std::string_view name, std::string_view value) const noexcept {
133129
find_result_t r;
134-
auto it = set.find(table_entry{name, value});
135-
if (it == set.end())
136-
return r;
137-
if (name == it->name()) {
138-
r.header_name_index = indexof(*it);
139-
if (value == it->value())
140-
r.value_indexed = true;
130+
auto [b, e] = set.equal_range(name);
131+
for (; b != e; ++b) {
132+
if (b->name() == name) {
133+
r.header_name_index = indexof(*b);
134+
if (b->value() == value) {
135+
r.value_indexed = true;
136+
return r;
137+
}
138+
}
141139
}
142140
return r;
143141
}
144142

145-
find_result_t dynamic_table_t::find(index_type name, std::string_view value) noexcept {
143+
find_result_t dynamic_table_t::find(index_type name, std::string_view value) const noexcept {
146144
assert(name <= current_max_index());
147-
find_result_t r;
148-
if (name < static_table_t::first_unused_index || name > current_max_index() || name == 0)
149-
return r;
150-
table_entry e = get_entry(name);
151-
if (e.value == value) {
152-
r.header_name_index = name;
153-
r.value_indexed = true;
154-
return r;
155-
}
156-
auto it = set.find(table_entry{e.name, value});
157-
assert(it != set.end());
158-
if (e.name == it->name()) {
159-
r.header_name_index = indexof(*it);
160-
if (value == it->value())
161-
r.value_indexed = true;
162-
}
163-
return r;
145+
if (name == 0) [[unlikely]]
146+
return {};
147+
return find(get_entry(name).name, value);
164148
}
165149

166150
void dynamic_table_t::reset() noexcept {
@@ -175,15 +159,17 @@ void dynamic_table_t::evict_until_fits_into(size_type bytes) noexcept {
175159
size_type i = 0;
176160
for (; _current_size > bytes; ++i) {
177161
_current_size -= entry_size(*entries[i]);
178-
set.erase(set.s_iterator_to(*entries[i]));
162+
set.erase(set.iterator_to(*entries[i]));
179163
entry_t::destroy(entries[i], _resource);
180164
}
181165
// evicts should be rare operation
182166
entries.erase(entries.begin(), entries.begin() + i);
183167
}
184168

185169
table_entry dynamic_table_t::get_entry(index_type index) const noexcept {
186-
assert(index >= static_table_t::first_unused_index && index <= current_max_index());
170+
assert(index != 0 && index <= current_max_index());
171+
if (index < static_table_t::first_unused_index)
172+
return static_table_t::get_entry(index);
187173
auto& e = *(&entries.back() - (index - static_table_t::first_unused_index));
188174
return table_entry{e->name(), e->value()};
189175
}

0 commit comments

Comments
 (0)