Skip to content

Commit 0f33d2a

Browse files
authored
Memory allocation review (#132)
* Added a static_vector type to start avoiding allocations during URL parsing * Tried to find a way to reduce the number of allocations during domain name parsing * Added tests to see what allocations are happening for host parsing * Fixed some build errors following rebase * Succeeded in removing some allocations during domain name parsing * Fixed trait in static_vector constructor noexcept specification
1 parent c06a630 commit 0f33d2a

File tree

17 files changed

+600
-164
lines changed

17 files changed

+600
-164
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright 2020 Glyn Matthews.
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// (See accompanying file LICENSE_1_0.txt or copy at
4+
// http://www.boost.org/LICENSE_1_0.txt)
5+
6+
#ifndef SKYR_V1_CONTAINERS_STATIC_VECTOR_HPP
7+
#define SKYR_V1_CONTAINERS_STATIC_VECTOR_HPP
8+
9+
#include <cstdlib>
10+
#include <array>
11+
#include <type_traits>
12+
#include <optional>
13+
14+
namespace skyr {
15+
inline namespace v1 {
16+
///
17+
/// \tparam T
18+
/// \tparam Capacity
19+
template <
20+
class T,
21+
std::size_t Capacity
22+
>
23+
class static_vector {
24+
private:
25+
26+
using impl_type = std::array<T, Capacity>;
27+
28+
impl_type impl_;
29+
std::size_t size_ = 0;
30+
31+
public:
32+
33+
///
34+
using value_type = T;
35+
///
36+
using const_reference = const T &;
37+
///
38+
using reference = T &;
39+
///
40+
using const_pointer = const T *;
41+
///
42+
using pointer = T *;
43+
///
44+
using size_type = std::size_t;
45+
///
46+
using difference_type = std::ptrdiff_t;
47+
///
48+
using const_iterator = typename impl_type::const_iterator;
49+
///
50+
using iterator = typename impl_type::iterator;
51+
52+
/// Constructor
53+
constexpr static_vector() = default;
54+
55+
/// Gets the first const element in the vector
56+
/// \return a const T &
57+
/// \pre `size() > 0`
58+
constexpr auto front() const noexcept -> const_reference {
59+
return impl_[0];
60+
}
61+
62+
/// Gets the first element in the vector
63+
/// \return a T &
64+
/// \pre `size() > 0`
65+
constexpr auto front() noexcept -> reference {
66+
return impl_[0];
67+
}
68+
69+
///
70+
/// \return
71+
/// \pre `size() > 0`
72+
constexpr auto back() const noexcept -> const_reference {
73+
return impl_[size_ - 1];
74+
}
75+
76+
///
77+
/// \return
78+
/// \pre `size() > 0`
79+
constexpr auto back() noexcept -> reference {
80+
return impl_[size_ - 1];
81+
}
82+
83+
///
84+
/// \param value
85+
/// \return
86+
/// \pre `size() < `capacity()`
87+
/// \post `size() > 0 && size() <= capacity()`
88+
constexpr auto push_back(const_reference value) noexcept -> reference {
89+
impl_[size_++] = value;
90+
return impl_[size_ - 1];
91+
}
92+
93+
///
94+
/// \tparam Args
95+
/// \param args
96+
/// \return
97+
/// \pre `size() < `capacity()`
98+
/// \post `size() > 0 && size() <= capacity()`
99+
template <class... Args>
100+
constexpr auto emplace_back(Args &&... args)
101+
noexcept(std::is_trivially_move_assignable_v<T>) -> reference {
102+
impl_[size_++] = value_type{args...};
103+
return impl_[size_ - 1];
104+
}
105+
106+
///
107+
/// \pre `size() > 0`
108+
constexpr void pop_back() noexcept {
109+
--size_;
110+
}
111+
112+
///
113+
/// \return
114+
[[nodiscard]] constexpr auto data() const noexcept -> const value_type * {
115+
return impl_.data();
116+
}
117+
118+
///
119+
/// \return
120+
[[nodiscard]] constexpr auto size() const noexcept -> size_type {
121+
return size_;
122+
}
123+
124+
///
125+
/// \return
126+
[[nodiscard]] constexpr auto max_size() const noexcept -> size_type {
127+
return Capacity;
128+
}
129+
130+
///
131+
/// \return `true` if there are elements
132+
[[nodiscard]] constexpr auto empty() const noexcept -> bool {
133+
return size_ == 0;
134+
}
135+
136+
///
137+
/// \return
138+
[[nodiscard]] constexpr auto begin() noexcept -> iterator {
139+
return impl_.begin();
140+
}
141+
142+
///
143+
/// \return
144+
[[nodiscard]] constexpr auto end() noexcept -> iterator {
145+
auto last = impl_.begin();
146+
std::advance(last, size_);
147+
return last;
148+
}
149+
150+
///
151+
/// \return
152+
[[nodiscard]] constexpr auto begin() const noexcept -> const_iterator {
153+
return impl_.begin();
154+
}
155+
156+
///
157+
/// \return
158+
[[nodiscard]] constexpr auto end() const noexcept -> const_iterator {
159+
auto last = impl_.begin();
160+
std::advance(last, size_);
161+
return last;
162+
}
163+
164+
};
165+
} // namespace v1
166+
} // namespace skyr
167+
168+
#endif // SKYR_V1_CONTAINERS_STATIC_VECTOR_HPP

include/skyr/v1/domain/errors.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ enum class domain_errc {
2323
invalid_length,
2424
/// Empty domain
2525
empty_string,
26+
/// The number of labels in the domain is too large
27+
too_many_labels,
2628
};
2729
} // namespace v1
2830
} // namespace skyr

