Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

18.02.2024

Рівночасне та паралельне програмування

Зі всіма цими написами про рівночасність та паралельність легко заплутатись. Що я не раз зустрічав у плануванні алгоритмів та підходів. (До речі, правильний український термін - рівночасність, а не одночасність, як я писав раніше. Жодний з них не є самоочевидним, на жаль.)

Рівночасність (concurrency) - це здатність програми утримувати декілька потоків виконання. Побутова аналогія: я можу “рівночасно” робити роботу та збирати Лего. Поки працюю — Лего не збирається. Потім припиняю роботу — сідаю за конструктор. Як робота, так і конструктор продовжуються з того місця, де я зупинився. Це рівночасні процеси. Але я не можу збирати в той самий час, як працюю. Це вже…

Паралелізм (parallelism) - це здатність виконувати більш ніж один потік у момент часу. Паралелізм потребує, в першу чергу, більше одного ядра процесора. Бо тут криється головний нюанс: йдеться про виконання процесором, не очікування на зовнішні ресурси. Очікування на ресурси є апріорі “паралельним”, навіть коли “паралелізм” нам недоступний — наприклад, з одним ядром процесора чи в мові з GIL.

Якщо продовжити аналогію, то поки замовлений конструктор тільки їде поштою, нічого не заважає мені працювати. А ось зібрати його в той самий час як я працюю, може тільки дружина — це вже справжній паралелізм!

Хиба в плануванні рівночасних програм полягає в відсутності розрізнення роботи процесора та вводу/виводу. Коли програма здебільшого зайнята вводом/виводом — здатність до паралелізму не важлива — ані за властивостями мови, ані за кількістю ядер процесора. А якщо потрібно прискорити обчислення — тоді треба шукати паралелізм.


17.02.2024

Навіщо потрібен GIL (глобальне блокування інтерпретатору)

Ще трохи розбирався з паралельним виконанням в Ruby. Хотів зрозуміти, чому в Ruby є GIL. Дізнався, що нічого в характері мови не потребує глобального блокування як такого. GIL це архітектурне рішення.

В будь-якій програмі будь-якою мовою з одночасністю виникають проблеми псування памʼяті через паралельний доступ. Байдуже, динамічна мова, чи інтерпретована, чи статична, чи використовує віртуальну машину. Щоб злагодити доступ, використовують різні механізми блокування. Навіть якщо код застосунку блокувань не містить, то системні підпрограми обовʼязково.

GIL - це найпростіша форма блокування. З GIL можна практично не згадувати про паралельний доступ в коді інтерпретатора. Проте, на жаль, мова втрачає всяку можливість паралельної роботи взагалі. Це не так страшно насправді оскільки головною задачею одночасного виконання є ефективна обробка вводу-виводу, а не паралельне використання ядер процесора.

Чому розробники інтерпретаторів йшли на такий компроміс? Як я розумію, причина суто історична. Інтерпретатори з GIL (Ruby MRI, CPython) зʼявилися ще у девʼяності, коли багатоядерних процесорів взагалі не існувало. Пізніше додати в код систему детальних блокувань практично нереально, тому й зробили просте глобальне блокування.

А є й популярні мови PHP та JavaScript - які взагалі не мають моделі паралельного виконання. Немає там можливості створити потік. І це їм не заважає бути одними з найпоширеніших мов на сервері.


16.02.2024

TimeTag - інтервал впевненості

Перейшов потроху зі збору даних (який працює досить непогано!) до аналізу. Тут не все просто: оскільки тайм трекер питає тебе в випадкові моменти, то не можна просто підрахувати розподіл результатів. На додаток маємо обчислити інтервал впевненості: “цією міткою відмічено 2 години плюс мінус 30 хвилин”.

Шляхом реверс-інжинірінгу алгоритму та пошуку в інтернеті (я в статистиці нічого не розумію) знайшов купу різних формул. Проблема тут така, що одні терміни спираються на інші, і так далі, та часто взагалі не зрозуміло, чи рухаєшся в правильному напрямку.

