Improving libcron performance (#9)

Co-authored-by: Heinz-Peter Liechtenecker <h.liechtenecker@fh-kaernten.at>
Co-authored-by: Per Malmberg <PerMalmberg@users.noreply.github.com>
This commit is contained in:
Heinz-Peter Liechtenecker
2020-09-26 13:32:54 +02:00
committed by GitHub
parent 7ef39558a1
commit f3fddf5f19
9 changed files with 481 additions and 92 deletions

View File

@@ -3,8 +3,15 @@ project(cron_test)
set(CMAKE_CXX_STANDARD 17)
# Deactivate Iterator-Debugging on Windows
option(LIBCRON_DEACTIVATE_ITERATOR_DEBUGGING "Build with iterator-debugging (MSVC only)." OFF)
if( MSVC )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
if (LIBCRON_DEACTIVATE_ITERATOR_DEBUGGING)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_HAS_ITERATOR_DEBUGGING=0")
endif()
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
endif()
@@ -19,12 +26,12 @@ add_executable(
${PROJECT_NAME}
CronDataTest.cpp
CronRandomizationTest.cpp
CronScheduleTest.cpp
CronTest.cpp)
CronScheduleTest.cpp
CronTest.cpp)
target_link_libraries(${PROJECT_NAME} libcron)
set_target_properties(${PROJECT_NAME} PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out")
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out")

View File

@@ -1,5 +1,6 @@
#include <catch.hpp>
#include <libcron/include/libcron/Cron.h>
#include <libcron/externals/date/include/date/date.h>
#include <thread>
#include <iostream>
@@ -209,6 +210,7 @@ SCENARIO("Task priority")
}
AND_WHEN("Waiting based on the time given by the Cron instance")
{
auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(c.time_until_next());
std::this_thread::sleep_for(c.time_until_next());
c.tick();
@@ -446,3 +448,198 @@ SCENARIO("Tasks can be added and removed from the scheduler")
}
}
}
SCENARIO("Testing CRON-Tick Performance")
{
GIVEN("A Cron instance with no task")
{
using namespace std::chrono_literals;
using clock = std::chrono::high_resolution_clock;
using std::chrono::milliseconds;
using std::chrono::duration_cast;
Cron<TestClock> c1{};
auto& cron_clock1 = c1.get_clock();
Cron<TestClock> c2{};
auto& cron_clock2 = c2.get_clock();
Cron<TestClock> c3{};
auto& cron_clock3 = c3.get_clock();
int count1 = 0;
int count2 = 0;
int count3 = 0;
WHEN("Creating 1000 CronData Objects")
{
std::string cron_job = "* * * * * ?";
auto begin_cron_data = clock::now();
for(int i = 1; i <= 1000; i++)
{
auto cron = CronData::create(cron_job);
}
auto end_cron_data = clock::now();
auto msec_cron_data = duration_cast<milliseconds>(end_cron_data - begin_cron_data);
// Hopefully Creating a lot of Cron Objects does not take more than a second
REQUIRE(msec_cron_data <= 1000ms);
}
WHEN("Adding 1000 Tasks where with an invalid CRON-String with std::map<std::string, std::string>")
{
std::map<std::string, std::string> name_schedule_map;
for(int i = 1; i <= 1000; i++)
{
name_schedule_map["Task-" + std::to_string(i)] = "* * * * * ?";
}
name_schedule_map["Task-1000"] = "invalid";
auto res = c1.add_schedule(name_schedule_map,
[](auto&) { });
REQUIRE_FALSE(std::get<0>(res));
REQUIRE(std::get<1>(res) == "Task-1000");
REQUIRE(std::get<2>(res) == "invalid");
}
WHEN("Adding a std::vector<std::tuple<std::string, std::string>>")
{
std::vector<std::tuple<std::string, std::string>> name_schedule_map;
for(int i = 1; i <= 1000; i++)
{
name_schedule_map.push_back(std::make_tuple("Task-" + std::to_string(i), "* * * * * ?"));
}
auto res = c1.add_schedule(name_schedule_map,
[](auto&) { });
REQUIRE(std::get<0>(res));
}
WHEN("Adding a std::vector<std::pair<std::string, std::string>>")
{
std::vector<std::pair<std::string, std::string>> name_schedule_map;
for(int i = 1; i <= 1000; i++)
{
name_schedule_map.push_back(std::make_pair("Task-" + std::to_string(i), "* * * * * ?"));
}
auto res = c1.add_schedule(name_schedule_map,
[](auto&) { });
REQUIRE(std::get<0>(res));
}
WHEN("Adding a std::unordered_map<std::string, std::string>")
{
std::unordered_map<std::string, std::string> name_schedule_map;
for(int i = 1; i <= 1000; i++)
{
name_schedule_map["Task-" + std::to_string(i)] = "* * * * * ?";
}
auto res = c1.add_schedule(name_schedule_map,
[](auto&) { });
REQUIRE(std::get<0>(res));
}
WHEN("Adding 1000 Tasks to two Cron-Objects expiring after 1 second calling add_schedule")
{
auto begin_add_sequential = clock::now();
for(int i = 1; i <= 1000; i++)
{
REQUIRE(c1.add_schedule("Task-" + std::to_string(i), "* * * * * ?",
[&count1](auto&)
{
count1++;
})
);
REQUIRE(c2.add_schedule("Task-" + std::to_string(i), "* * * * * ?",
[&count2](auto&)
{
count2++;
})
);
}
auto end_add_sequential = clock::now();
std::map<std::string, std::string> name_schedule_map{};
for(int i = 1; i <= 1000; i++)
{
name_schedule_map["Task-" + std::to_string(i)] = "* * * * * ?";
}
auto begin_add_batch = clock::now();
REQUIRE(std::get<0>(c3.add_schedule(name_schedule_map,
[&count3](auto&)
{
count3++;
}))
);
auto end_add_batch = clock::now();
auto time_sequential = duration_cast<milliseconds>(end_add_sequential - begin_add_sequential)/2;
auto time_batch = duration_cast<milliseconds>(end_add_batch - begin_add_batch);
// This should hopefully take only a few second?
REQUIRE(time_sequential < 10000ms);
REQUIRE(time_batch < 5000ms);
REQUIRE(time_batch < time_sequential);
REQUIRE(c1.count() == 1000);
REQUIRE(c2.count() == 1000);
REQUIRE(c3.count() == 1000);
THEN("Execute all Tasks 10 Times")
{
milliseconds msec1;
auto t1 = std::thread([&cron_clock1, &c1, &msec1]() {
auto begin_tick = clock::now();
for(auto i = 0; i < 10; ++i)
{
cron_clock1.add(std::chrono::seconds{1});
c1.tick();
}
auto end_tick = clock::now();
msec1 = duration_cast<milliseconds>(end_tick - begin_tick)/10;
});
milliseconds msec2;
auto t2 = std::thread([&cron_clock2, &c2, &msec2]() {
auto begin_tick = clock::now();
for(auto i = 0; i < 10; ++i)
{
cron_clock2.add(std::chrono::seconds{1});
c2.tick();
}
auto end_tick = clock::now();
msec2 = duration_cast<milliseconds>(end_tick - begin_tick)/10;
});
milliseconds msec3;
auto t3 = std::thread([&cron_clock3, &c3, &msec3]() {
auto begin_tick = clock::now();
for(auto i = 0; i < 10; ++i)
{
cron_clock3.add(std::chrono::seconds{1});
c3.tick();
}
auto end_tick = clock::now();
msec3 = duration_cast<milliseconds>(end_tick - begin_tick)/10;
});
t1.join();
t2.join();
t3.join();
REQUIRE(count1 == 10000);
REQUIRE(count2 == 10000);
REQUIRE(count3 == 10000);
// Hopefully executing a more or less empty task does only take some milliseconds
REQUIRE(msec1 <= 10ms);
REQUIRE(msec2 <= 10ms);
REQUIRE(msec3 <= 10ms);
}
}
}
}