Compare commits

...

32 Commits

Author SHA1 Message Date
Hyunjin Song
f54ac456dd Remove leftover of mingw-std-threads 2024-10-03 20:34:08 +09:00
Hyunjin Song
7782954e6e Fix namespace 2024-10-03 20:33:08 +09:00
Hyunjin Song
7ce62fe0cb Always build libcds as a static library 2024-10-03 19:48:14 +09:00
Hyunjin Song
47e6a3090a Merge branch 'master' into refac/memory 2024-10-03 17:05:29 +09:00
Hyunjin Song
ac5b3f07d7 Revert irrelevant changes 2024-09-21 21:58:05 +09:00
Lukas W
9df27e9e8e Merge branch 'master' into refac/memory 2020-12-11 09:47:41 +01:00
Lukas W
846ca178f5 CMake: Build lmms shared library instead of object library
Resolves some inexplicable linking errors on Windows. Saves us from
working around incomplete CMake support of object libraries.
2020-05-22 17:23:17 +02:00
Lukas W
ac0081de10 Fix crash on exit with MSVC 2020-05-14 10:14:16 +02:00
Lukas W
74ee6354e6 Merge remote-tracking branch 'origin/master' into refac/memory 2020-05-12 13:54:19 +02:00
Lukas W
a64f83e0ed Fix libcds MSVC compilation
* Update to include 4227281c367a0dd36765bba58bd9bb6f43ceb88b fixing
  https://github.com/khizmax/libcds/issues/124
* Make CDS_BUILD_STATIC_LIB compile definition public to avoid dllimport
  attribute in defs.h
* Make dependencies' defintions actually propagate to lmmsobjs
2020-05-12 13:39:28 +02:00
Lukas W
5ae42cafc4 Try to fix libcds counters issues
* For unknown reasons, _cdslib::_thread_counter is never initialized,
  trigger initialization manually by referencing during MemoryPool's
  construction.
* CircleCI linux.gcc test crashes on exit with an "invalid delete" at
  hpGC.reset() in libcds.cpp. I don't know what causes this, but it
  turns out we don't need garbage collection currently (we only use
  VyukovMPMCCycleQueue), so we can just remove the garbage collection
  for now.
2020-05-11 19:17:24 +02:00
Lukas W
78c92e8314 Tests: Allow specifying test suit name 2020-05-11 15:01:43 +02:00
Lukas W
29df871800 Add nifty counter instance to MmAllocaator
Prevents exit crashes in rare cases e.g. when Engine is not destroyed
2020-05-11 15:01:43 +02:00
Lukas W
1cd8b7a895 Fix libcds nifty counter typo
Thanks @PhysSong

Co-authored-by: Hyunjin Song <tteu.ingog@gmail.com>
2020-05-11 14:55:29 +02:00
Lukas W
62606b64fe Try to fix CircleCI linux build 2020-05-06 12:38:12 +02:00
Lukas W
68d7157c8e Merge branch 'master' into refac/memory 2020-05-04 20:28:32 +02:00
Lukas W
e21c00e9b7 Apply suggestions by @PhysSong 2020-05-04 18:00:47 +02:00
Lukas W
bd1ee2983b Silence int conversion warning 2019-08-27 14:33:36 +02:00
Lukas W
d35df8ee7b Move BufferPool::clear to MixHelpers and rewrite
* Move out of BufferPool because it's not related to memory allocation
* Rewrite because memset() is not portable because it sets all bytes to
  zero which depends on the platform's float representation of 0.0 to be
  all zero bytes
