Skip to content

Commit

Permalink
Change locking strategy of Booster, allow for share and unique locks (#…
Browse files Browse the repository at this point in the history
…2760)

* Add capability to get possible max and min values for a model

* Change implementation to have return value in tree.cpp, change naming to upper and lower bound, move implementation to gdbt.cpp

* Update include/LightGBM/c_api.h

Co-Authored-By: Nikita Titov <nekit94-08@mail.ru>

* Change iteration to avoid potential overflow, add bindings to R and Python and a basic test

* Adjust test values

* Consider const correctness and multithreading protection

* Put everything possible as const

* Include shared_mutex, for now as unique_lock

* Update test values

* Put everything possible as const

* Include shared_mutex, for now as unique_lock

* Make PredictSingleRow const and share the lock with other reading threads

* Update test values

* Add test to check that model is exactly the same in all platforms

* Try to parse the model to get the expected values

* Try to parse the model to get the expected values

* Fix implementation, num_leaves can be lower than the leaf_value_ size

* Do not check for num_leaves to be smaller than actual size and get back to test with hardcoded value

* Change test order

* Add gpu_use_dp option in test

* Remove helper test method

* Remove TODO

* Add preprocessing option to compile with c++17

* Update python-package/setup.py

Co-Authored-By: Nikita Titov <nekit94-08@mail.ru>

* Remove unwanted changes

* Move option

* Fix problems introduced by conflict fix

* Avoid switching to c++17 and use yamc mutex library to access shared lock functionality

* Add extra yamc include

* Change header order

* some lint fix

* change include order and remove some extra blank lines

* Further fix lint issues

* Update c_api.cpp

* Further fix lint issues

* Move yamc include files to a new yamc folder

* Use standard unique_lock

* Update windows/LightGBM.vcxproj

Co-authored-by: Guolin Ke <guolin.ke@outlook.com>

* Update windows/LightGBM.vcxproj.filters

Co-authored-by: Guolin Ke <guolin.ke@outlook.com>

* Update windows/LightGBM.vcxproj.filters

Co-authored-by: Nikita Titov <nekit94-08@mail.ru>

* Update windows/LightGBM.vcxproj.filters

Co-authored-by: Nikita Titov <nekit94-08@mail.ru>

* Update windows/LightGBM.vcxproj.filters

Co-authored-by: Nikita Titov <nekit94-08@mail.ru>

* Fix problems coming from merge conflict resolution

Co-authored-by: Nikita Titov <nekit94-08@mail.ru>
Co-authored-by: joanfontanals <jfontanals@ntent.com>
Co-authored-by: Guolin Ke <guolin.ke@outlook.com>
  • Loading branch information
4 people authored Jul 19, 2020
1 parent f5f27ca commit 1c35c3b
Show file tree
Hide file tree
Showing 6 changed files with 631 additions and 42 deletions.
212 changes: 212 additions & 0 deletions include/LightGBM/utils/yamc/alternate_shared_mutex.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*
* alternate_shared_mutex.hpp
*
* MIT License
*
* Copyright (c) 2017 yohhoy
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef YAMC_ALTERNATE_SHARED_MUTEX_HPP_
#define YAMC_ALTERNATE_SHARED_MUTEX_HPP_

#include <cassert>
#include <chrono>
#include <condition_variable>
#include <mutex>

#include "yamc_rwlock_sched.hpp"

