Коли програмуєш на Lua в асинхронному середовищі, потрібно пам’ятати про повільні операції. З нашого досвіду операція конкатенації (об’єднання значень змінних типу string
) може виконуватися повільно і при підстановці “на льоту” може давати несподівані і часто негативні результати, що будуть призводити до помилок і зависань контролера, котрі майже неможливо відслідкувати.
Наприклад:
Маємо власну функцію:
1 2 3 |
function myFunction (a7, a6, a5, a4, a3, a2, a1, a0) i2c.write(id, tonumber(a7..a6..a5..a4..a3..a2..a1..a0, 2)) end |
Ми написали дуже зручну функцію, адже її параметри дозволяють у дружньому, побітовому вигляді задати значення регістра і передати його по шині I2C у підпорядкований девайс. Це може бути 8-бітне значення кольору світлодіода для новорічної святкової ілюмінації.
Ба більше, все у наведеному коді вірно і буде працювати, поки ви не запустите цю функцію у промислову експлуатацію.
Недолік #1
Експлуатація з робочим навантаженням – ось тоді і почнуться справжні проблеми: система буде в залежності від наявних ресурсів встигати чи не встигати перетворювати ваше число і підставляти його значення, адже в цій частині коду:
1 |
tonumber(a7..a6..a5..a4..a3..a2..a1..a0, 2) |
відбувається наступна робота:
- конкатинація параметрів у текстовий рядок;
- текстовий рядок сприймається як двійкове число і перетворюється у десяткове;
- строкове значення перетворюється у число типу integer;
- підставляється у якості параметра для передачі по шині I2C.
Вручну можна самостійно ввести команду – все наче нормально працює:
1 |
print(tonumber("1".."1".."1".."1".."1".."1".."1".."1", 2)) |
За умов великого навантаження і асинхронного виконання коду – ця функція не буде встигати виконуватися і рано чи пізно підвісить весь пристрій. Будьте певні, перевірено!
Недолік #2
Хоча дуже зручно передавати 0/1 як вхідні параметри функції, але зверніть увагу, скільки пам’яті буде зарезервовано під таку функцію? – У вісім разів більше, ніж якщо в якості параметра функції застосовувати готове десяткове чи шістнадцяткове число.
Висновок:
Враховуючи все вищезазначене, рекомендуємо переписати фунцію у такий спосіб:
1 2 3 |
function myFunction (my_data_byte) i2c.write(id, my_data_byte) end |
Це вивільнить купу пам’яті, буде не так зручно, але точно буде працювати за високої частоти викликання функції у процесі роботи пристрою.
Перевірка на практиці
Щоб перевірити, створимо дві функції і запустимо їх на виконання.
1 2 3 4 5 6 7 8 9 10 |
i2c.setup(0,2,1,i2c.SLOW) -- i2c bus init function myFunction1 (a7, a6, a5, a4, a3, a2, a1, a0) print(tmr.now()); i2c.write(0, a7..a6..a5..a4..a3..a2..a1..a0); return tmr.now() end > print(myFunction1(1,1,1,1,1,1,1,1)) 89576965 89583397 -- 89583397 - 89576965 = 6432 microseconds |
1 2 3 4 5 6 7 8 9 10 |
function myFunction2 (a) print(tmr.now()); i2c.write(0, a); return tmr.now() end > print(myFunction2(1)) 240454913 240458251 -- 240458251 - 240454913 = 3338 microseconds -- myFunction2(1) runs 48,10% faster comparing to myfunction1 execution time |
Як можна бачити, функція myFunction2
виконується майже у два рази швидше. Звісно, абсолютно точно стверджувати складно, але припустимо, що за всіх інших рівних умов, уповільнення відбувається на етапі обробки параметрів фунції myFunction1
, а також під час виконання конкатенації і підстановки у якості параметрів методу i2c.write()
.
Бажаємо успіхів!