Нарешті, знайшов як сформулювати питання та знайшов таке саме на StackOverflow. Яке ж було моє здивування, коли помітив, що питання поставив оригінальний автор алгоритму! На щастя, він знайшов правильну формулу для інтервалу впевненості, та ще й саме для релевантної задачі.

Є тільки одна маленька проблема… формула використовує обернену гамма-функцію (TIL.) У автора вона написана в пакеті Mathematica. А як мені обчислити таку в коді? Спочатку знайшов npm-пакет, та навіть почав перекладати на Swift. Проте, перекладати складні математичні функції без розуміння ну дуже ризиковано — там добра тисяча рядків коду. Та й де гарантії, що автор невідомого NPM-пакету не встиг вже наробити помилок?

Тоді згадав про найстаріший трюк математичного програмування — обчислення за таблицею. Все, що мені потрібно — це знати значення оберненої гамма-функції для можливих значень кількості моментів з міткою. А значення можна взяти в Mathematica, точніше, у Wolfram Alpha. Причому відразу масивом — безплатно дає до 2 тисяч значень за раз, більше не проходить за часом:

InverseGammaRegularized[Range[8001,10000],0.025]

Для 10000 моментів інтервал впевненості дорівнює 4% - я думаю, що похибка “вимірювання” буде більша, тож немає сенсу збільшувати математичну точність. А зберегти в коді 20000 значень незрівнянно легше (та швидше у виконанні!), ніж обчислювати їх. Колись таким чином ігри зберігали тригонометричні функції та таке інше.


15.02.2024

Виправив підписку на Twitter через RSS

Як я дуже давно писав, для стеження за джерелами інформації використовую RSSHub. От є тільки невеличка проблемка — з Twitter останнім часом щось… трохи сталося та стрічки вже з пів року не працювали. Нарешті виправив.

Взагалі зараз для отримання постів потрібна авторизація. Але й з нею є різні дивні обмеження. Через це, наприклад, відомий альтернативний фронтенд Nitter, скоріше за все, припиняє існування.

Останнім часом Nitter використовував ось такий кривий хак: після встановлення застосунку Twitter на Android тобі видавався тимчасовий гостьовий акаунт. Ось цей акаунт вони й використовували — в сукупності з клієнтськими ключами того ж застосунку — для доступу через внутрішній API, на котрому побудовані офіційні застосунки. Такі відчайдушні рішення довго не тривають — особливо на публічному сервісі, яким був Nitter. Так і стало — гостьові акаунти більше не видають.

А для моєї особистої копії RSSHub поки працює такий саме підхід, тільки вже з особистим акаунтом. Для того дехто написав скрипт, яким можна “зайти в застосунок Android” та отримати ключі. Навіть не потрібно створювати свій проєкт для доступу до API. Далі ключі додаються в RSSHub та — о диво — стрічки знову працюють. Тільки чи надовго?


14.02.2024

Автозберігання у SwiftData

Як писав, SwiftData дуже відрізняється від рішень зберігання даних на вебі. Сьогодні переконався ще в одній суттєвій різниці.

У SwiftData всі зміни зберігаються автоматично. Коли ти змінюєш значення поля — воно відразу заноситься в базу. Збереження відбувається, як розумію, “миттєво”, а точніше, по закінченню поточного “циклу виконання” (тобто коли контроль повертається від програми до системи).

Це, прям, змушує переосмислити свій підхід до роботи з моделлю. Наприклад, валідація має відбуватись окремо. Та створювати обʼєкти моделей можна тільки тоді, коли вони готові бути збереженими (та стати доступними в будь-якому іншому контексті.) До речі, не відразу, але знайшов офіційний посібник, який радить використовувати для форм окремі змінні, а не поля моделі.