include/skyr/v1/unicode/traits/range_iterator.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace unicode::traits {
1414
template <class Range>
1515
class range_iterator {
1616
public:
17-
using type = typename Range::const_iterator;
17+
using type = typename std::decay_t<Range>::const_iterator;
1818
};
1919

2020
///

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ target_sources(skyr-url
2929
v1/domain/domain.cpp
3030
v1/domain/idna.hpp
3131
v1/domain/idna.cpp
32+
v1/domain/idna_code_point_map_iterator.hpp
3233
v1/domain/punycode.hpp
3334
v1/domain/punycode.cpp
3435
v1/url/url.cpp
@@ -50,6 +51,7 @@ target_sources(skyr-url
5051
${PROJECT_SOURCE_DIR}/include/skyr/v1/unicode/ranges/sentinel.hpp
5152
${PROJECT_SOURCE_DIR}/include/skyr/v1/unicode/details/to_u8.hpp
5253
${PROJECT_SOURCE_DIR}/include/skyr/v1/string/starts_with.hpp
54+
${PROJECT_SOURCE_DIR}/include/skyr/v1/containers/static_vector.hpp
5355
${PROJECT_SOURCE_DIR}/include/skyr/v1/domain/errors.hpp
5456
${PROJECT_SOURCE_DIR}/include/skyr/v1/domain/domain.hpp
5557
${PROJECT_SOURCE_DIR}/include/skyr/v1/platform/endianness.hpp

src/v1/core/host.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@
88
#include <skyr/v1/core/errors.hpp>
99
#include <skyr/v1/percent_encoding/percent_encoded_char.hpp>
1010
#include <skyr/v1/percent_encoding/percent_decode.hpp>
11+
#include <skyr/v1/containers/static_vector.hpp>
1112
#include <skyr/v1/domain/domain.hpp>
1213

14+
#if !defined(SKYR_DOMAIN_MAX_DOMAIN_LENGTH)
15+
#define SKYR_DOMAIN_MAX_DOMAIN_LENGTH 253
16+
#endif // !defined(SKYR_DOMAIN_MAX_DOMAIN_LENGTH)
17+
1318
namespace skyr {
1419
inline namespace v1 {
1520
namespace {
@@ -73,12 +78,16 @@ auto parse_host(
7378
[] (auto &&h) -> tl::expected<host, url_parse_errc> { return host{h}; });
7479
}
7580

76-
auto domain = percent_decode(input);
77-
if (!domain) {
78-
return tl::make_unexpected(url_parse_errc::cannot_decode_host_point);
81+
auto domain = static_vector<char, SKYR_DOMAIN_MAX_DOMAIN_LENGTH>{};
82+
auto range = percent_encoding::percent_decode_range{input};
83+
for (auto it = std::cbegin(range); it != std::cend(range); ++it) {
84+
if ((domain.size() == domain.max_size()) || !*it) {
85+
return tl::make_unexpected(url_parse_errc::cannot_decode_host_point);
86+
}
87+
domain.push_back((*it).value());
7988
}
8089

81-
auto ascii_domain = domain_to_ascii(domain.value());
90+
auto ascii_domain = domain_to_ascii(std::string_view(domain.data(), domain.size()));
8291
if (!ascii_domain) {
8392
return tl::make_unexpected(url_parse_errc::domain_error);
8493
}

src/v1/core/url_parser_context.cpp

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,24 +75,11 @@ inline auto is_windows_drive_letter(std::string_view segment) noexcept {
7575
}
7676

7777
inline auto is_single_dot_path_segment(std::string_view segment) noexcept {
78-
return
79-
(segment == ".") ||
80-
(segment == "%2e") ||
81-
(segment == "%2E");
78+
return (segment == ".") || (segment == "%2E");
8279
}
8380

8481
auto is_double_dot_path_segment(std::string_view segment) noexcept {
85-
constexpr static auto to_lower = [] (auto byte) -> decltype(byte) {
86-
return std::tolower(byte, std::locale::classic());
87-
};
88-
89-
auto lower = std::string(segment);
90-
std::transform(begin(lower), end(lower), begin(lower), to_lower);
91-
return (
92-
(lower == "..") ||
93-
(lower == ".%2e") ||
94-
(lower == "%2e.") ||
95-
(lower == "%2e%2e"));
82+
return (segment == "..") || (segment == "%2E.") || (segment == ".%2E") || (segment == "%2E%2E");
9683
}
9784

9885
void shorten_path(std::string_view scheme, std::vector<std::string> &path) {

0 commit comments

Comments
 (0)