Merge branch 'bugfix/counting-buffers' into develop

This commit is contained in:
Paul Ferrand 2019-12-02 12:35:28 +01:00
commit 3056f20a35
8 changed files with 170 additions and 44 deletions

View file

@ -273,8 +273,11 @@ int main(int argc, char** argv)
signal(SIGQUIT, done); signal(SIGQUIT, done);
while (!shouldClose){ while (!shouldClose){
// synth.garbageCollect(); #ifndef NDEBUG
std::this_thread::sleep_for(1s); std::cout << "Allocated buffers: " << synth.getAllocatedBuffers() << '\n';
std::cout << "Total size: " << synth.getAllocatedBytes() << '\n';
#endif
std::this_thread::sleep_for(2s);
} }
std::cout << "Closing..." << '\n'; std::cout << "Closing..." << '\n';

View file

@ -63,6 +63,7 @@
#define DEFAULT_VOICES 64 #define DEFAULT_VOICES 64
#define DEFAULT_OVERSAMPLING SFIZZ_OVERSAMPLING_X1 #define DEFAULT_OVERSAMPLING SFIZZ_OVERSAMPLING_X1
#define DEFAULT_PRELOAD 8192 #define DEFAULT_PRELOAD 8192
#define LOG_SAMPLE_COUNT 96000
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
typedef struct typedef struct
@ -121,6 +122,7 @@ typedef struct
sfizz_oversampling_factor_t oversampling; sfizz_oversampling_factor_t oversampling;
bool changing_state; bool changing_state;
int max_block_size; int max_block_size;
int sample_counter;
float sample_rate; float sample_rate;
} sfizz_plugin_t; } sfizz_plugin_t;
@ -226,6 +228,7 @@ instantiate(const LV2_Descriptor *descriptor,
self->oversampling = DEFAULT_OVERSAMPLING; self->oversampling = DEFAULT_OVERSAMPLING;
self->preload_size = DEFAULT_PRELOAD; self->preload_size = DEFAULT_PRELOAD;
self->changing_state = false; self->changing_state = false;
self->sample_counter = 0;
// Get the features from the host and populate the structure // Get the features from the host and populate the structure
for (const LV2_Feature *const *f = features; *f; f++) for (const LV2_Feature *const *f = features; *f; f++)
@ -575,6 +578,17 @@ run(LV2_Handle instance, uint32_t sample_count)
} }
} }
#ifndef NDEBUG
// Log the buffer usage
self->sample_counter += (int)sample_count;
if (self->sample_counter > LOG_SAMPLE_COUNT)
{
lv2_log_note(&self->logger, "[run] Allocated buffers: %d\n", sfizz_get_num_buffers(self->synth));
lv2_log_note(&self->logger, "[run] Allocated bytes: %d\n", sfizz_get_num_bytes(self->synth));
self->sample_counter -= LOG_SAMPLE_COUNT;
}
#endif
// Render the block // Render the block
sfizz_render_block(self->synth, self->output_buffers, 2, (int)sample_count); sfizz_render_block(self->synth, self->output_buffers, 2, (int)sample_count);
} }

View file

