Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use virtual functions instead of if-else in switch kernel #152

Merged
merged 18 commits into from
Sep 10, 2021

Conversation

wenzeslaus
Copy link
Member

@wenzeslaus wenzeslaus commented Aug 20, 2021

Experimental code of a more dynamic system for handling runtime switching between multiple kernels.
Instead of if-else statements in operator(), different classes are created in the kernel creation functions.
Switching in the operator() happens using virtual table rather than if-else in the switch kernel's operator()
and the actual kernels is access though a pointer (to an abstract base class). This happens within the kernel which switches between natural and anthropogenic kernels. Unlike with switch kernel where all kernels needed to be instantiated, now only the needed ones are and there is no switch kernel for the natural and anthropogenic kernels because the kernel which switches between them uses pointers to access them.

Natural and anthropogenic kernel creation functions moved out of Model class into separate files.

Natural and anthropogenic kernels are now actually different from each other. So far the difference is that anthropogenic kernel has a network kernel.

Newly, random number generator type is a template parameter of Model and Simulation, set as Generator = std::default_random_engine in both cases. Model just passes the type to Simulation. Simulation creates the object as before. The templating is useful in general, but it was needed to make the dynamic kernel generator-independent because while virtual function and template don't work together (that would be indefinite number of functions) while templating the class works (one function for one class).

Model now takes kernel factory (function) as a parameter (in constructor, the type is a template parameter with a default matching the default for the constructor).

This may make handling multiple kernels more flexible or easier and a variation of this seems to be needed for supporting multiple network kernels. One example now is that the switch kernel is not used for natural and anthorpogenic kernels, so they can be now different without need for another version of the switch kernel.

The overflow kernel is still the original implementation and there is still only one network supported.

Experimental code of a more dynamic system for handling runtime switching between multiple kernels.
Instead of if-else statements in operator(), different classes are created in the kernel creation functions.
Switching in the operator() happens using virtual table rather than if-else in the switch kernel's operator()
and the actual kernels is access though a pointer (to an abstract base class).

This may make handling multiple kernels more flexible or easier and a variation of this seems to be needed for supporting multiple network kernels.
…the initialization did, so not visible until now)
…flexibility and to avoid possible optimization
@wenzeslaus
Copy link
Member Author

According to my benchmarks, dynamically implemented kernels (virtual methods/OOP/polymorphism) are just as fast as the statically implemented ones (all kernels instantiated, if statements decide which one to use). A complex static implementation are only as fast as the dynamic implementation in a pure PoPS Core benchmark. In a r.pops.spread benchmark, the dynamic implementation significantly outperforms the static implementation although a simple static implementation is faster as expected and a slightly complex (similar to the original pre-1.0 kernel) is just as fast as the dynamic implementation.

Pure PoPS Core

Hard-coded kernel in the code

hard-coded kernel, rows = 500, cols = 1000, reproductive_rate = 2, all susceptible = 100, all infected = 10, 10 runs (test repetitions), release build, GCC

simulation or model implementation steps time
model static 5 30.1345 +- 0.0441
model dynamic 5 31.096 +- 0.544
simulation static 5 8.999 +- 0.121
simulation dynamic 5 10.635 +- 0.209

User-provided kernel

user-provided (variable) kernel, rows = 500, cols = 1000, reproductive_rate = 2, all susceptible = 100, all infected = 10, 10 runs (test repetitions), release build, GCC

simulation or model implementation steps kernel type time
model static 5 exponential 31.428 +- 0.219
model dynamic 5 exponential 31.727 +- 0.260
simulation static 5 exponential 29.5094 +- 0.0372
simulation dynamic 5 exponential 29.7102 +- 0.0266

r.pops.spread test suite

full test suite for r.pops.spread with modified pops-core, 10 runs (test repetitions), GCC optimizations enabled (-O3), no debug (no -ggdb)

implementation kernel type time test pass/fail
static single radial kernel 27.861 +- 0.350 fail
static natural+anthro radial 34.376 +- 0.196 pass
dynamic full 34.3597 +- 0.0973 pass
static full 144.77 +- 1.38 pass

@ChrisJones687
Copy link
Member

The speed-up looks great as we will be using it in r.pops.spread and rpops with full kernel suite.

@wenzeslaus wenzeslaus marked this pull request as ready for review September 8, 2021 18:12
…asses are merged into one called DynamicWrapperKernel
@wenzeslaus wenzeslaus added enhancement New feature or request refactoring Code needs refactoring labels Sep 10, 2021
@wenzeslaus wenzeslaus merged commit 8dcb1c1 into main Sep 10, 2021
@wenzeslaus wenzeslaus deleted the make-kernels-dynamic branch September 10, 2021 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request refactoring Code needs refactoring
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants