markzzz
markzzz

Reputation: 47945

Is access by pointer so expensive?

I've a Process() function that is called very heavy within my DLL (VST plugin) loaded in a DAW (Host software), such as:

for (int i = 0; i < nFrames; i++) {
    // ...

    for (int voiceIndex = 0; voiceIndex < PLUG_VOICES_BUFFER_SIZE; voiceIndex++) {
        Voice &voice = pVoiceManager->mVoices[voiceIndex];
        if (voice.mIsPlaying) {
            for (int envelopeIndex = 0; envelopeIndex < ENVELOPES_CONTAINER_NUM_ENVELOPE_MANAGER; envelopeIndex++) {
                Envelope &envelope = pEnvelopeManager[envelopeIndex]->mEnvelope;
                envelope.Process(voice);
            }           
        }
    }
}

void Envelope::Process(Voice &voice) {
    if (mIsEnabled) {
        // update value
        mValue[voice.mIndex] = (mBlockStartAmp[voice.mIndex] + (mBlockStep[voice.mIndex] * mBlockFraction[voice.mIndex]));
    }
    else {
        mValue[voice.mIndex] = 0.0;
    }
}

It basically takes 2% of CPU within the Host (which is nice).

Now, if I slightly change the code to this (which basically are increments and assignment):

void Envelope::Process(Voice &voice) {
    if (mIsEnabled) {
        // update value
        mValue[voice.mIndex] = (mBlockStartAmp[voice.mIndex] + (mBlockStep[voice.mIndex] * mBlockFraction[voice.mIndex]));

        // next phase
        mBlockStep[voice.mIndex] += mRate;
        mStep[voice.mIndex] += mRate;
    }
    else {
        mValue[voice.mIndex] = 0.0;
    }

    // connectors
    mOutputConnector_CV.mPolyValue[voice.mIndex] = mValue[voice.mIndex];
}

CPU go to 6/7% (note, those var don't interact with other part of codes, or at least I think so).

The only reason I can think is that access to pointer is heavy? How can I reduce this amount of CPU?

Those arrays are basic double "pointer" arrays (the most lighter C++ container):

double mValue[PLUG_VOICES_BUFFER_SIZE];
double mBlockStartAmp[PLUG_VOICES_BUFFER_SIZE];
double mBlockFraction[PLUG_VOICES_BUFFER_SIZE];
double mBlockStep[PLUG_VOICES_BUFFER_SIZE];
double mStep[PLUG_VOICES_BUFFER_SIZE];

OutputConnector mOutputConnector_CV;

Any suggestions?

Upvotes: 1

Views: 143

Answers (2)

Ajay
Ajay

Reputation: 18411

  1. Pack all the data items in a structure and create an array of structure. I would simply use a vector.
  2. In Process function get the single element out of this vector, and use its parameters. At the cache-line/instruction level, all items would be (efficiently) brought into local cache (L1), as the data element (members of struct) as contiguous. Use reference or pointer of struct type to avoid copying.
  3. Try to use integer data-types unless double is needed.

EDIT:

struct VoiceInfo 
{ 
   double mValue; 
   ...
}; 
VoiceInfo voices[PLUG_VOICES_BUFFER_SIZE];
// Or vector<VoiceInfo> voices;

...

void Envelope::Process(Voice &voice) 
{
     // Get the object (by ref/pointer)
     VoiceInfo& info = voices[voice.mIndex];
     // Work with reference 'info'
  ...
}

Upvotes: 1

MSalters
MSalters

Reputation: 179779

You might be thinking that "pointer arrays" are the lightest containers. but CPU's don't think in terms of containers. They just read and write values through pointers.

The problem here might very well be that you know that two containers do not overlap (there are no "sub-containers"). But the CPU might not be told that by the compiler. Writing to mBlockStep might affect mBlockFraction. The compiler doesn't have run-time values, so it needs to handle the case where it does. This will mean introducing more memory reads, and less caching of values in registers.

Upvotes: 2

Related Questions