namespace yamc {

/*
* alternate implementation of shared mutex variants
*
* - yamc::alternate::shared_mutex
* - yamc::alternate::shared_timed_mutex
* - yamc::alternate::basic_shared_mutex<RwLockPolicy>
* - yamc::alternate::basic_shared_timed_mutex<RwLockPolicy>
*/
namespace alternate {

namespace detail {

template <typename RwLockPolicy>
class shared_mutex_base {
protected:
typename RwLockPolicy::state state_;
std::condition_variable cv_;
std::mutex mtx_;

void lock() {
std::unique_lock<decltype(mtx_)> lk(mtx_);
RwLockPolicy::before_wait_wlock(state_);
while (RwLockPolicy::wait_wlock(state_)) {
cv_.wait(lk);
}
RwLockPolicy::after_wait_wlock(state_);
RwLockPolicy::acquire_wlock(&state_);
}

bool try_lock() {
std::lock_guard<decltype(mtx_)> lk(mtx_);
if (RwLockPolicy::wait_wlock(state_)) return false;
RwLockPolicy::acquire_wlock(state_);
return true;
}

void unlock() {
std::lock_guard<decltype(mtx_)> lk(mtx_);
RwLockPolicy::release_wlock(&state_);
cv_.notify_all();
}

void lock_shared() {
std::unique_lock<decltype(mtx_)> lk(mtx_);
while (RwLockPolicy::wait_rlock(state_)) {
cv_.wait(lk);
}
RwLockPolicy::acquire_rlock(&state_);
}

bool try_lock_shared() {
std::lock_guard<decltype(mtx_)> lk(mtx_);
if (RwLockPolicy::wait_rlock(state_)) return false;
RwLockPolicy::acquire_rlock(state_);
return true;
}

void unlock_shared() {
std::lock_guard<decltype(mtx_)> lk(mtx_);
if (RwLockPolicy::release_rlock(&state_)) {
cv_.notify_all();
}
}
};

} // namespace detail

template <typename RwLockPolicy>
class basic_shared_mutex : private detail::shared_mutex_base<RwLockPolicy> {
using base = detail::shared_mutex_base<RwLockPolicy>;

public:
basic_shared_mutex() = default;
~basic_shared_mutex() = default;

basic_shared_mutex(const basic_shared_mutex&) = delete;
basic_shared_mutex& operator=(const basic_shared_mutex&) = delete;

using base::lock;
using base::try_lock;
using base::unlock;

using base::lock_shared;
using base::try_lock_shared;
using base::unlock_shared;
};

using shared_mutex = basic_shared_mutex<YAMC_RWLOCK_SCHED_DEFAULT>;

template <typename RwLockPolicy>
class basic_shared_timed_mutex
: private detail::shared_mutex_base<RwLockPolicy> {
using base = detail::shared_mutex_base<RwLockPolicy>;

using base::cv_;
using base::mtx_;
using base::state_;

template <typename Clock, typename Duration>
bool do_try_lockwait(const std::chrono::time_point<Clock, Duration>& tp) {
std::unique_lock<decltype(mtx_)> lk(mtx_);
RwLockPolicy::before_wait_wlock(state_);
while (RwLockPolicy::wait_wlock(state_)) {
if (cv_.wait_until(lk, tp) == std::cv_status::timeout) {
if (!RwLockPolicy::wait_wlock(state_)) // re-check predicate
break;
RwLockPolicy::after_wait_wlock(state_);
return false;
}
}
RwLockPolicy::after_wait_wlock(state_);
RwLockPolicy::acquire_wlock(state_);
return true;
}

template <typename Clock, typename Duration>
bool do_try_lock_sharedwait(
const std::chrono::time_point<Clock, Duration>& tp) {
std::unique_lock<decltype(mtx_)> lk(mtx_);
while (RwLockPolicy::wait_rlock(state_)) {
if (cv_.wait_until(lk, tp) == std::cv_status::timeout) {
if (!RwLockPolicy::wait_rlock(state_)) // re-check predicate
break;
return false;
}
}
RwLockPolicy::acquire_rlock(state_);
return true;
}

public:
basic_shared_timed_mutex() = default;
~basic_shared_timed_mutex() = default;

basic_shared_timed_mutex(const basic_shared_timed_mutex&) = delete;
basic_shared_timed_mutex& operator=(const basic_shared_timed_mutex&) = delete;

using base::lock;
using base::try_lock;
using base::unlock;

template <typename Rep, typename Period>
bool try_lock_for(const std::chrono::duration<Rep, Period>& duration) {
const auto tp = std::chrono::steady_clock::now() + duration;
return do_try_lockwait(tp);
}

template <typename Clock, typename Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& tp) {
return do_try_lockwait(tp);
}

using base::lock_shared;
using base::try_lock_shared;
using base::unlock_shared;

template <typename Rep, typename Period>
bool try_lock_shared_for(const std::chrono::duration<Rep, Period>& duration) {
const auto tp = std::chrono::steady_clock::now() + duration;
return do_try_lock_sharedwait(tp);
}

template <typename Clock, typename Duration>
bool try_lock_shared_until(
const std::chrono::time_point<Clock, Duration>& tp) {
return do_try_lock_sharedwait(tp);
}
};

using shared_timed_mutex = basic_shared_timed_mutex<YAMC_RWLOCK_SCHED_DEFAULT>;

} // namespace alternate
} // namespace yamc