Збереження в кінці циклу виконання надає йому неявний пакетний характер: всі зміни, зроблені за один цикл, будуть збережені разом — тож автоматичне збереження має працювати достатньо ефективно, наприклад, коли ми оновлюємо тисячу обʼєктів в циклі. Та й взагалі, так воно більш передбачувано працює: наприклад, якщо перший рядок призначає одне поле, а другий — інше, то можна бути впевненим, що збережені вони будуть разом.

Спочатку я спробував вимкнути автоматичне збереження, щоб зробити код більш звичним. Проте це працює погано. Наприклад: чомусь призначення асоціації все ж завжди зберігаються автоматично. Може, я винний, але так само ймовірно, що це якийсь баг у SwiftData. Що я розумію про Swift та все навколо нього — краще дотримуватись стандартного підходу. (А може, це гарний підхід всюди, де в тебе немає глибокого досвіду.)


13.02.2024

TimeTag - прогрес, автозаповнення

Стохастичний таймтрекер набуває форму. Зʼясував, чому (ще) сповіщення не були надійними — виявилося, що максимальна кількість запланованих сповіщень це 64. А я створював 100. При цьому 36 (практично випадкових) сповіщень затиралися іншими, без жодних попереджень. Обожнюю, коли так відбувається.

Після цього виправлення застосунок став працювати так, як я хотів. Залишається додати ряд функцій для зручності: від можливості повторити попередній набір тегів для наступного запису, до автозаповнення тегів, спираючись на різні джерела.

Одним з таких джерел є Apple Health. В базі Health зберігається журнал сну та тренувань. Для збереження принципу випадковості я не можу просто скопіювати їх у трекер. (Нагадаю, що трекер будується на тій ідеї, що історія станів у випадкові моменти дає репрезентативну статистику.) Доведеться знайти ті (випадково заплановані) пропущені записи, що припадають, наприклад, на сон, та помітити їх як сон.

Нарешті, щоб ефективно застосувати масив проміжків сну до масиву пропущених записів, варто уникнути простого вкладеного циклу, який веде до квадратичної складності. Натомість обидва масиви потрібно відсортувати за часом, та просуватись обома масивами одночасно — а точніше, просуватися масивом проміжків, та всередині “доганяти” поточний проміжок вказівником на поточний запис. Так отримуємо лінійну складність: кожним масивом ми пройдемося тільки один раз.


12.02.2024

Failure demand

В мене є модель мислення, про яку не часто чую. Це failure demand - в моєму перекладі “попит через відмову”. Модель ця зʼявилася у сфері обслуговування, але гарно допомагає оцінювати потреби інженерних команд.

Модель каже, що попит на продукт складається з двох частин: “попит на цінність” - це звернення клієнтів для отримання від продукту того, що вони хочуть; та “попит через відмову” - це звернення тих клієнтів, які не отримують те, чого хочуть. Ніби й те, і те — попит, але тільки один приносить користь — бо попит через відмову лише виводить в нуль попередньо завдані втрати.

Так само можна розділити розробку програм. Є розробка, яка впроваджує функції, які потрібні користувачам. А є розробка, яка робить не те що потрібно. Це, в першу чергу, виправлення багів, але також і розробка зайвих функцій або розробка в такому вигляді, який буде незрозумілим та неоціненим.

В програмних продуктах завжди хочеться отримати результат швидше. Можна працювати більше — брати більше людей, збільшувати робочий день. Проте збільшення людиногодин задовольняє обидва попити без розбору. Тобто виходить, що тільки ще більше ресурсів піде на попит через відмову!

Тому, кажуть, команда повинна мати мету знижувати попит через відмову. Але, оскільки цей показник зовнішній, вплинути на нього можна не напряму, а через покращення процесів. Не тільки для уникання багів, але й кращого розуміння розробників про продукт та потреби користувач. Коли розробнику треба “взяти спринт на виправлення багів”, то перед залученням другого розробника варто зрозуміти, звідки ті багі беруться.


11.02.2024

Go 1.22