@ -305,6 +305,26 @@ void sfizz_set_num_voices(sfizz_synth_t* synth, int num_voices);
*/ */
int sfizz_get_num_voices(sfizz_synth_t* synth); int sfizz_get_num_voices(sfizz_synth_t* synth);
/**
* @brief Get the number of allocated buffers from the synth.
*
* @param synth The synth
*
* @return The number of buffers held by the synth
*/
int sfizz_get_num_buffers(sfizz_synth_t* synth);
/**
* @brief Get the number of bytes allocated from the synth. Note that this
* value can be less than the actual memory usage since it only
* counts the buffer objects managed by sfizz.
*
* @param synth The synth
*
* @return The number of bytes held by the synth in buffers;
*/
int sfizz_get_num_bytes(sfizz_synth_t* synth);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -29,20 +29,65 @@
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#ifdef DEBUG
#include <iostream>
#endif
namespace sfz namespace sfz
{ {
/** /**
* @brief A heap buffer structure that tries to align its beginning and adds a small offset * @brief A buffer counting class that tries to track the memory usage.
* at the end for alignment too. */
class BufferCounter
{
public:
BufferCounter() = default;
~BufferCounter()
{
#ifndef NDEBUG
std::cout << "Remaining buffers on destruction: " << numBuffers << '\n';
std::cout << "Total size: " << bytes << '\n';
#endif
}
void newBuffer(int size)
{
numBuffers++;
bytes.fetch_add(size);
}
void bufferResized(int oldSize, int newSize)
{
bytes.fetch_add(newSize);
bytes.fetch_sub(oldSize);
}
void bufferDeleted(int size)
{
numBuffers--;
bytes.fetch_sub(size);
}
int getNumBuffers() { return numBuffers; }
int getTotalBytes() { return bytes; }
private:
std::atomic<int> numBuffers { 0 };
std::atomic<int> bytes { 0 };
};
/**
* @brief A heap buffer structure that tries to align its beginning and
* adds a small offset at the end for alignment too.
* *
* Apparently on Linux this effort is mostly useless, and in the end most of the SIMD operations * Apparently on Linux this effort is mostly useless, and in the end
* are coded with alignment checks and sentinels so this class could probably be much simpler. * most of the SIMD operations are coded with alignment checks and
* It does however wrap realloc which in some cases should be a bit more efficient than * sentinels so this class could probably be much simpler. It does
* allocating a whole new block. * however wrap realloc which in some cases should be a bit more
* efficient than allocating a whole new block.
* *
* @tparam Type The buffer type * @tparam Type The buffer type
* @tparam Alignment the required alignment in bytes (defaults to SIMDConfig::defaultAlignment) * @tparam Alignment the required alignment in bytes (defaults to
* SIMDConfig::defaultAlignment)
*/ */
template <class Type, unsigned int Alignment = SIMDConfig::defaultAlignment> template <class Type, unsigned int Alignment = SIMDConfig::defaultAlignment>
class Buffer { class Buffer {
@ -64,7 +109,7 @@ public:
*/ */
Buffer() Buffer()
{ {
counter().newBuffer(0);
} }
/** /**
@ -74,6 +119,7 @@ public:
*/ */
Buffer(size_t size) Buffer(size_t size)
{ {
counter().newBuffer(0);
resize(size); resize(size);
} }
@ -98,6 +144,7 @@ public:
return false; return false;
} }
counter().bufferResized(largerSize * sizeof(value_type), tempSize * sizeof(value_type));
largerSize = tempSize; largerSize = tempSize;
alignedSize = newSize; alignedSize = newSize;
paddedData = static_cast<pointer>(newData); paddedData = static_cast<pointer>(newData);
@ -118,6 +165,7 @@ public:
*/ */
void clear() void clear()
{ {
counter().bufferResized(largerSize * sizeof(value_type), 0);
largerSize = 0; largerSize = 0;
alignedSize = 0; alignedSize = 0;
std::free(paddedData); std::free(paddedData);
@ -127,6 +175,7 @@ public:
} }
~Buffer() ~Buffer()
{ {
counter().bufferDeleted(largerSize * sizeof(value_type));
std::free(paddedData); std::free(paddedData);
} }
@ -147,18 +196,19 @@ public:
* *
* @param other * @param other
*/ */
Buffer(Buffer<Type>&& other) Buffer(Buffer<Type>&& other) = delete;
{ // {
if (this != &other) { // if (this != &other) {
std::free(paddedData); // counter().bufferDeleted(largerSize * sizeof(value_type));
largerSize = std::exchange(other.largerSize, 0); // std::free(paddedData);
alignedSize = std::exchange(other.alignedSize, 0); // largerSize = std::exchange(other.largerSize, 0);
paddedData = std::exchange(other.paddedData, nullptr); // alignedSize = std::exchange(other.alignedSize, 0);
normalData = std::exchange(other.normalData, nullptr); // paddedData = std::exchange(other.paddedData, nullptr);
normalEnd = std::exchange(other.normalEnd, nullptr); // normalData = std::exchange(other.normalData, nullptr);
_alignedEnd = std::exchange(other._alignedEnd, nullptr); // normalEnd = std::exchange(other.normalEnd, nullptr);
} // _alignedEnd = std::exchange(other._alignedEnd, nullptr);
} // }
// }
Buffer<Type>& operator=(const Buffer<Type>& other) Buffer<Type>& operator=(const Buffer<Type>& other)
{ {
@ -169,19 +219,20 @@ public:
return *this; return *this;
} }
Buffer<Type>& operator=(Buffer<Type>&& other) Buffer<Type>& operator=(Buffer<Type>&& other) = delete;
{ // {
if (this != &other) { // if (this != &other) {
std::free(paddedData); // counter().bufferDeleted(largerSize * sizeof(value_type));
largerSize = std::exchange(other.largerSize, 0); // std::free(paddedData);
alignedSize = std::exchange(other.alignedSize, 0); // largerSize = std::exchange(other.largerSize, 0);
paddedData = std::exchange(other.paddedData, nullptr); // alignedSize = std::exchange(other.alignedSize, 0);
normalData = std::exchange(other.normalData, nullptr); // paddedData = std::exchange(other.paddedData, nullptr);
normalEnd = std::exchange(other.normalEnd, nullptr); // normalData = std::exchange(other.normalData, nullptr);
_alignedEnd = std::exchange(other._alignedEnd, nullptr); // normalEnd = std::exchange(other.normalEnd, nullptr);
} // _alignedEnd = std::exchange(other._alignedEnd, nullptr);
return *this; // }
} // return *this;
// }
Type& operator[](int idx) { return *(normalData + idx); } Type& operator[](int idx) { return *(normalData + idx); }
constexpr pointer data() const noexcept { return normalData; } constexpr pointer data() const noexcept { return normalData; }
@ -192,6 +243,18 @@ public:
constexpr pointer alignedEnd() noexcept { return _alignedEnd; } constexpr pointer alignedEnd() noexcept { return _alignedEnd; }
/**
* @brief Return the buffer counter object.
*
* TODO: In C++ 17 all of this can be static inline.
*
* @return The buffer counter on which you can call various methods.
*/
static BufferCounter& counter()
{
static BufferCounter counter;
return counter;
}
private: private:
static constexpr auto AlignmentMask { Alignment - 1 }; static constexpr auto AlignmentMask { Alignment - 1 };
static constexpr auto TypeAlignment { Alignment / sizeof(value_type) }; static constexpr auto TypeAlignment { Alignment / sizeof(value_type) };

View file

@ -173,7 +173,7 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept
{ {
// Update all the preloaded sizes // Update all the preloaded sizes
for (auto& preloadedFile : preloadedFiles) { for (auto& preloadedFile : preloadedFiles) {
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames(); const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / oversamplingFactor;
const uint32_t maxOffset = numFrames > this->preloadSize ? numFrames - this->preloadSize : 0; const uint32_t maxOffset = numFrames > this->preloadSize ? numFrames - this->preloadSize : 0;
fs::path file { rootDirectory / std::string(preloadedFile.first) }; fs::path file { rootDirectory / std::string(preloadedFile.first) };
SndfileHandle sndFile(reinterpret_cast<const char*>(file.c_str())); SndfileHandle sndFile(reinterpret_cast<const char*>(file.c_str()));
@ -246,7 +246,7 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept
{ {
float samplerateChange { static_cast<float>(factor) / static_cast<float>(this->oversamplingFactor) }; float samplerateChange { static_cast<float>(factor) / static_cast<float>(this->oversamplingFactor) };
for (auto& preloadedFile : preloadedFiles) { for (auto& preloadedFile : preloadedFiles) {
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames(); const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / this->oversamplingFactor;
const uint32_t maxOffset = numFrames > this->preloadSize ? numFrames - this->preloadSize : 0; const uint32_t maxOffset = numFrames > this->preloadSize ? numFrames - this->preloadSize : 0;
fs::path file { rootDirectory / std::string(preloadedFile.first) }; fs::path file { rootDirectory / std::string(preloadedFile.first) };
SndfileHandle sndFile(reinterpret_cast<const char*>(file.c_str())); SndfileHandle sndFile(reinterpret_cast<const char*>(file.c_str()));

View file

@ -310,6 +310,20 @@ public:
* @return Oversampling * @return Oversampling
*/ */
uint32_t getPreloadSize() const noexcept; uint32_t getPreloadSize() const noexcept;
/**
* @brief Gets the number of allocated buffers.
*
* @return The allocated buffers.
*/
int getAllocatedBuffers() const noexcept { return Buffer<float>::counter().getNumBuffers(); }
/**
* @brief Gets the number of bytes allocated through the buffers
*
* @return The allocated bytes.
*/
int getAllocatedBytes() const noexcept { return Buffer<float>::counter().getTotalBytes(); }
protected: protected:
/** /**
* @brief The parser callback; this is called by the parent object each time * @brief The parser callback; this is called by the parent object each time

View file

@ -192,6 +192,18 @@ int sfizz_get_num_voices(sfizz_synth_t* synth)
return self->getNumVoices(); return self->getNumVoices();
} }
int sfizz_get_num_buffers(sfizz_synth_t* synth)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
return self->getAllocatedBuffers();
}
int sfizz_get_num_bytes(sfizz_synth_t* synth)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
return self->getAllocatedBytes();
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -157,8 +157,8 @@ TEST_CASE("[Buffer] Copy and move")
checkBoundaries(copyConstructed, baseSize); checkBoundaries(copyConstructed, baseSize);
REQUIRE(std::all_of(copyConstructed.begin(), copyConstructed.end(), [](auto value) { return value == 1.0f; })); REQUIRE(std::all_of(copyConstructed.begin(), copyConstructed.end(), [](auto value) { return value == 1.0f; }));
sfz::Buffer<float> moveConstructed { std::move(buffer) }; // sfz::Buffer<float> moveConstructed { std::move(buffer) };
REQUIRE(buffer.empty()); // REQUIRE(buffer.empty());
checkBoundaries(moveConstructed, baseSize); // checkBoundaries(moveConstructed, baseSize);
REQUIRE(std::all_of(moveConstructed.begin(), moveConstructed.end(), [](auto value) { return value == 1.0f; })); // REQUIRE(std::all_of(moveConstructed.begin(), moveConstructed.end(), [](auto value) { return value == 1.0f; }));
} }