-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support chunked_vector and other json/httpd changes #2647
base: master
Are you sure you want to change the base?
Changes from 7 commits
d668345
051f94f
41b5489
f207d2f
4f878f7
94c4c7a
df6953f
5117252
9f22206
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ | |
#ifndef SEASTAR_MODULE | ||
#include <algorithm> | ||
#include <cassert> | ||
#include <iterator> | ||
#include <type_traits> | ||
#include <seastar/util/modules.hh> | ||
#endif | ||
|
@@ -176,10 +177,11 @@ public: | |
public: | ||
chunked_fifo() noexcept = default; | ||
chunked_fifo(chunked_fifo&& x) noexcept; | ||
chunked_fifo(const chunked_fifo& X) = delete; | ||
chunked_fifo(const chunked_fifo&); | ||
~chunked_fifo(); | ||
chunked_fifo& operator=(const chunked_fifo&) = delete; | ||
chunked_fifo& operator=(const chunked_fifo&); | ||
chunked_fifo& operator=(chunked_fifo&&) noexcept; | ||
inline bool operator==(const chunked_fifo& rhs) const; | ||
inline void push_back(const T& data); | ||
inline void push_back(T&& data); | ||
T& back() noexcept; | ||
|
@@ -296,6 +298,25 @@ chunked_fifo<T, items_per_chunk>::chunked_fifo(chunked_fifo&& x) noexcept | |
x._nfree_chunks = 0; | ||
} | ||
|
||
template <typename T, size_t items_per_chunk> | ||
inline | ||
chunked_fifo<T, items_per_chunk>::chunked_fifo(const chunked_fifo& rhs) | ||
: chunked_fifo() { | ||
std::copy_n(rhs.begin(), rhs.size(), std::back_inserter(*this)); | ||
} | ||
|
||
template <typename T, size_t items_per_chunk> | ||
inline | ||
chunked_fifo<T, items_per_chunk>& | ||
chunked_fifo<T, items_per_chunk>::operator=(const chunked_fifo& rhs) { | ||
if (&rhs != this) { | ||
clear(); | ||
std::copy_n(rhs.begin(), rhs.size(), std::back_inserter(*this)); | ||
shrink_to_fit(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In copy() you decided to use reserve() and here shrink_to_fit(). Is there a reason? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By the way, I'm pretty sure that bulk copy can be done more efficiently than copying the items one by one, but I don't know if it matters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @nyh for your feedback!
In the In the assignment operation the LHS may already have allocated memory and there are sort of three primary cases as I see it:
The goal is not not reallocate memory if not necessary (case 2) and not leave the container with a capacity totally out of line with its contents (case 3). The chosen approach does that.
The reason for the two versions is as above, so it seems like the common code is really only the
Yes, and it is easiest in case 2, where the LHS is already big enough, then we can simply use In general it doesn't seem like existing routines in chunked_fifo are optimized to that level, but I'm happy to go this route if you think that's what's required here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. No, I don't think that super-optimizing this code is very important. By definition, making a copy of a whole vector is already non-optimal... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Indeed, and here we are adding the copy assignment operator only because that's a requirement of the json elements in seastar: we auto-generate a copy constructor (which internally uses assignment) so we require all types used as json elements to have at least an assignment operator. |
||
} | ||
return *this; | ||
} | ||
|
||
template <typename T, size_t items_per_chunk> | ||
inline | ||
chunked_fifo<T, items_per_chunk>& | ||
|
@@ -455,6 +476,11 @@ chunked_fifo<T, items_per_chunk>::emplace_back(Args&&... args) { | |
++_back_chunk->end; | ||
} | ||
|
||
template <typename T, size_t items_per_chunk> | ||
inline bool chunked_fifo<T, items_per_chunk>::operator==(const chunked_fifo& rhs) const { | ||
return size() == rhs.size() && std::equal(begin(), end(), rhs.begin()); | ||
} | ||
|
||
template <typename T, size_t items_per_chunk> | ||
inline void | ||
chunked_fifo<T, items_per_chunk>::push_back(const T& data) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,7 +43,7 @@ namespace internal { | |
|
||
template<typename T> | ||
concept is_map = requires { | ||
typename T::mapped_type; | ||
typename std::remove_reference_t<T>::mapped_type; | ||
}; | ||
|
||
template<typename T> | ||
|
@@ -357,8 +357,8 @@ public: | |
*/ | ||
template<std::ranges::input_range Range> | ||
requires (!internal::is_string_like<Range>) | ||
static future<> write(output_stream<char>& s, const Range& range) { | ||
return do_with(std::move(range), [&s] (const auto& range) { | ||
static future<> write(output_stream<char>& s, Range&& range) { | ||
return do_with(std::forward<Range>(range), [&s] (const auto& range) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, the original code pretends to move but actually copies. The new code moves when it's safe. |
||
if constexpr (internal::is_map<Range>) { | ||
return write(s, state::map, std::ranges::begin(range), std::ranges::end(range)); | ||
} else { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,18 @@ | |
"VAL2", | ||
"VAL3" | ||
] | ||
}, | ||
{ | ||
"name": "stream_enum", | ||
"description": "Whether to return the response as a stream_object", | ||
"required": true, | ||
"allowMultiple": false, | ||
"type": "string", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i was about to suggest to use "boolean". but ironically, it turns out that the "string" type with enum contraints is indeed simpler. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Happy to use boolean if you prefer. I actually used enum since I did a quick check and dind't see any existing uses of boolean but looking at seastar-json2code.py a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's less surprising to have a boolean here. |
||
"paramType": "query", | ||
"enum": [ | ||
"no", | ||
"yes" | ||
] | ||
} | ||
] | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be more symmetrical to require x = y.copy(), no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind allowing both the copy constructor and assignment operator. C++ is a copyful language, and pretending it isn't usually just makes life harder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but ... the main problem I'm trying to solve is that json2code generates a "copy constructor" which expects all the elements of the object to be copy-assignable, here's an example from api.json in this repo:
As shown, this copy constructor does element-wise assignment (not sure why it doesn't use member init in order to use copy-ctor instead), so that's why I'm adding the assignment operator: to make the above compile with chunked_fifo as an element.
Actually
copy()
is not needed here at all, it is vestigial from a different approach I tried first, though we do use thiscopy()
pattern in Redpanda, exactly as you wanted: the container class declares move-assignment, but not move-copy, so you'd usex = y.copy()
if you wanted to "force" a copy-assignment.I'm happy to whatever here:
chunked_fifo
and update the code generation to use the copy-ctorcopy()
and update the code generation to callcopy()
explicitly (I had a change along these lines originally)Please advise.
I agree that containers without copy are kind of anti-C++ though I know having them as saved us many unecessary copies in Redpanda as it forces you to get move working everywhere (with
copy()
as an escape hatch).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also 5. change json2code to call a helper template function, which defaults to a regular copy, but which we can override to do something else for specific types.
But I think supporting the copy constructor is the path of least friction (though it opens the door to bad surprises).
We handle unexpected copies by having our small-scale performance tests monitor allocation count (and task count, and instruction count) per op and watching for changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me.
Push 44e087a adds full copy support to chunked_fifo, and removes
copy()
. This actually simplifies the fix to the dangling code that @nvartolomei pointed out: currently it's hard to fully support move-only types all the way down the serialization hierarchy as we have only a virtualwrite(ostream) const
method on json elements: this is not suitable for move-only types, which need a && overload but then can't even compile theconst
method, so it all gets very messy (e.g., the code generator would need to track whether the current object was "tainted" anywhere by a move-only type and then stub out the const write method to throw an exception, or implement a different interface or something.I love this idea. Is this source-available so I can peek at it?