Pre-allocate storage in http2::HuffmanEncode().
Also, do not call http2::HuffmanEncode() in
QpackInstructionEncoder::DoStartString() if it does not result in size savings.
Note that performance gets worse for short strings, presumably because
std::string::reserve() for small values is a no-op due to small string
optimization. However, I plan to modify http2::HuffmanEncode() not to clear the
output string and modify QpackInstructionEncoder to avoid copying, which should
result in a performance improvement overall.
Also I'll add a slightly faster implementation in the next CL, so this is really
only to change the API to make tests simpler.
PiperOrigin-RevId: 334909897
Change-Id: Idd7414c0dcaa56fc62d0822ec06250d480d2972c
diff --git a/http2/hpack/huffman/hpack_huffman_encoder.cc b/http2/hpack/huffman/hpack_huffman_encoder.cc
index 8b3f29f..305bb5a 100644
--- a/http2/hpack/huffman/hpack_huffman_encoder.cc
+++ b/http2/hpack/huffman/hpack_huffman_encoder.cc
@@ -61,9 +61,12 @@
return (bits + 7) / 8;
}
-void HuffmanEncode(quiche::QuicheStringPiece plain, std::string* huffman) {
+void HuffmanEncode(quiche::QuicheStringPiece plain,
+ size_t encoded_size,
+ std::string* huffman) {
DCHECK(huffman != nullptr);
huffman->clear(); // Note that this doesn't release memory.
+ huffman->reserve(encoded_size);
uint64_t bit_buffer = 0; // High-bit is next bit to output. Not clear if that
// is more performant than having the low-bit be the
// last to be output.
diff --git a/http2/hpack/huffman/hpack_huffman_encoder.h b/http2/hpack/huffman/hpack_huffman_encoder.h
index 2ee4de2..b7012b4 100644
--- a/http2/hpack/huffman/hpack_huffman_encoder.h
+++ b/http2/hpack/huffman/hpack_huffman_encoder.h
@@ -29,11 +29,13 @@
QUICHE_EXPORT_PRIVATE size_t
BoundedHuffmanSize(quiche::QuicheStringPiece plain);
-// Encode the plain text string |plain| with the Huffman encoding defined in
-// the HPACK RFC, 7541. |*huffman| does not have to be empty, it is cleared at
-// the beginning of this function. This allows reusing the same string object
-// across multiple invocations.
+// Encode the plain text string |plain| with the Huffman encoding defined in the
+// HPACK RFC, 7541. |encoded_size| is used to pre-allocate storage and it
+// should be the value returned by ExactHuffmanSize(). |*huffman| does not have
+// to be empty, it is cleared at the beginning of this function. This allows
+// reusing the same string object across multiple invocations.
QUICHE_EXPORT_PRIVATE void HuffmanEncode(quiche::QuicheStringPiece plain,
+ size_t encoded_size,
std::string* huffman);
} // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_encoder_benchmark.cc b/http2/hpack/huffman/hpack_huffman_encoder_benchmark.cc
index 1f365c6..9e4163f 100644
--- a/http2/hpack/huffman/hpack_huffman_encoder_benchmark.cc
+++ b/http2/hpack/huffman/hpack_huffman_encoder_benchmark.cc
@@ -9,14 +9,14 @@
//
// Benchmark Time(ns) CPU(ns) Allocs Iterations
// -----------------------------------------------------------------------------
-// BM_EncodeSmallStrings 239 239 0 2456085 0.000B peak-mem
-// BM_EncodeLargeString/1k 4560 4561 5 153325 1.125kB peak-mem
-// BM_EncodeLargeString/4k 18787 18788 7 38430 4.500kB peak-mem
-// BM_EncodeLargeString/32k 147680 147657 10 4664 36.000kB peak-mem
-// BM_EncodeLargeString/256k 1161688 1161511 13 601 288.000kB peak-mem
-// BM_EncodeLargeString/2M 10042722 10036764 16 75 2.250MB peak-mem
-// BM_EncodeLargeString/16M 76127338 76138839 19 9 18.000MB peak-mem
-// BM_EncodeLargeString/128M 640008098 640154859 22 1 144.000MB peak-mem
+// BM_EncodeSmallStrings 256 256 0 2614132 0.000B peak-mem
+// BM_EncodeLargeString/1k 4295 4296 1 164072 656.000B peak-mem
+// BM_EncodeLargeString/4k 17077 17078 1 40336 2.516kB peak-mem
+// BM_EncodeLargeString/32k 136586 136584 1 5126 20.016kB peak-mem
+// BM_EncodeLargeString/256k 1094913 1094867 1 632 160.016kB peak-mem
+// BM_EncodeLargeString/2M 8771916 8773555 1 80 1.250MB peak-mem
+// BM_EncodeLargeString/16M 72575563 72590767 1 10 10.000MB peak-mem
+// BM_EncodeLargeString/128M 602461676 602487805 1 1 80.000MB peak-mem
//
#include <string>
@@ -37,9 +37,9 @@
":method", ":path", "cookie", "set-cookie", "vary", "accept-encoding"};
for (auto s : state) {
for (const auto& input : inputs) {
+ size_t encoded_size = ExactHuffmanSize(input);
std::string result;
- ExactHuffmanSize(input);
- HuffmanEncode(input, &result);
+ HuffmanEncode(input, encoded_size, &result);
}
}
}
@@ -47,9 +47,9 @@
void BM_EncodeLargeString(benchmark::State& state) {
const std::string input(state.range(0), 'a');
for (auto s : state) {
+ size_t encoded_size = ExactHuffmanSize(input);
std::string result;
- ExactHuffmanSize(input);
- HuffmanEncode(input, &result);
+ HuffmanEncode(input, encoded_size, &result);
}
}
diff --git a/http2/hpack/huffman/hpack_huffman_encoder_test.cc b/http2/hpack/huffman/hpack_huffman_encoder_test.cc
index 0c0968b..5528408 100644
--- a/http2/hpack/huffman/hpack_huffman_encoder_test.cc
+++ b/http2/hpack/huffman/hpack_huffman_encoder_test.cc
@@ -25,11 +25,12 @@
for (size_t i = 0; i != QUICHE_ARRAYSIZE(test_table); i += 2) {
const std::string& huffman_encoded(test_table[i]);
const std::string& plain_string(test_table[i + 1]);
- EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size());
- EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size());
+ size_t encoded_size = ExactHuffmanSize(plain_string);
+ EXPECT_EQ(huffman_encoded.size(), encoded_size);
+ EXPECT_EQ(BoundedHuffmanSize(plain_string), encoded_size);
std::string buffer;
buffer.reserve();
- HuffmanEncode(plain_string, &buffer);
+ HuffmanEncode(plain_string, encoded_size, &buffer);
EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string;
}
}
@@ -56,14 +57,12 @@
for (size_t i = 0; i != QUICHE_ARRAYSIZE(test_table); i += 2) {
const std::string& huffman_encoded(test_table[i]);
const std::string& plain_string(test_table[i + 1]);
- EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size());
- EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size());
+ size_t encoded_size = ExactHuffmanSize(plain_string);
+ EXPECT_EQ(huffman_encoded.size(), encoded_size);
+ EXPECT_EQ(BoundedHuffmanSize(plain_string), encoded_size);
std::string buffer;
- buffer.reserve(huffman_encoded.size());
- const size_t capacity = buffer.capacity();
- HuffmanEncode(plain_string, &buffer);
+ HuffmanEncode(plain_string, encoded_size, &buffer);
EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string;
- EXPECT_EQ(capacity, buffer.capacity());
}
}
@@ -84,9 +83,10 @@
for (size_t i = 0; i != QUICHE_ARRAYSIZE(test_table); ++i) {
const std::string& plain_string = test_table[i];
+ size_t encoded_size = ExactHuffmanSize(plain_string);
std::string huffman_encoded;
- HuffmanEncode(plain_string, &huffman_encoded);
- EXPECT_EQ(huffman_encoded.size(), ExactHuffmanSize(plain_string));
+ HuffmanEncode(plain_string, encoded_size, &huffman_encoded);
+ EXPECT_EQ(encoded_size, huffman_encoded.size());
EXPECT_LE(BoundedHuffmanSize(plain_string), plain_string.size());
EXPECT_LE(BoundedHuffmanSize(plain_string), ExactHuffmanSize(plain_string));
}
diff --git a/http2/hpack/huffman/hpack_huffman_transcoder_test.cc b/http2/hpack/huffman/hpack_huffman_transcoder_test.cc
index 1123d26..329f193 100644
--- a/http2/hpack/huffman/hpack_huffman_transcoder_test.cc
+++ b/http2/hpack/huffman/hpack_huffman_transcoder_test.cc
@@ -73,8 +73,10 @@
AssertionResult TranscodeAndValidateSeveralWays(
quiche::QuicheStringPiece plain,
quiche::QuicheStringPiece expected_huffman) {
+ size_t encoded_size = ExactHuffmanSize(plain);
std::string encoded;
- HuffmanEncode(plain, &encoded);
+ HuffmanEncode(plain, encoded_size, &encoded);
+ VERIFY_EQ(encoded_size, encoded.size());
if (expected_huffman.size() > 0 || plain.empty()) {
VERIFY_EQ(encoded, expected_huffman);
}
diff --git a/quic/core/qpack/qpack_instruction_encoder.cc b/quic/core/qpack/qpack_instruction_encoder.cc
index 41e2d76..7255216 100644
--- a/quic/core/qpack/qpack_instruction_encoder.cc
+++ b/quic/core/qpack/qpack_instruction_encoder.cc
@@ -135,12 +135,14 @@
string_to_write_ =
(field_->type == QpackInstructionFieldType::kName) ? name : value;
- http2::HuffmanEncode(string_to_write_, &huffman_encoded_string_);
+ size_t encoded_size = http2::ExactHuffmanSize(string_to_write_);
- if (huffman_encoded_string_.size() < string_to_write_.size()) {
+ if (encoded_size < string_to_write_.size()) {
DCHECK_EQ(0, byte_ & (1 << field_->param));
-
byte_ |= (1 << field_->param);
+
+ http2::HuffmanEncode(string_to_write_, encoded_size,
+ &huffman_encoded_string_);
string_to_write_ = huffman_encoded_string_;
}