Про micros() или странная оптимизация

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

Доброй субботы, аксакалы!

Пока даже примерно не могу сообразить, растут ли ноги вопроса в аппаратных есебенностях ESP, особенностях оптимизации IDE или моей необразованности, поэтому - в песочнице.

Набросал вспомогательную функцию для формирования имен файлов для даталоггера на SD. Краткое содержание функции: получает адрес частично сформированного шаблона и вставляет в нужные места дату и id исполнительного устройства: 0ID_ММДД.ext 

Для ускорения работы добавлены условные варианты. В легких случайх обходимся простым присваиванием сонстанты. В неочевидных - немного математики. То есть, например id = 5 (005) должен подставляться быстрее скажем 241. 

В общем, дальше просто выложу листинг с результатами.

// ESP32 devkit v1  4mB 
// v.1.8.13
// ESP core v 1.0.4

void setup() {
  Serial.begin(115200);
  Serial.println('\n'); 
  Serial.println("Из setup: ");
    
  char filename[13] = "***_****.day" ;
  delay(1000);

  Serial.print("fName, mks: ");
  uint32_t mks = micros();
  fName1(filename, 123, 12, 31);
  Serial.println(micros() - mks);
  Serial.println(filename);
  
  Serial.print("fName, mks: ");
  mks = micros();
  fName1(filename, 1, 2, 3);          
  Serial.println(micros() - mks);
  Serial.println(filename);
  
  Serial.print("fName, mks: ");
  mks = micros();
  fName1(filename, 123, 12, 31);
  Serial.println(micros() - mks);
  Serial.println(filename);

  Serial.println('\n');
  Serial.println("Из функции: ");    // Те же действия, перепакованные в функцию
  bla(1, 1, 1);
  bla(10, 1, 1);
  bla(123, 1, 1);
  bla(1, 10, 1);
  bla(1, 1, 10);
  bla(123, 12, 31);
}

 
void bla(uint8_t id, uint8_t m, uint8_t d){     
  char filename[13] = "***_****.day" ;
  Serial.print("fName, mks: ");
  uint32_t mks = micros();
  fName1(filename, id, m, d);
  Serial.println(micros() - mks);
  Serial.println(filename);
 }


/*
 * char filename[13] = "0ID_MMDD.ext" (8.3);
 * fName(filename,relay_id, mon, day,  );   
 */
void fName1(char * ch, uint8_t id, uint8_t mon, uint8_t day){             // 11-20 mks
  if (id > 99) *ch = (char)(floor(id/100)+48); else *ch = '0';
  if (id > 9) *(ch+1) = (char)(floor((id%100)/10)+48); else *(ch+1) = '0';
  if (id > 9) *(ch+2) = (char)((id%10)+48); else *(ch+2) = (char)(id + 48);
  
  if (mon>9) 
  {*(ch+4) = '1'; *(ch+5) = (char)(mon + 38);}
  else {*(ch+4) = '0'; *(ch+5) = (char)(mon + 48);}

  if (day>9) 
  { uint8_t d1 = floor(day/10);
    *(ch+6) = (char)(d1 + 48); *(ch+7) = (char)(day - (d1*10) + 48);}
  else {*(ch+6) = '0'; *(ch+7) = (char)(day + 48);}
}



void loop() {}

 

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

Результат работы: 



Из setup: 
fName, mks: 22
123_1231.day
fName, mks: 1
001_0203.day
fName, mks: 4
123_1231.day


Из функции: 
fName, mks: 2
001_0101.day
fName, mks: 2
010_0101.day
fName, mks: 2
123_0101.day
fName, mks: 1
001_1001.day
fName, mks: 2
001_0110.day
fName, mks: 2
123_1231.day
Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

Собственно вопросы вызывают странные результаты micros():

Первый вызов функции fName() происходит существенно дольше всех последующих. Я даже там делэй(1000) добавил, чтобы ЕСПшка все свои стартовые дела успела доделать, хотя понимаю, что это фигня.

Следующие вызовы из setup хоть и подозрительно короткие, но логично отличаются по времени выполнения. 

С вызовами из промежуточной функции вообще логики мало. Такое ощущение, что там микрос от балды лепится. 

Подозрений несколько: 

1. особенности реализации micros() при первом вызове. 

2. Хитрый компилятор шельмует с повторным вызовом fName, хотя я не понимаю КАК.

