Skip to content

Commit

Permalink
Refactor use of strings in the engine. 10-20% callback speed improvem…
Browse files Browse the repository at this point in the history
…ent.

* Add ChannelHandle, a wrapper class for an integer handle that starts
  at 0 and increments.
* Add ChannelHandleFactory for assigning integer handles to group
  strings.
* Add ChannelHandleMap, an associative container mapping ChannelHandle
  to a template data type T. Backed by a QVarLengthArray with a
  pre-allocation of 256. Supports fast O(1) lookups (simple memory
  indexing) and amortized O(1) inserts.
* Re-factor engine and effects code to use ChannelHandle.
* Update EffectProcessor to use ChannelHandleMap instead of QHash.
* Update EngineEffectChain to use ChannelHandleMap instead of QLinkedList.

I did two tests -- one with effects inactive and one with effects
active. I measured total-callback processing time improvements of 20%
(1 track, no effects) and 13% (2 tracks, effects enabled). From this I
conclude that the QHash and QLinkedList introduced tons of wasted
cycles (which agrees with my profiling results from a month or two
ago).

Test 1:
* No effects active
* optimize=portable
* single track playing for 1 minute
* began sampling 20 seconds in to get rid of the load-track jitters

Base:
Debug [Main]: Stat("SoundDevicePortAudio::callbackProcess prepare 1, Built-in Output","count=45014,sum=3.82151e+09ns,average=84896ns,min=26393ns,max=429257ns,variance=9.41766e+08ns^2,stddev=30688.2ns")

Experiment:
Debug [Main]: Stat("SoundDevicePortAudio::callbackProcess prepare 1, Built-in Output","count=44856,sum=3.02069e+09ns,average=67342ns,min=20400ns,max=323169ns,variance=5.93287e+08ns^2,stddev=24357.5ns")

Results:
Minimum: reduced by 22.7%
Maximum: reduced by 24.7%
Average: reduced by 20.7%
StdDev: reduced by 20.6%

Test 2:
* One effect active on [Master].
* One effect active on [Channel1]
* optimize=portable
* two tracks playing for 1 minute
* began sampling 20 seconds in to get rid of the load-track jitters

Base:
Debug [Main]: Stat("SoundDevicePortAudio::callbackProcess prepare 1, Built-in Output","count=44937,sum=4.76277e+09ns,average=105988ns,min=38434ns,max=326061ns,variance=1.49815e+09ns^2,stddev=38706ns")

Experiment:
Debug [Main]: Stat("SoundDevicePortAudio::callbackProcess prepare 1, Built-in Output","count=45191,sum=4.15799e+09ns,average=92009.3ns,min=31693ns,max=333664ns,variance=1.11131e+09ns^2,stddev=33336.3ns")

Results:
Minimum: reduced by 17.5%
Maximum: increase by 0.02%
Average: reduced by 13.2%
StdDev: reduced by 13.9%
  • Loading branch information
