Menu

Alloc is faster when reading if address is pre-calculated

2026-08-21
2026-08-23
  • Roger Jönsson

    Roger Jönsson - 2026-08-21

    Today's interesting finding. I don't remember if we have discussed this.
    The two examples of codes below does exactly the same thing, at slightly different speed.
    When reading the alloc addres, If do the addition within its brackets, it is 25% slower. Why is this?

    I am not complaing, on the contrary I am pretty pleased with todays experiment! I started at 11us and by removing 8 multiplications, then adding to the alloc address in advance instead of within its brackets I'm down to 6,9us!
    The DAC is updated 8 times the sample frequency vid DMA, with the Least Significant Bit toggled (dither pattern) to achieve 2-3 bits higher resolution mostly efficient at lower levels. In a perfect world I should have gotten 11bit resolution out of the built in 8-bit DAC, but I guess real world figure is about 10 bits. Still quite an improvement in audible distortion.
    With a sine shifted so that only 2 bits are left, it actually resembles a sine wave again when the 3 PWM bits shifted out are inserted. The whole process including handlig the sample takes 10-12us (including some shifting to lower the volume in order to stress test the sound at very low levels etc etc).

    //6,9us:
    SignalArray(0)=SoundarrayOUT + PWMinterpol(LSBofSoundarray)
    LSBofSoundarrayadded=LSBofSoundarray+1
    SignalArray(1)=SoundarrayOUT + PWMinterpol(LSBofSoundarrayadded)
    LSBofSoundarrayadded=LSBofSoundarray+2
    SignalArray(2)=SoundarrayOUT + PWMinterpol(LSBofSoundarrayadded)
    LSBofSoundarrayadded=LSBofSoundarray+3
    SignalArray(3)=SoundarrayOUT + PWMinterpol(LSBofSoundarrayadded)
    LSBofSoundarrayadded=LSBofSoundarray+4
    SignalArray(4)=SoundarrayOUT + PWMinterpol(LSBofSoundarrayadded)
    LSBofSoundarrayadded=LSBofSoundarray+5
    SignalArray(5)=SoundarrayOUT + PWMinterpol(LSBofSoundarrayadded)
    LSBofSoundarrayadded=LSBofSoundarray+6
    SignalArray(6)=SoundarrayOUT + PWMinterpol(LSBofSoundarrayadded)
    LSBofSoundarrayadded=LSBofSoundarray+7
    SignalArray(7)=SoundarrayOUT + PWMinterpol(LSBofSoundarrayadded)
    
    //9,2us:
    SignalArray(0)=SoundarrayOUT + PWMinterpol(LSBofSoundarray)
    SignalArray(1)=SoundarrayOUT + PWMinterpol(LSBofSoundarray+1)
    SignalArray(2)=SoundarrayOUT + PWMinterpol(LSBofSoundarray+2)
    SignalArray(3)=SoundarrayOUT + PWMinterpol(LSBofSoundarray+3)
    SignalArray(4)=SoundarrayOUT + PWMinterpol(LSBofSoundarray+4)
    SignalArray(5)=SoundarrayOUT + PWMinterpol(LSBofSoundarray+5)
    SignalArray(6)=SoundarrayOUT + PWMinterpol(LSBofSoundarray+6)
    SignalArray(7)=SoundarrayOUT + PWMinterpol(LSBofSoundarray+7)
    
     
  • Anobium

    Anobium - 2026-08-21

    Now that is interesting.

    what does ASM show?

     
  • Roger Jönsson

    Roger Jönsson - 2026-08-21

    The reason for starting fiddling with this is that when I tried another resolution step, adding 4 bits = 16 x oversampling, then the ISR lagged and the frequency dropped. I thought there should still be margins, so I hadn't measured. Testing about a little here, a little there, the way I have a tendency to do, I found this.
    The nice compact code is slower.

    Snippets:

    ;LSBofSoundarrayadded=LSBofSoundarray+1
        incf    LSBOFSOUNDARRAY,W,ACCESS
        movwf   LSBOFSOUNDARRAYADDED,ACCESS
    ;SignalArray(1)=SoundarrayOUT + PWMinterpol(LSBofSoundarrayadded)
        lfsr    0,PWMINTERPOL
        movf    LSBOFSOUNDARRAYADDED,W,ACCESS
        addwf   AFSR0,F,ACCESS
        movlw   0
        addwfc  AFSR0_H,F,ACCESS           
        movff   POSTINC0,SysArrayTemp1
        movf    SysArrayTemp1,W,ACCESS
        addwf   SOUNDARRAYOUT,W,ACCESS
        movwf   SYSSIGNALARRAY_1,BANKED
    
    
     ;SignalArray(1)=SoundarrayOUT + PWMinterpol(LSBofSoundarray+1)
        lfsr    0,PWMINTERPOL
        movf    LSBOFSOUNDARRAY,W,ACCESS
        addwf   AFSR0,W,ACCESS
        movwf   SysTemp1,ACCESS
        movlw   0
        addwfc  AFSR0_H,W,ACCESS
        movwf   SysTemp1_H,ACCESS
        movlw   1
        addwf   SysTemp1,W,ACCESS
        movwf   AFSR0,ACCESS
        movlw   0
        addwfc  SysTemp1_H,W,ACCESS
        movwf   AFSR0_H,ACCESS
        movff   POSTINC0,SysArrayTemp1
        movf    SysArrayTemp1,W,ACCESS
        addwf   SOUNDARRAYOUT,W,ACCESS
        movwf   SYSSIGNALARRAY_1,BANKED
    
     
  • Anobium

    Anobium - 2026-08-22

    Nice bit of detective work, Roger. This is a genuinely useful pattern for GCBASIC on the PIC18, and it is easy to apply everywhere once you know it.

    The finding: when you are indexing into an array or a table inside a tight or repeated block, pre-compute the index into its own variable first, and then use that plain variable in the brackets, rather than writing the arithmetic directly inside the subscript. In your case that took a 9.2 microsecond operation down to 6.9 microseconds. A solid win for zero extra logic.

    Advice for anyone hitting this:

    • Wherever you have Array(expression) inside a loop or an ISR that runs often, split it out. Compute idx = expression on its own line, then use Array(idx).
    • This matters most in unrolled loops such as yours, where the same pattern repeats eight times back to back. The saving multiplies with every repeat.
    • It costs one extra line of source and no extra RAM, since you are simply reusing or overwriting a working variable. That is a cheap habit to build for anything timing critical.
    • Good places to check: ISRs, DMA or DAC feed routines, anything driven off a sample-rate timer. Exactly the sort of code where you are already counting microseconds.

    Given you are already down in the weeds on the dither and oversampling work, this is a handy tool to keep in the back pocket: a simple rewrite that buys real headroom without touching the logic. Good find, and thank you for posting the ASM. That kind of before-and-after comparison is exactly what helps the next person optimising a tight PIC18 loop in GCBASIC.

     
  • Roger Jönsson

    Roger Jönsson - 2026-08-23

    I did a version using SPI instead sending out 4 LSB make up bits as a PWM dithered pattern (x 16 oversampling), increasing the built in 8-bit DAC with 4 extra bits. Looking at the oscilloscope, it looks close to 12bits resolution at low levels!
    This one has the disadvantage of needing two resistors to mix the 4 bit PWM make up signal in.
    The big advantage it is only stealing 2.1uS!

    The data for SPI1TXB registers are pre calculated (only the resulting variables for SPI1TXB) because for some reason it otherwise affects timing so that distortion increases.

    I will present fully in a future project.
    -Pretty cool use of the SPI-module, eh?

    //(18FxxQ84)
    IndexPreCalc=LSBofSoundarray*2+0  :  SPIlowbyte=PWMinterpol(IndexPreCalc)  
    IndexPreCalc=LSBofSoundarray*2+1  :  SPIhibyte=PWMinterpol(IndexPreCalc)
    SPI1STATUS.2=1 // CLB Clear Buffer Control (0=Take no action)
    SPI1TCNTL = 1 // SPI Transfer Counter Least Significant Byte
    SPI1TXB=SPIlowbyte 
    //wait while SPI1RXIF = SPI_RX_IN_PROGRESS //not needed will be finished in time (two byte fifo)
    SPI1TXB=SPIhibyte
    

    The picture shows a sine wave shifted right so that only 4bits are left for the built in 8-bit DAC to output (-24dB). The remaining 4 bits are resinserted through a PWM dithered 1-bit pattern (x 16 oversampling) though the SPI port. The DAC and SPI outputs are mixed together externally with two resistors and the sine wave is restored to near perfection.

     

    Last edit: Roger Jönsson 2026-08-23

Log in to post a comment.