diff --git a/.gitmodules b/.gitmodules index 58c4d8526d4..679e28ffc1c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,6 +25,9 @@ [submodule "src/3rdparty/weakjack/weakjack"] path = src/3rdparty/weakjack/weakjack url = https://github.com/x42/weakjack.git +[submodule "src/3rdparty/libcds"] + path = src/3rdparty/libcds + url = https://github.com/khizmax/libcds.git [submodule "doc/wiki"] path = doc/wiki url = https://github.com/lmms/lmms.wiki.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bb4adc4aa8..022de169029 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -698,6 +698,7 @@ include(CompileCache) ADD_SUBDIRECTORY(cmake) ADD_SUBDIRECTORY(src) ADD_SUBDIRECTORY(plugins) +ADD_SUBDIRECTORY(benchmarks) ADD_SUBDIRECTORY(tests) ADD_SUBDIRECTORY(data) ADD_SUBDIRECTORY(doc) diff --git a/LICENSE.MIT.txt b/LICENSE.MIT.txt new file mode 100644 index 00000000000..764a44ba728 --- /dev/null +++ b/LICENSE.MIT.txt @@ -0,0 +1,18 @@ +The MIT License + +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. diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 00000000000..7e300ae8690 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,13 @@ +SET(CMAKE_AUTOMOC ON) + +ADD_EXECUTABLE(benchmarks + EXCLUDE_FROM_ALL + benchmark.cpp +) + +# TODO replace usages of include_directories in src/CMakeLists.txt with target_include_directories +# and remove this line (also in tests/CMakeLists.txt) +target_include_directories(benchmarks PRIVATE $) + +target_static_libraries(benchmarks PRIVATE lmmsobjs) +target_compile_features(benchmarks PRIVATE cxx_std_17) diff --git a/benchmarks/benchmark.cpp b/benchmarks/benchmark.cpp new file mode 100644 index 00000000000..86f9ee414da --- /dev/null +++ b/benchmarks/benchmark.cpp @@ -0,0 +1,130 @@ +#include + +#include +#include + +#include "libcds.h" +#include + +#include "Engine.h" +#include "PerfLog.h" + +#include "LocklessList.h" +#include "MemoryPool.h" + +#include "NotePlayHandle.h" + +using namespace lmms; + +template +using LocklessQueue = cds::container::VyukovMPMCCycleQueue; + +template +void benchmark_allocator(QString name, Alloc&& alloc, size_t n, size_t I) +{ + using T = typename Alloc::value_type; + constexpr size_t S = sizeof(T); + + std::vector ptrs{n}; + PerfLogTimer timer(QString("Allocate: %1 x %2 x %3 bytes, %4") + .arg(I).arg(n).arg(S).arg(name)); + + for (size_t i=0; i < I; i++) + { + for (size_t j=0; j < n; j++) { + ptrs[j] = alloc.allocate(1); + } + for (size_t j=0; j < n; j++) { + alloc.deallocate(ptrs[j], 1); + } + } +} + + +template +void benchmark_allocator_threaded(QString name, Alloc&& alloc, size_t n, size_t t) +{ + using T = typename Alloc::value_type; + constexpr size_t S = sizeof(T); + + LocklessQueue ptrs{n}; + + PerfLogTimer timer(QString("Allocate multi-threaded: %1 x %2 bytes using %3 threads, %4") + .arg(n).arg(S).arg(t).arg(name)); + + std::vector threads; threads.reserve(t*2); + + std::atomic_uint_fast64_t allocated{0}; + std::atomic_uint_fast64_t deallocated{0}; + + for (size_t i=0; i < t; i++) { + threads.emplace_back([&]() { + while(allocated++ < n) { + auto ptr = alloc.allocate(1); + ptrs.push(ptr); + } + }); + } + for (size_t i=0; i < t; i++) { + threads.emplace_back([&]() { + while(deallocated++ < n) { + T* ptr; + while (! ptrs.pop(ptr)); + alloc.deallocate(ptr, 1); + } + }); + } + + for (std::thread& thread : threads) { + thread.join(); + } +} + +int main(int argc, char* argv[]) +{ + new QCoreApplication(argc, argv); + + using Stack = LocklessList; + { + size_t n = 100 * 1000 * 1000; + Stack stack{n}; + PerfLogTimer timer("LocklessList: Insert 100m entries, single-threaded, pre-allocated"); + for (size_t i=0; i < n; i++) { + stack.push(i); + } + } + { + size_t n = 50 * 1000 * 1000; + size_t t = 5; + Stack stack{n}; + std::vector threads; threads.reserve(t); + PerfLogTimer timer("LocklessList: Push 50m entries, multi-threaded, pre-allocated"); + + for (int i=0; i < 5; i++) { + threads.emplace_back([&]() { + for (size_t j=0; j < n / t; j++) { + stack.push(j); + } + }); + } + + for (int i=0; i < 5; i++) { + threads.at(i).join(); + } + } + + { + size_t n = 10 * 1000 * 1000; + constexpr size_t S = 256; + using T = std::array; + benchmark_allocator("std::allocator", std::allocator{}, n, 1); + benchmark_allocator("MemoryPool", MemoryPool{n}, n, 1); + } + { + size_t n = 10 * 1000 * 1000; + constexpr size_t S = 256; + using T = std::array; + benchmark_allocator_threaded("std::allocator", std::allocator{}, n, 4); + benchmark_allocator_threaded("MemoryPool", MemoryPool{n}, n, 4); + } +} diff --git a/cmake/modules/CheckSubmodules.cmake b/cmake/modules/CheckSubmodules.cmake index b9ea2077874..5c8257a0bb5 100644 --- a/cmake/modules/CheckSubmodules.cmake +++ b/cmake/modules/CheckSubmodules.cmake @@ -44,8 +44,8 @@ SET(ENV{LC_ALL} "C") SET(ENV{LANG} "en_US") # Submodule list pairs, unparsed (WARNING: Assumes alpha-numeric paths) -STRING(REGEX MATCHALL "path = [-0-9A-Za-z/]+" SUBMODULE_LIST_RAW ${SUBMODULE_DATA}) -STRING(REGEX MATCHALL "url = [.:%-0-9A-Za-z/]+" SUBMODULE_URL_RAW ${SUBMODULE_DATA}) +STRING(REGEX MATCHALL "path = [-0-9A-Za-z/_]+" SUBMODULE_LIST_RAW ${SUBMODULE_DATA}) +STRING(REGEX MATCHALL "url = [.:%-0-9A-Za-z/_]+" SUBMODULE_URL_RAW ${SUBMODULE_DATA}) # Submodule list pairs, parsed SET(SUBMODULE_LIST "") diff --git a/include/BufferManager.h b/include/BufferPool.h similarity index 86% rename from include/BufferManager.h rename to include/BufferPool.h index 84602f1215d..65e403174dd 100644 --- a/include/BufferManager.h +++ b/include/BufferPool.h @@ -1,5 +1,5 @@ /* - * BufferManager.h - A buffer caching/memory management system + * BufferPool.h * * Copyright (c) 2014 Vesa Kivimäki * Copyright (c) 2006-2014 Tobias Doerffel @@ -23,8 +23,7 @@ * */ -#ifndef LMMS_BUFFER_MANAGER_H -#define LMMS_BUFFER_MANAGER_H +#pragma once #include "lmms_export.h" #include "lmms_basics.h" @@ -34,7 +33,8 @@ namespace lmms class SampleFrame; -class LMMS_EXPORT BufferManager +/// Legacy interface for buffer re-use. Uses MemoryPool internally now. +class LMMS_EXPORT BufferPool { public: static void init( fpp_t fpp ); @@ -47,5 +47,3 @@ class LMMS_EXPORT BufferManager } // namespace lmms - -#endif // LMMS_BUFFER_MANAGER_H diff --git a/include/LocklessAllocator.h b/include/LocklessAllocator.h deleted file mode 100644 index 1652ac71d66..00000000000 --- a/include/LocklessAllocator.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * LocklessAllocator.h - allocator with lockless alloc and free - * - * Copyright (c) 2016 Javier Serrano Polo - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#ifndef LMMS_LOCKLESS_ALLOCATOR_H -#define LMMS_LOCKLESS_ALLOCATOR_H - -#include -#include - - -namespace lmms -{ - - -class LocklessAllocator -{ -public: - LocklessAllocator( size_t nmemb, size_t size ); - virtual ~LocklessAllocator(); - void * alloc(); - void free( void * ptr ); - - -private: - char * m_pool; - size_t m_capacity; - size_t m_elementSize; - - std::atomic_int * m_freeState; - size_t m_freeStateSets; - - std::atomic_size_t m_available; - std::atomic_size_t m_startIndex; - -} ; - - - - -template -class LocklessAllocatorT : private LocklessAllocator -{ -public: - LocklessAllocatorT( size_t nmemb ) : - LocklessAllocator( nmemb, sizeof( T ) ) - { - } - - ~LocklessAllocatorT() override = default; - - T * alloc() - { - return (T *)LocklessAllocator::alloc(); - } - - void free( T * ptr ) - { - LocklessAllocator::free( ptr ); - } - -} ; - - -} // namespace lmms - -#endif // LMMS_LOCKLESS_ALLOCATOR_H diff --git a/include/LocklessList.h b/include/LocklessList.h index 4a0c27de09d..c3fdfed7846 100644 --- a/include/LocklessList.h +++ b/include/LocklessList.h @@ -25,7 +25,7 @@ #ifndef LMMS_LOCKLESS_LIST_H #define LMMS_LOCKLESS_LIST_H -#include "LocklessAllocator.h" +#include "MemoryPool.h" #include @@ -44,7 +44,7 @@ class LocklessList LocklessList( size_t size ) : m_first(nullptr), - m_allocator(new LocklessAllocatorT(size)) + m_allocator(new MemoryPool(size)) { } @@ -55,7 +55,7 @@ class LocklessList void push( T value ) { - Element * e = m_allocator->alloc(); + Element * e = m_allocator->allocate(); e->value = value; e->next = m_first.load(std::memory_order_relaxed); @@ -84,14 +84,13 @@ class LocklessList void free( Element * e ) { - m_allocator->free( e ); + m_allocator->destroy( e ); } private: std::atomic m_first; - LocklessAllocatorT * m_allocator; - + MemoryPool * m_allocator; } ; diff --git a/include/MemoryPool.h b/include/MemoryPool.h new file mode 100644 index 00000000000..425bb82cda6 --- /dev/null +++ b/include/MemoryPool.h @@ -0,0 +1,88 @@ +/* + * MemoryPool.h + * + * Copyright (c) 2018 Lukas W + * + * This file is part of LMMS - https://lmms.io + * + * This file is licensed under the MIT license. See LICENSE.MIT.txt file in the + * project root for details. + * + */ + +#pragma once + +#include +#include +#include "lmms_export.h" + +namespace lmms +{ + +class LMMS_EXPORT _MemoryPool_Private; + +class LMMS_EXPORT _MemoryPool_Base +{ +public: + _MemoryPool_Base(size_t size, size_t nmemb); + virtual ~_MemoryPool_Base(); + void * allocate(); + void * allocate_bounded(); + void deallocate(void * ptr); + +private: + const std::unique_ptr<_MemoryPool_Private> _imp; +}; + +/// Thread-safe, lockless memory pool. Only supports allocate(n) with n=1. When +/// the pool is exhausted, MmAllocator is used. +template +class MemoryPool : private _MemoryPool_Base +{ +public: + using value_type = T; + template struct rebind { typedef MemoryPool other; }; + + MemoryPool(size_t nmemb) : _MemoryPool_Base(sizeof(T), nmemb) {} + + T * allocate(size_t n = 1) + { + if (n != 1) { throw std::bad_alloc{}; } + return reinterpret_cast(_MemoryPool_Base::allocate()); + } + + T * allocate_bounded() + { + return reinterpret_cast(_MemoryPool_Base::allocate_bounded()); + } + + void deallocate(T * ptr, size_t n = 1) + { + _MemoryPool_Base::deallocate(ptr); + } + + template + T * construct(Args&&... args) + { + T* buffer = allocate(); + return ::new ((void*)buffer) T(std::forward(args)...); + } + + template + T * construct_bounded(Args&&... args) + { + T* buffer = allocate_bounded(); + if (buffer) { + ::new ((void*)buffer) T(std::forward(args)...); + } + return buffer; + } + + void destroy(T* ptr) + { + ptr->~T(); + deallocate(ptr); + } +} ; + +} // namespace lmms diff --git a/include/NiftyCounter.h b/include/NiftyCounter.h new file mode 100644 index 00000000000..d5b618407f8 --- /dev/null +++ b/include/NiftyCounter.h @@ -0,0 +1,65 @@ +/* + * NiftyCounter.h + * + * Copyright (c) 2018 Lukas W + * + * This file is part of LMMS - https://lmms.io + * + * This file is licensed under the MIT license. See LICENSE.MIT.txt file in the + * project root for details. + * + */ + +#pragma once + +namespace lmms +{ + +/// Nifty counter, also known as "Schwarz Counter". Used for ensuring global +/// static object initialization and destruction order. + +template class _NiftyCounter_Base +{ +public: + _NiftyCounter_Base() + { + if (! T::inc()) T::init(); + } + _NiftyCounter_Base(const _NiftyCounter_Base& other) : _NiftyCounter_Base() {} + + ~_NiftyCounter_Base() + { + if (! T::dec()) T::deinit(); + } +}; + +template class _NiftyCounterCD_Base : public _NiftyCounter_Base +{ + friend class _NiftyCounter_Base; +private: + static void init() { C(); } + static void deinit() { D(); } + static int inc() { return T::s_count++; } + static int dec() { return --T::s_count; } +}; + + +/// Pass construction and destruction functions as template arguments C and D. +template class NiftyCounter : public _NiftyCounterCD_Base, C,D> +{ + friend class _NiftyCounterCD_Base, C,D>; +private: + static int s_count; +}; +template int NiftyCounter::s_count = 0; + +/// Thread-local version of NiftyCounter +template class NiftyCounterTL : public _NiftyCounterCD_Base, C,D> +{ + friend class _NiftyCounterCD_Base, C,D>; +private: + thread_local static int s_count; +}; +template thread_local int NiftyCounterTL::s_count = 0; + +} // namespace lmms diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 0c0acf6940c..3c7676c4ac7 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -32,6 +32,7 @@ #include "Note.h" #include "PlayHandle.h" #include "Track.h" +#include "MemoryPool.h" class QReadWriteLock; @@ -332,32 +333,7 @@ class LMMS_EXPORT NotePlayHandle : public PlayHandle, public Note bool m_frequencyNeedsUpdate; // used to update pitch } ; - -const int INITIAL_NPH_CACHE = 256; -const int NPH_CACHE_INCREMENT = 16; - -class NotePlayHandleManager -{ -public: - static void init(); - static NotePlayHandle * acquire( InstrumentTrack* instrumentTrack, - const f_cnt_t offset, - const f_cnt_t frames, - const Note& noteToPlay, - NotePlayHandle* parent = nullptr, - int midiEventChannel = -1, - NotePlayHandle::Origin origin = NotePlayHandle::Origin::MidiClip ); - static void release( NotePlayHandle * nph ); - static void extend( int i ); - static void free(); - -private: - static NotePlayHandle ** s_available; - static QReadWriteLock s_mutex; - static std::atomic_int s_availableIndex; - static int s_size; -}; - +extern MemoryPool NotePlayHandlePool; } // namespace lmms diff --git a/include/libcds.h b/include/libcds.h new file mode 100644 index 00000000000..64754b94105 --- /dev/null +++ b/include/libcds.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "NiftyCounter.h" + +namespace lmms::_cdslib +{ + void init(); + void deinit(); + void thread_init(); + void thread_deinit(); + + static NiftyCounter _counter; + static thread_local NiftyCounterTL<_cdslib::thread_init, _cdslib::thread_deinit> _thread_counter; +} // namespace lmms:_cdslib + +#define CDS_THREAD_GUARD() (void)lmms::_cdslib::_thread_counter; diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index e5cb6252756..77a83d27b2c 100644 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -11,6 +11,30 @@ target_include_directories(jack_headers INTERFACE jack2/common) ADD_SUBDIRECTORY(hiir) ADD_SUBDIRECTORY(weakjack) +ADD_LIBRARY(cds ${CDS_LIBRARY_TYPE} + libcds/src/init.cpp + libcds/src/hp.cpp + libcds/src/dhp.cpp + libcds/src/urcu_gp.cpp + libcds/src/urcu_sh.cpp + libcds/src/thread_data.cpp + libcds/src/topology_hpux.cpp + libcds/src/topology_linux.cpp + libcds/src/topology_osx.cpp + libcds/src/dllmain.cpp +) +SET_TARGET_PROPERTIES(cds PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON +) +TARGET_INCLUDE_DIRECTORIES(cds + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/libcds" +) + +TARGET_COMPILE_DEFINITIONS(cds + PUBLIC CDS_BUILD_STATIC_LIB +) + # The lockless ring buffer library is linked as part of the core add_library(ringbuffer OBJECT ringbuffer/src/lib/ringbuffer.cpp diff --git a/src/3rdparty/libcds b/src/3rdparty/libcds new file mode 160000 index 00000000000..44c052bdb69 --- /dev/null +++ b/src/3rdparty/libcds @@ -0,0 +1 @@ +Subproject commit 44c052bdb69aaf26d7010074547356c80e55421a diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7645e49e34e..110910c4b4c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -172,7 +172,7 @@ SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} target_link_libraries(lmmsobjs ${LMMS_REQUIRED_LIBS} ) -target_static_libraries(lmmsobjs ringbuffer) +target_static_libraries(lmmsobjs ringbuffer cds) set_target_properties(lmms PROPERTIES ENABLE_EXPORTS ON diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 77453a8bad4..57d4eb3a5a5 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -37,6 +37,7 @@ #include "NotePlayHandle.h" #include "ConfigManager.h" #include "SamplePlayHandle.h" +#include "BufferPool.h" // platform-specific audio-interface-classes #include "AudioAlsa.h" @@ -59,8 +60,6 @@ #include "MidiApple.h" #include "MidiDummy.h" -#include "BufferManager.h" - namespace lmms { @@ -131,8 +130,8 @@ AudioEngine::AudioEngine( bool renderOnly ) : // allocte the FIFO from the determined size m_fifo = new Fifo( fifoSize ); - // now that framesPerPeriod is fixed initialize global BufferManager - BufferManager::init( m_framesPerPeriod ); + // now that framesPerPeriod is fixed initialize global BufferPool + BufferPool::init( m_framesPerPeriod ); m_outputBufferRead = std::make_unique(m_framesPerPeriod); m_outputBufferWrite = std::make_unique(m_framesPerPeriod); @@ -329,7 +328,7 @@ void AudioEngine::renderStageNoteSetup() ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::Type::NotePlayHandle ) { - NotePlayHandleManager::release( (NotePlayHandle*) *it ); + NotePlayHandlePool.destroy( (NotePlayHandle*) *it ); } else delete *it; m_playHandles.erase( it ); @@ -392,7 +391,7 @@ void AudioEngine::renderStageEffects() ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::Type::NotePlayHandle ) { - NotePlayHandleManager::release( (NotePlayHandle*) *it ); + NotePlayHandlePool.destroy( (NotePlayHandle*) *it ); } else delete *it; it = m_playHandles.erase( it ); @@ -610,7 +609,7 @@ bool AudioEngine::addPlayHandle( PlayHandle* handle ) if( handle->type() == PlayHandle::Type::NotePlayHandle ) { - NotePlayHandleManager::release( (NotePlayHandle*)handle ); + NotePlayHandlePool.destroy( (NotePlayHandle*)handle ); } else delete handle; @@ -661,7 +660,7 @@ void AudioEngine::removePlayHandle(PlayHandle * ph) { if (ph->type() == PlayHandle::Type::NotePlayHandle) { - NotePlayHandleManager::release(dynamic_cast(ph)); + NotePlayHandlePool.destroy(dynamic_cast(ph)); } else { delete ph; } } @@ -687,7 +686,7 @@ void AudioEngine::removePlayHandlesOfTypes(Track * track, PlayHandle::Types type ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::Type::NotePlayHandle ) { - NotePlayHandleManager::release( (NotePlayHandle*) *it ); + NotePlayHandlePool.destroy( (NotePlayHandle*) *it ); } else delete *it; it = m_playHandles.erase( it ); diff --git a/src/core/BufferManager.cpp b/src/core/BufferPool.cpp similarity index 67% rename from src/core/BufferManager.cpp rename to src/core/BufferPool.cpp index 47598c63325..48a07133a9c 100644 --- a/src/core/BufferManager.cpp +++ b/src/core/BufferPool.cpp @@ -1,7 +1,7 @@ /* - * BufferManager.cpp - A buffer caching/memory management system + * BufferPool.cpp * - * Copyright (c) 2017 Lukas W + * Copyright (c) 2018 Lukas W * Copyright (c) 2014 Vesa Kivimäki * Copyright (c) 2006-2014 Tobias Doerffel * @@ -24,34 +24,32 @@ * */ -#include "BufferManager.h" +#include "BufferPool.h" #include "SampleFrame.h" #include - +#include "MemoryPool.h" namespace lmms { -fpp_t BufferManager::s_framesPerPeriod; +static std::unique_ptr<_MemoryPool_Base> pool; +const int BM_INITIAL_BUFFERS = 256; -void BufferManager::init( fpp_t fpp ) +void BufferPool::init( fpp_t framesPerPeriod ) { - s_framesPerPeriod = fpp; + pool.reset(new _MemoryPool_Base(framesPerPeriod * sizeof(SampleFrame), BM_INITIAL_BUFFERS)); } - -SampleFrame* BufferManager::acquire() +SampleFrame * BufferPool::acquire() { - return new SampleFrame[s_framesPerPeriod]; + return reinterpret_cast(pool->allocate()); } - - -void BufferManager::release( SampleFrame* buf ) +void BufferPool::release( SampleFrame * buf ) { - delete[] buf; + pool->deallocate(buf); } } // namespace lmms diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1e2c4f3cfdb..512367f4e91 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -10,7 +10,7 @@ set(LMMS_SRCS core/AutomationNode.cpp core/BandLimitedWave.cpp core/base64.cpp - core/BufferManager.cpp + core/BufferPool.cpp core/Clipboard.cpp core/ComboBoxModel.cpp core/ConfigManager.cpp @@ -38,7 +38,7 @@ set(LMMS_SRCS core/LadspaManager.cpp core/LfoController.cpp core/LinkedModelGroups.cpp - core/LocklessAllocator.cpp + core/MemoryPool.cpp core/MeterModel.cpp core/Metronome.cpp core/MicroTimer.cpp @@ -132,5 +132,7 @@ set(LMMS_SRCS core/midi/MidiPort.cpp core/midi/MidiWinMM.cpp + core/libcds.cpp + PARENT_SCOPE ) diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 3687c0b7445..23296406a65 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -265,7 +265,7 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n ) // create sub-note-play-handle, only note is // different Engine::audioEngine()->addPlayHandle( - NotePlayHandleManager::acquire( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy, + NotePlayHandlePool.construct( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy, _n, -1, NotePlayHandle::Origin::NoteStacking ) ); } @@ -513,7 +513,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) // create sub-note-play-handle, only ptr to note is different // and is_arp_note=true Engine::audioEngine()->addPlayHandle( - NotePlayHandleManager::acquire( _n->instrumentTrack(), + NotePlayHandlePool.construct( _n->instrumentTrack(), frames_processed, gated_frames, Note( TimePos( 0 ), TimePos( 0 ), sub_note_key, _n->getVolume(), diff --git a/src/core/LocklessAllocator.cpp b/src/core/LocklessAllocator.cpp deleted file mode 100644 index 92eadd75128..00000000000 --- a/src/core/LocklessAllocator.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - * LocklessAllocator.cpp - allocator with lockless alloc and free - * - * Copyright (c) 2016 Javier Serrano Polo - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#include "LocklessAllocator.h" - -#include -#include - -#include "lmmsconfig.h" - -#ifndef LMMS_BUILD_WIN32 -#include -#endif - -namespace lmms -{ - -static const size_t SIZEOF_SET = sizeof( int ) * 8; - - -static size_t align( size_t size, size_t alignment ) -{ - size_t misalignment = size % alignment; - if( misalignment ) - { - size += alignment - misalignment; - } - return size; -} - - - - -LocklessAllocator::LocklessAllocator( size_t nmemb, size_t size ) -{ - m_capacity = align( nmemb, SIZEOF_SET ); - m_elementSize = align( size, sizeof( void * ) ); - m_pool = new char[m_capacity * m_elementSize]; - - m_freeStateSets = m_capacity / SIZEOF_SET; - m_freeState = new std::atomic_int[m_freeStateSets]; - std::fill(m_freeState, m_freeState + m_freeStateSets, 0); - - m_available = m_capacity; - m_startIndex = 0; -} - - - - -LocklessAllocator::~LocklessAllocator() -{ - if (m_available != m_capacity) - { - fprintf( stderr, "LocklessAllocator: " - "Destroying with elements still allocated\n" ); - } - - delete[] m_pool; - delete[] m_freeState; -} - - - - -#ifdef LMMS_BUILD_WIN32 -static int ffs( int i ) -{ - if( !i ) - { - return 0; - } - for( int j = 0;; ) - { - if( i & 1 << j++ ) - { - return j; - } - } -} -#endif - - - - -void * LocklessAllocator::alloc() -{ - // Some of these CAS loops could probably use relaxed atomics, as discussed - // in http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange. - // Let's use sequentially-consistent ops to be safe for now. - auto available = m_available.load(); - do - { - if( !available ) - { - fprintf( stderr, "LocklessAllocator: No free space\n" ); - return nullptr; - } - } - while (!m_available.compare_exchange_weak(available, available - 1)); - - const size_t startIndex = m_startIndex++ % m_freeStateSets; - for (size_t set = startIndex;; set = ( set + 1 ) % m_freeStateSets) - { - for (int freeState = m_freeState[set]; freeState != -1;) - { - int bit = ffs( ~freeState ) - 1; - if (m_freeState[set].compare_exchange_weak(freeState, - freeState | 1 << bit ) ) - { - return m_pool + ( SIZEOF_SET * set + bit ) - * m_elementSize; - } - } - } -} - - - - -void LocklessAllocator::free( void * ptr ) -{ - ptrdiff_t diff = (char *)ptr - m_pool; - if( diff < 0 || diff % m_elementSize ) - { -invalid: - fprintf( stderr, "LocklessAllocator: Invalid pointer\n" ); - return; - } - size_t offset = diff / m_elementSize; - if( offset >= m_capacity ) - { - goto invalid; - } - size_t set = offset / SIZEOF_SET; - int bit = offset % SIZEOF_SET; - int mask = 1 << bit; - int prevState = m_freeState[set].fetch_and(~mask); - if ( !( prevState & mask ) ) - { - fprintf( stderr, "LocklessAllocator: Block not in use\n" ); - return; - } - ++m_available; -} - - -} // namespace lmms \ No newline at end of file diff --git a/src/core/MemoryPool.cpp b/src/core/MemoryPool.cpp new file mode 100644 index 00000000000..b529228f846 --- /dev/null +++ b/src/core/MemoryPool.cpp @@ -0,0 +1,125 @@ +/* + * MemoryPool.cpp + * + * Copyright (c) 2018 Lukas W + * + * This file is part of LMMS - https://lmms.io + * + * This file is licensed under the MIT license. See LICENSE.MIT.txt file in the + * project root for details. + * + */ + +#include "MemoryPool.h" + +#include + +#include "libcds.h" +#include + +namespace lmms +{ + +class _MemoryPool_Private +{ +public: + _MemoryPool_Private(size_t size, size_t nmemb) + : m_elementSize(size) + , m_numElms(nmemb) + , m_freelist(nmemb) + { + CDS_THREAD_GUARD(); + + m_buffer = new char[m_elementSize * m_numElms]; + for (size_t i = 0; i < m_numElms; i++) { + m_freelist.push(m_buffer + (i * m_elementSize)); + } + } + + ~_MemoryPool_Private() + { + char* ptr = nullptr; + while (m_freelist.pop(ptr)) { + if (! is_from_pool(ptr)) { + delete[] ptr; + } + } + delete[] m_buffer; + } + + void * allocate() + { + void* ptr = allocate_bounded(); + if (ptr) { + return ptr; + } else { + qWarning() << "MemoryPool exhausted"; + return new char[m_elementSize]; + } + } + + void * allocate_bounded() + { + char* ptr = nullptr; + m_freelist.pop(ptr); + return ptr; + } + + void deallocate(void * ptr) + { + if (is_from_pool(ptr)) { + bool pushed = m_freelist.push(reinterpret_cast(ptr)); + assert(pushed); Q_UNUSED(pushed); + } else { + do_deallocate(ptr); + } + } + +private: + void* do_allocate() + { + return new char[m_elementSize]; + } + void do_deallocate(void* ptr) + { + delete[] reinterpret_cast(ptr); + } + + bool is_from_pool(void* ptr) + { + auto buff = reinterpret_cast(m_buffer); + auto p = reinterpret_cast(ptr); + return p >= buff && p < (buff + (m_elementSize * m_numElms)); + } + + const size_t m_elementSize; + const size_t m_numElms; + + char* m_buffer; + cds::container::VyukovMPMCCycleQueue m_freelist; +}; + +_MemoryPool_Base::_MemoryPool_Base( size_t size, size_t nmemb ) + : _imp(new _MemoryPool_Private(size, nmemb)) +{} + +_MemoryPool_Base::~_MemoryPool_Base() +{} + +void * _MemoryPool_Base::allocate() +{ + return _imp->allocate(); +} + +void *_MemoryPool_Base::allocate_bounded() +{ + return _imp->allocate_bounded(); +} + +void _MemoryPool_Base::deallocate(void * ptr) +{ + return _imp->deallocate(ptr); +} + + +} // namespace lmms diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 7e3fc1f60c5..1dcc8b304bd 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -26,7 +26,7 @@ #include "AudioEngine.h" #include "AudioEngineWorkerThread.h" -#include "BufferManager.h" +#include "BufferPool.h" #include "Mixer.h" #include "MixHelpers.h" #include "Song.h" diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 7d649e1dd9f..0571bd1741a 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -601,77 +601,8 @@ void NotePlayHandle::resize( const bpm_t _new_tempo ) } } - -NotePlayHandle ** NotePlayHandleManager::s_available; -QReadWriteLock NotePlayHandleManager::s_mutex; -std::atomic_int NotePlayHandleManager::s_availableIndex; -int NotePlayHandleManager::s_size; - - -void NotePlayHandleManager::init() -{ - s_available = new NotePlayHandle*[INITIAL_NPH_CACHE]; - - auto n = static_cast(std::malloc(sizeof(NotePlayHandle) * INITIAL_NPH_CACHE)); - - for( int i=0; i < INITIAL_NPH_CACHE; ++i ) - { - s_available[ i ] = n; - ++n; - } - s_availableIndex = INITIAL_NPH_CACHE - 1; - s_size = INITIAL_NPH_CACHE; -} - - -NotePlayHandle * NotePlayHandleManager::acquire( InstrumentTrack* instrumentTrack, - const f_cnt_t offset, - const f_cnt_t frames, - const Note& noteToPlay, - NotePlayHandle* parent, - int midiEventChannel, - NotePlayHandle::Origin origin ) -{ - // TODO: use some lockless data structures - s_mutex.lockForWrite(); - if (s_availableIndex < 0) { extend(NPH_CACHE_INCREMENT); } - NotePlayHandle * nph = s_available[s_availableIndex--]; - s_mutex.unlock(); - - new( (void*)nph ) NotePlayHandle( instrumentTrack, offset, frames, noteToPlay, parent, midiEventChannel, origin ); - return nph; -} - - -void NotePlayHandleManager::release( NotePlayHandle * nph ) -{ - nph->NotePlayHandle::~NotePlayHandle(); - s_mutex.lockForRead(); - s_available[++s_availableIndex] = nph; - s_mutex.unlock(); -} - - -void NotePlayHandleManager::extend( int c ) -{ - s_size += c; - auto tmp = new NotePlayHandle*[s_size]; - delete[] s_available; - s_available = tmp; - - auto n = static_cast(std::malloc(sizeof(NotePlayHandle) * c)); - - for( int i=0; i < c; ++i ) - { - s_available[++s_availableIndex] = n; - ++n; - } -} - -void NotePlayHandleManager::free() -{ - delete[] s_available; -} +const size_t INITIAL_NPH_CACHE = 256; +MemoryPool NotePlayHandlePool{INITIAL_NPH_CACHE}; } // namespace lmms diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index a875cf2d4f4..88b631651ba 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -30,7 +30,6 @@ #include #endif -#include "BufferManager.h" #include "Engine.h" #include "AudioEngine.h" #include "AutomatableModel.h" diff --git a/src/core/PlayHandle.cpp b/src/core/PlayHandle.cpp index 134fcd31100..7eb6dbf01d3 100644 --- a/src/core/PlayHandle.cpp +++ b/src/core/PlayHandle.cpp @@ -22,10 +22,10 @@ * */ +#include "BufferPool.h" #include "PlayHandle.h" -#include "AudioEngine.h" -#include "BufferManager.h" #include "Engine.h" +#include "AudioEngine.h" #include @@ -37,7 +37,7 @@ PlayHandle::PlayHandle(const Type type, f_cnt_t offset) : m_type(type), m_offset(offset), m_affinity(QThread::currentThread()), - m_playHandleBuffer(BufferManager::acquire()), + m_playHandleBuffer(BufferPool::acquire()), m_bufferReleased(true), m_usesBuffer(true) { @@ -46,7 +46,7 @@ PlayHandle::PlayHandle(const Type type, f_cnt_t offset) : PlayHandle::~PlayHandle() { - BufferManager::release(m_playHandleBuffer); + BufferPool::release(m_playHandleBuffer); } diff --git a/src/core/PresetPreviewPlayHandle.cpp b/src/core/PresetPreviewPlayHandle.cpp index e7e67185ed2..596f27518c5 100644 --- a/src/core/PresetPreviewPlayHandle.cpp +++ b/src/core/PresetPreviewPlayHandle.cpp @@ -173,7 +173,7 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file, Engine::audioEngine()->requestChangeInModel(); // create note-play-handle for it - m_previewNote = NotePlayHandleManager::acquire( + m_previewNote = NotePlayHandlePool.construct( s_previewTC->previewInstrumentTrack(), 0, std::numeric_limits::max() / 2, Note( 0, 0, DefaultKey, 100 ) ); diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index dc26bf2b554..7c61cd90f49 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -33,7 +33,6 @@ #include #endif -#include "BufferManager.h" #include "AudioEngine.h" #include "Engine.h" #include "Song.h" diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 8efdd2c11a7..5dcd074a4d0 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -29,7 +29,7 @@ #include "Mixer.h" #include "Engine.h" #include "MixHelpers.h" -#include "BufferManager.h" +#include "BufferPool.h" namespace lmms { @@ -38,7 +38,7 @@ AudioPort::AudioPort( const QString & _name, bool _has_effect_chain, FloatModel * volumeModel, FloatModel * panningModel, BoolModel * mutedModel ) : m_bufferUsage( false ), - m_portBuffer( BufferManager::acquire() ), + m_portBuffer( BufferPool::acquire() ), m_extOutputEnabled( false ), m_nextMixerChannel( 0 ), m_name( "unnamed port" ), @@ -58,7 +58,7 @@ AudioPort::~AudioPort() { setExtOutputEnabled( false ); Engine::audioEngine()->removeAudioPort( this ); - BufferManager::release( m_portBuffer ); + BufferPool::release( m_portBuffer ); } diff --git a/src/core/libcds.cpp b/src/core/libcds.cpp new file mode 100644 index 00000000000..c019db334d8 --- /dev/null +++ b/src/core/libcds.cpp @@ -0,0 +1,34 @@ +#include "libcds.h" + +#include + +#include + +namespace lmms::_cdslib +{ + +void init() +{ + cds::Initialize(); +} + +void deinit() +{ + cds::Terminate(); +} + +void thread_init() +{ + if (! cds::threading::Manager::isThreadAttached()) { + cds::threading::Manager::attachThread(); + } +} + +void thread_deinit() +{ + if (cds::threading::Manager::isThreadAttached()) { + cds::threading::Manager::detachThread(); + } +} + +} // namespace lmms::_cdslib diff --git a/src/core/main.cpp b/src/core/main.cpp index 3e6c2c85f06..7f9664f4d5f 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -353,9 +353,6 @@ int main( int argc, char * * argv ) } #endif - // initialize memory managers - NotePlayHandleManager::init(); - // intialize RNG srand( getpid() + time( 0 ) ); @@ -984,8 +981,5 @@ int main( int argc, char * * argv ) } #endif - - NotePlayHandleManager::free(); - return ret; } diff --git a/src/gui/widgets/Oscilloscope.cpp b/src/gui/widgets/Oscilloscope.cpp index 28ddff93841..0d37c53abf6 100644 --- a/src/gui/widgets/Oscilloscope.cpp +++ b/src/gui/widgets/Oscilloscope.cpp @@ -34,7 +34,6 @@ #include "Engine.h" #include "Song.h" #include "embed.h" -#include "BufferManager.h" namespace lmms::gui { diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 66992bc4f6d..f82ac4610ca 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -340,7 +340,7 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim if (m_notes[event.key()] == nullptr && event.key() >= firstKey() && event.key() <= lastKey()) { NotePlayHandle* nph = - NotePlayHandleManager::acquire( + NotePlayHandlePool.construct( this, offset, std::numeric_limits::max() / 2, Note(TimePos(), Engine::getSong()->getPlayPos(Engine::getSong()->playMode()), @@ -781,7 +781,7 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, ? 0 : currentNote->length().frames(frames_per_tick); - NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire(this, _offset, noteFrames, *currentNote); + NotePlayHandle* notePlayHandle = NotePlayHandlePool.construct(this, _offset, noteFrames, *currentNote); notePlayHandle->setPatternTrack(pattern_track); // are we playing global song? if( _clip_num < 0 ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 625601a3ebb..12c51d82473 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,6 +9,8 @@ set(LMMS_TESTS src/core/MathTest.cpp src/core/ProjectVersionTest.cpp src/core/RelativePathsTest.cpp + src/core/MemoryPoolTest.cpp + src/tracks/AutomationTrackTest.cpp ) diff --git a/tests/scripted/check-namespace b/tests/scripted/check-namespace index a816f0d7bd5..3394e583857 100755 --- a/tests/scripted/check-namespace +++ b/tests/scripted/check-namespace @@ -55,7 +55,7 @@ if not Path('.gitmodules').is_file(): print('You need to call this script from the LMMS top directory') exit(1) -result = subprocess.run(['git', 'ls-files', '*.[ch]', '*.[ch]pp', ':!tests/*'], +result = subprocess.run(['git', 'ls-files', '*.[ch]', '*.[ch]pp', ':!tests/*', ':!benchmarks/*'], capture_output=True, text=True, check=True) known_no_namespace_lmms = { diff --git a/tests/src/core/MemoryPoolTest.cpp b/tests/src/core/MemoryPoolTest.cpp new file mode 100644 index 00000000000..dcf964a5aa7 --- /dev/null +++ b/tests/src/core/MemoryPoolTest.cpp @@ -0,0 +1,62 @@ +/* + * MemoryPoolTest.cpp + * + * Copyright (c) 2018 Lukas W + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include "MemoryPool.h" + +#include +#include + +using namespace lmms; + +class MemoryPoolTest : public QObject +{ + Q_OBJECT +private slots: + void MemoryPoolTests() + { + using T = std::array; + int n = 256; + MemoryPool pool(n); + + std::stack ptrs; + + for (int i=0; i < n; i++) { + ptrs.push(pool.allocate_bounded()); + QVERIFY(ptrs.top()); + } + QCOMPARE(pool.allocate_bounded(), static_cast(nullptr)); + ptrs.push(pool.allocate()); + QVERIFY(ptrs.top()); + + while (!ptrs.empty()) { + pool.deallocate(ptrs.top()); + ptrs.pop(); + } + } +}; + +QTEST_GUILESS_MAIN(MemoryPoolTest) + +#include "MemoryPoolTest.moc"