rryan committed Feb 24, 2015
1 parent 1f953c9 commit 7652b56
Show file tree
Hide file tree
Showing 62 changed files with 697 additions and 334 deletions.
4 changes: 3 additions & 1 deletion src/basetrackplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent,
m_pSpeed(NULL),
m_pPitch(NULL),
m_replaygainPending(false) {
m_pChannel = new EngineDeck(getGroup(), pConfig, pMixingEngine,
ChannelHandleAndGroup channelGroup =
pMixingEngine->registerChannelGroup(group);
m_pChannel = new EngineDeck(channelGroup, pConfig, pMixingEngine,
pEffectsManager, defaultOrientation);

EngineBuffer* pEngineBuffer = m_pChannel->getEngineBuffer();
Expand Down
2 changes: 1 addition & 1 deletion src/effects/effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ void Effect::addToEngine(EngineEffectChain* pChain, int iIndex) {
return;
}
m_pEngineEffect = new EngineEffect(m_manifest,
m_pEffectsManager->registeredGroups(),
m_pEffectsManager->registeredChannels(),
m_pInstantiator);
EffectsRequest* request = new EffectsRequest();
request->type = EffectsRequest::ADD_EFFECT_TO_CHAIN;
Expand Down
34 changes: 17 additions & 17 deletions src/effects/effectchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ EffectChainPointer EffectChain::clone(EffectChainPointer pChain) {
pClone->setEnabled(pChain->enabled());
pClone->setName(pChain->name());
pClone->setMix(pChain->mix());
foreach (const QString& group, pChain->enabledGroups()) {
pClone->enableForGroup(group);
foreach (const ChannelHandleAndGroup& group, pChain->enabledChannels()) {
pClone->enableForChannel(group);
}
foreach (EffectPointer pEffect, pChain->effects()) {
EffectPointer pClonedEffect = pChain->m_pEffectsManager
Expand Down Expand Up @@ -141,37 +141,37 @@ void EffectChain::setEnabled(bool enabled) {
emit(enabledChanged(enabled));
}

void EffectChain::enableForGroup(const QString& group) {
if (!m_enabledGroups.contains(group)) {
m_enabledGroups.insert(group);
void EffectChain::enableForChannel(const ChannelHandleAndGroup& group) {
if (!m_enabledChannels.contains(group)) {
m_enabledChannels.insert(group);

EffectsRequest* request = new EffectsRequest();
request->type = EffectsRequest::ENABLE_EFFECT_CHAIN_FOR_GROUP;
request->type = EffectsRequest::ENABLE_EFFECT_CHAIN_FOR_CHANNEL;
request->pTargetChain = m_pEngineEffectChain;
request->group = group;
request->channel = group.handle();
m_pEffectsManager->writeRequest(request);

emit(groupStatusChanged(group, true));
emit(channelStatusChanged(group.name(), true));
}
}

bool EffectChain::enabledForGroup(const QString& group) const {
return m_enabledGroups.contains(group);
bool EffectChain::enabledForChannel(const ChannelHandleAndGroup& group) const {
return m_enabledChannels.contains(group);
}

const QSet<QString>& EffectChain::enabledGroups() const {
return m_enabledGroups;
const QSet<ChannelHandleAndGroup>& EffectChain::enabledChannels() const {
return m_enabledChannels;
}

void EffectChain::disableForGroup(const QString& group) {
if (m_enabledGroups.remove(group)) {
void EffectChain::disableForChannel(const ChannelHandleAndGroup& group) {
if (m_enabledChannels.remove(group)) {
EffectsRequest* request = new EffectsRequest();
request->type = EffectsRequest::DISABLE_EFFECT_CHAIN_FOR_GROUP;
request->type = EffectsRequest::DISABLE_EFFECT_CHAIN_FOR_CHANNEL;
request->pTargetChain = m_pEngineEffectChain;
request->group = group;
request->channel = group.handle();
m_pEffectsManager->writeRequest(request);

emit(groupStatusChanged(group, false));
emit(channelStatusChanged(group.name(), false));
}
}

Expand Down
13 changes: 7 additions & 6 deletions src/effects/effectchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "util.h"
#include "effects/effect.h"
#include "engine/channelhandle.h"

class EffectsManager;
class EngineEffectRack;
Expand Down Expand Up @@ -37,10 +38,10 @@ class EffectChain : public QObject {
void setEnabled(bool enabled);

// Activates EffectChain processing for the provided group.
void enableForGroup(const QString& group);
bool enabledForGroup(const QString& group) const;
const QSet<QString>& enabledGroups() const;
void disableForGroup(const QString& group);
void enableForChannel(const ChannelHandleAndGroup& group);
bool enabledForChannel(const ChannelHandleAndGroup& group) const;
const QSet<ChannelHandleAndGroup>& enabledChannels() const;
void disableForChannel(const ChannelHandleAndGroup& group);

EffectChainPointer prototype() const;

Expand Down Expand Up @@ -105,7 +106,7 @@ class EffectChain : public QObject {
void enabledChanged(bool enabled);
void mixChanged(double v);
void insertionTypeChanged(EffectChain::InsertionType type);
void groupStatusChanged(const QString& group, bool enabled);
void channelStatusChanged(const QString& group, bool enabled);

private:
QString debugString() const {
Expand All @@ -124,7 +125,7 @@ class EffectChain : public QObject {
InsertionType m_insertionType;
double m_dMix;

QSet<QString> m_enabledGroups;
QSet<ChannelHandleAndGroup> m_enabledChannels;
QList<EffectPointer> m_effects;
EngineEffectChain* m_pEngineEffectChain;
bool m_bAddedToEngine;
Expand Down
12 changes: 6 additions & 6 deletions src/effects/effectchainmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ EffectChainManager::~EffectChainManager() {
//qDebug() << debugString() << "destroyed";
}

void EffectChainManager::registerGroup(const QString& group) {
if (m_registeredGroups.contains(group)) {
qWarning() << debugString() << "WARNING: Group already registered:"
<< group;
void EffectChainManager::registerChannel(const ChannelHandleAndGroup& group) {
if (m_registeredChannels.contains(group)) {
qWarning() << debugString() << "WARNING: Channel already registered:"
<< group.name();
return;
}
m_registeredGroups.insert(group);
m_registeredChannels.insert(group);

foreach (StandardEffectRackPointer pRack, m_standardEffectRacks) {
pRack->registerGroup(group);
pRack->registerChannel(group);
}
}

Expand Down
9 changes: 5 additions & 4 deletions src/effects/effectchainmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "util.h"
#include "effects/effectchain.h"
#include "effects/effectrack.h"
#include "engine/channelhandle.h"

class EffectsManager;

Expand All @@ -22,9 +23,9 @@ class EffectChainManager : public QObject {
EffectsManager* pEffectsManager);
virtual ~EffectChainManager();

void registerGroup(const QString& group);
const QSet<QString>& registeredGroups() const {
return m_registeredGroups;
void registerChannel(const ChannelHandleAndGroup& group);
const QSet<ChannelHandleAndGroup>& registeredChannels() const {
return m_registeredChannels;
}

StandardEffectRackPointer addStandardEffectRack();
Expand Down Expand Up @@ -63,7 +64,7 @@ class EffectChainManager : public QObject {
QList<QuickEffectRackPointer> m_quickEffectRacks;
QHash<QString, EffectRackPointer> m_effectRacksByGroup;
QList<EffectChainPointer> m_effectChains;
QSet<QString> m_registeredGroups;
QSet<ChannelHandleAndGroup> m_registeredChannels;
DISALLOW_COPY_AND_ASSIGN(EffectChainManager);
};

Expand Down
65 changes: 34 additions & 31 deletions src/effects/effectchainslot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ EffectChainSlot::EffectChainSlot(EffectRack* pRack, const QString& group,
connect(m_pControlChainSelector, SIGNAL(valueChanged(double)),
this, SLOT(slotControlChainSelector(double)));

connect(&m_groupStatusMapper, SIGNAL(mapped(const QString&)),
this, SLOT(slotGroupStatusChanged(const QString&)));
connect(&m_channelStatusMapper, SIGNAL(mapped(const QString&)),
this, SLOT(slotChannelStatusChanged(const QString&)));
}

EffectChainSlot::~EffectChainSlot() {
Expand All @@ -86,10 +86,10 @@ EffectChainSlot::~EffectChainSlot() {
delete m_pControlChainNextPreset;
delete m_pControlChainSelector;

for (QMap<QString, ControlObject*>::iterator it = m_groupEnableControls.begin();
it != m_groupEnableControls.end();) {
for (QMap<QString, ChannelInfo*>::iterator it = m_channelInfoByName.begin();
it != m_channelInfoByName.end();) {
delete it.value();
it = m_groupEnableControls.erase(it);
it = m_channelInfoByName.erase(it);
}

m_slots.clear();
Expand Down Expand Up @@ -138,11 +138,11 @@ void EffectChainSlot::slotChainInsertionTypeChanged(EffectChain::InsertionType t
emit(updated());
}

void EffectChainSlot::slotChainGroupStatusChanged(const QString& group,
bool enabled) {
ControlObject* pGroupControl = m_groupEnableControls.value(group, NULL);
if (pGroupControl != NULL) {
pGroupControl->set(enabled);
void EffectChainSlot::slotChainChannelStatusChanged(const QString& group,
bool enabled) {
ChannelInfo* pInfo = m_channelInfoByName.value(group, NULL);
if (pInfo != NULL && pInfo->pEnabled != NULL) {
pInfo->pEnabled->set(enabled);
emit(updated());
}
}
Expand Down Expand Up @@ -192,8 +192,8 @@ void EffectChainSlot::loadEffectChain(EffectChainPointer pEffectChain) {
this, SLOT(slotChainMixChanged(double)));
connect(m_pEffectChain.data(), SIGNAL(insertionTypeChanged(EffectChain::InsertionType)),
this, SLOT(slotChainInsertionTypeChanged(EffectChain::InsertionType)));
connect(m_pEffectChain.data(), SIGNAL(groupStatusChanged(const QString&, bool)),
this, SLOT(slotChainGroupStatusChanged(const QString&, bool)));
connect(m_pEffectChain.data(), SIGNAL(channelStatusChanged(const QString&, bool)),
this, SLOT(slotChainChannelStatusChanged(const QString&, bool)));

m_pControlChainLoaded->setAndConfirm(true);
m_pControlChainInsertionType->set(m_pEffectChain->insertionType());
Expand All @@ -202,12 +202,13 @@ void EffectChainSlot::loadEffectChain(EffectChainPointer pEffectChain) {
// not of the chain. Propagate the current settings to the chain.
m_pEffectChain->setMix(m_pControlChainMix->get());
m_pEffectChain->setEnabled(m_pControlChainEnabled->get() > 0.0);
for (QMap<QString, ControlObject*>::iterator it = m_groupEnableControls.begin();
it != m_groupEnableControls.end(); ++it) {
if (it.value()->get() > 0.0) {
m_pEffectChain->enableForGroup(it.key());
for (QMap<QString, ChannelInfo*>::iterator it = m_channelInfoByName.begin();
it != m_channelInfoByName.end(); ++it) {
const ChannelInfo& channelInfo = *it.value();
if (channelInfo.pEnabled->toBool()) {
m_pEffectChain->enableForChannel(channelInfo.group);
} else {
m_pEffectChain->disableForGroup(it.key());
m_pEffectChain->disableForChannel(channelInfo.group);
}
}

Expand Down Expand Up @@ -266,20 +267,22 @@ EffectSlotPointer EffectChainSlot::addEffectSlot(const QString& group) {
return pSlot;
}

void EffectChainSlot::registerGroup(const QString& group) {
if (m_groupEnableControls.contains(group)) {
void EffectChainSlot::registerChannel(const ChannelHandleAndGroup& group) {
if (m_channelInfoByName.contains(group.name())) {
qWarning() << debugString()
<< "WARNING: registerGroup already has group registered:"
<< group;
<< "WARNING: registerChannel already has channel registered:"
<< group.name();
return;
}
ControlPushButton* pEnableControl = new ControlPushButton(
ConfigKey(m_group, QString("group_%1_enable").arg(group)));
ConfigKey(m_group, QString("group_%1_enable").arg(group.name())));
pEnableControl->setButtonMode(ControlPushButton::POWERWINDOW);
m_groupEnableControls[group] = pEnableControl;
m_groupStatusMapper.setMapping(pEnableControl, group);

ChannelInfo* pInfo = new ChannelInfo(group, pEnableControl);
m_channelInfoByName[group.name()] = pInfo;
m_channelStatusMapper.setMapping(pEnableControl, group.name());
connect(pEnableControl, SIGNAL(valueChanged(double)),
&m_groupStatusMapper, SLOT(map()));
&m_channelStatusMapper, SLOT(map()));
}

void EffectChainSlot::slotEffectLoaded(EffectPointer pEffect, unsigned int slotNumber) {
Expand Down Expand Up @@ -397,15 +400,15 @@ void EffectChainSlot::slotControlChainPrevPreset(double v) {
}
}

void EffectChainSlot::slotGroupStatusChanged(const QString& group) {
void EffectChainSlot::slotChannelStatusChanged(const QString& group) {
if (m_pEffectChain) {
ControlObject* pGroupControl = m_groupEnableControls.value(group, NULL);
if (pGroupControl != NULL) {
bool bEnable = pGroupControl->get() > 0;
ChannelInfo* pChannelInfo = m_channelInfoByName.value(group, NULL);
if (pChannelInfo != NULL && pChannelInfo->pEnabled != NULL) {
bool bEnable = pChannelInfo->pEnabled->toBool();
if (bEnable) {
m_pEffectChain->enableForGroup(group);
m_pEffectChain->enableForChannel(pChannelInfo->group);
} else {
m_pEffectChain->disableForGroup(group);
m_pEffectChain->disableForChannel(pChannelInfo->group);
}
}
}
Expand Down
24 changes: 19 additions & 5 deletions src/effects/effectchainslot.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "effects/effect.h"
#include "effects/effectslot.h"
#include "effects/effectchain.h"
#include "engine/channelhandle.h"

class ControlObject;
class ControlPushButton;
Expand All @@ -35,7 +36,7 @@ class EffectChainSlot : public QObject {
void loadEffectChain(EffectChainPointer pEffectChain);
EffectChainPointer getEffectChain() const;

void registerGroup(const QString& group);
void registerChannel(const ChannelHandleAndGroup& group);

double getSuperParameter() const;
void setSuperParameter(double value);
Expand Down Expand Up @@ -98,7 +99,7 @@ class EffectChainSlot : public QObject {
void slotChainEnabledChanged(bool enabled);
void slotChainMixChanged(double mix);
void slotChainInsertionTypeChanged(EffectChain::InsertionType type);
void slotChainGroupStatusChanged(const QString& group, bool enabled);
void slotChainChannelStatusChanged(const QString& group, bool enabled);

void slotEffectLoaded(EffectPointer pEffect, unsigned int slotNumber);
// Clears the effect in the given position in the loaded EffectChain.
Expand All @@ -115,7 +116,7 @@ class EffectChainSlot : public QObject {
void slotControlChainSelector(double v);
void slotControlChainNextPreset(double v);
void slotControlChainPrevPreset(double v);
void slotGroupStatusChanged(const QString& group);
void slotChannelStatusChanged(const QString& group);

private:
QString debugString() const {
Expand All @@ -140,10 +141,23 @@ class EffectChainSlot : public QObject {
ControlPushButton* m_pControlChainNextPreset;
ControlPushButton* m_pControlChainPrevPreset;

QMap<QString, ControlObject*> m_groupEnableControls;
struct ChannelInfo {
// Takes ownership of pEnabled.
ChannelInfo(const ChannelHandleAndGroup& group, ControlObject* pEnabled)
: group(group),
pEnabled(pEnabled) {

}
~ChannelInfo() {
delete pEnabled;
}
ChannelHandleAndGroup group;
ControlObject* pEnabled;
};
QMap<QString, ChannelInfo*> m_channelInfoByName;

QList<EffectSlotPointer> m_slots;
QSignalMapper m_groupStatusMapper;
QSignalMapper m_channelStatusMapper;

DISALLOW_COPY_AND_ASSIGN(EffectChainSlot);
};
Expand Down
Loading

0 comments on commit 7652b56

Please sign in to comment.