[ntcore] Optimize scan of outgoing messages (#5227)

The algorithm being used for scanning outgoing messages was O(n^2)
because it did a full linear search and then appended. This scan is
performed for each client. If there is a burst of outgoing changes, the
outgoing queue can get quite deep all at once and this scan can be very
slow. Replacing with a map fixes this.
This commit is contained in:
Peter Johnson
2023-03-25 15:20:22 -07:00
committed by GitHub
parent b510c17ef6
commit 0a66479693
2 changed files with 103 additions and 18 deletions

View File

@@ -3,6 +3,7 @@
// the WPILib BSD license file in the root directory of this project.
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdlib>
@@ -18,6 +19,7 @@
#include "ntcore_cpp.h"
void bench();
void bench2();
void stress();
int main(int argc, char* argv[]) {
@@ -25,6 +27,10 @@ int main(int argc, char* argv[]) {
bench();
return EXIT_SUCCESS;
}
if (argc == 2 && std::string_view{argv[1]} == "bench2") {
bench2();
return EXIT_SUCCESS;
}
if (argc == 2 && std::string_view{argv[1]} == "stress") {
stress();
return EXIT_SUCCESS;
@@ -114,6 +120,79 @@ void bench() {
PrintTimes(flushTimes);
}
void bench2() {
// set up instances
auto client1 = nt::CreateInstance();
auto client2 = nt::CreateInstance();
auto server = nt::CreateInstance();
// connect client and server
nt::StartServer(server, "bench2.json", "127.0.0.1", 10001, 10000);
nt::StartClient4(client1, "client1");
nt::StartClient3(client2, "client2");
nt::SetServer(client1, "127.0.0.1", 10000);
nt::SetServer(client2, "127.0.0.1", 10001);
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
// add "typical" set of subscribers on client and server
nt::SubscribeMultiple(client1, {{std::string_view{}}});
nt::SubscribeMultiple(client2, {{std::string_view{}}});
nt::SubscribeMultiple(server, {{std::string_view{}}});
// create 1000 entries
std::array<NT_Entry, 1000> pubs;
for (int i = 0; i < 1000; ++i) {
pubs[i] = nt::GetEntry(
nt::GetTopic(server,
fmt::format("/some/long/name/with/lots/of/slashes/{}", i)),
NT_DOUBLE_ARRAY, "double[]");
}
// warm up
for (int i = 1; i <= 100; ++i) {
for (auto pub : pubs) {
double vals[3] = {i * 0.01, i * 0.02, i * 0.03};
nt::SetDoubleArray(pub, vals);
}
nt::FlushLocal(server);
std::this_thread::sleep_for(0.02s);
}
std::vector<int64_t> flushTimes;
flushTimes.reserve(1001);
std::vector<int64_t> times;
times.reserve(1001);
// benchmark
auto start = std::chrono::high_resolution_clock::now();
int64_t now = nt::Now();
for (int i = 1; i <= 1000; ++i) {
for (auto pub : pubs) {
double vals[3] = {i * 0.01, i * 0.02, i * 0.03};
nt::SetDoubleArray(pub, vals);
}
int64_t prev = now;
now = nt::Now();
times.emplace_back(now - prev);
nt::FlushLocal(server);
nt::Flush(server);
flushTimes.emplace_back(nt::Now() - now);
std::this_thread::sleep_for(0.02s);
now = nt::Now();
}
auto stop = std::chrono::high_resolution_clock::now();
fmt::print("total time: {}us\n",
std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
.count());
PrintTimes(times);
fmt::print("-- Flush --\n");
PrintTimes(flushTimes);
}
static std::random_device r;
static std::mt19937 gen(r());
static std::uniform_real_distribution<double> dist;