Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Intro:
Hi, my name is Everett, and I wanted to make some improvements to the spectatord project. I firmly believe I can really contribute to this team and project. I recently applied for the L4 role.
I took it upon myself to look at the spectatord repo and tried to find an area for improvement. I stumbled upon the
compressed_buffer
implementation, which I assume is called quite frequently, so I made some changes and will describe them below.Improvement 1: Simplifying Function Overloads
The first change I made was function unification. The compressed_buffer had several overloads of the
Append()
function. The internal implementations of the functions were also nearly identical. The only difference being theparameter types
,argument count,
and amount of calls topush_back()
.Rather than the style above, which also included multiple implementations, I consolidated this into the two functions below. I created a templated variadic function called
Append()
that serves as a public wrapper to a private methodAppendImpl()
.AppendImpl()
will then execute the original code depending on the arguments and their types.I have included a snapshot of these functions below for reference:
Lets talk about
Append()
:The new public
Append()
function examines the parameter packs types at compile time and asserts that every argument provided toAppend()
is either implicitly convertible tostring_view
or is strictly auint8_t
. If you try to call this function with arguments outside of those types, it will fail at compile time. You can test this by uncommenting the unit tests I added on lines51–69
. This assert will now force you to cast the types you are trying to append so implicit conversion does not occur. Implicit conversion can result in possible loss of precision or data truncation.static_cast
the value being added because it was causing animplicit narrowing conversion
. This line should probably be examined for a potential bit manipulation error becausemid & 0x7FU
results in the creation of anunsigned int
. With the new templated function, you will be informed at compile time if this occurs in other locations. You can also enable your compiler to catch these errors by adding the following flags to yourCMakeLists.txt
file online 10
:-Wconversion -Wsign-conversion
.Once the types are confirmed to be valid (all params implicitly convertible to
string_view
, or all params are strictlyuint8_t
), the function proceeds with the original code, but nowAppendImpl()
handles writing to theprivate vector cur_
Lets talk about Private
AppendImpl()
:The private
AppendImpl()
function also contains asserts to match the original behavior. Your initial implementation only allowed for onestring_view
, or oneuint8_t
, or multipleuint8_t
values in the inclusive range 1 through 6. The newAppendImpl()
checks these conditions during compile timeThe compile time assert logic simply says if every parameter in the parameter pack is of the type
string_view
lets also make sure that there is only one parameter in that pack. If this fails it jumps to the next section which says if every parameter is of the typeuint8_t
lets make sure we have at least one parameter in the pack and at most six.Here is a brief example of what happens at compile time but you could just take a look at my unit tests
Improvement 2: Performance
I also made a performance fix:
For implicit
string_view
types, you were previously usingstd::copy(s.begin(), s.end(), std::back_inserter(cur_));
. I’ve changed this to ->(cur_.insert(cur_.end(), std::forward<Args>(args).begin(), std::forward<Args>(args).end()), ...);
This is more efficient because
std::back_inserter()
defaults to callingpush_back()
. So for every byte in your string glibCC will do the following:Step 3 will not happen becuase you called
Reserve()
forcur_
on construction, but step 1 and step 2 are still occurring for each byte in the string. By usinginsert()
we will just check the internal size once and then insert all the bytes if possible.For the sequential
push_back()
calls that were occurring in the originalAppend()
when the types wereuint8_t
, I replaced them with a fold expression:Ideally, this should be done with an initializer list. You could make sure
cur
has the internal capacity and then insert all the elements at once. This again would avoid the repetitive nature of going through glibccpush_back()
call multiple times.Here are some profiling images, as we increase the amount of bytes we want to copy the benefits of
insert()
show.In the first image the trade off is negligible because the amount of bytes you are copying is 5 which is a small amount. The GLibC Insert() implementation has a bit more initial work to do because of the iterators passed in, but as the copy amount increases, the initial performance cost will be offset.