Skip to content

Commit de1952f

Browse files
committed
chore: use BackedArguments for implementing a pipeline message
Remove duplicate code for building backed arguments, similarly to #6171 Signed-off-by: Roman Gershman <[email protected]>
1 parent 6ec47d4 commit de1952f

File tree

9 files changed

+76
-104
lines changed

9 files changed

+76
-104
lines changed

src/common/backed_args.h

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@ class BackedArguments {
1818
BackedArguments() {
1919
}
2020

21-
template <typename I> BackedArguments(I begin, I end);
21+
// Construct the arguments from iterator range.
22+
// TODO: In general we could get away without the len argument,
23+
// but that would require fixing base::it::CompoundIterator to support subtraction.
24+
// Similarly, I wish that CompoundIterator supported the -> operator.
25+
template <typename I> BackedArguments(I begin, I end, size_t len) {
26+
Assign(begin, end, len);
27+
}
28+
29+
template <typename I> void Assign(I begin, I end, size_t len);
2230

2331
size_t HeapMemory() const {
2432
size_t s1 = lens_.capacity() <= kLenCap ? 0 : lens_.capacity() * sizeof(uint32_t);
@@ -57,22 +65,29 @@ class BackedArguments {
5765

5866
static_assert(sizeof(BackedArguments) == 128);
5967

60-
template <typename I> BackedArguments::BackedArguments(I begin, I end) {
61-
lens_.reserve(end - begin);
68+
template <typename I> void BackedArguments::Assign(I begin, I end, size_t len) {
69+
lens_.resize(len);
6270
size_t total_size = 0;
71+
unsigned idx = 0;
6372
for (auto it = begin; it != end; ++it) {
64-
lens_.push_back(it->size());
65-
total_size += it->size() + 1; // +1 for '\0'
73+
size_t sz = (*it).size();
74+
lens_[idx++] = sz;
75+
total_size += sz + 1; // +1 for '\0'
6676
}
6777
storage_.resize(total_size);
6878

79+
// Reclaim memory if we have too much allocated.
80+
if (storage_.capacity() > kStorageCap && total_size < storage_.capacity() / 2)
81+
storage_.shrink_to_fit();
82+
6983
char* next = storage_.data();
7084
for (auto it = begin; it != end; ++it) {
71-
if (!it->empty()) {
72-
memcpy(next, it->data(), it->size());
85+
size_t sz = (*it).size();
86+
if (sz > 0) {
87+
memcpy(next, (*it).data(), sz);
7388
}
74-
next[it->size()] = '\0';
75-
next += it->size() + 1;
89+
next[sz] = '\0';
90+
next += sz + 1;
7691
}
7792
}
7893

src/facade/dragonfly_connection.cc

Lines changed: 27 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ void UpdateIoBufCapacity(const io::IoBuf& io_buf, ConnectionStats* stats,
152152
}
153153
}
154154

155+
size_t UsedMemoryInternal(const Connection::PipelineMessage& msg) {
156+
return sizeof(msg) + msg.HeapMemory();
157+
}
158+
155159
struct TrafficLogger {
156160
// protects agains closing the file while writing or data races when opening the file.
157161
// Also, makes sure that LogTraffic are executed atomically.
@@ -377,19 +381,6 @@ class PipelineCacheSizeTracker {
377381

378382
thread_local PipelineCacheSizeTracker tl_pipe_cache_sz_tracker;
379383

380-
void Connection::PipelineMessage::SetArgs(const RespVec& args) {
381-
auto* next = storage.data();
382-
for (size_t i = 0; i < args.size(); ++i) {
383-
RespExpr::Buffer buf = args[i].GetBuf();
384-
size_t s = buf.size();
385-
if (s)
386-
memcpy(next, buf.data(), s);
387-
next[s] = '\0';
388-
this->args[i] = MutableSlice(next, s);
389-
next += (s + 1);
390-
}
391-
}
392-
393384
Connection::MCPipelineMessage::MCPipelineMessage(MemcacheParser::Command cmd_in,
394385
std::string_view value_in)
395386
: cmd{std::move(cmd_in)}, value{value_in}, backing_size{0} {
@@ -425,23 +416,13 @@ Connection::MCPipelineMessage::MCPipelineMessage(MemcacheParser::Command cmd_in,
425416
}
426417
}
427418

428-
void Connection::PipelineMessage::Reset(size_t nargs, size_t capacity) {
429-
storage.resize(capacity);
430-
args.resize(nargs);
431-
}
432-
433-
size_t Connection::PipelineMessage::StorageCapacity() const {
434-
return storage.capacity() + args.capacity();
435-
}
436-
437419
size_t Connection::MessageHandle::UsedMemory() const {
438420
struct MessageSize {
439421
size_t operator()(const PubMessagePtr& msg) {
440422
return sizeof(PubMessage) + (msg->channel.size() + msg->message.size());
441423
}
442424
size_t operator()(const PipelineMessagePtr& msg) {
443-
return sizeof(PipelineMessage) + msg->args.capacity() * sizeof(MutableSlice) +
444-
msg->storage.capacity();
425+
return UsedMemoryInternal(*msg);
445426
}
446427
size_t operator()(const MonitorMessage& msg) {
447428
return msg.capacity();
@@ -555,11 +536,10 @@ void Connection::AsyncOperations::operator()(const PubMessage& pub_msg) {
555536
}
556537

557538
void Connection::AsyncOperations::operator()(Connection::PipelineMessage& msg) {
558-
DVLOG(2) << "Dispatching pipeline: " << ToSV(msg.args.front());
539+
DVLOG(2) << "Dispatching pipeline: " << msg.Front();
559540

560541
++self->local_stats_.cmds;
561-
self->service_->DispatchCommand(ParsedArgs{msg.args}, self->reply_builder_.get(),
562-
self->cc_.get());
542+
self->service_->DispatchCommand(ParsedArgs{msg}, self->reply_builder_.get(), self->cc_.get());
563543

564544
self->last_interaction_ = time(nullptr);
565545
self->skip_next_squashing_ = false;
@@ -644,7 +624,7 @@ Connection::Connection(Protocol protocol, util::HttpListenerBase* http_listener,
644624
static atomic_uint32_t next_id{1};
645625

646626
constexpr size_t kReqSz = sizeof(Connection::PipelineMessage);
647-
static_assert(kReqSz <= 256 && kReqSz >= 200);
627+
static_assert(kReqSz <= 256);
648628

649629
switch (protocol) {
650630
case Protocol::REDIS:
@@ -1536,36 +1516,32 @@ void Connection::SquashPipeline() {
15361516
DCHECK_EQ(dispatch_q_.size(), pending_pipeline_cmd_cnt_);
15371517
DCHECK_EQ(reply_builder_->GetProtocol(), Protocol::REDIS); // Only Redis is supported.
15381518

1539-
vector<ParsedArgs> squash_cmds;
1540-
squash_cmds.reserve(dispatch_q_.size());
1519+
unsigned pipeline_count = std::min<uint32_t>(dispatch_q_.size(), pipeline_squash_limit_cached);
15411520

15421521
uint64_t start = CycleClock::Now();
15431522

1544-
for (const auto& msg : dispatch_q_) {
1545-
CHECK(holds_alternative<PipelineMessagePtr>(msg.handle))
1546-
<< msg.handle.index() << " on " << DebugInfo();
1523+
// We use indexes as iterators are invalidated when pushing into the queue.
1524+
auto get_next_fn = [i = 0, this]() mutable -> ParsedArgs {
1525+
const auto& elem = dispatch_q_[i++];
1526+
CHECK(holds_alternative<PipelineMessagePtr>(elem.handle));
1527+
const auto& pmsg = get<PipelineMessagePtr>(elem.handle);
15471528

1548-
auto& pmsg = get<PipelineMessagePtr>(msg.handle);
1549-
squash_cmds.emplace_back(ParsedArgs(pmsg->args));
1550-
if (squash_cmds.size() >= pipeline_squash_limit_cached) {
1551-
// We reached the limit of commands to squash, so we dispatch them.
1552-
break;
1553-
}
1554-
}
1529+
return *pmsg;
1530+
};
15551531

15561532
// async_dispatch is a guard to prevent concurrent writes into reply_builder_, hence
15571533
// it must guard the Flush() as well.
15581534
cc_->async_dispatch = true;
15591535

15601536
DispatchManyResult result =
1561-
service_->DispatchManyCommands(absl::MakeSpan(squash_cmds), reply_builder_.get(), cc_.get());
1537+
service_->DispatchManyCommands(get_next_fn, pipeline_count, reply_builder_.get(), cc_.get());
15621538

15631539
uint32_t dispatched = result.processed;
15641540
uint64_t before_flush = CycleClock::Now();
15651541
//
15661542
// TODO: to investigate if always flushing will improve P99 latency because otherwise we
15671543
// wait for the next batch to finish before fully flushing the current response.
1568-
if (pending_pipeline_cmd_cnt_ == squash_cmds.size() ||
1544+
if (pending_pipeline_cmd_cnt_ == pipeline_count ||
15691545
always_flush_pipeline_cached) { // Flush if no new commands appeared
15701546
reply_builder_->Flush();
15711547
reply_builder_->SetBatchMode(false); // in case the next dispatch is sync
@@ -1597,7 +1573,7 @@ void Connection::SquashPipeline() {
15971573
dispatch_q_.erase(it, it + dispatched);
15981574

15991575
// If interrupted due to pause, fall back to regular dispatch
1600-
skip_next_squashing_ = dispatched != squash_cmds.size();
1576+
skip_next_squashing_ = dispatched != pipeline_count;
16011577
}
16021578

16031579
void Connection::ClearPipelinedMessages() {
@@ -1755,25 +1731,15 @@ void Connection::AsyncFiber() {
17551731
}
17561732

17571733
Connection::PipelineMessagePtr Connection::FromArgs(const RespVec& args) {
1758-
DCHECK(!args.empty());
1759-
size_t backed_sz = 0;
1760-
for (const auto& arg : args) {
1761-
CHECK_EQ(RespExpr::STRING, arg.type);
1762-
backed_sz += arg.GetBuf().size() + 1; // for '\0'
1763-
}
1764-
DCHECK(backed_sz);
1765-
1766-
static_assert(alignof(PipelineMessage) == 8);
1767-
17681734
PipelineMessagePtr ptr;
1769-
if (ptr = GetFromPipelinePool(); ptr) {
1770-
ptr->Reset(args.size(), backed_sz);
1771-
} else {
1735+
if (ptr = GetFromPipelinePool(); !ptr) {
17721736
// We must construct in place here, since there is a slice that uses memory locations
1773-
ptr = make_unique<PipelineMessage>(args.size(), backed_sz);
1737+
ptr = make_unique<PipelineMessage>();
17741738
}
17751739

1776-
ptr->SetArgs(args);
1740+
auto map = [](const RespExpr& expr) { return expr.GetView(); };
1741+
auto range = base::it::Transform(map, base::it::Range(args.begin(), args.end()));
1742+
ptr->Assign(range.begin(), range.end(), args.size());
17771743
return ptr;
17781744
}
17791745

@@ -1782,7 +1748,7 @@ void Connection::ShrinkPipelinePool() {
17821748
return;
17831749

17841750
if (tl_pipe_cache_sz_tracker.CheckAndUpdateWatermark(pipeline_req_pool_.size())) {
1785-
stats_->pipeline_cmd_cache_bytes -= pipeline_req_pool_.back()->StorageCapacity();
1751+
stats_->pipeline_cmd_cache_bytes -= UsedMemoryInternal(*pipeline_req_pool_.back());
17861752
pipeline_req_pool_.pop_back();
17871753
}
17881754
}
@@ -1792,7 +1758,7 @@ Connection::PipelineMessagePtr Connection::GetFromPipelinePool() {
17921758
return nullptr;
17931759

17941760
auto ptr = std::move(pipeline_req_pool_.back());
1795-
stats_->pipeline_cmd_cache_bytes -= ptr->StorageCapacity();
1761+
stats_->pipeline_cmd_cache_bytes -= UsedMemoryInternal(*ptr);
17961762
pipeline_req_pool_.pop_back();
17971763
return ptr;
17981764
}
@@ -1968,7 +1934,7 @@ void Connection::RecycleMessage(MessageHandle msg) {
19681934
pending_pipeline_cmd_cnt_--;
19691935
pending_pipeline_bytes_ -= used_mem;
19701936
if (stats_->pipeline_cmd_cache_bytes < qbp.pipeline_cache_limit) {
1971-
stats_->pipeline_cmd_cache_bytes += (*pipe)->StorageCapacity();
1937+
stats_->pipeline_cmd_cache_bytes += UsedMemoryInternal(*(*pipe));
19721938
pipeline_req_pool_.push_back(std::move(*pipe));
19731939
}
19741940
}

src/facade/dragonfly_connection.h

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <utility>
1414
#include <variant>
1515

16+
#include "common/backed_args.h"
1617
#include "facade/acl_commands_def.h"
1718
#include "facade/facade_types.h"
1819
#include "facade/memcache_parser.h"
@@ -80,23 +81,7 @@ class Connection : public util::Connection {
8081
};
8182

8283
// Pipeline message, accumulated Redis command to be executed.
83-
struct PipelineMessage {
84-
PipelineMessage(size_t nargs, size_t capacity) : args(nargs), storage(capacity) {
85-
}
86-
87-
void Reset(size_t nargs, size_t capacity);
88-
89-
void SetArgs(const RespVec& args);
90-
91-
size_t StorageCapacity() const;
92-
93-
// mi_stl_allocator uses mi heap internally.
94-
// The capacity is chosen so that we allocate a fully utilized (256 bytes) block.
95-
using StorageType = absl::InlinedVector<char, kReqStorageSize>;
96-
97-
absl::InlinedVector<std::string_view, 6> args;
98-
StorageType storage;
99-
};
84+
using PipelineMessage = cmn::BackedArguments;
10085

10186
// Pipeline message, accumulated Memcached command to be executed.
10287
struct MCPipelineMessage {

src/facade/ok_main.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ class OkService : public ServiceInterface {
2828
return DispatchResult::OK;
2929
}
3030

31-
DispatchManyResult DispatchManyCommands(absl::Span<ParsedArgs> args_lists,
31+
DispatchManyResult DispatchManyCommands(std::function<ParsedArgs()> arg_gen, unsigned count,
3232
SinkReplyBuilder* builder,
3333
ConnectionContext* cntx) final {
34-
for (auto args : args_lists)
34+
for (unsigned i = 0; i < count; i++) {
35+
ParsedArgs args = arg_gen();
3536
DispatchCommand(args, builder, cntx);
37+
}
3638
DispatchManyResult result{
37-
.processed = static_cast<uint32_t>(args_lists.size()),
39+
.processed = static_cast<uint32_t>(count),
3840
.account_in_stats = true,
3941
};
4042
return result;

src/facade/service_interface.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ class ServiceInterface {
3939
virtual DispatchResult DispatchCommand(ParsedArgs args, SinkReplyBuilder* builder,
4040
ConnectionContext* cntx) = 0;
4141

42-
virtual DispatchManyResult DispatchManyCommands(absl::Span<ParsedArgs> commands,
43-
SinkReplyBuilder* builder,
42+
virtual DispatchManyResult DispatchManyCommands(std::function<ParsedArgs()> arg_gen,
43+
unsigned count, SinkReplyBuilder* builder,
4444
ConnectionContext* cntx) = 0;
4545

4646
virtual void DispatchMC(const MemcacheParser::Command& cmd, std::string_view value,

src/server/conn_context.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ static void SendSubscriptionChangedResponse(string_view action, std::optional<st
3636

3737
StoredCmd::StoredCmd(const CommandId* cid, facade::ArgSlice args, facade::ReplyMode mode)
3838
: cid_{cid}, args_{args}, reply_mode_{mode} {
39-
backed_ = std::make_unique<cmn::BackedArguments>(args.begin(), args.end());
39+
backed_ = std::make_unique<cmn::BackedArguments>(args.begin(), args.end(), args.size());
4040
args_ = facade::ParsedArgs{*backed_};
4141
}
4242

src/server/main_service.cc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,13 +1725,13 @@ DispatchResult Service::InvokeCmd(const CommandId* cid, CmdArgList tail_args,
17251725
return res;
17261726
}
17271727

1728-
DispatchManyResult Service::DispatchManyCommands(absl::Span<facade::ParsedArgs> args_list,
1729-
SinkReplyBuilder* builder,
1728+
DispatchManyResult Service::DispatchManyCommands(std::function<facade::ParsedArgs()> arg_gen,
1729+
unsigned count, SinkReplyBuilder* builder,
17301730
facade::ConnectionContext* cntx) {
17311731
ConnectionContext* dfly_cntx = static_cast<ConnectionContext*>(cntx);
17321732
DCHECK(!dfly_cntx->conn_state.exec_info.IsRunning());
17331733
DCHECK_EQ(builder->GetProtocol(), Protocol::REDIS);
1734-
DCHECK_GT(args_list.size(), 1u);
1734+
DCHECK_GT(count, 1u);
17351735

17361736
auto* ss = dfly::ServerState::tlocal();
17371737
// Don't even start when paused. We can only continue if DispatchTracker is aware of us running.
@@ -1771,7 +1771,8 @@ DispatchManyResult Service::DispatchManyCommands(absl::Span<facade::ParsedArgs>
17711771
stored_cmds.clear();
17721772
};
17731773

1774-
for (const auto& args : args_list) {
1774+
for (unsigned i = 0; i < count; i++) {
1775+
ParsedArgs args = arg_gen();
17751776
string cmd = absl::AsciiStrToUpper(args.Front());
17761777
const auto [cid, tail_args] = registry_.FindExtended(cmd, args.Tail());
17771778

@@ -1789,7 +1790,7 @@ DispatchManyResult Service::DispatchManyCommands(absl::Span<facade::ParsedArgs>
17891790
const bool is_blocking = cid != nullptr && cid->IsBlocking();
17901791

17911792
if (!is_multi && !is_eval && !is_blocking && cid != nullptr) {
1792-
stored_cmds.reserve(args_list.size());
1793+
stored_cmds.reserve(count);
17931794
stored_cmds.emplace_back(cid, tail_args); // Shallow copy
17941795
continue;
17951796
}

src/server/main_service.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class Service : public facade::ServiceInterface {
4040
facade::ConnectionContext* cntx) final;
4141

4242
// Execute multiple consecutive commands, possibly in parallel by squashing
43-
facade::DispatchManyResult DispatchManyCommands(absl::Span<facade::ParsedArgs> args_list,
44-
facade::SinkReplyBuilder* builder,
43+
facade::DispatchManyResult DispatchManyCommands(std::function<facade::ParsedArgs()> arg_gen,
44+
unsigned count, facade::SinkReplyBuilder* builder,
4545
facade::ConnectionContext* cntx) final;
4646

4747
// Check VerifyCommandExecution and invoke command with args

src/server/test_utils.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,13 +486,16 @@ void BaseFamilyTest::RunMany(const std::vector<std::vector<std::string>>& cmds)
486486
TestConnWrapper* conn_wrapper = AddFindConn(Protocol::REDIS, GetId());
487487
auto* context = conn_wrapper->cmd_cntx();
488488
context->ns = &namespaces->GetDefaultNamespace();
489-
vector<facade::ParsedArgs> args_vec(cmds.size());
490489
vector<cmn::BackedArguments> backed_args_vec(cmds.size());
491490
for (size_t i = 0; i < cmds.size(); ++i) {
492-
backed_args_vec[i] = cmn::BackedArguments(cmds[i].begin(), cmds[i].end());
493-
args_vec[i] = facade::ParsedArgs{backed_args_vec[i]};
491+
backed_args_vec[i] = cmn::BackedArguments(cmds[i].begin(), cmds[i].end(), cmds[i].size());
494492
}
495-
service_->DispatchManyCommands(absl::MakeSpan(args_vec), conn_wrapper->builder(), context);
493+
auto next_fn = [it = backed_args_vec.begin()]() mutable {
494+
ParsedArgs args(*it);
495+
++it;
496+
return args;
497+
};
498+
service_->DispatchManyCommands(next_fn, cmds.size(), conn_wrapper->builder(), context);
496499
DCHECK(context->transaction == nullptr);
497500
}
498501

0 commit comments

Comments
 (0)