#endif
149 changes: 149 additions & 0 deletions include/LightGBM/utils/yamc/yamc_rwlock_sched.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* yamc_rwlock_sched.hpp
*
* MIT License
*
* Copyright (c) 2017 yohhoy
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef YAMC_RWLOCK_SCHED_HPP_
#define YAMC_RWLOCK_SCHED_HPP_

#include <cassert>
#include <cstddef>

/// default shared_mutex rwlock policy
#ifndef YAMC_RWLOCK_SCHED_DEFAULT
#define YAMC_RWLOCK_SCHED_DEFAULT yamc::rwlock::ReaderPrefer
#endif

namespace yamc {

/*
* readers-writer locking policy for basic_shared_(timed)_mutex<RwLockPolicy>
*
* - yamc::rwlock::ReaderPrefer
* - yamc::rwlock::WriterPrefer
*/
namespace rwlock {

/// Reader prefer scheduling
///
/// NOTE:
// This policy might introduce "Writer Starvation" if readers continuously
// hold shared lock. PThreads rwlock implementation in Linux use this
// scheduling policy as default. (see also PTHREAD_RWLOCK_PREFER_READER_NP)
//
struct ReaderPrefer {
static const std::size_t writer_mask = ~(~std::size_t(0u) >> 1); // MSB 1bit
static const std::size_t reader_mask = ~std::size_t(0u) >> 1;

struct state {
std::size_t rwcount = 0;
};

static void before_wait_wlock(const state&) {}
static void after_wait_wlock(const state&) {}

static bool wait_wlock(const state& s) { return (s.rwcount != 0); }

static void acquire_wlock(state* s) {
assert(!(s->rwcount & writer_mask));
s->rwcount |= writer_mask;
}

static void release_wlock(state* s) {
assert(s->rwcount & writer_mask);
s->rwcount &= ~writer_mask;
}

static bool wait_rlock(const state& s) { return (s.rwcount & writer_mask) != 0; }

static void acquire_rlock(state* s) {
assert((s->rwcount & reader_mask) < reader_mask);
++(s->rwcount);
}

static bool release_rlock(state* s) {
assert(0 < (s->rwcount & reader_mask));
return (--(s->rwcount) == 0);
}
};

/// Writer prefer scheduling
///
/// NOTE:
/// If there are waiting writer, new readers are blocked until all shared lock
/// are released,
// and the writer thread can get exclusive lock in preference to blocked
// reader threads. This policy might introduce "Reader Starvation" if writers
// continuously request exclusive lock.
/// (see also PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP)
///
struct WriterPrefer {
static const std::size_t locked = ~(~std::size_t(0u) >> 1); // MSB 1bit
static const std::size_t wait_mask = ~std::size_t(0u) >> 1;

struct state {
std::size_t nwriter = 0;
std::size_t nreader = 0;
};

static void before_wait_wlock(state* s) {
assert((s->nwriter & wait_mask) < wait_mask);
++(s->nwriter);
}

static bool wait_wlock(const state& s) {
return ((s.nwriter & locked) || 0 < s.nreader);
}

static void after_wait_wlock(state* s) {
assert(0 < (s->nwriter & wait_mask));
--(s->nwriter);
}

static void acquire_wlock(state* s) {
assert(!(s->nwriter & locked));
s->nwriter |= locked;
}

static void release_wlock(state* s) {
assert(s->nwriter & locked);
s->nwriter &= ~locked;
}

static bool wait_rlock(const state& s) { return (s.nwriter != 0); }

static void acquire_rlock(state* s) {
assert(!(s->nwriter & locked));
++(s->nreader);
}

static bool release_rlock(state* s) {
assert(0 < s->nreader);
return (--(s->nreader) == 0);
}
};

} // namespace rwlock
} // namespace yamc

#endif
Loading

0 comments on commit 1c35c3b

Please sign in to comment.