Merge pull request #319 from jpcima/spin-mutex

Add the spinlock mutex
This commit is contained in:
JP Cimalando 2020-08-06 16:49:55 +02:00 committed by GitHub
commit 443e89c45d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 156 additions and 27 deletions

1
dpf.mk
View file

@ -106,6 +106,7 @@ SFIZZ_SOURCES = \
src/sfizz/simd/HelpersAVX.cpp \
src/sfizz/Synth.cpp \
src/sfizz/Tuning.cpp \
src/sfizz/utility/SpinMutex.cpp \
src/sfizz/Voice.cpp \
src/sfizz/Wavetables.cpp

View file

@ -27,6 +27,8 @@ set (SFIZZ_HEADERS
sfizz/Config.h
sfizz/Curve.h
sfizz/Debug.h
sfizz/utility/SpinMutex.h
sfizz/utility/SpinMutex.cpp
sfizz/effects/impl/ResonantArray.h
sfizz/effects/impl/ResonantArrayAVX.h
sfizz/effects/impl/ResonantArraySSE.h

View file

@ -108,7 +108,7 @@ sfz::EQPool::EQPool(const MidiState& state, int numEQs)
sfz::EQHolderPtr sfz::EQPool::getEQ(const EQDescription& description, unsigned numChannels, float velocity)
{
const std::unique_lock<std::mutex> lock { eqGuard, std::try_to_lock };
const std::unique_lock<SpinMutex> lock { eqGuard, std::try_to_lock };
if (!lock.owns_lock())
return {};
@ -132,7 +132,7 @@ size_t sfz::EQPool::getActiveEQs() const
size_t sfz::EQPool::setnumEQs(size_t numEQs)
{
const std::lock_guard<std::mutex> eqLock { eqGuard };
const std::lock_guard<SpinMutex> eqLock { eqGuard };
swapAndPopAll(eqs, [](sfz::EQHolderPtr& eq) { return eq.use_count() == 1; });

View file

@ -2,6 +2,7 @@
#include "SfzFilter.h"
#include "EQDescription.h"
#include "MidiState.h"
#include "utility/SpinMutex.h"
#include <vector>
#include <memory>
#include <mutex>
@ -115,7 +116,7 @@ public:
*/
void setSampleRate(float sampleRate);
private:
std::mutex eqGuard;
SpinMutex eqGuard;
float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
std::vector<EQHolderPtr> eqs;

View file

@ -345,7 +345,7 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept
void sfz::FilePool::tryToClearPromises()
{
const std::lock_guard<std::mutex> promiseLock { promiseGuard };
const std::lock_guard<SpinMutex> promiseLock { promiseGuard };
for (auto& promise: promisesToClear) {
if (promise->dataStatus != FilePromise::DataStatus::Wait)
@ -427,7 +427,7 @@ void sfz::FilePool::clear()
void sfz::FilePool::cleanupPromises() noexcept
{
const std::unique_lock<std::mutex> lock { promiseGuard, std::try_to_lock };
const std::unique_lock<SpinMutex> lock { promiseGuard, std::try_to_lock };
if (!lock.owns_lock())
return;

View file

@ -32,6 +32,7 @@
#include "AudioSpan.h"
#include "FileId.h"
#include "SIMDHelpers.h"
#include "utility/SpinMutex.h"
#include "ghc/fs_std.hpp"
#include <absl/container/flat_hash_map.h>
#include <absl/types/optional.h>
@ -288,7 +289,7 @@ private:
std::vector<FilePromisePtr> emptyPromises;
std::vector<FilePromisePtr> temporaryFilePromises;
std::vector<FilePromisePtr> promisesToClear;
std::mutex promiseGuard;
SpinMutex promiseGuard;
// Preloaded data
absl::flat_hash_map<FileId, FileDataHandle> preloadedFiles;

View file

@ -109,7 +109,7 @@ sfz::FilterPool::FilterPool(const MidiState& state, int numFilters)
sfz::FilterHolderPtr sfz::FilterPool::getFilter(const FilterDescription& description, unsigned numChannels, int noteNumber, float velocity)
{
const std::unique_lock<std::mutex> lock { filterGuard, std::try_to_lock };
const std::unique_lock<SpinMutex> lock { filterGuard, std::try_to_lock };
if (!lock.owns_lock())
return {};
@ -133,7 +133,7 @@ size_t sfz::FilterPool::getActiveFilters() const
size_t sfz::FilterPool::setNumFilters(size_t numFilters)
{
const std::lock_guard<std::mutex> filterLock { filterGuard };
const std::lock_guard<SpinMutex> filterLock { filterGuard };
swapAndPopAll(filters, [](sfz::FilterHolderPtr& filter) { return filter.use_count() == 1; });

View file

@ -3,6 +3,7 @@
#include "FilterDescription.h"
#include "MidiState.h"
#include "Defaults.h"
#include "utility/SpinMutex.h"
#include <vector>
#include <memory>
#include <mutex>
@ -120,7 +121,7 @@ public:
*/
void setSampleRate(float sampleRate);
private:
std::mutex filterGuard;
SpinMutex filterGuard;
float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
std::vector<FilterHolderPtr> filters;

View file

@ -30,7 +30,7 @@ sfz::Synth::Synth()
sfz::Synth::Synth(int numVoices)
{
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
parser.setListener(this);
effectFactory.registerStandardEffectTypes();
effectBuses.reserve(5); // sufficient room for main and fx1-4
@ -39,7 +39,7 @@ sfz::Synth::Synth(int numVoices)
sfz::Synth::~Synth()
{
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
for (auto& voice : voices)
voice->reset();
@ -166,7 +166,7 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
void sfz::Synth::clear()
{
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
for (auto& voice : voices)
voice->reset();
@ -396,7 +396,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
{
clear();
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
std::error_code ec;
fs::path realFile = fs::canonical(file, ec);
@ -417,7 +417,7 @@ bool sfz::Synth::loadSfzString(const fs::path& path, absl::string_view text)
{
clear();
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
parser.parseString(path, text);
if (parser.getErrorCount() > 0)
return false;
@ -647,7 +647,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
{
ASSERT(samplesPerBlock < config::maxBlockSize);
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
this->samplesPerBlock = samplesPerBlock;
for (auto& voice : voices)
@ -663,7 +663,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
void sfz::Synth::setSampleRate(float sampleRate) noexcept
{
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
this->sampleRate = sampleRate;
for (auto& voice : voices)
@ -702,7 +702,7 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
if (resources.synthConfig.freeWheeling)
resources.filePool.waitForBackgroundLoading();
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
const std::unique_lock<SpinMutex> lock { callbackGuard, std::try_to_lock };
if (!lock.owns_lock())
return;
@ -801,7 +801,7 @@ void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
resources.midiState.noteOnEvent(delay, noteNumber, normalizedVelocity);
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
const std::unique_lock<SpinMutex> lock { callbackGuard, std::try_to_lock };
if (!lock.owns_lock())
return;
@ -817,7 +817,7 @@ void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
resources.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity);
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
const std::unique_lock<SpinMutex> lock { callbackGuard, std::try_to_lock };
if (!lock.owns_lock())
return;
@ -970,7 +970,7 @@ void sfz::Synth::hdcc(int delay, int ccNumber, float normValue) noexcept
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
resources.midiState.ccEvent(delay, ccNumber, normValue);
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
const std::unique_lock<SpinMutex> lock { callbackGuard, std::try_to_lock };
if (!lock.owns_lock())
return;
@ -1281,7 +1281,7 @@ int sfz::Synth::getNumVoices() const noexcept
void sfz::Synth::setNumVoices(int numVoices) noexcept
{
ASSERT(numVoices > 0);
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
// fast path
if (numVoices == this->numVoices)
@ -1329,7 +1329,7 @@ void sfz::Synth::applySettingsPerVoice()
void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept
{
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
// fast path
if (factor == oversamplingFactor)
@ -1352,7 +1352,7 @@ sfz::Oversampling sfz::Synth::getOversamplingFactor() const noexcept
void sfz::Synth::setPreloadSize(uint32_t preloadSize) noexcept
{
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
// fast path
if (preloadSize == resources.filePool.getPreloadSize())
@ -1385,7 +1385,7 @@ void sfz::Synth::resetAllControllers(int delay) noexcept
{
resources.midiState.resetAllControllers(delay);
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
const std::unique_lock<SpinMutex> lock { callbackGuard, std::try_to_lock };
if (!lock.owns_lock())
return;
@ -1440,7 +1440,7 @@ void sfz::Synth::disableLogging() noexcept
void sfz::Synth::allSoundOff() noexcept
{
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
for (auto& voice : voices)
voice->reset();

View file

@ -17,10 +17,10 @@
#include "AudioSpan.h"
#include "parser/Parser.h"
#include "VoiceStealing.h"
#include "utility/SpinMutex.h"
#include "absl/types/span.h"
#include <absl/types/optional.h>
#include <random>
#include <mutex>
#include <set>
#include <string_view>
#include <vector>
@ -748,7 +748,7 @@ private:
// Distribution used to generate random value for the *rand opcodes
std::uniform_real_distribution<float> randNoteDistribution { 0, 1 };
std::mutex callbackGuard;
SpinMutex callbackGuard;
// Singletons passed as references to the voices
Resources resources;

View file

@ -0,0 +1,43 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "SpinMutex.h"
#include <atomic_queue/defs.h>
#include <thread>
// based on Timur Doumler's implementation advice for spinlocks
void SpinMutex::lock() noexcept
{
for (int i = 0; i < 5; ++i) {
if (try_lock())
return;
}
for (int i = 0; i < 10; ++i) {
if (try_lock())
return;
atomic_queue::spin_loop_pause();
}
for (;;) {
for (int i = 0; i < 3000; ++i) {
if (try_lock())
return;
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
atomic_queue::spin_loop_pause();
}
std::this_thread::yield();
}
}

View file

@ -0,0 +1,28 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
#include <atomic>
class SpinMutex {
public:
void lock() noexcept;
bool try_lock() noexcept;
void unlock() noexcept;
private:
std::atomic_flag flag_ = ATOMIC_FLAG_INIT;
};
inline bool SpinMutex::try_lock() noexcept
{
return !flag_.test_and_set(std::memory_order_acquire);
}
inline void SpinMutex::unlock() noexcept
{
flag_.clear(std::memory_order_release);
}

View file

@ -35,6 +35,7 @@ set(SFIZZ_TEST_SOURCES
SemaphoreT.cpp
SwapAndPopT.cpp
TuningT.cpp
ConcurrencyT.cpp
)
add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES})

51
tests/ConcurrencyT.cpp Normal file
View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "sfizz/utility/SpinMutex.h"
#include "catch2/catch.hpp"
#include <thread>
#include <mutex>
#include <condition_variable>
TEST_CASE("[SpinMutex] Basic synchronization")
{
constexpr size_t num_threads = 8;
constexpr size_t num_iterations = 1000000;
volatile size_t counter = 0;
SpinMutex counter_mutex;
std::thread threads[num_threads];
volatile bool ready = false;
std::condition_variable ready_cv;
std::mutex ready_mtx;
auto thread_run = [&]()
{
std::unique_lock<std::mutex> lock(ready_mtx);
ready_cv.wait(lock, [&]() -> bool { return ready; });
lock.unlock();
for (size_t i = 0; i < num_iterations; ++i) {
std::unique_lock<SpinMutex> lock(counter_mutex);
++counter;
}
};
for (unsigned i = 0; i < num_threads; ++i)
threads[i] = std::thread(thread_run);
std::unique_lock<std::mutex> lock(ready_mtx);
ready = true;
ready_cv.notify_all();
lock.unlock();
for (unsigned i = 0; i < num_threads; ++i)
threads[i].join();
REQUIRE(counter == num_threads * num_iterations);
}