Подскажите, куда рыть. Вопрос не принципиальный, просто хочу все знать))

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

А где вопрос то?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Блин, да Вы троллите нас!

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

ЕвгенийП пишет:

Блин, да Вы троллите нас!

Евгений Петрович, и в мыслях не было. Вот туплю где-то жестко, это да. Вот только никак понять не могу где

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

andriano пишет:

А где вопрос то?

Вопрос простой. Почему такая разница в показаниях при выполнении одного кода. Точнее, вопрос в том, где я туплю

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Rumata пишет:

Вот только никак понять не могу где

floor - это функция для плавающей точки. Целочисленное деление И ТАК выполняется с отбрасыванием остатка. Вы же зачем-то вызываете floor для результата целочисленного деления, т.е. преобразуете аргумент к float, берёте целую часть, и преобразуете обратно к целому. При этом, при первом вызове, инициализируете библиотеку плавающей точки.

Но, это не всё. Чего Вы навертели в функции fName1? Нафига такие сложности-то? Показать Вам как она пишется?

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

ЕвгенийП пишет:

Rumata пишет:

Вот только никак понять не могу где

floor - это функция для плавающей точки. Целочисленное деление И ТАК выполняется с отбрасыванием остатка. Вы же зачем-то вызываете floor для результата целочисленного деления, т.е. преобразуете аргумент к float, берёте целую часть, и преобразуете обратно к целому. При этом, при первом вызове, инициализируете библиотеку плавающей точки.

Но, это не всё. Чего Вы навертели в функции fName1? Нафига такие сложности-то? Показать Вам как она пишется?

Про округление целочисленных вниз я если и знал, то забыл. В этом себе всю боль о организовал.  За науку признателен. Все вопросы отпали. Теперь сам все сделаю, это же элементарно. Но Ваш пример хотел бы увидеть. Не для того, чтобы скопипастить, это не мой путь. Для знакомства с лучшими практиками 

 

ЗЫ: пожалуй, выпью сегодня после баньки за ваше здоровье бокал эсторского))

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

Всё понимаю - но нахера - не понимаю.

А если электричество выключат? Или кошка-мышка хвостом махнёт?

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

mykaida пишет:

Всё понимаю - но нахера - не понимаю.

А если электричество выключат? Или кошка-мышка хвостом махнёт?

Не в то окно, да? :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, вот, смотрите, Вы пишете(я убрал floor)

  if (id > 99) *ch = (char)((id/100)+48); else *ch = '0'; 

Но ведь, если id < 99, то частное от деления id/100 будет равен 0. Не проще ли написать

* ch = id / 100 + '0'; 

Да, этот вариант хуже тем, что деление выполняется всегда, а в Вашем случае только когда надо. Но такая экономия имела бы смысл, если бы это было в цикле с миллионом проходов. А так ... вы экономите копейки, зато усложняете программу (а усложнение - это удобрение почвы на которой баги растут).

Я бы сделал отдельную функцию, которая преобразует одно число, и вызывал бы её три раза. Примерно, так:

inline void convertNum(uint8_t num, char * dst, const bool from100 = false) {
	if (from100) {
		*dst++ = num / 100 + '0'; 
		num = num % 100;
	}
	*dst++ = num / 10 + '0';
	*dst = num % 10 + '0';
}

void fName1(char * const ch, const uint8_t id, const uint8_t mon, const uint8_t day) {
	convertNum(id, ch, true);
	convertNum(mon, ch + 4);
	convertNum(day, ch + 6);
}

 

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

ЕвгенийП пишет:

Ну, вот, смотрите, Вы пишете(я убрал floor)

  if (id > 99) *ch = (char)((id/100)+48); else *ch = '0'; 

Но ведь, если id < 99, то частное от деления id/100 будет равен 0. Не проще ли написать

* ch = id / 100 + '0'; 

Да, этот вариант хуже тем, что деление выполняется всегда, а в Вашем случае только когда надо. Но такая экономия имела бы смысл, если бы это было в цикле с миллионом проходов. А так ... вы экономите копейки, зато усложняете программу (а усложнение - это удобрение почвы на которой баги растут).

Вот не зря тему создал. Сейчас кажется, что это элементарное решение, но сам бы до этого в текущем году не дошел бы. После того, как функция стала укладываться в 1 мкс затратами процессорного времени на выполнение деления готов пренебречь)) Еще раз спасибо!