-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change locking strategy of Booster, allow for share and unique locks (#…
…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
1 parent
f5f27ca
commit 1c35c3b
Showing
6 changed files
with
631 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.