Noticed the PR description states:
There appears to be a 1-2% slow down on Ubuntu and MacOS but a 5% speed up on Windows
I've been experimenting with making LineReader return string_view instead and it seems to deliver an almost 1% speed up (not much, but pretty clear signal).
Benchmarking approach
exercise REST interface which hopefully doesn't actually do much, since we are more interested in measuring the overhead.
./build/bin/bitcoind -rest -noconnect
hey -n 5000000 -c 14 -cpus 14 http://127.0.0.1:8332/rest/headers/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f.bin?count=1
string_view results
Total: 54.0753 secs
Total: 54.0719 secs
Total: 53.9186 secs
string results
Total: 54.4270 secs
Total: 54.4189 secs
Total: 54.3816 secs
<details><summary>string_view LineReader diff</summary>
diff --git a/src/httpserver.cpp b/src/httpserver.cpp
index db45d31f21..ab02b6fe8f 100644
--- a/src/httpserver.cpp
+++ b/src/httpserver.cpp
@@ -35,6 +35,7 @@
#include <optional>
#include <span>
#include <string>
+#include <string_view>
#include <thread>
#include <unordered_map>
#include <vector>
@@ -306,7 +307,7 @@ bool HTTPHeaders::Read(util::LineReader& reader)
while (auto maybe_line = reader.ReadLine()) {
if (reader.Consumed() > MAX_HEADERS_SIZE) throw std::runtime_error("HTTP headers exceed size limit");
- const std::string& line = *maybe_line;
+ const std::string_view& line = *maybe_line;
// An empty line indicates end of the headers section https://www.rfc-editor.org/rfc/rfc2616#section-4
if (line.empty()) return true;
@@ -317,18 +318,18 @@ bool HTTPHeaders::Read(util::LineReader& reader)
// within any protocol elements other than the content.
// A recipient of such a bare CR MUST consider that element to be invalid...
// https://httpwg.org/specs/rfc9112.html#rfc.section.2.2
- if (line.find_first_of("\r\n\0", 0, 3) != std::string::npos) throw std::runtime_error("Header contains invalid character");
+ if (line.find_first_of("\r\n\0", 0, 3) != std::string_view::npos) throw std::runtime_error("Header contains invalid character");
// Header line must have at least one ":"
// keys are not allowed to have delimiters like ":" but values are
// https://httpwg.org/specs/rfc9110.html#rfc.section.5.6.2
const size_t pos{line.find(':')};
- if (pos == std::string::npos) throw std::runtime_error("HTTP header missing colon (:)");
+ if (pos == std::string_view::npos) throw std::runtime_error("HTTP header missing colon (:)");
// Whitespace is strictly not allowed in the field-name (key)
// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2
- std::string key = line.substr(0, pos);
- if (key.find_first_of(" \t") != std::string::npos) throw std::runtime_error("Invalid header field-name contains whitespace");
+ std::string_view key = line.substr(0, pos);
+ if (key.find_first_of(" \t") != std::string_view::npos) throw std::runtime_error("Invalid header field-name contains whitespace");
// Whitespace is optional in the value and can be trimmed
std::string value = util::TrimString(std::string_view(line).substr(pos + 1));
@@ -337,7 +338,7 @@ bool HTTPHeaders::Read(util::LineReader& reader)
// that can not be empty.
if (key.empty()) throw std::runtime_error("Empty HTTP header name");
- Write(std::move(key), std::move(value));
+ Write(std::string(key), std::move(value));
}
return false;
@@ -365,7 +366,7 @@ bool HTTPRequest::LoadControlData(LineReader& reader)
{
auto maybe_line = reader.ReadLine();
if (!maybe_line) return false;
- const std::string& request_line = *maybe_line;
+ const std::string_view& request_line = *maybe_line;
// Request Line aka Control Data https://httpwg.org/specs/rfc9110.html#rfc.section.6.2
// Three words separated by spaces, terminated by \n or \r\n
@@ -373,7 +374,7 @@ bool HTTPRequest::LoadControlData(LineReader& reader)
// "Field values containing CR, LF, or NUL characters are invalid and dangerous"
// https://httpwg.org/specs/rfc9110.html#rfc.section.5.5
- if (request_line.find('\0') != std::string::npos) throw std::runtime_error("Invalid request line contains NUL");
+ if (request_line.find('\0') != std::string_view::npos) throw std::runtime_error("Invalid request line contains NUL");
const std::vector<std::string_view> parts{Split<std::string_view>(request_line, " ")};
if (parts.size() != 3) throw std::runtime_error("HTTP request line malformed");
diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp
index 0893596d37..8c5357938a 100644
--- a/src/torcontrol.cpp
+++ b/src/torcontrol.cpp
@@ -191,7 +191,7 @@ bool TorControlConnection::ProcessBuffer()
// Parse: <code><separator><data>
// <status>(-|+| )<data>
m_message.code = ToIntegral<int>(line->substr(0, 3)).value_or(0);
- m_message.lines.push_back(line->substr(4));
+ m_message.lines.emplace_back(line->substr(4));
char separator = (*line)[3]; // '-', '+', or ' '
if (separator == ' ') {
diff --git a/src/util/string.cpp b/src/util/string.cpp
index 3a2cfbdd65..34b6fa5521 100644
--- a/src/util/string.cpp
+++ b/src/util/string.cpp
@@ -20,7 +20,7 @@ void ReplaceAll(std::string& in_out, const std::string& search, const std::strin
LineReader::LineReader(std::span<const std::byte> buffer, size_t max_line_length)
: start(buffer.begin()), end(buffer.end()), max_line_length(max_line_length), it(buffer.begin()) {}
-std::optional<std::string> LineReader::ReadLine()
+std::optional<std::string_view> LineReader::ReadLine()
{
if (it == end) {
return std::nullopt;
@@ -37,8 +37,7 @@ std::optional<std::string> LineReader::ReadLine()
// The \n itself does not count against max_line_length.
if (c == '\n') {
const std::string_view untrimmed_line(reinterpret_cast<const char*>(std::to_address(line_start)), count);
- const std::string_view line = TrimStringView(untrimmed_line, /*pattern=*/"\r\n"); // delete trailing CRLF
- return std::string(line);
+ return TrimStringView(untrimmed_line, /*pattern=*/"\r\n"); // delete trailing CRLF
}
// If the character we just consumed gives us a line length greater
// than max_line_length, and we are not at the end of the line (or buffer) yet,
@@ -57,11 +56,11 @@ std::optional<std::string> LineReader::ReadLine()
}
// Ignores max_line_length but won't overflow
-std::string LineReader::ReadLength(size_t len)
+std::string_view LineReader::ReadLength(size_t len)
{
- if (len == 0) return "";
+ if (len == 0) return std::string_view{};
if (Remaining() < len) throw std::runtime_error("Not enough data in buffer");
- std::string out(reinterpret_cast<const char*>(std::to_address(it)), len);
+ std::string_view out(reinterpret_cast<const char*>(std::to_address(it)), len);
it += len;
return out;
}
diff --git a/src/util/string.h b/src/util/string.h
index da4ce5a33a..c2fc5021a3 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -280,7 +280,7 @@ struct LineReader {
* std::nullopt if end of buffer is reached without finding a \n.
* [@throws](/github-metadata-backup-bitcoin-bitcoin/contributor/throws/) a std::runtime_error if max_line_length + 1 bytes are read without finding \n.
*/
- std::optional<std::string> ReadLine();
+ std::optional<std::string_view> ReadLine();
/**
* Returns string from current iterator position of specified length
@@ -290,7 +290,7 @@ struct LineReader {
* [@returns](/github-metadata-backup-bitcoin-bitcoin/contributor/returns/) a string of the expected length.
* [@throws](/github-metadata-backup-bitcoin-bitcoin/contributor/throws/) a std::runtime_error if there is not enough data in the buffer.
*/
- std::string ReadLength(size_t len);
+ std::string_view ReadLength(size_t len);
/**
* Returns remaining size of bytes in buffer
</details>