Вийшов тиждень тому Go 1.22. Для мене це чудова новина, бо цей реліз чекаю ще десь з початку осені. Не тому, що вона щось таке нове приносить, а через комбінацію обставин.

По-перше, все ж дещо нове зʼявилось у версії 1.21 - це структурне логування. Я б хотів уніфікувати все логування в проєкті навколо стандартної бібліотеки, включаючи надсилання помилок в Sentry. (Про все це напишу, як відбудеться.)

По-друге, в тій же ж 1.21 виправили цікаву помилку в розборі поштових повідомлень — а саме, повідомлень без змісту, а тільки з заголовками, що цілком дозволено стандартом. Стандарту тридцять років, а баги все виправляють. Причому оскільки бібліотека розповсюджується з Go, то я не знаю способу оновити її без переходу на нову версію компілятора та всього іншого.

Чому ж я досі не на 1.21? А там вилізла інша помилка, ще страшніша, бо сервіс геть вилітав з повідомленням fatal: morestack on g0. Довелося відкотитися на 1.20. Помилку виправили, але тільки для 1.22, а бекпорт для 1.21 так і не встиг вийти. Тож той код, якому потрібно було розбирати повідомлення без змісту, чекав на 1.22 - і нарешті дочекався.


10.02.2024

Сповіщення в iOS: не все так просто

Для проєкту стохастичного таймтрекера сповіщення видались не такою простою задачею, як я собі уявляв.

Парафразую: сповіщення — це привілей, а не право застосунку. В мене за звичкою з більш прямолінійних систем було уявлення, що скільки я сповіщень створю, стільки й зʼявиться. Але на практиці виходить не так. Є купа незрозумілих обставин, за якими сповіщення ти побачиш тільки в журналі, постфактум. Так що доведеться багато експериментувати з налаштуваннями. Наприклад, є таке як interruptionLevel = .timeSensitive. Емпірично, воно не рятує, але допомагає.

Є ще критичні сповіщення — це такі, які приходять від “Тривоги”, наприклад. Критичні сповіщення обходять всі обмеження системи (якщо користувач їх не вимкне.) На них потрібний спеціальний дозвіл, який видає підтримка Apple. Оце рівень ексклюзивності! Я для MVP спробую туди не лізти.

Ще виявилося, що зміст сповіщення повинний вказувати звук — інакше сповіщення зʼявиться абсолютно непомітно. В мене взагалі завжди звук вимкнений, тому і в цьому застосунку я планував спиратись на вібрацію Apple Watch - але щоб отримати цю вібрацію, маємо зазначити звук. Така не дуже очевидна конфігурація.


09.02.2024

Імпорт з CSV в Taxer

Сьогодні — останній день подачі податкової декларації. А значить… я дізнався про новий функціонал Таксера — імпорт операцій з CSV. (Ну, можливо, вона там існує з минулого року, але я тільки помітив.)

В сукупності з експортом CSV з кабінету ПриватБанку чи Монобанку маємо просте напівавтоматичне рішення для підготовки звітів. Та, коли мова йде про фінансові сервіси, мені спокійніше так, ніж інтеграцією через API.

Тепер, про технічну реалізацію. Таксер просить CSV в суворо фіксованому форматі. Це хоч трохи незручно, зате спрощує логіку на їхньому боці — а це значить, знижує шанс непередбачуваної ситуації. Що знов-таки вигідно під час роботи з фінансовими даними.

Щоб збудувати CSV вірного формату, можна скористатися або редактором таблиць, або написати скрипт. Я зробив невеличкий скрипт на Ruby, ділитися яким немає сенсу — він надто спеціалізований. Ну, хіба що ось розбір CSV з Монобанку.

Під час імпорту є не тільки валідація, а й “транзакційність” в тому сенсі, що якщо бодай одна операція буде хибною (уводить рахунок в мінус, наприклад), то весь CSV не буде імпортований. Думаю, всім сервісам так варто робити, бо після виправлення помилок не доведеться ще й відфільтровувати вже імпортовані рядки.