Implement a function that gathers data from bunch of string_views into a single buffer.
This is similar to CopyFromIovec, notable distinctions being:
1. It takes string_views instead of iovecs.
2. It does not allocate anything.
3. The next string_view is always prefetched (CopyFromIovec only prefetches the second one at the beginning).
The hope is to use it to speed up QuicStreamSendBuffer::WriteStreamData at some point in the future.
PiperOrigin-RevId: 698508368
diff --git a/build/source_list.bzl b/build/source_list.bzl
index a27b05a..5b95fcf 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -58,6 +58,7 @@
"common/quiche_text_utils.h",
"common/simple_buffer_allocator.h",
"common/structured_headers.h",
+ "common/vectorized_io_utils.h",
"common/wire_serialization.h",
"http2/adapter/chunked_buffer.h",
"http2/adapter/data_source.h",
@@ -422,6 +423,7 @@
"common/quiche_text_utils.cc",
"common/simple_buffer_allocator.cc",
"common/structured_headers.cc",
+ "common/vectorized_io_utils.cc",
"http2/adapter/chunked_buffer.cc",
"http2/adapter/event_forwarder.cc",
"http2/adapter/header_validator.cc",
@@ -1102,6 +1104,7 @@
"common/structured_headers_test.cc",
"common/test_tools/mock_streams_test.cc",
"common/test_tools/quiche_test_utils_test.cc",
+ "common/vectorized_io_utils_test.cc",
"common/wire_serialization_test.cc",
"http2/adapter/chunked_buffer_test.cc",
"http2/adapter/event_forwarder_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 17315d7..dc5104f 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -58,6 +58,7 @@
"src/quiche/common/quiche_text_utils.h",
"src/quiche/common/simple_buffer_allocator.h",
"src/quiche/common/structured_headers.h",
+ "src/quiche/common/vectorized_io_utils.h",
"src/quiche/common/wire_serialization.h",
"src/quiche/http2/adapter/chunked_buffer.h",
"src/quiche/http2/adapter/data_source.h",
@@ -422,6 +423,7 @@
"src/quiche/common/quiche_text_utils.cc",
"src/quiche/common/simple_buffer_allocator.cc",
"src/quiche/common/structured_headers.cc",
+ "src/quiche/common/vectorized_io_utils.cc",
"src/quiche/http2/adapter/chunked_buffer.cc",
"src/quiche/http2/adapter/event_forwarder.cc",
"src/quiche/http2/adapter/header_validator.cc",
@@ -1103,6 +1105,7 @@
"src/quiche/common/structured_headers_test.cc",
"src/quiche/common/test_tools/mock_streams_test.cc",
"src/quiche/common/test_tools/quiche_test_utils_test.cc",
+ "src/quiche/common/vectorized_io_utils_test.cc",
"src/quiche/common/wire_serialization_test.cc",
"src/quiche/http2/adapter/chunked_buffer_test.cc",
"src/quiche/http2/adapter/event_forwarder_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 5ea661c..5b746d0 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -57,6 +57,7 @@
"quiche/common/quiche_text_utils.h",
"quiche/common/simple_buffer_allocator.h",
"quiche/common/structured_headers.h",
+ "quiche/common/vectorized_io_utils.h",
"quiche/common/wire_serialization.h",
"quiche/http2/adapter/chunked_buffer.h",
"quiche/http2/adapter/data_source.h",
@@ -421,6 +422,7 @@
"quiche/common/quiche_text_utils.cc",
"quiche/common/simple_buffer_allocator.cc",
"quiche/common/structured_headers.cc",
+ "quiche/common/vectorized_io_utils.cc",
"quiche/http2/adapter/chunked_buffer.cc",
"quiche/http2/adapter/event_forwarder.cc",
"quiche/http2/adapter/header_validator.cc",
@@ -1102,6 +1104,7 @@
"quiche/common/structured_headers_test.cc",
"quiche/common/test_tools/mock_streams_test.cc",
"quiche/common/test_tools/quiche_test_utils_test.cc",
+ "quiche/common/vectorized_io_utils_test.cc",
"quiche/common/wire_serialization_test.cc",
"quiche/http2/adapter/chunked_buffer_test.cc",
"quiche/http2/adapter/event_forwarder_test.cc",
diff --git a/quiche/common/quiche_buffer_allocator.h b/quiche/common/quiche_buffer_allocator.h
index 0f36842..4fb1bd4 100644
--- a/quiche/common/quiche_buffer_allocator.h
+++ b/quiche/common/quiche_buffer_allocator.h
@@ -10,6 +10,7 @@
#include <memory>
#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/platform/api/quiche_iovec.h"
@@ -109,6 +110,7 @@
absl::string_view AsStringView() const {
return absl::string_view(data(), size());
}
+ absl::Span<char> AsSpan() { return absl::Span<char>(data(), size()); }
// Releases the ownership of the underlying buffer.
QuicheUniqueBufferPtr Release() {
diff --git a/quiche/common/quiche_stream.h b/quiche/common/quiche_stream.h
index 187e362..5a8a1f7 100644
--- a/quiche/common/quiche_stream.h
+++ b/quiche/common/quiche_stream.h
@@ -199,15 +199,6 @@
return stream.Writev(absl::Span<const absl::string_view>(), options);
}
-inline size_t TotalStringViewSpanSize(
- absl::Span<const absl::string_view> span) {
- size_t total = 0;
- for (absl::string_view view : span) {
- total += view.size();
- }
- return total;
-}
-
} // namespace quiche
#endif // QUICHE_COMMON_QUICHE_STREAM_H_
diff --git a/quiche/common/vectorized_io_utils.cc b/quiche/common/vectorized_io_utils.cc
new file mode 100644
index 0000000..1ce421a
--- /dev/null
+++ b/quiche/common/vectorized_io_utils.cc
@@ -0,0 +1,56 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/vectorized_io_utils.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstring>
+
+#include "absl/base/optimization.h"
+#include "absl/base/prefetch.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+
+namespace quiche {
+
+size_t TotalStringViewSpanSize(absl::Span<const absl::string_view> span) {
+ size_t total = 0;
+ for (absl::string_view view : span) {
+ total += view.size();
+ }
+ return total;
+}
+
+size_t GatherStringViewSpan(absl::Span<const absl::string_view> inputs,
+ absl::Span<char> output) {
+ size_t bytes_copied = 0;
+ for (size_t i = 0; i < inputs.size(); ++i) {
+ if (inputs[i].empty()) {
+ continue;
+ }
+ const size_t bytes_to_copy = std::min(inputs[i].size(), output.size());
+ if (bytes_to_copy == 0) {
+ break;
+ }
+ const absl::Span<char> next_output = output.subspan(bytes_to_copy);
+
+ // Prefetch the first two lines of the next input; the hardware prefetcher
+ // is expected to take care of the rest.
+ if (!next_output.empty() && (i + 1) < inputs.size() &&
+ !inputs[i + 1].empty()) {
+ absl::PrefetchToLocalCache(&inputs[i + 1][0]);
+ if (inputs[i + 1].size() > ABSL_CACHELINE_SIZE) {
+ absl::PrefetchToLocalCache(&inputs[i + 1][ABSL_CACHELINE_SIZE]);
+ }
+ }
+
+ memcpy(output.data(), inputs[i].data(), bytes_to_copy);
+ bytes_copied += bytes_to_copy;
+ output = next_output;
+ }
+ return bytes_copied;
+}
+
+} // namespace quiche
diff --git a/quiche/common/vectorized_io_utils.h b/quiche/common/vectorized_io_utils.h
new file mode 100644
index 0000000..2ac0002
--- /dev/null
+++ b/quiche/common/vectorized_io_utils.h
@@ -0,0 +1,26 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_COMMON_VECTORIZED_IO_UTILS_H_
+#define QUICHE_COMMON_VECTORIZED_IO_UTILS_H_
+
+#include <cstddef>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+
+namespace quiche {
+
+// Computes the total size of all strings in the provided span.
+size_t TotalStringViewSpanSize(absl::Span<const absl::string_view> span);
+
+// Copies data contained in `inputs` into `output`, up until either the `output`
+// is full or the `inputs` are copied fully; returns the actual number of bytes
+// copied.
+size_t GatherStringViewSpan(absl::Span<const absl::string_view> inputs,
+ absl::Span<char> output);
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_VECTORIZED_IO_UTILS_H_
diff --git a/quiche/common/vectorized_io_utils_test.cc b/quiche/common/vectorized_io_utils_test.cc
new file mode 100644
index 0000000..dfa5e9c
--- /dev/null
+++ b/quiche/common/vectorized_io_utils_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/vectorized_io_utils.h"
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace quiche {
+namespace {
+
+using ::testing::ElementsAre;
+
+TEST(VectorizedIoUtils, GatherStringViewSpanEmpty) {
+ std::vector<absl::string_view> views = {"a", "b", "c"};
+ size_t bytes_copied = GatherStringViewSpan(views, absl::Span<char>());
+ EXPECT_EQ(bytes_copied, 0);
+}
+
+TEST(VectorizedIoUtils, GatherStringViewSpanSingle) {
+ std::vector<absl::string_view> views = {"test"};
+ char buffer_small[2];
+ size_t bytes_copied =
+ GatherStringViewSpan(views, absl::MakeSpan(buffer_small));
+ EXPECT_EQ(bytes_copied, 2);
+ EXPECT_THAT(buffer_small, ElementsAre('t', 'e'));
+
+ char buffer_exact[4];
+ bytes_copied = GatherStringViewSpan(views, absl::MakeSpan(buffer_exact));
+ EXPECT_EQ(bytes_copied, 4);
+ EXPECT_THAT(buffer_exact, ElementsAre('t', 'e', 's', 't'));
+
+ char buffer_large[6] = {'\0'};
+ bytes_copied = GatherStringViewSpan(views, absl::MakeSpan(buffer_large));
+ EXPECT_EQ(bytes_copied, 4);
+ EXPECT_THAT(buffer_large, ElementsAre('t', 'e', 's', 't', '\0', '\0'));
+}
+
+TEST(VectorizedIoUtils, GatherStringViewSpanMultiple) {
+ const std::vector<absl::string_view> views = {"foo", ",", "bar"};
+ constexpr absl::string_view kViewsJoined = "foo,bar";
+ char buffer[kViewsJoined.size()];
+ for (size_t i = 0; i < sizeof(buffer); ++i) {
+ size_t bytes_copied =
+ GatherStringViewSpan(views, absl::Span<char>(buffer, i));
+ absl::string_view result(buffer, bytes_copied);
+ EXPECT_EQ(result, kViewsJoined.substr(0, i));
+ }
+}
+
+TEST(VectorizedIoUtils, GatherStringViewSpanEmptyElement) {
+ const std::vector<absl::string_view> views = {"foo", "", "bar"};
+ constexpr absl::string_view kViewsJoined = "foobar";
+ char buffer[kViewsJoined.size()];
+ size_t bytes_copied = GatherStringViewSpan(views, absl::MakeSpan(buffer));
+ absl::string_view result(buffer, bytes_copied);
+ EXPECT_EQ(result, kViewsJoined);
+}
+
+TEST(VectorizedIoUtils, GatherStringViewSpanLarge) {
+ const std::string a(8192, 'a');
+ const std::string b(8192, 'b');
+ const std::vector<absl::string_view> views = {a, b};
+ const std::string joined = a + b;
+ char buffer[8192 * 2];
+ size_t bytes_copied = GatherStringViewSpan(views, absl::MakeSpan(buffer));
+ EXPECT_EQ(bytes_copied, 8192 * 2);
+ absl::string_view result(buffer, bytes_copied);
+ EXPECT_EQ(result, joined);
+}
+
+} // namespace
+} // namespace quiche
diff --git a/quiche/quic/core/http/web_transport_stream_adapter.cc b/quiche/quic/core/http/web_transport_stream_adapter.cc
index 5f84081..0900323 100644
--- a/quiche/quic/core/http/web_transport_stream_adapter.cc
+++ b/quiche/quic/core/http/web_transport_stream_adapter.cc
@@ -8,7 +8,7 @@
#include <limits>
#include <optional>
#include <string>
-#include <vector>
+#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
@@ -25,8 +25,11 @@
#include "quiche/quic/platform/api/quic_bug_tracker.h"
#include "quiche/quic/platform/api/quic_flags.h"
#include "quiche/quic/platform/api/quic_logging.h"
-#include "quiche/common/quiche_mem_slice_storage.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_mem_slice.h"
+#include "quiche/common/quiche_buffer_allocator.h"
#include "quiche/common/quiche_stream.h"
+#include "quiche/common/vectorized_io_utils.h"
#include "quiche/web_transport/web_transport.h"
namespace quic {
@@ -79,21 +82,20 @@
return initial_check_status;
}
- std::vector<iovec> iovecs;
- size_t total_size = 0;
- iovecs.resize(data.size());
- for (size_t i = 0; i < data.size(); i++) {
- // QuicheMemSliceStorage only reads iovec, thus this is safe.
- iovecs[i].iov_base = const_cast<char*>(data[i].data());
- iovecs[i].iov_len = data[i].size();
- total_size += data[i].size();
+ size_t total_size = quiche::TotalStringViewSpanSize(data);
+ quiche::QuicheMemSlice slice;
+ if (total_size > 0) {
+ quiche::QuicheBuffer buffer(
+ session_->connection()->helper()->GetStreamSendBufferAllocator(),
+ total_size);
+ size_t bytes_copied = quiche::GatherStringViewSpan(data, buffer.AsSpan());
+ QUICHE_DCHECK_EQ(total_size, bytes_copied);
+ slice = quiche::QuicheMemSlice(std::move(buffer));
}
- quiche::QuicheMemSliceStorage storage(
- iovecs.data(), iovecs.size(),
- session_->connection()->helper()->GetStreamSendBufferAllocator(),
- GetQuicFlag(quic_send_buffer_max_data_slice_size));
QuicConsumedData consumed = stream_->WriteMemSlices(
- storage.ToSpan(), /*fin=*/options.send_fin(),
+ slice.empty() ? absl::Span<quiche::QuicheMemSlice>()
+ : absl::MakeSpan(&slice, 1),
+ /*fin=*/options.send_fin(),
/*buffer_uncondtionally=*/options.buffer_unconditionally());
if (consumed.bytes_consumed == total_size) {
diff --git a/quiche/web_transport/encapsulated/encapsulated_web_transport.cc b/quiche/web_transport/encapsulated/encapsulated_web_transport.cc
index b6380e8..30ab1d4 100644
--- a/quiche/web_transport/encapsulated/encapsulated_web_transport.cc
+++ b/quiche/web_transport/encapsulated/encapsulated_web_transport.cc
@@ -36,6 +36,7 @@
#include "quiche/common/quiche_circular_deque.h"
#include "quiche/common/quiche_status_utils.h"
#include "quiche/common/quiche_stream.h"
+#include "quiche/common/vectorized_io_utils.h"
#include "quiche/web_transport/web_transport.h"
namespace webtransport {