Skip to content

Commit

Permalink
Added benchmark for CallbackList append/remove callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
wqking committed Jul 25, 2018
1 parent 5e7829c commit abd1ec7
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 30 deletions.
14 changes: 10 additions & 4 deletions doc/benchmark.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Benchmarks

Hardware: Intel(R) Xeon(R) CPU E3-1225 V2 @ 3.20GHz
Software: Windows 10, MSVC 2017, MinGW GCC 7.2.0
Time unit: milliseconds (unless explicitly specified)

## EventQueue enqueue and process -- single threading

<table>
Expand Down Expand Up @@ -41,7 +45,7 @@
</tr>
<table>

Given `eventpp::EventQueue<int, void (int), Policies>`, which `Policies` is either single threading or multi threading, the benchmark adds `Listener count` listeners to the queue, each listener is an empty lambda. Then the benchmark starts timing. It loops `Iterations` times. In each loop, the benchmark puts `Queue size` events, then process the event queue.
Given `eventpp::EventQueue<size_t, void (size_t), Policies>`, which `Policies` is either single threading or multi threading, the benchmark adds `Listener count` listeners to the queue, each listener is an empty lambda. Then the benchmark starts timing. It loops `Iterations` times. In each loop, the benchmark puts `Queue size` events, then process the event queue.
There are `Event types` kinds of event type. `Event count` is `Iterations * Queue size`.
The EventQueue is processed in one thread. The Single/Multi threading in the table means the policies used.

Expand Down Expand Up @@ -109,12 +113,14 @@ The EventQueue is processed in one thread. The Single/Multi threading in the tab
There are `Enqueue threads` threads enqueuing events to the queue, and `Process threads` threads processing the events. The total event count is `Event count`.
The multi threading version shows slower than previous single threading version, since the mutex locks cost time.

## CallbackList append/remove callbacks

The benchmark loops 100K times, in each loop it appends 1000 empty callbacks to a CallbackList, then remove all that 1000 callbacks. So there are totally 100M append/remove operations.
The total benchmarked time is about 21000 milliseconds. That's to say in 1 milliseconds there can be 5000 append/remove operations.

## CallbackList invoking VS native function invoking

Hardware: Intel(R) Xeon(R) CPU E3-1225 V2 @ 3.20GHz
Software: Windows 10, MSVC 2017, MinGW GCC 7.2.0
Iterations: 100,000,000
Time unit: milliseconds

<table>
<tr>
Expand Down
48 changes: 27 additions & 21 deletions include/eventpp/callbacklist.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@

#include "eventpolicies.h"

#include <atomic>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <utility>
#include <vector>

