Skip to content

Commit

Permalink
Implement a function that gathers data from bunch of string_views int…
Browse files Browse the repository at this point in the history
…o 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
  • Loading branch information
vasilvv authored and copybara-github committed Nov 20, 2024
1 parent f49f729 commit 3145fd0
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 24 deletions.
3 changes: 3 additions & 0 deletions build/source_list.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ quiche_core_hdrs = [
"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",
Expand Down Expand Up @@ -422,6 +423,7 @@ quiche_core_srcs = [
"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",
Expand Down Expand Up @@ -1102,6 +1104,7 @@ quiche_tests_srcs = [
"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",
Expand Down
3 changes: 3 additions & 0 deletions build/source_list.gni
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ quiche_core_hdrs = [
"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",
Expand Down Expand Up @@ -422,6 +423,7 @@ quiche_core_srcs = [
"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",
Expand Down Expand Up @@ -1103,6 +1105,7 @@ quiche_tests_srcs = [
"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",
Expand Down
3 changes: 3 additions & 0 deletions build/source_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions quiche/common/quiche_buffer_allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -109,6 +110,7 @@ class QUICHE_EXPORT QuicheBuffer {
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() {
Expand Down
9 changes: 0 additions & 9 deletions quiche/common/quiche_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,6 @@ inline absl::Status SendFinOnStream(WriteStream& stream) {
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_
56 changes: 56 additions & 0 deletions quiche/common/vectorized_io_utils.cc
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions quiche/common/vectorized_io_utils.h
Original file line number Diff line number Diff line change
@@ -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_
79 changes: 79 additions & 0 deletions quiche/common/vectorized_io_utils_test.cc
Original file line number Diff line number Diff line change
@@ -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
32 changes: 17 additions & 15 deletions quiche/quic/core/http/web_transport_stream_adapter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include <limits>
#include <optional>
#include <string>
#include <vector>
#include <utility>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
Expand All @@ -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 {
Expand Down Expand Up @@ -79,21 +82,20 @@ absl::Status WebTransportStreamAdapter::Writev(
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 3145fd0

Please sign in to comment.