Skip to content

Commit de701bf

Browse files
committed
add opt-in, simple parallelisation to model base class
1 parent 17db642 commit de701bf

File tree

6 files changed

+164
-59
lines changed

6 files changed

+164
-59
lines changed

esl/agent.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ namespace esl {
7575
///
7676
/// \param rhs
7777
/// \return
78-
[[nodiscard]] constexpr bool operator!=(const agent &rhs) const
78+
[[nodiscard]] constexpr bool operator != (const agent &rhs) const
7979
{
8080
return !(*this == rhs);
8181
}

esl/computation/environment.cpp

+19-13
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,27 @@ namespace esl::computation {
126126
size_t environment::send_messages(simulation::model &simulation)
127127
{
128128
size_t messages_ = 0;
129-
for(auto &[i, a] : simulation.agents.local_agents_) {
130-
(void) i;
131-
for(const auto &m: a->outbox) {
132-
auto iterator_ =
133-
simulation.agents.local_agents_.find(m->recipient);
134-
if(simulation.agents.local_agents_.end() == iterator_) {
135-
// not in distributed mode, and no local agent matching
136-
throw std::logic_error("agent not found "
137-
+ m->recipient.representation());
129+
if(simulation.threads <= 1) {
130+
for(auto &[i, a] : simulation.agents.local_agents_) {
131+
(void)i;
132+
for(const auto &m : a->outbox) {
133+
auto iterator_ =
134+
simulation.agents.local_agents_.find(m->recipient);
135+
if(simulation.agents.local_agents_.end() == iterator_) {
136+
// not in distributed mode, and no local agent matching
137+
throw std::logic_error("agent not found "
138+
+ m->recipient.representation());
139+
}
140+
iterator_->second->inbox.insert({m->received, m});
141+
++messages_;
138142
}
139-
iterator_->second->inbox.insert({m->received, m});
140-
++messages_;
143+
a->outbox.clear();
144+
a->outbox.shrink_to_fit();
141145
}
142-
a->outbox.clear();
143-
a->outbox.shrink_to_fit();
146+
}else{
147+
148+
149+
144150
}
145151
return messages_;
146152
}

esl/simulation/entity.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ namespace esl {
8686
///
8787
///
8888
///
89-
entity() : entity(identity<entity_type_>())
89+
entity()
90+
: entity(identity<entity_type_>())
9091
{
9192

9293
}
@@ -123,7 +124,6 @@ namespace esl {
123124
}
124125

125126
public:
126-
127127
constexpr bool operator == (const entity_type_ &operand) const
128128
{
129129
return this->identifier == operand.identifier;

esl/simulation/model.cpp

+65-28
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626

2727
#include <chrono>
2828
using std::chrono::high_resolution_clock;
29+
#include <numeric>
2930

3031
#include <esl/agent.hpp>
3132
#include <esl/computation/environment.hpp>
3233
#include <esl/data/log.hpp>
34+
#include <esl/quantity.hpp>
3335

3436

3537
namespace esl::simulation {
@@ -44,6 +46,7 @@ namespace esl::simulation {
4446
, sample(parameters.get<std::uint64_t>("sample"))
4547
, agents(e)
4648
, verbosity(parameters.get<std::uint64_t>("verbosity"))
49+
, threads( std::max(1u, parameters.get<unsigned int>("threads")))
4750
{
4851

4952
}
@@ -65,35 +68,41 @@ namespace esl::simulation {
6568
environment_.before_step();
6669

6770
// read the sample index from the parameter collection
68-
auto first_event_ = step.upper;
71+
72+
std::mutex mutex_first_event_;
73+
time_point first_event_ = step.upper;
6974
unsigned int round_ = 0;
7075
do {
7176

7277
if (verbosity > 0 && 0 == (rounds_ % verbosity)){
7378
LOG(notice) << "time " << step << " round " << round_ << std::endl;
7479
}
7580
first_event_ = step.upper;
76-
for(auto &[i, a] : agents.local_agents_) {
77-
78-
//double agent_cb_end_;
79-
//timings_.emplace(i, 0.);
80-
//timings_cb_.emplace(i, 0.);
81-
//timings_act_.emplace(i, 0.);
82-
//auto agent_start_ = high_resolution_clock::now();
83-
//auto agent_act_ = high_resolution_clock::now();
81+
82+
83+
auto job_ = [&](std::shared_ptr<agent> a){
84+
// double agent_cb_end_;
85+
// timings_.emplace(i, 0.);
86+
// timings_cb_.emplace(i, 0.);
87+
// timings_act_.emplace(i, 0.);
88+
// auto agent_start_ = high_resolution_clock::now();
89+
// auto agent_act_ = high_resolution_clock::now();
8490
// The seed is deterministic in the following variables:
8591
std::seed_seq seed_ {
86-
std::uint64_t(std::hash<identity<agent>>()(i)),
87-
std::uint64_t(step.lower),
88-
std::uint64_t(round_),
89-
sample
90-
};
91-
92-
//try {
93-
first_event_ = std::min(first_event_, a->process_messages(step, seed_));
94-
//agent_cb_end_ = double((high_resolution_clock::now() - agent_start_).count());
95-
//agent_act_ = high_resolution_clock::now();
92+
std::uint64_t(std::hash<identity<agent>>()(a->identifier)),
93+
std::uint64_t(step.lower), std::uint64_t(round_),
94+
sample};
95+
96+
// try {
97+
98+
{
99+
std::unique_lock lock_(mutex_first_event_);
100+
first_event_ = std::min(first_event_,
101+
a->process_messages(step, seed_));
102+
// agent_cb_end_ = double((high_resolution_clock::now() - agent_start_).count()); agent_act_ = high_resolution_clock::now();
96103
first_event_ = std::min(first_event_, a->act(step, seed_));
104+
}
105+
97106
//} catch(const std::runtime_error &e) {
98107
// LOG(errorlog) << e.what() << std::endl;
99108
// throw e;
@@ -103,17 +112,45 @@ namespace esl::simulation {
103112
//} catch(...) {
104113
// throw;
105114
//}
106-
a->inbox.clear();
107-
//auto agent_end_ = high_resolution_clock::now() - agent_start_;
108-
109-
110-
//timings_cb_[a->identifier] += agent_cb_end_;
111-
//timings_act_[a->identifier] += double( (high_resolution_clock::now() - agent_act_).count());
112-
//timings_[a->identifier] += (double(agent_end_.count()) );
113-
//LOG(notice) << "a" << a->identifier << " tcb " << step << " " << std::setprecision(8) << timings_cb_[a->identifier]/ 1e+9/step.lower << " s" << std::endl;
114-
//LOG(notice) << "a" << a->identifier << " tac " << step << " " << std::setprecision(8) << timings_act_[a->identifier]/ 1e+9/step.lower << " s" << std::endl;
115+
a->inbox.clear();
116+
// auto agent_end_ = high_resolution_clock::now() - agent_start_;
117+
118+
119+
// timings_cb_[a->identifier] += agent_cb_end_;
120+
// timings_act_[a->identifier] += double( (high_resolution_clock::now() - agent_act_).count());
121+
// timings_[a->identifier] += (double(agent_end_.count()) );
122+
// LOG(notice) << "a" << a->identifier << " tcb " << step << " " << std::setprecision(8) << timings_cb_[a->identifier]/ 1e+9/step.lower << " s" << std::endl; LOG(notice) << "a" << a->identifier << " tac " << step << " " << std::setprecision(8) << timings_act_[a->identifier]/ 1e+9/step.lower << " s" << std::endl;
123+
124+
};
125+
126+
// important: if using a single thread, run everything in main
127+
if(threads <= 1) {
128+
for(auto &[i, a] : agents.local_agents_) {
129+
job_(a);
130+
}
131+
}else{
132+
std::vector<std::thread> threads_;
133+
auto iterator_ = agents.local_agents_.begin();
134+
for(const auto& tasks_: quantity(agents.local_agents_.size()) / threads){
135+
std::vector<std::shared_ptr<agent>> task_split_;
136+
for(auto i = quantity(0); i < tasks_; ++i){
137+
task_split_.push_back(iterator_->second);
138+
std::advance(iterator_, 1);
139+
}
140+
141+
threads_.emplace_back([&](std::vector<std::shared_ptr<agent>> ts){
142+
for(const auto& a: ts){
143+
job_(a);
144+
}
145+
}, task_split_);
146+
}
147+
148+
for(auto &t: threads_){
149+
t.join();
150+
}
115151

116152
}
153+
117154
environment_.send_messages(*this);
118155
++round_;
119156
++rounds_;

esl/simulation/parameter/parametrization.hpp

+8-5
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,18 @@ namespace esl::simulation::parameter {
8585
, time_point start = time_point()
8686
, time_point end = time_point() + 1
8787
#ifdef ESL_RELEASE
88-
, std::uint64_t verbosity = 1==ESL_RELEASE ? 1000 : 100 )
88+
, std::uint64_t verbosity = 0
8989
#else
90-
, std::uint64_t verbosity = 100 )
90+
, std::uint64_t verbosity = 1
9191
#endif
92+
, unsigned int threads = 1)
93+
9294
{
93-
values["sample"] = std::make_shared<constant<std::uint64_t>>(sample);
94-
values["start"] = std::make_shared<constant<time_point>>(start);
95-
values["end"] = std::make_shared<constant<time_point>>(end);
95+
values["sample"] = std::make_shared<constant<std::uint64_t>>(sample);
96+
values["start"] = std::make_shared<constant<time_point>>(start);
97+
values["end"] = std::make_shared<constant<time_point>>(end);
9698
values["verbosity"] = std::make_shared<constant<std::uint64_t>>(verbosity);
99+
values["threads"] = std::make_shared<constant<unsigned int>>(verbosity);
97100
}
98101

99102

test/test_environment.cpp

+69-10
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,28 @@
3434
#include <esl/agent.hpp>
3535

3636

37+
using namespace esl;
38+
using namespace esl::simulation;
39+
3740
struct test_agent
38-
: public esl::agent
41+
: public agent
3942
{
40-
using esl::agent::agent;
43+
using agent::agent;
44+
45+
unsigned int delay = 0;
4146

42-
esl::simulation::time_point act(esl::simulation::time_interval step,
47+
time_point act(time_interval step,
4348
std::seed_seq &seed) override
4449
{
45-
return agent::act(step, seed);
50+
return step.lower + delay;
4651
}
4752
};
4853

4954

5055
struct test_model
51-
: public esl::simulation::model
56+
: public model
5257
{
53-
using esl::simulation::model::model;
58+
using model::model;
5459

5560
int initialize_called = 0;
5661

@@ -74,9 +79,9 @@ BOOST_AUTO_TEST_SUITE(ESL)
7479

7580
BOOST_AUTO_TEST_CASE(environment_constructor)
7681
{
77-
esl::computation::environment e;
82+
computation::environment e;
7883

79-
test_model tm(e, esl::simulation::parameter::parametrization(0, 1, 100));
84+
test_model tm(e, parameter::parametrization(0, 1, 100));
8085

8186
BOOST_CHECK_EQUAL(tm.start, 1);
8287
BOOST_CHECK_EQUAL(tm.time, 1);
@@ -85,14 +90,68 @@ BOOST_AUTO_TEST_SUITE(ESL)
8590

8691
BOOST_AUTO_TEST_CASE(environment_basic_time_stepping)
8792
{
88-
esl::computation::environment e;
93+
computation::environment e;
94+
test_model tm(e, parameter::parametrization(0, 0, 100));
95+
96+
auto next_ = tm.step( {0, 3});
97+
98+
std::cout << next_ << std::endl;
99+
100+
BOOST_CHECK_EQUAL(next_, 3);
101+
}
102+
103+
104+
BOOST_AUTO_TEST_CASE(environment_time_step_with_agents)
105+
{
106+
computation::environment e;
107+
test_model tm(e, parameter::parametrization(0, 0, 100));
89108

90-
test_model tm(e, esl::simulation::parameter::parametrization(0, 0, 100));
109+
auto a1 = tm.create<test_agent>();
110+
a1->delay = 5;
111+
112+
auto next_ = tm.step( {0, 3});
113+
std::cout << next_ << std::endl;
114+
BOOST_CHECK_EQUAL(next_, 3);
115+
116+
next_ = tm.step( {3, 5});
117+
118+
std::cout << next_ << std::endl;
119+
120+
BOOST_CHECK_EQUAL(next_, 5);
121+
122+
}
123+
124+
125+
126+
///
127+
/// \brief
128+
///
129+
BOOST_AUTO_TEST_CASE(environment_run_agents_parallel)
130+
{
131+
computation::environment e;
132+
unsigned int threads = 8;
133+
134+
test_model tm(e, parameter::parametrization(0, 0, 100, 0, threads));
135+
136+
// We create one agent with delay 5, and other agents have more.
137+
// If the parallelisation fails, we might miss that particular agent
138+
// and end with a higher time point although this is nondeterministic
139+
// and we are thus not guaranteed to catch it.
140+
auto test_agents = 100'000;
141+
for(auto i = 0; i < test_agents; ++i){
142+
auto a1 = tm.create<test_agent>();
143+
a1->delay = 4 + test_agents - i;
144+
}
91145

92146
auto next_ = tm.step( {0, 3});
93147

94148
BOOST_CHECK_EQUAL(next_, 3);
149+
150+
// agents will delay again
151+
next_ = tm.step( {3, 1234});
152+
BOOST_CHECK_EQUAL(next_, 8);
95153
}
96154

97155

156+
98157
BOOST_AUTO_TEST_SUITE_END() // ESL

0 commit comments

Comments
 (0)