2019-08-27 14:33:36 +02:00
Lukas W
5349be6a63 NiftyCounter: Fix decrement 2019-08-25 23:22:06 +02:00
Lukas W
71e9b45446 Memory: Fix wrong rebind 2019-08-25 23:22:06 +02:00
Lukas W
119efeec01 Fix tests crash due to incomplete cleanup 2019-08-25 23:22:06 +02:00
Lukas W
2f8e231b8c Fix macOS linking problem 2019-08-25 23:22:06 +02:00
Lukas W
8b122d5a4c Fix libcds on MinGW 2019-08-25 23:21:53 +02:00
Lukas W
ef7b8c68d5 Add naive benchmarks 2019-08-25 22:22:21 +02:00
Lukas W
2cb4455a5f Replace AllignedAllocator implementation with rpmalloc calls 2019-08-25 22:22:21 +02:00
Lukas W
178888af94 BufferManager: Use MemoryPool, rename to BufferPool 2019-08-25 22:22:21 +02:00
Lukas W
3e1a96693d Replace NotePlayHandleManager implementation with MemoryPool 2019-08-25 22:22:21 +02:00
Lukas W
1cd8e15942 Replace LocklessAllocator with new MemoryPool class
MemoryPool maintains a free list of pre-allocated entries stored in a
libcds MPMC Queue. In contrast to LocklessAllocator, it's a lot faster,
less (and less complex) code to maintain for us and it doesn't fail when
it's full.
2019-08-25 22:19:48 +02:00
Lukas W
1340c273c7 Memory.h fixes
* Use a Nifty Counter for rpmalloc initialization. This fixes init order
  with other static objects using MmAllocator or MemoryManagger
* Fix exception throwing in AlignedAllocator
* Add missing constructor to MmAllocator for compatibility with std
  containers
2019-08-25 22:18:47 +02:00
Lukas W
3c73270c23 Rename MemoryManager.h to Memory.h 2019-08-25 22:18:47 +02:00
Lukas W
2a08909380 Move aligned malloc to MemoryManager.h, port to stl allocator 2019-08-25 22:17:37 +02:00
36 changed files with 638 additions and 416 deletions

3
.gitmodules vendored
View File

@@ -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

View File

@@ -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)

18
LICENSE.MIT.txt Normal file
View File

@@ -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.

13
benchmarks/CMakeLists.txt Normal file
View File

@@ -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_PROPERTY:lmmsobjs,INCLUDE_DIRECTORIES>)
target_static_libraries(benchmarks PRIVATE lmmsobjs)
target_compile_features(benchmarks PRIVATE cxx_std_17)

130
benchmarks/benchmark.cpp Normal file
View File

@@ -0,0 +1,130 @@
#include <thread>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include "libcds.h"
#include <cds/container/vyukov_mpmc_cycle_queue.h>
#include "Engine.h"
#include "PerfLog.h"
#include "LocklessList.h"
#include "MemoryPool.h"
#include "NotePlayHandle.h"
using namespace lmms;
template<typename T>
using LocklessQueue = cds::container::VyukovMPMCCycleQueue<T>;
template<typename Alloc>
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<T*> 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<typename Alloc>
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<T*> 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<std::thread> 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>;
{
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<std::thread> 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<char, S>;
benchmark_allocator("std::allocator", std::allocator<T>{}, n, 1);
benchmark_allocator("MemoryPool", MemoryPool<T>{n}, n, 1);
}
{
size_t n = 10 * 1000 * 1000;
constexpr size_t S = 256;
using T = std::array<char, S>;
benchmark_allocator_threaded("std::allocator", std::allocator<T>{}, n, 4);
benchmark_allocator_threaded("MemoryPool", MemoryPool<T>{n}, n, 4);
}
}

View File

@@ -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 "")

View File