namespace eventpp {

Expand Down Expand Up @@ -197,7 +198,7 @@ class CallbackListBase<

Handle append(const Callback & callback)
{
NodePtr node(std::make_shared<Node>(callback, getNextCounter()));
NodePtr node(doAllocateNode(callback));

std::lock_guard<Mutex> lockGuard(mutex);

Expand All @@ -216,7 +217,7 @@ class CallbackListBase<

Handle prepend(const Callback & callback)
{
NodePtr node(std::make_shared<Node>(callback, getNextCounter()));
NodePtr node(doAllocateNode(callback));

std::lock_guard<Mutex> lockGuard(mutex);

Expand All @@ -237,7 +238,7 @@ class CallbackListBase<
{
NodePtr beforeNode = before.lock();
if(beforeNode) {
NodePtr node(std::make_shared<Node>(callback, getNextCounter()));
NodePtr node(doAllocateNode(callback));

std::lock_guard<Mutex> lockGuard(mutex);

Expand All @@ -254,7 +255,7 @@ class CallbackListBase<
std::lock_guard<Mutex> lockGuard(mutex);
auto node = handle.lock();
if(node) {
doRemoveNode(node);
doFreeNode(node);
return true;
}

Expand Down Expand Up @@ -336,7 +337,26 @@ class CallbackListBase<
return func(node->callback);
}

void doRemoveNode(NodePtr & node)
void doInsert(NodePtr & node, NodePtr & beforeNode)
{
node->previous = beforeNode->previous;
node->next = beforeNode;
if(beforeNode->previous) {
beforeNode->previous->next = node;
}
beforeNode->previous = node;

if(beforeNode == head) {
head = node;
}
}

NodePtr doAllocateNode(const Callback & callback)
{
return std::make_shared<Node>(callback, getNextCounter());
}

void doFreeNode(NodePtr & node)
{
if(node->next) {
node->next->previous = node->previous;
Expand All @@ -359,20 +379,6 @@ class CallbackListBase<
// because node may be still used in a loop.
}

void doInsert(NodePtr & node, NodePtr & beforeNode)
{
node->previous = beforeNode->previous;
node->next = beforeNode;
if(beforeNode->previous) {
beforeNode->previous->next = node;
}
beforeNode->previous = node;

if(beforeNode == head) {
head = node;
}
}

Counter getNextCounter()
{
Counter result = ++currentCounter;;
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ eventpp is a C++ event library that provides tools that allow your application c
- **Fast**
- The EventQueue can process 10M events in 1 second (10K events per millisecond).
- The CallbackList can invoke 100M callbacks in 1 second (100K callbacks per millisecond).
- The CallbackList can add/remove 5M callbacks in 1 second (5K callbacks per millisecond).
- **Flexible and easy to use**
- The listeners and events can be any type, no need to inherit from any base class.
- Header only, no source file, no need to build. No dependencies on other libraries.
Expand Down
36 changes: 31 additions & 5 deletions tests/benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,15 @@ void doExecuteEventQueue(
size_t listenerCount = 0
)
{
using EQ = eventpp::EventQueue<int, void (int), Policies>;
using EQ = eventpp::EventQueue<size_t, void (size_t), Policies>;
EQ eventQueue;

if(listenerCount == 0) {
listenerCount = eventCount;
}

for(size_t i = 0; i < listenerCount; ++i) {
eventQueue.appendListener(i % eventCount, [](int) {});
eventQueue.appendListener(i % eventCount, [](size_t) {});
}

const uint64_t time = measureElapsedTime([
Expand Down Expand Up @@ -171,15 +171,15 @@ void doMultiThreadingExecuteEventQueue(
size_t listenerCount = 0
)
{
using EQ = eventpp::EventQueue<int, void (int), Policies>;
using EQ = eventpp::EventQueue<size_t, void (size_t), Policies>;
EQ eventQueue;

if(listenerCount == 0) {
listenerCount = eventCount;
}

for(size_t i = 0; i < listenerCount; ++i) {
eventQueue.appendListener(i % eventCount, [](int) { });
eventQueue.appendListener(i % eventCount, [](size_t) { });
}

std::atomic<bool> start(false);
Expand All @@ -194,7 +194,7 @@ void doMultiThreadingExecuteEventQueue(
}

for(size_t i = begin; i < end; ++i) {
eventQueue.enqueue(i % eventCount);
eventQueue.enqueue(i % eventCount);
}
});
}
Expand Down Expand Up @@ -611,5 +611,31 @@ TEST_CASE("benchmark, EventQueue")
doMultiThreadingExecuteEventQueue<PoliciesMultiThreading>(2, 2, 1000 * 1000 * 100, 100);
}

TEST_CASE("benchmark, CallbackList add/remove callbacks")
{
using CL = eventpp::CallbackList<void ()>;
constexpr size_t callbackCount = 1000;
constexpr size_t iterateCount = 1000 * 100;
CL callbackList;
std::vector<CL::Handle> handleList(callbackCount);
const uint64_t time = measureElapsedTime(
[callbackCount, iterateCount, &callbackList, &handleList]() {
for(size_t iterate = 0; iterate < iterateCount; ++iterate) {
for(size_t i = 0; i < callbackCount; ++i) {
handleList[i] = callbackList.append([]() {});
}
for(size_t i = 0; i < callbackCount; ++i) {
callbackList.remove(handleList[i]);
}
}
});

std::cout
<< "CallbackList add/remove callbacks,"
<< " callbackCount: " << callbackCount
<< " iterateCount: " << iterateCount
<< " time: " << time
<< std::endl;
}

#endif

0 comments on commit abd1ec7

Please sign in to comment.