Help needed understanding Due performance
What does the Arduino library do to make loop() so inefficient?


Alot.  For a start it's a function, so it has to push registers onto the stack before it runs, and pop them off afterwards.  Secondly there is the "serial event" system outside the loop.  In main.cpp in the core you can see how loop is called:

setup();

for (;;)
{
    loop();
    if (serialEventRun) serialEventRun();
}



  Is there any harm in keeping all your code inside an infinite loop in setup() and never returning out of it?


If you don't use serial events, no harm whatsoever.


  Why would I sometimes see the number of increments being 2 or 3 times higher than usual? Could the counter be getting cached (despite being marked as volatile) and not reset properly? If so, how can I prevent this?


My guess is the operations you are using are not atomic.  This is, the interrupt is able to be fired right in the middle of doing the series of instructions that perform the increment. The temporary registers end up not being the same as they were at the start of the operation, and consequently the value is corrupted.  You should wrap the increment in a critical section (i.e., disable interrupts before it, and enable them afterwards).

To better analyse the speed of execution you can look at the generated assembly code of your program.  If we look at the disassembled main() function for the bit where it loops you can get this:

80596:  f7ff fe05   bl  801a4 &lt;loop&gt;
8059a:  4b04        ldr r3, [pc, #16]   ; (805ac &lt;main+0x30&gt;)
8059c:  2b00        cmp r3, #0
8059e:  d0fa        beq.n   80596 &lt;main+0x1a&gt;
805a0:  f7ff feec   bl  8037c &lt;_Z14serialEventRunv&gt;
805a4:  e7f7        b.n 80596 &lt;main+0x1a&gt;
805a6:  bf00        nop


loop() consists of this:

801a4:  4b02        ldr r3, [pc, #8]    ; (801b0 &lt;loop+0xc&gt;)
801a6:  681a        ldr r2, [r3, #0]
801a8:  3201        adds    r2, #1
801aa:  601a        str r2, [r3, #0]
801ac:  4770        bx  lr
801ae:  bf00        nop


If we feed that into main() where it calls loop() the finished code looks like:

80596:  f7ff fe05   bl  801a4 &lt;loop&gt;
801a4:  4b02        ldr r3, [pc, #8]    ; (801b0 &lt;loop+0xc&gt;)
801a6:  681a        ldr r2, [r3, #0]
801a8:  3201        adds    r2, #1
801aa:  601a        str r2, [r3, #0]
801ac:  4770        bx  lr
801ae:  bf00        nop
8059a:  4b04        ldr r3, [pc, #16]   ; (805ac &lt;main+0x30&gt;)
8059c:  2b00        cmp r3, #0
8059e:  d0fa        beq.n   80596 &lt;main+0x1a&gt;
805a0:  f7ff feec   bl  8037c &lt;_Z14serialEventRunv&gt;
805a4:  e7f7        b.n 80596 &lt;main+0x1a&gt;
805a6:  bf00        nop


So that is what gets executed for every single iteration of loop().  If we (incorrectly) assume that each instruction takes 1 clock tick, that's 13 ticks.  84,000,000 / 13 = ~6.4 million loops per second.

Now - if we map the instructions against actually how many clock cycles they take, we can get a clearer picture:

bl                  1+P  (P = 1 to 3)
ldr (pc relative)   2
ldr (word)          2
adds                1
str (word)          2
bx                  1+P  (P = 1 to 3)
nop                 1
ldr (pc relative)   2
cmp                 1
beq.n               1 or 1+P (P = 1 to 3)
bl                  1+P (Only if serial event runs)
b.n                 1 or 1+P (P = 1 to 3)
nop                 1


So taking a worst case scenario where P = 3 (pipeline re-fill clock counts, that is), and no serial event running, we can calculate a total number of clock cycles of 4+2+2+1+2+4+1+2+1+4+0+4+1 = 28.

84,000,000 / 28 = 3,000,000 loops per second.

But hang on - that's still much more than you're seeing.  That's because you're still only looking at the small picture.  You're looking at just your sketch and forgetting what else there is going on.

Every millisecond the SysTick_Handler() gets called which does various tasks, including calling the TimeTick_Increment() routine to deal with millis().  That's 1,000 times per second, and that eats into your processing time somewhat.

Another important point is it can't be incrementing while you're running your interrupt.  The more you do in your interrupt (and using serial is a very heavy-weight thing to do in an interrupt) the less of your 1-second slice you get for doing your incrementing.

As an experiment, why not try disabling all the interrupts except just the one you are using for your timer and see how it changes the value.