@@ -1,5 +1,5 @@
/*
* BufferManager.h - A buffer caching/memory management system
* BufferPool.h
*
* Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
* Copyright (c) 2006-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
@@ -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 @@ private:
} // namespace lmms
#endif // LMMS_BUFFER_MANAGER_H

View File

@@ -1,87 +0,0 @@
/*
* LocklessAllocator.h - allocator with lockless alloc and free
*
* Copyright (c) 2016 Javier Serrano Polo <javier@jasp.net>
*
* 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 <atomic>
#include <cstddef>
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<typename T>
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

View File

@@ -25,7 +25,7 @@
#ifndef LMMS_LOCKLESS_LIST_H
#define LMMS_LOCKLESS_LIST_H
#include "LocklessAllocator.h"
#include "MemoryPool.h"
#include <atomic>
@@ -44,7 +44,7 @@ public:
LocklessList( size_t size ) :
m_first(nullptr),
m_allocator(new LocklessAllocatorT<Element>(size))
m_allocator(new MemoryPool<Element>(size))
{
}
@@ -55,7 +55,7 @@ public:
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 @@ public:
void free( Element * e )
{
m_allocator->free( e );
m_allocator->destroy( e );
}
private:
std::atomic<Element*> m_first;
LocklessAllocatorT<Element> * m_allocator;
MemoryPool<Element> * m_allocator;
} ;

88
include/MemoryPool.h Normal file
View File

@@ -0,0 +1,88 @@
/*
* MemoryPool.h
*
* Copyright (c) 2018 Lukas W <lukaswhl/at/gmail.com>
*
* 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 <cstddef>
#include <memory>
#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<typename T>
class MemoryPool : private _MemoryPool_Base
{
public:
using value_type = T;
template<class U> struct rebind { typedef MemoryPool<U> 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<T*>(_MemoryPool_Base::allocate());
}
T * allocate_bounded()
{
return reinterpret_cast<T*>(_MemoryPool_Base::allocate_bounded());
}
void deallocate(T * ptr, size_t n = 1)
{
_MemoryPool_Base::deallocate(ptr);
}
template<class... Args>
T * construct(Args&&... args)
{
T* buffer = allocate();
return ::new ((void*)buffer) T(std::forward<Args>(args)...);
}
template<class... Args >
T * construct_bounded(Args&&... args)
{
T* buffer = allocate_bounded();
if (buffer) {
::new ((void*)buffer) T(std::forward<Args>(args)...);
}
return buffer;
}
void destroy(T* ptr)
{
ptr->~T();
deallocate(ptr);
}
} ;
} // namespace lmms

65
include/NiftyCounter.h Normal file
View File

@@ -0,0 +1,65 @@
/*
* NiftyCounter.h
*
* Copyright (c) 2018 Lukas W <lukaswhl/at/gmail.com>
*
* 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 <typename T> 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 <typename T, void C(), void D()> class _NiftyCounterCD_Base : public _NiftyCounter_Base<T>
{
friend class _NiftyCounter_Base<T>;
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 <void C(), void D()> class NiftyCounter : public _NiftyCounterCD_Base<NiftyCounter<C,D>, C,D>
{
friend class _NiftyCounterCD_Base<NiftyCounter<C,D>, C,D>;
private:
static int s_count;
};
template <void C(), void D()> int NiftyCounter<C, D>::s_count = 0;
/// Thread-local version of NiftyCounter
template <void C(), void D()> class NiftyCounterTL : public _NiftyCounterCD_Base<NiftyCounterTL<C,D>, C,D>
{
friend class _NiftyCounterCD_Base<NiftyCounterTL<C,D>, C,D>;
private:
thread_local static int s_count;
};
template <void C(), void D()> thread_local int NiftyCounterTL<C, D>::s_count = 0;
} // namespace lmms

View File

@@ -32,6 +32,7 @@
#include "Note.h"
#include "PlayHandle.h"
#include "Track.h"
#include "MemoryPool.h"
class QReadWriteLock;
@@ -332,32 +333,7 @@ private:
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<NotePlayHandle> NotePlayHandlePool;
} // namespace lmms

18
include/libcds.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <thread>
#include "NiftyCounter.h"
namespace lmms::_cdslib
{
void init();
void deinit();
void thread_init();
void thread_deinit();
static NiftyCounter<init, deinit> _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;

View File

@@ -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

1
src/3rdparty/libcds vendored Submodule

Submodule src/3rdparty/libcds added at 44c052bdb6

View File

@@ -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

View File

@@ -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<SampleFrame[]>(m_framesPerPeriod);
m_outputBufferWrite = std::make_unique<SampleFrame[]>(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<NotePlayHandle*>(ph));
NotePlayHandlePool.destroy(dynamic_cast<NotePlayHandle*>(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 );

View File

@@ -1,7 +1,7 @@
/*
* BufferManager.cpp - A buffer caching/memory management system
* BufferPool.cpp
*
* Copyright (c) 2017 Lukas W <lukaswhl/at/gmail.com>
* Copyright (c) 2018 Lukas W <lukaswhl/at/gmail.com>
* Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
* Copyright (c) 2006-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
@@ -24,34 +24,32 @@
*
*/
#include "BufferManager.h"
#include "BufferPool.h"
#include "SampleFrame.h"
#include <cstring>
#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<SampleFrame*>(pool->allocate());
}
void BufferManager::release( SampleFrame* buf )
void BufferPool::release( SampleFrame * buf )
{
delete[] buf;
pool->deallocate(buf);
}
} // namespace lmms

