Why is micros() so much slower than millis()?

I set up a sketch to test the hz of the main loop with millis() vs micros().
With millis() hz was giving a reading between 288,935 and 289,232 but with micros is was reading 174,730 which is substantially slower. Is is just because the arduino has to deal with bigger numbers or because it has to update the number more frequently, or is it something completely different? Is there a way to count time in a faster way?

Edit: I should have specified that I am using a Nano

millis()

unsigned long hz;
unsigned long timer;
unsigned long timerOld;
void setup() {
  Serial.begin(2000000);
}

void loop() {
  hz++;
  timer = millis() - timerOld;
  if(timer >= 1000){
    timerOld = millis();
    Serial.println(hz);
    hz = 0;
  }
}

micros()

unsigned long hz;
unsigned long timer;
unsigned long timerOld;
void setup() {
  Serial.begin(2000000);
}

void loop() {
  hz++;
  timer = micros() - timerOld;
  if(timer >= 1000000){
    timerOld = micros();
    Serial.println(hz);
    hz = 0;
  }
}

How many time do you supposed the counter rolled over during your test period?
Paul

None. I used an unsigned long which holds a maximum of 4,294,967,295. That's a bigger number than the Arduino could have possibly counted to in 1 second seeing as it has a 16Mhz clock.

Calculating micros is slightly more time consuming as it requires some maths.

millis() is basically just returning the value of timer0_millis

See for yourself

millis is here ArduinoCore-avr/wiring.c at 24e6edd475c287cdafee0a4db2eb98927ce3cf58 · arduino/ArduinoCore-avr · GitHub


unsigned long millis()
{
	unsigned long m;
	uint8_t oldSREG = SREG;

	// disable interrupts while we read timer0_millis or we might get an
	// inconsistent value (e.g. in the middle of a write to timer0_millis)
	cli();
	m = timer0_millis;
	SREG = oldSREG;

	return m;
}

Micros is there ArduinoCore-avr/wiring.c at 24e6edd475c287cdafee0a4db2eb98927ce3cf58 · arduino/ArduinoCore-avr · GitHub


unsigned long micros() {
	unsigned long m;
	uint8_t oldSREG = SREG, t;
	
	cli();
	m = timer0_overflow_count;
#if defined(TCNT0)
	t = TCNT0;
#elif defined(TCNT0L)
	t = TCNT0L;
#else
	#error TIMER 0 not defined
#endif

#ifdef TIFR0
	if ((TIFR0 & _BV(TOV0)) && (t < 255))
		m++;
#else
	if ((TIFR & _BV(TOV0)) && (t < 255))
		m++;
#endif

	SREG = oldSREG;
	
	return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

In your case this is very visible because your loop does nothing but increment a counter, so that is very light compared to the overhead needed for micros()

Using a hardware timer would be the less intrusive way of measuring the time spent in the loop

The millis() function only reads the variables already accumulated by the Timer0 overflow ISR and just returns their values.
The micros() function reads the current counter value of Timer0 and calculates the elapsed time, because return value need even higher time resolution.

Therefore, the execution time of the micros() function is considerably longer than millis() function.

The detailed code can be found below.

millis()

micros()

1 Like

@J-M-L Sorry, we got same posts, I leave that. :confounded:

That’s ok
I was probably editing mine as you were typing

OP has the right answer I guess that’s what matters :wink:

Which may have been optimized away.

Probably not as the value gets printed so compiler knows it’s needed

1 Like

Ah. Missed that.

The difference is about 36 instruction cycles. I think most of that will be the micros() function but a little will be:
if(timer >= 1000){
vs.
if(timer >= 1000000){

In the millis() case you compare a 32-bit integer to a 16-bit constant and with the micros() case you compare a 32-bit integer to a 32-bit constant. I suspect the full 32-bit comparison takes more cycles.

Try change this
if(timer >= 1000000){
to this
if(timer >= 1000000UL){
.

What does that do exactly?

https://www.arduino.cc/reference/en/language/variables/constants/integerconstants/

Yeah so it shouldn't change anything since its treated as an unsigned long anyway, due to its size.

I did test it and it changed nothing.

No, that does fit in a signed long, so that’s what the compiler will go for.

That’s not necessary

When comparing a signed value with unsigned value, the compiler converts the signed value to unsigned. so you get UL automagically

1 Like

I did test it and it changed nothing.

indeed - not surprising, the signed long is promoted to unsigned for the test.

So anyway, from what I gather, using a clock that I could plug into one of the I/O pins would be faster?

Not sure what you mean.