Skip to content

Commit

Permalink
Merge pull request #31 from leapmotion/test-priorityboost
Browse files Browse the repository at this point in the history
Moving another performance test over to the dedicated benchmarking project
  • Loading branch information
gtremper committed Aug 5, 2014
2 parents 8e4ced6 + 4312c11 commit dee3a78
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 76 deletions.
1 change: 1 addition & 0 deletions src/autowiring/benchmark/CMakeLists.txt
Expand Up @@ -4,6 +4,7 @@ endif()

set(AutowiringBenchmarkTest_SRCS
AutowiringBenchmarkTest.cpp
CanBoostPriorityTest.cpp
)

ADD_MSVC_PRECOMPILED_HEADER("stdafx.h" "stdafx.cpp" AutowiringBenchmarkTest_SRCS)
Expand Down
82 changes: 82 additions & 0 deletions src/autowiring/benchmark/CanBoostPriorityTest.cpp
@@ -0,0 +1,82 @@
#include "stdafx.h"
#include <autowiring/CoreThread.h>

class BoostPriorityTest:
public testing::Test
{};

template<ThreadPriority priority>
class JustIncrementsANumber:
public CoreThread
{
public:
JustIncrementsANumber() :
val(0)
{}

volatile int64_t val;

// This will be a hotly contested conditional variable
AutoRequired<std::mutex> contended;

void Run(void) override {
ElevatePriority p(*this, priority);

while(!ShouldStop()) {
// Obtain the lock and then increment our value:
std::lock_guard<std::mutex> lk(*contended);
val++;
}
}
};

#ifdef _MSC_VER
#include "windows.h"