View File

@@ -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
)

View File

@@ -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(),

View File

@@ -1,169 +0,0 @@
/*
* LocklessAllocator.cpp - allocator with lockless alloc and free
*
* Copyright (c) 2016 Javier Serrano Polo <javier@jasp.net>
*
* 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 <algorithm>
#include <cstdio>
#include "lmmsconfig.h"
#ifndef LMMS_BUILD_WIN32
#include <strings.h>
#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

125
src/core/MemoryPool.cpp Normal file
View File

@@ -0,0 +1,125 @@
/*
* MemoryPool.cpp
*
* Copyright (c) 2018 Lukas W <lukaswhl/at/gmail.com>
*
* 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 <QtCore/QDebug>
#include "libcds.h"
#include <cds/container/vyukov_mpmc_cycle_queue.h>
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<char*>(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<char*>(ptr);
}
bool is_from_pool(void* ptr)
{
auto buff = reinterpret_cast<uintptr_t>(m_buffer);
auto p = reinterpret_cast<uintptr_t>(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<char*> 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

View File

@@ -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"

View File

@@ -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<NotePlayHandle *>(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<NotePlayHandle *>(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<NotePlayHandle> NotePlayHandlePool{INITIAL_NPH_CACHE};
} // namespace lmms

View File

@@ -30,7 +30,6 @@
#include <thread>
#endif
#include "BufferManager.h"
#include "Engine.h"
#include "AudioEngine.h"
#include "AutomatableModel.h"

View File

@@ -22,10 +22,10 @@
*
*/
#include "BufferPool.h"
#include "PlayHandle.h"
#include "AudioEngine.h"
#include "BufferManager.h"
#include "Engine.h"
#include "AudioEngine.h"
#include <QThread>
@@ -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);
}

View File

@@ -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<f_cnt_t>::max() / 2,
Note( 0, 0, DefaultKey, 100 ) );

View File

@@ -33,7 +33,6 @@
#include <windows.h>
#endif
#include "BufferManager.h"
#include "AudioEngine.h"
#include "Engine.h"
#include "Song.h"

View File

@@ -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 );
}

34
src/core/libcds.cpp Normal file
View File

@@ -0,0 +1,34 @@
#include "libcds.h"
#include <cds/init.h>
#include <memory>
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

View File

@@ -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;
}

View File

@@ -34,7 +34,6 @@
#include "Engine.h"
#include "Song.h"
#include "embed.h"
#include "BufferManager.h"
namespace lmms::gui
{

View File

@@ -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<f_cnt_t>::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 )

View File

@@ -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
)

View File

@@ -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 = {

View File

@@ -0,0 +1,62 @@
/*
* MemoryPoolTest.cpp
*
* Copyright (c) 2018 Lukas W <lukaswhl/at/gmail.com>
*
* 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 <QtTest/QtTest>
#include "MemoryPool.h"
#include <array>
#include <stack>
using namespace lmms;
class MemoryPoolTest : public QObject
{
Q_OBJECT
private slots:
void MemoryPoolTests()
{
using T = std::array<char, 16>;
int n = 256;
MemoryPool<T> pool(n);
std::stack<T*> ptrs;
for (int i=0; i < n; i++) {
ptrs.push(pool.allocate_bounded());
QVERIFY(ptrs.top());
}
QCOMPARE(pool.allocate_bounded(), static_cast<T*>(nullptr));
ptrs.push(pool.allocate());
QVERIFY(ptrs.top());
while (!ptrs.empty()) {
pool.deallocate(ptrs.top());
ptrs.pop();
}
}
};
QTEST_GUILESS_MAIN(MemoryPoolTest)
#include "MemoryPoolTest.moc"