Commit 2f3b69cc authored by Andre Pinto's avatar Andre Pinto Committed by Alecs King

Optimize perf of EventBase with new option.

Summary:
Add option to disable time measurement in EventBase::loopBody()
to improve performances when time measurement is not used.

Test Plan:
Unit tests, benchmarking and manual tests.

Reviewed By: pavlo@fb.com

Subscribers: trunkagent, folly-diffs@, yfeldblum

FB internal diff: D1846020

Tasks: 6086197

Signature: t1:1846020:1424459703:9686a84027e1137ee43eb0458b0baa4fee3a8abf
parent 215e9064
...@@ -141,7 +141,7 @@ static std::mutex libevent_mutex_; ...@@ -141,7 +141,7 @@ static std::mutex libevent_mutex_;
* EventBase methods * EventBase methods
*/ */
EventBase::EventBase() EventBase::EventBase(bool enableTimeMeasurement)
: runOnceCallbacks_(nullptr) : runOnceCallbacks_(nullptr)
, stop_(false) , stop_(false)
, loopThread_(0) , loopThread_(0)
...@@ -150,6 +150,7 @@ EventBase::EventBase() ...@@ -150,6 +150,7 @@ EventBase::EventBase()
, maxLatency_(0) , maxLatency_(0)
, avgLoopTime_(2000000) , avgLoopTime_(2000000)
, maxLatencyLoopTime_(avgLoopTime_) , maxLatencyLoopTime_(avgLoopTime_)
, enableTimeMeasurement_(enableTimeMeasurement)
, nextLoopCnt_(-40) // Early wrap-around so bugs will manifest soon , nextLoopCnt_(-40) // Early wrap-around so bugs will manifest soon
, latestLoopCnt_(nextLoopCnt_) , latestLoopCnt_(nextLoopCnt_)
, startWork_(0) , startWork_(0)
...@@ -177,7 +178,7 @@ EventBase::EventBase() ...@@ -177,7 +178,7 @@ EventBase::EventBase()
} }
// takes ownership of the event_base // takes ownership of the event_base
EventBase::EventBase(event_base* evb) EventBase::EventBase(event_base* evb, bool enableTimeMeasurement)
: runOnceCallbacks_(nullptr) : runOnceCallbacks_(nullptr)
, stop_(false) , stop_(false)
, loopThread_(0) , loopThread_(0)
...@@ -187,6 +188,7 @@ EventBase::EventBase(event_base* evb) ...@@ -187,6 +188,7 @@ EventBase::EventBase(event_base* evb)
, maxLatency_(0) , maxLatency_(0)
, avgLoopTime_(2000000) , avgLoopTime_(2000000)
, maxLatencyLoopTime_(avgLoopTime_) , maxLatencyLoopTime_(avgLoopTime_)
, enableTimeMeasurement_(enableTimeMeasurement)
, nextLoopCnt_(-40) // Early wrap-around so bugs will manifest soon , nextLoopCnt_(-40) // Early wrap-around so bugs will manifest soon
, latestLoopCnt_(nextLoopCnt_) , latestLoopCnt_(nextLoopCnt_)
, startWork_(0) , startWork_(0)
...@@ -247,6 +249,7 @@ void EventBase::setMaxReadAtOnce(uint32_t maxAtOnce) { ...@@ -247,6 +249,7 @@ void EventBase::setMaxReadAtOnce(uint32_t maxAtOnce) {
// Set smoothing coefficient for loop load average; input is # of milliseconds // Set smoothing coefficient for loop load average; input is # of milliseconds
// for exp(-1) decay. // for exp(-1) decay.
void EventBase::setLoadAvgMsec(uint32_t ms) { void EventBase::setLoadAvgMsec(uint32_t ms) {
assert(enableTimeMeasurement_);
uint64_t us = 1000 * ms; uint64_t us = 1000 * ms;
if (ms > 0) { if (ms > 0) {
maxLatencyLoopTime_.setTimeInterval(us); maxLatencyLoopTime_.setTimeInterval(us);
...@@ -257,6 +260,7 @@ void EventBase::setLoadAvgMsec(uint32_t ms) { ...@@ -257,6 +260,7 @@ void EventBase::setLoadAvgMsec(uint32_t ms) {
} }
void EventBase::resetLoadAvg(double value) { void EventBase::resetLoadAvg(double value) {
assert(enableTimeMeasurement_);
avgLoopTime_.reset(value); avgLoopTime_.reset(value);
maxLatencyLoopTime_.reset(value); maxLatencyLoopTime_.reset(value);
} }
...@@ -291,15 +295,23 @@ bool EventBase::loopBody(int flags) { ...@@ -291,15 +295,23 @@ bool EventBase::loopBody(int flags) {
bool blocking = !(flags & EVLOOP_NONBLOCK); bool blocking = !(flags & EVLOOP_NONBLOCK);
bool once = (flags & EVLOOP_ONCE); bool once = (flags & EVLOOP_ONCE);
// time-measurement variables.
std::chrono::steady_clock::time_point prev;
int64_t idleStart;
int64_t busy;
int64_t idle;
loopThread_.store(pthread_self(), std::memory_order_release); loopThread_.store(pthread_self(), std::memory_order_release);
if (!name_.empty()) { if (!name_.empty()) {
setThreadName(name_); setThreadName(name_);
} }
auto prev = std::chrono::steady_clock::now(); if (enableTimeMeasurement_) {
int64_t idleStart = std::chrono::duration_cast<std::chrono::microseconds>( prev = std::chrono::steady_clock::now();
idleStart = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count(); std::chrono::steady_clock::now().time_since_epoch()).count();
}
// TODO: Read stop_ atomically with an acquire barrier. // TODO: Read stop_ atomically with an acquire barrier.
while (!stop_) { while (!stop_) {
...@@ -325,9 +337,11 @@ bool EventBase::loopBody(int flags) { ...@@ -325,9 +337,11 @@ bool EventBase::loopBody(int flags) {
ranLoopCallbacks = runLoopCallbacks(); ranLoopCallbacks = runLoopCallbacks();
int64_t busy = std::chrono::duration_cast<std::chrono::microseconds>( if (enableTimeMeasurement_) {
std::chrono::steady_clock::now().time_since_epoch()).count() - startWork_; busy = std::chrono::duration_cast<std::chrono::microseconds>(
int64_t idle = startWork_ - idleStart; std::chrono::steady_clock::now().time_since_epoch()).count() -
startWork_;
idle = startWork_ - idleStart;
avgLoopTime_.addSample(idle, busy); avgLoopTime_.addSample(idle, busy);
maxLatencyLoopTime_.addSample(idle, busy); maxLatencyLoopTime_.addSample(idle, busy);
...@@ -360,6 +374,11 @@ bool EventBase::loopBody(int flags) { ...@@ -360,6 +374,11 @@ bool EventBase::loopBody(int flags) {
// Our loop run did real work; reset the idle timer // Our loop run did real work; reset the idle timer
idleStart = std::chrono::duration_cast<std::chrono::microseconds>( idleStart = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count(); std::chrono::steady_clock::now().time_since_epoch()).count();
} else {
VLOG(11) << "EventBase " << this << " did not timeout "
" time measurement is disabled "
" nothingHandledYet(): "<< nothingHandledYet();
}
// If the event loop indicate that there were no more events, and // If the event loop indicate that there were no more events, and
// we also didn't have any loop callbacks to run, there is nothing left to // we also didn't have any loop callbacks to run, there is nothing left to
...@@ -375,8 +394,10 @@ bool EventBase::loopBody(int flags) { ...@@ -375,8 +394,10 @@ bool EventBase::loopBody(int flags) {
} }
} }
if (enableTimeMeasurement_) {
VLOG(5) << "EventBase " << this << " loop time: " << VLOG(5) << "EventBase " << this << " loop time: " <<
getTimeDelta(&prev).count(); getTimeDelta(&prev).count();
}
if (once) { if (once) {
break; break;
......
...@@ -143,8 +143,14 @@ class EventBase : private boost::noncopyable, ...@@ -143,8 +143,14 @@ class EventBase : private boost::noncopyable,
/** /**
* Create a new EventBase object. * Create a new EventBase object.
*
* @param enableTimeMeasurement Informs whether this event base should measure
* time. Disabling it would likely improve
* performance, but will disable some features
* that relies on time-measurement, including:
* observer, max latency and avg loop time.
*/ */
EventBase(); explicit EventBase(bool enableTimeMeasurement = true);
/** /**
* Create a new EventBase object that will use the specified libevent * Create a new EventBase object that will use the specified libevent
...@@ -152,8 +158,14 @@ class EventBase : private boost::noncopyable, ...@@ -152,8 +158,14 @@ class EventBase : private boost::noncopyable,
* *
* The EventBase will take ownership of this event_base, and will call * The EventBase will take ownership of this event_base, and will call
* event_base_free(evb) when the EventBase is destroyed. * event_base_free(evb) when the EventBase is destroyed.
*
* @param enableTimeMeasurement Informs whether this event base should measure
* time. Disabling it would likely improve
* performance, but will disable some features
* that relies on time-measurement, including:
* observer, max latency and avg loop time.
*/ */
explicit EventBase(event_base* evb); explicit EventBase(event_base* evb, bool enableTimeMeasurement = true);
~EventBase(); ~EventBase();
/** /**
...@@ -384,12 +396,15 @@ class EventBase : private boost::noncopyable, ...@@ -384,12 +396,15 @@ class EventBase : private boost::noncopyable,
/** /**
* Set the maximum desired latency in us and provide a callback which will be * Set the maximum desired latency in us and provide a callback which will be
* called when that latency is exceeded. * called when that latency is exceeded.
* OBS: This functionality depends on time-measurement.
*/ */
void setMaxLatency(int64_t maxLatency, const Cob& maxLatencyCob) { void setMaxLatency(int64_t maxLatency, const Cob& maxLatencyCob) {
assert(enableTimeMeasurement_);
maxLatency_ = maxLatency; maxLatency_ = maxLatency;
maxLatencyCob_ = maxLatencyCob; maxLatencyCob_ = maxLatencyCob;
} }
/** /**
* Set smoothing coefficient for loop load average; # of milliseconds * Set smoothing coefficient for loop load average; # of milliseconds
* for exp(-1) (1/2.71828...) decay. * for exp(-1) (1/2.71828...) decay.
...@@ -405,6 +420,7 @@ class EventBase : private boost::noncopyable, ...@@ -405,6 +420,7 @@ class EventBase : private boost::noncopyable,
* Get the average loop time in microseconds (an exponentially-smoothed ave) * Get the average loop time in microseconds (an exponentially-smoothed ave)
*/ */
double getAvgLoopTime() const { double getAvgLoopTime() const {
assert(enableTimeMeasurement_);
return avgLoopTime_.get(); return avgLoopTime_.get();
} }
...@@ -484,8 +500,8 @@ class EventBase : private boost::noncopyable, ...@@ -484,8 +500,8 @@ class EventBase : private boost::noncopyable,
int64_t oldBusyLeftover_; int64_t oldBusyLeftover_;
}; };
void setObserver( void setObserver(const std::shared_ptr<EventBaseObserver>& observer) {
const std::shared_ptr<EventBaseObserver>& observer) { assert(enableTimeMeasurement_);
observer_ = observer; observer_ = observer;
} }
...@@ -632,6 +648,11 @@ class EventBase : private boost::noncopyable, ...@@ -632,6 +648,11 @@ class EventBase : private boost::noncopyable,
// callback called when latency limit is exceeded // callback called when latency limit is exceeded
Cob maxLatencyCob_; Cob maxLatencyCob_;
// Enables/disables time measurements in loopBody(). if disabled, the
// following functionality that relies on time-measurement, will not
// be supported: avg loop time, observer and max latency.
const bool enableTimeMeasurement_;
// we'll wait this long before running deferred callbacks if the event // we'll wait this long before running deferred callbacks if the event
// loop is idle. // loop is idle.
static const int kDEFAULT_IDLE_WAIT_USEC = 20000; // 20ms static const int kDEFAULT_IDLE_WAIT_USEC = 20000; // 20ms
......
/*
* Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Benchmark.h>
#include <folly/io/async/EventBase.h>
#include <gflags/gflags.h>
using namespace folly;
class CountedLoopCallback : public EventBase::LoopCallback {
public:
CountedLoopCallback(EventBase* eventBase, unsigned int count)
: eventBase_(eventBase)
, count_(count) {}
virtual void runLoopCallback() noexcept {
--count_;
if (count_ > 0) {
eventBase_->runInLoop(this);
}
}
private:
EventBase* eventBase_;
unsigned int count_;
};
BENCHMARK(timeMeasurementsOn, n) {
EventBase eventBase;
while (n--) {
CountedLoopCallback c(&eventBase, 10);
eventBase.runInLoop(&c);
eventBase.loop();
}
}
BENCHMARK_RELATIVE(timeMeasurementsOff, n) {
EventBase eventBase(/* enableTimeMeasurement */ false);
while (n--) {
CountedLoopCallback c(&eventBase, 10);
eventBase.runInLoop(&c);
eventBase.loop();
}
}
/**
* --bm_min_iters=1000000
*
* ============================================================================
* folly/io/async/test/EventBaseBenchmark.cpp relative time/iter iters/s
* ============================================================================
* timeMeasurementsOn 2.02us 494.57K
* timeMeasurementsOff 231.19% 874.58ns 1.14M
* ============================================================================
*/
int main(int argc, char** argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
runBenchmarks();
}
...@@ -1265,6 +1265,21 @@ TEST(EventBaseTest, RepeatedRunInLoop) { ...@@ -1265,6 +1265,21 @@ TEST(EventBaseTest, RepeatedRunInLoop) {
ASSERT_EQ(c.getCount(), 0); ASSERT_EQ(c.getCount(), 0);
} }
// Test that EventBase::loop() works as expected without time measurements.
TEST(EventBaseTest, RunInLoopNoTimeMeasurement) {
EventBase eventBase(false);
CountedLoopCallback c(&eventBase, 10);
eventBase.runInLoop(&c);
// The callback shouldn't have run immediately
ASSERT_EQ(c.getCount(), 10);
eventBase.loop();
// loop() should loop until the CountedLoopCallback stops
// re-installing itself.
ASSERT_EQ(c.getCount(), 0);
}
// Test runInLoop() calls with terminateLoopSoon() // Test runInLoop() calls with terminateLoopSoon()
TEST(EventBaseTest, RunInLoopStopLoop) { TEST(EventBaseTest, RunInLoopStopLoop) {
EventBase eventBase; EventBase eventBase;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment