Время исполнения программы и "фокусы" компилятора
- Войдите на сайт для отправки комментариев
По мотивам Конкурса. В теме поднимал вопрос (#154,#252) по различиям во времени выполнения программ, созданных при помощи разных версий ИДЕ, но откликов не было. Поэтому решил создать новую тему.
В ходе решения задачи конкурса было много изменений кода и когда был близок к окончательному решению обнаружилась следующая странность. В коде осталось два "лишних" цикла, не влияющих на результат
for (uint8_t i = 0; i < minQ - qOn; i++) //сделали их "on"
spanners[currArr[i]].included = on;
и
for (uint8_t i = 0; i < minQ - qOn; i++) //сбросили ключи в "off"
spanners[currArr[i]].included = off;
каждый из которых выполнялся 5113 раз (i=6, потом меняется i=7 ) для исходного набора. Я убрал их, ожидая, что время сократится. Программа перестала выполнять более 10 тыс. циклов из 6 - 7 итераций - но время выполнения не уменьшилось, как я ожидал, а УВЕЛИЧИЛОСЬ !!??
Стоимость набора: 6367 руб. 66 коп. Время расчёта: 198 миллисекунд
с "лишними" циклами
Стоимость набора: 6367 руб. 66 коп. Время расчёта: 202 миллисекунд
без них.
Использовалась "чистая", без доустановки чего-либо ИДЕ версии 1.8.13, ситема Win 7x64.
Полная версия программы
//
// Тип данных для хранения ключа
//
struct Spanner : public Printable {
uint32_t price;// цена в копейках
uint8_t size1; // размер 1
uint8_t size2; // размер 2
uint8_t included; // если 1, то включается в результирующий набор,0 - возможно будет включен,10 - исключен из перебора
//ТУТ НЕ ТАК,КАК В ВАС! included МОЖЕТ ПРИНИМАТЬ: если 1 (оn), то включается в результирующий набор,0 (off) - возможно будет включен,
Spanner(const uint8_t s1, const uint8_t s2, const uint32_t p, const uint8_t ini = 0)
: price(p), size1(s1), size2(s2), included(ini) {}
size_t printTo(Print & p) const {
size_t res = p.print(size1);
res += p.print('-');
res += p.print(size2);
res += p.print(": ");
res += printPrice(p, price);
return res;
}
static size_t printPrice(Print & p, const uint32_t priceCop) {
size_t res = p.print(priceCop / 100);
res += p.print(" руб. ");
res += p.print(priceCop % 100);
res += p.print(" коп.");
return res;
}
};
//
// Массив всех ключей
//
static Spanner spanners[] = {
{6, 8 , 38208},
{8, 9 , 41520},
{8, 10, 43054},
{9, 11, 49597},
{10, 12, 51455},
{12, 13, 56544},
{12, 14, 57675},
{13, 15, 62845},
{14, 15, 61714},
{14, 17, 70276},
{16, 17, 76496},
{16, 18, 81989},
{17, 19, 82312},
{18, 19, 82877},
{19, 22, 110826},
{22, 24, 145560},
{24, 27, 171570},
//{9, 27, 71570}
};
static constexpr uint8_t totalSpanners = sizeof(spanners) / sizeof(spanners[0]);
const uint8_t on = 1, off = 0, excluded = 10;// если 1, то включается в результирующий набор,0 - возможно будет включен,10 - исключен из перебора
//
// Эта функция вычисляет оптимальный набор ключей
// и устанваливает поле included в on для ключей,
// которые попадают в решение, и off или excluded для ключей,
// которые не попадают в решение.
//
void doCalculations(void) {
spanSort(spanners); //отсортировали
uint8_t quantityOff; //ввели переменную кол-ва ключей, которые могут быть включены "off" в набор
uint8_t quantityOn; //ввели переменную кол-ва ключей, которые включены "On" в набор
uint8_t quantitySize = totalSize();//кол-во уникальных размеров и заодно удалили (excluded) дубли
uint8_t minQuantSpann; //ввели переменную миним. кол-во ключей, из которых может состоять набор
if (quantitySize % 2 == 0)
minQuantSpann = quantitySize / 2;
else minQuantSpann = quantitySize / 2 + 1; //Вычислили мин. возможное кол-во ключей
quantityOn = unique(); //включили уникальные 6*8; 9*11; 24*27.
exclud(); //Тут исключать 8*9
uint8_t quantitySizeOn = onSize(quantityOn); //ввели переменную кол-ва размеров в "On" - ключах
uint8_t arrBruteForce[quantitySize - quantitySizeOn] = {};
arrey__B_F(quantityOff, quantityOn, arrBruteForce);
//for (int k = 0; k < quantitySize - quantitySizeOn; k++){Serial.print(arrBruteForce[k]);Serial.print(',');}
quantityOff = offSpanners(); //
if (quantityOff == 0)return;
sortOff(spanners); //Сортировка невклученных офф вперед
//перебор вариантов.Почти все, что выше исключительно для умеьшения возм. сочетаний и времени.
bruteForce(minQuantSpann, quantityOff, quantityOn, quantitySize,arrBruteForce, quantitySize - quantitySizeOn);
}
//
// Функция печатает результирующий
// набор ключей и его стоимость
//
void printResults(void) {
Serial.println("Ключи");
uint32_t total = 0;
for (uint8_t i = 0; i < totalSpanners; i++) {
if (spanners[i].included == on) {
Serial.print(i + 1);
Serial.print(". ");
Serial.println(spanners[i]);
total += spanners[i].price;
}
}
Serial.print("Стоимость набора: ");
Spanner::printPrice(Serial, total);
Serial.println();
}
//--------------------------------------------------
//
// Функция пeребора вариантов сочетаний ключей
//
void bruteForce(uint8_t minQ, uint8_t qOff, uint8_t qOn, uint8_t qSize,uint8_t *arr_B_F, uint8_t sizeA_B_F)
{
uint8_t tempArr[qOff];
uint32_t tempTotal = 4294967295;
bool complectYes = false;
for ( minQ; minQ <= qOff + qOn; minQ++)
{ //Serial.println(minQ);
bool flagComplect = false;
uint8_t *currArr;
currArr = new uint8_t[minQ - qOn];
for (uint8_t i = 0; i < minQ - qOn; i++) //первое сочетание ключей
{
currArr[i] = i ;
}
for (uint8_t i = 0; i < minQ - qOn; i++) //сделали их "on"
spanners[currArr[i]].included = on;
if (totalSizeOn1(arr_B_F, sizeA_B_F, currArr, minQ, qOn ) == true) //если собрался правильный набор набор
{
uint32_t currTotal = 0;
for (uint8_t i = 0; i < minQ - qOn; i++) //подсчитали цену
{
currTotal += spanners[currArr[i]].price;
}
if (currTotal < tempTotal) //если цена меньше записанной или это первый набор
{
tempTotal = currTotal; //записываем цену
for (uint8_t i = 0; i < qOff; i++) //темп аррей делаем пустым
tempArr[i] = 0;
for (uint8_t i = 0; i < minQ - qOn; i++)
tempArr[currArr[i]] = 1; //записываем в темп аррей
flagComplect = true; //поднимаем флаг "собран комплект"
}
}
for (uint8_t i = 0; i < minQ - qOn; i++) //сбросили ключи в "off"
spanners[currArr[i]].included = off;
//Здесь начинается бред. Строки 135,136; 158,159; 178,179; 198,199
//в этом коде лишние, они тут просто не нужны
//Но если их закомментить, время выполнения УВЕЛИЧИВАЕТСЯ!!!
while (NextSet(currArr, qOff-1, minQ - qOn)) //новое сочетание ключей
{
uint32_t currTotal = 0;
for (uint8_t i = 0; i < minQ - qOn; i++) //подсчитали цену
{
currTotal += spanners[currArr[i]].price;
}
if (currTotal >= tempTotal) //"граница" по цене
{
continue;
}
for (uint8_t i = 0; i < minQ - qOn; i++) //сделали их "on"
spanners[currArr[i]].included = on;
if (totalSizeOn1(arr_B_F, sizeA_B_F, currArr, minQ, qOn ) == true) //если собрался правильный набор набор
{
if (currTotal < tempTotal ) //если цена меньше записанной или это первый набор
{
tempTotal = currTotal; //записываем цену
for (uint8_t i = 0; i < qOff; i++) //темп аррей делаем пустым
tempArr[i] = 0;
for (uint8_t i = 0; i < minQ - qOn; i++)
{
tempArr[currArr[i]] = 1; //записываем в темп аррей
}
flagComplect = true; //поднимаем флаг "собран комплект"
}
}
for (uint8_t i = 0; i < minQ - qOn; i++) //сбросили ключи в "off"
spanners[currArr[i]].included = off;
}
delete[] currArr; // динамич. массив удаляем
if (flagComplect == true) // комплект был создан мин.1 раз
complectYes = true;
if (flagComplect == false && complectYes == true) //если в иттерации не нашли комплект дешевле
{ //и комплект был создан ранее выходим
for (uint8_t i = 0; i < qOff; i++)
spanners[i].included = tempArr[i];
break;
}
if ( qOff == minQ + qOn) //Последняя иттер.
{
for (uint8_t i = 0; i < qOff; i++)
spanners[i].included = tempArr[i];
}
}
}
//-------------------------------------------------
//
//Функция вычисляет количество существующих размеров среди включенных "On"ключей и сравнивает с кол-вом сущ. размеров
//
bool totalSizeOn1(uint8_t *arr_B_F, uint8_t sizeA_B_F, uint8_t *currA, uint8_t minQ, uint8_t qOn )
{
uint8_t inSize = 0;
for (uint8_t i = 0; i < sizeA_B_F; i++)
{
bool flag = false;
for (uint8_t k = 0; k < minQ - qOn; k++)
{
if (arr_B_F[i] == spanners[currA[k]].size1 ||
arr_B_F[i] == spanners[currA[k]].size2)
{
flag = true;
}
}
if (flag) inSize++;
}
if (inSize != sizeA_B_F)
return false;
else return true;
}
//---------------------------------------------------------
//
//Функция всех сочетаний для чисел 1…n по m.
//Честно содрана на <a href="https://prog-cpp.ru/combinations/" title="https://prog-cpp.ru/combinations/" rel="nofollow">https://prog-cpp.ru/combinations/</a>
//
bool NextSet(uint8_t *a, uint8_t n, uint8_t m)
{
uint8_t k = m;
for (int i = k - 1; i >= 0; --i)
if (a[i] < n - k + i + 1)
{
++a[i];
for (uint8_t j = i + 1; j < k; ++j)
{
a[j] = a[j - 1] + 1;
}
return true;
}
return false;
}
//-----------------------------------------------------
//
//Функция подсчета невключенных(off)ключей
//
uint8_t offSpanners()
{
uint8_t j = 0;
for (uint8_t i = 0; i < (totalSpanners ); i++)
{
if (spanners[i].included == off)
j++;
}
return j;
}
//----------------------------------------
//
// функция сортировки ключей по "off"; ключи со значением included == off станут первыми
//
void sortOff(Spanner * spanarray)
{
Spanner tmp[] = {{0, 0, 0}};
for (uint8_t i = 0; i < (totalSpanners ); i++)
{
for (int j = 0; j < totalSpanners - 1; j++)
{
if ((spanarray[j].included) > (spanarray[j + 1].included))
{
tmp[0] = spanarray[j];
spanarray[j] = spanarray[j + 1];
spanarray[j + 1] = tmp[0];
}
}
}
}
//------------------------------------------------------
//
//Функция исключает из анализа (excluded) ключи, размеры которых дублируют обязательно включенные функцией unique()
//
//
void exclud()
{
uint8_t inSp = 0;
for (uint8_t i = 0; i < totalSpanners; i++)
{
if (spanners[i].included == on) inSp++;
}
inSp = inSp * 2;
uint8_t *arrayTemp = new uint8_t[inSp];
uint8_t j = 0;
for (uint8_t i = 0; i < totalSpanners; i++)
{
if (spanners[i].included == on)
{
arrayTemp[j] = spanners[i].size1;
j++;
arrayTemp[j] = spanners[i].size2;
j++;
}
}
for (uint8_t i = 0; i < totalSpanners; i++)
{
bool flag1 = false;
bool flag2 = false;
if (spanners[i].included == off)
{
for (uint8_t k = 0; k < j; k++)
{
if (spanners[i].size1 == arrayTemp[k] )
{
flag1 = true;
}
if (spanners[i].size2 == arrayTemp[k])
{
flag2 = true;
}
}
}
if (flag1 == true && flag2 == true )
{
spanners[i].included = excluded;
}
}
delete[] arrayTemp;
}
//-----------------------------------------------------------
//
// Функция определяет неповторяющиеся размеры и ключи, которым они принадлежат, "делаем" "on"
//
uint8_t unique()
{
uint8_t uniqueSpanner = 0;
for (uint8_t i = 0; i < totalSpanners; i++)
{
bool flag1 = false;
bool flag2 = false;
for (uint8_t k = 0; k < totalSpanners; k++)
{
if (i == k) continue; //сам на себя
if (spanners[k].included == excluded) continue; //на удаленный
if (spanners[i].size1 == spanners[k].size1 ||
spanners[i].size1 == spanners[k].size2)
{
flag1 = true;
}
if (spanners[i].size2 == spanners[k].size1 ||
spanners[i].size2 == spanners[k].size2)
{
flag2 = true;
}
}
if (flag1 == false || flag2 == false)
{
spanners[i].included = on;
uniqueSpanner++;
}
}
//Serial.print(uniqueSpanner);
return uniqueSpanner;
}
//------------------------------------------
//
//Функция вычисляет общее количество существующих размеров и Функция удаляет (excluded) дубли ключей
//
uint8_t totalSize()
{
uint8_t arrSize[totalSpanners * 2] = {};
uint8_t inSize = 0;
uint8_t y = 0;
for (uint8_t i = 0; i < totalSpanners; i++)
{
arrSize[y] = spanners[i].size1;
y++;
arrSize[y] = spanners[i].size2;
y++;
for (int k = i - 1; k >= 0; k--)
{
if ((spanners[i].size1 == spanners[k].size1 ||
spanners[i].size1 == spanners[k].size2) &&
(spanners[i].size2 == spanners[k].size1 ||
spanners[i].size2 == spanners[k].size2))
{
spanners[i].included = excluded;
}
}
}
for (uint8_t i = 0; i < totalSpanners * 2; i++)
{
uint8_t j = 0;
while (j < i && arrSize[j] != arrSize[i])
++j;
if (j == i)
inSize ++;
}
//Serial.print(inSize);
return inSize;
}
//-------------------------------------------------------
//
//Функция вычисляет общее количество размеров в "он" ключах
//
uint8_t onSize(int8_t qOn)
{
uint8_t arrSize[qOn * 2] = {};
uint8_t inSize = 0;
uint8_t y = 0;
for (uint8_t i = 0; i < totalSpanners; i++)
{
if (spanners[i].included == on)
{
arrSize[y] = spanners[i].size1;
y++;
arrSize[y] = spanners[i].size2;
y++;
}
}
for (uint8_t i = 0; i < qOn * 2; i++)
{
uint8_t j = 0;
while (j < i && arrSize[j] != arrSize[i])
++j;
if (j == i)
inSize ++;
}
//Serial.print(inSize);
return inSize;
}
//-------------------------------------------------------
void arrey__B_F(uint8_t qOff, uint8_t qOn, uint8_t *arrB_F)
{
uint8_t arrOn[qOn * 2] = {};
uint8_t arrSize[totalSpanners * 2] = {};
uint8_t inSize = 0;
uint8_t y = 0;
uint8_t z = 0;
for (uint8_t i = 0; i < totalSpanners; i++)
{
if (spanners[i].included == on)
{
arrOn[y] = spanners[i].size1;
y++;
arrOn[y] = spanners[i].size2;
y++;
}
arrSize[z] = spanners[i].size1;
z++;
arrSize[z] = spanners[i].size2;
z++;
}
for (uint8_t i = 0; i < totalSpanners * 2; i++)
{
uint8_t j = 0;
while (j < i && arrSize[j] != arrSize[i])
++j;
if (j == i)
{
bool flag = true;
for (uint8_t k = 0; k < qOn * 2; k++)
if (arrSize[i] == arrOn[k])
{
flag = false;
}
if (flag)
{
arrB_F[inSize] = arrSize[i];
//Serial.print(inSize); Serial.print(',');
inSize ++;
}
}
}
}
//
// функция сортировки ключей от дешевых к дорогим
//
void spanSort(Spanner * spanarray)
{
Spanner tmp[] = {{0, 0, 0}};
for (uint8_t i = 0; i < (totalSpanners ); i++)
{
for (int j = 0; j < totalSpanners - 1; j++)
{
if ((spanarray[j].price) > (spanarray[j + 1].price))
{
tmp[0] = spanarray[j];
spanarray[j] = spanarray[j + 1];
spanarray[j + 1] = tmp[0];
}
}
}
}
//------------------------------------------------------
void setup(void) {
Serial.begin(9600);
const uint32_t start = millis();
doCalculations();
const uint32_t duration = millis() - start;
printResults();
Serial.print("Время расчёта: ");
Serial.print(duration);
Serial.println(" миллисекунд");
}
void loop(void) {}
Циклы, о которых идет речь - стр. 178,179 и 198,199.
Собственно вопрос - что не так с Ардуиновским компилятором?
ПС. В моей версии ИДЕ 1.8.7 все ожидаемо: время с циклами 183 мс, без - 167мс
А почему Вы решили, что "не так" именно с компилятором?
1. Компилятор - оптимизирующий. Т.е. "лишние"циклы он и сам уберет.
1. 2% - это в пределах погрешности.
Следовательно, просто нет предмета для обсуждения: предполагаемый эффект находится в пределах погрешности, следовательно, наличие самого эффекта достоверно не обнаружено.
1. Компилятор - оптимизирующий. Т.е. "лишние"циклы он и сам уберет.
В данном случае он убрать их не может - посмотрите, пожалуйста, код. С таким же успехом можно утверждать, что компилятор может предугадать, для чего программист заложил то или иное логическое действие в программе и по своему усмотрению "оптимизировать" его. Это же не лишняя объявленная переменная.
1. 2% - это в пределах погрешности.
Следовательно, просто нет предмета для обсуждения: предполагаемый эффект находится в пределах погрешности, следовательно, наличие самого эффекта достоверно не обнаружено.
Погрешность millis в коде, где не вызывается ни одного прерывания и исполняемого одним и тем же МК??
Погрешность millis в коде, где не вызывается ни одного прерывания и исполняемого одним и тем же МК??
За 200 мс не вызывается ни одного прерывания? Да Вы в своем уме? Если у нас запрещены прерывания, так и millis работать не будет.
Upd: в любом случае, при 2% разницы предмета для обсуждения нет. Расставьте millis (micros) по разным местам своей программы, чтобы знать точно, какой фрагмент сколько выполняется (только печать вставляйте одну общую в конце, чтобы она не слишком влияла на измерения).
видимо потому, что программист писавший компилятор большой оригинал, я уже где-то об этом писал, что сделано всё взад, дракула откомментировал, что по тактам, как бы одинаково получается, а что затрагивается два флага, а при классическом коде всего один, так это вроде как бы и лучше, хотя Питер Нортон говорил об обратном (как раз в циклах) )))
Попробуйте прочитать пост #33 по возможности целиком, там и пояснения и пример.
В приведенном Вами примере на стадии компиляции уже были все данные для вычисления fact(5), что и было сделано, чтобы "облегчить жизнь" функции
void setup() { PORTB = fact(5); }По совету г-на andriano расставил временнЫе метки до и после пресловутых циклов. Цикл выполняется за 12 микросекунд(при 7 итерациях), компилятор его не "оптимизирует". Кажется логичным, что при исключения их из программы, время выполнения должно уменьшиться. Но это не так с точностью до наоборот.
Пытаюсь локализовать, где именно увеличивается время.
Да, это я написал про Вашу "лишнюю переменную". Там сейчас гораздо более серьёзные вещи оптимизируются.