TEST_F(BoostPriorityTest, VerifyCanBoostPriority) {
AutoCurrentContext ctxt;

// Create two spinners and kick them off at the same time:
AutoRequired<JustIncrementsANumber<ThreadPriority::BelowNormal>> lower;
AutoRequired<JustIncrementsANumber<ThreadPriority::Normal>> higher;
ctxt->Initiate();

// We want all of our threads to run on ONE cpu for awhile, and then we want to put it back at exit
DWORD_PTR originalAffinity, systemAffinity;
GetProcessAffinityMask(GetCurrentProcess(), &originalAffinity, &systemAffinity);
SetProcessAffinityMask(GetCurrentProcess(), 1);
auto onreturn = MakeAtExit([originalAffinity] {
SetProcessAffinityMask(GetCurrentProcess(), originalAffinity);
});

// Poke the conditional variable a lot:
AutoRequired<std::mutex> contended;
for(size_t i = 100; i--;) {
// We sleep while holding contention lock to force waiting threads into the sleep queue. The reason we have to do
// this is due to the way that mutex is implemented under the hood. The STL mutex uses a high-frequency variable
// and attempts to perform a CAS (check-and-set) on this variable. If it succeeds, the lock is obtained; if it
// fails, it will put the thread into a non-ready state by calling WaitForSingleObject on Windows or one of the
// mutex_lock methods on Unix.
//
// When a thread can't be run, it's moved from the OS's ready queue to the sleep queue. The scheduler knows that
// the thread can be moved back to the ready queue if a particular object is signalled, but in the case of a lock,
// only one of the threads waiting on the object can actually be moved to the ready queue. It's at THIS POINT that
// the operating system consults the thread priority--if only thread can be moved over, then the highest priority
// thread will wind up in the ready queue every time.
//
// Thread priority does _not_ necessarily influence the amount of time the scheduler allocates allocated to a ready
// thread with respect to other threads of the same process. This is why we hold the lock for a full millisecond,
// in order to force the thread over to the sleep queue and ensure that the priority resolution mechanism is
// directly tested.
std::lock_guard<std::mutex> lk(*contended);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

// Need to terminate before we try running a comparison.
ctxt->SignalTerminate();

ASSERT_LE(lower->val, higher->val) << "A lower-priority thread was moved out of the sleep queue more frequently than a high-priority thread";
}
#else
#pragma message "Warning: SetThreadPriority not implemented on Unix"
#endif
76 changes: 0 additions & 76 deletions src/autowiring/test/CoreThreadTest.cpp
Expand Up @@ -363,31 +363,6 @@ TEST_F(CoreThreadTest, WaitBeforeInitiate) {
ASSERT_TRUE(ctxt->Wait(std::chrono::seconds(0))) << "Failed to wait on a context which should have already been stopped";
}

template<ThreadPriority priority>
class JustIncrementsANumber:
public CoreThread
{
public:
JustIncrementsANumber():
val(0)
{}

volatile int64_t val;

// This will be a hotly contested conditional variable
AutoRequired<std::mutex> contended;

void Run(void) override {
ElevatePriority p(*this, priority);

while(!ShouldStop()) {
// Obtain the lock and then increment our value:
std::lock_guard<std::mutex> lk(*contended);
val++;
}
}
};

TEST_F(CoreThreadTest, ReentrantStopOnTeardown) {
AutoCurrentContext ctxt;
std::shared_ptr<CoreThread> ct = AutoRequired<CoreThread>();
Expand Down Expand Up @@ -462,54 +437,3 @@ TEST_F(CoreThreadTest, PendAfterShutdown) {
// Verify that the lambda was destroyed more or less right away
ASSERT_TRUE(v.unique()) << "Shared pointer in a lambda closure appears to have been leaked";
}

#ifdef _MSC_VER
#include "windows.h"

TEST_F(CoreThreadTest, VerifyCanBoostPriority) {
AutoCurrentContext ctxt;

// Create two spinners and kick them off at the same time:
AutoRequired<JustIncrementsANumber<ThreadPriority::BelowNormal>> lower;
AutoRequired<JustIncrementsANumber<ThreadPriority::Normal>> higher;
ctxt->Initiate();

// We want all of our threads to run on ONE cpu for awhile, and then we want to put it back at exit
DWORD_PTR originalAffinity, systemAffinity;
GetProcessAffinityMask(GetCurrentProcess(), &originalAffinity, &systemAffinity);
SetProcessAffinityMask(GetCurrentProcess(), 1);
auto onreturn = MakeAtExit([originalAffinity] {
SetProcessAffinityMask(GetCurrentProcess(), originalAffinity);
});

// Poke the conditional variable a lot:
AutoRequired<std::mutex> contended;
for(size_t i = 100; i--;) {
// We sleep while holding contention lock to force waiting threads into the sleep queue. The reason we have to do
// this is due to the way that mutex is implemented under the hood. The STL mutex uses a high-frequency variable
// and attempts to perform a CAS (check-and-set) on this variable. If it succeeds, the lock is obtained; if it
// fails, it will put the thread into a non-ready state by calling WaitForSingleObject on Windows or one of the
// mutex_lock methods on Unix.
//
// When a thread can't be run, it's moved from the OS's ready queue to the sleep queue. The scheduler knows that
// the thread can be moved back to the ready queue if a particular object is signalled, but in the case of a lock,
// only one of the threads waiting on the object can actually be moved to the ready queue. It's at THIS POINT that
// the operating system consults the thread priority--if only thread can be moved over, then the highest priority
// thread will wind up in the ready queue every time.
//
// Thread priority does _not_ necessarily influence the amount of time the scheduler allocates allocated to a ready
// thread with respect to other threads of the same process. This is why we hold the lock for a full millisecond,
// in order to force the thread over to the sleep queue and ensure that the priority resolution mechanism is
// directly tested.
std::lock_guard<std::mutex> lk(*contended);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

// Need to terminate before we try running a comparison.
ctxt->SignalTerminate();

ASSERT_LE(lower->val, higher->val) << "A lower-priority thread was moved out of the sleep queue more frequently than a high-priority thread";
}
#else
#pragma message "Warning: SetThreadPriority not implemented on Unix"
#endif

0 comments on commit dee3a78

Please sign in to comment.