Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
26.02.2024
embed.FS - включення файлів у програму на Go
Одна з моїх улюблених та недооцінених можливостей Go - це модуль embed. Завдяки йому можна скомпілювати в програму не тільки окремі файли, а й цілі директорії. Виглядає це приблизно так:
//go:embed resources/*.html resources/*.css
var resources embed.FS
index, err := resource.ReadFile("index.html")
У embed є дві форми: окремий файл та директорія. Файл в програмі стає звичайним рядком: дуже зручно. А директорія перетворюється на тип embed.FS, який сумісний з модулем io та тому підходить для багатьох споживачів — наприклад, можна роздавати ці файли по HTTP за допомогою http.FileServerFS.
Несподівано корисним embed є у тестах. Час від часу стає потреба завантажити до тесту допоміжні файли, які лежатимуть в той самій директорії. Але.. в якій такій тій самій, коли під час виконання ніякого вихідного коду вже немає? Добре, що тестова програма для модуля запускається саме в директорії модуля, тому можна вважати, що допоміжні файли поруч. Проте це працює тільки для файлів з того модуля, який тестується. А коли винесеш файли в інший модуль — доведеться вручну прописувати шлях. Так от, з embed ця проблема зникає — ніяких більше відносних шляхів.
25.02.2024
Водяне охолодження — з чого воно складається?
Система водяного охолодження має форму замкненого контуру з елементів, які поєднані трубками. На відміну від побутового водопроводу, який знаходиться під тиском, в системі охолодження вода ходить по колу, а значить, розгалужень бути не може. Відповідно, в кожного елемента завжди є один вхід та один вихід. Які то компоненти?
-
Теплообмінники (waterblock) - заміняють повітряні радіатори. Це важкущий блок з металу, який притискається до мікросхем, а всередині бігає вода та забирає тепло. Практично має сенс встановити теплообмінник на процесор та на відеокарту — хоч можна знайти й блоки для інших компонентів, але то вже зайве.
-
Радіатор (radiator) - який віддаватиме тепло у навколишнє середовище. На радіаторі будуть вентилятори — такі самі, які й в повітряній системі. Радіатор найскоріше кріпиться на корпус компʼютера. Сенс всієї системи в тому, що вода ефективно передає тепло від мікросхем до радіатора, а з радіатора можна ефективно “здути” тепло назовні — замість того, щоб вибудовувати складні повітряні потоки всередині корпуса.
-
Помпа (pump) - забезпечує потік води. Помпа — це єдиний новий активний елемент. Вода в системі мусить постійно рухатись — без цього вона перегріється. З погляду програмного забезпечення, помпа — це лише ще один “вентилятор”, який регулюється стандартним розʼємом. Часто помпа поєднується з бачком.
-
Бачок (reservoir) - просто містить резерв води. Резерв потрібний, бо вода завжди випаровується. Та й взагалі дуже складно було б без бачка заповнити всю систему без пухирів повітря — а пухирів нам абсолютно не потрібно, бо з ними охолодження не буде.
Чого тут немає, так це ніяких контролерів. Водяне охолодження керується самим компʼютером так само як і вентилятори, тільки криві незвичні. А про трубки та інші зʼєднувальні компоненти можна писати ще цілий пост.
24.02.2024
Водяне охолодження ПК — що воно таке?
В мене вже третій рік ПК працює на водяному охолодженні. Весь цей час збираюся щось написати, отже, сьогодні почну.
Мій ігровий ПК стоїть в зачиненій шафці під телевізором — в режимі HTPC - проте це потужний ігровий компʼютер. З самого початку йому не вистачало охолодження. Скільки вентиляторів не додавай в корпус компʼютера, шафка все одно перетворювалась на духовку. Можна було б придумати повітрообмінник з шафи назовні, але, після довгих роздумів, я все ж вирішив піти з водяною системою.
Що ж, водяне охолодження безперечно здатне перетворити теплові потреби компʼютера на щось абсолютно нове. Я обрав зовнішній радіатор — бо задача була винести тепло за межі шафки. Таке рішення працює абсолютно беззвучно більшість часу, та набагато тихіше звичайного охолодження під час найбільш навантаженої роботи: теплова маса радіатора дає величезну фору будь-якій іншій системі охолодження.
На мою думку, водяне охолодження варто своїх коштів… але кошти підуть немаленькі. Історій про фітинги, які протікають, трубки, які розкладаються, і так далі, достатньо, щоб зрозуміти, що заощаджувати не варто. Навпаки, компоненти від надійних виробників служать довго та абсолютно без проблем. Орієнтовно, я б закладав бюджет на систему у $500 - $1000.
Окрім витрат, доведеться мати базово-просунуті навички в цілій низці галузей. Розбирати компʼютер — очевидно. Але також, зняти радіатор з відеокарти (це зовсім не така типова задача, як з процесора!). Зібрати “сантехніку” та перевірити на протікання. З зовнішньою системою — подовжувати кабелі для вентиляторів. Вішати все це на стіну. А потім ще й налаштувати програмну частину, щоб керувати швидкістю помпи та вентиляторів (бо так, є ще й помпа зі своєю фізикою).
Одним словом, хобі як дороге, так і складне. Але принаймні результат того вартий. По-перше, це красиво та рідкісно. По-друге, дозволяє не згадувати про охолодження навіть у найвибагливіших іграх. Поки на цьому все, подробиці будуть пізніше.
23.02.2024
10 днів зі стохастичним таймтрекером
Вже трохи більше як тиждень користуюся своїм таймтрекером (який паралельно дописую). Коли виправив надсилання сповіщень, то, фактично, MVP вже був готовий. Нагадаю, що на відміну від типового трекера, цей — стохастичний — самостійно опитує (“пінгує”) тебе у випадкові моменти часу — так само як це роблять статистичні профайлери коду. В середньому, раз на 45 хвилин, але на практиці бачив і раз в 3 хвилини, і 3 години без пінгів.
-
Заносити записи легко, навіть без застосунку на Apple Watch. Поки є тільки застосунок на телефон, але випадків, коли телефона немає поруч, а можливість скористатися годинником є, дуже мало. На практиці я встигаю записати понад 90% записів. Якщо відразу не запишеш, потім часто це стає неможливим, оскільки…
-
Цей трекер потребує заносити те, чим займаєшся зараз. Якщо я пишу пост, а потім перемкнувся на новини, та у цей момент прийшов пінг трекера — запишу “читав новини”. Виходить, за винятком дійсно безперервних занять, через десять хвилин точні дані вже не згадаєш.
-
CтохастичнийТаймтрекер потребує міцної віри у статистику. З простого погляду, він виглядає нечесно, бо то пропускає важливі заняття (годину стояв в тягнучці, пінгів не було) або навпаки, перебільшує дрібні (три пінги за 10 хвилин вмивання). Тому треба памʼятати, що статистично цей метод є абсолютно чесним, більш чесним, ніж коли заносиш час на свій розсуд — бо в такому разі обминаєш “дрібні” або просто неприємні відрізки (такі, як перегляд новин під час “написання посту”.) А статистичний трекер навпаки привертає до таких “дрібниць” увагу — якщо я частіше “читаю новини” ніж “пишу пост”, то це вже привід задуматись над витратами часу.
-
Вплив на свідомість: відсутність передбачуваності пінгів та потреби власноруч заносити час призводить (може призвести) до загальної підвищеної усвідомленості — не заради трекера, а заради себе. Коли памʼятаєш, що в будь-який момент може прийти пінг та доведеться записати “читаю новини”, то не так вже й хочеться багато часу на те витрачати. (Але, також буває прикро, коли три години працюєш невпинно, а пінгів немає. Не пощастило. Але не в пінгах сенс.)
Поки експеримент мені подобається, далі буде.
22.02.2024
Проблема декількох версій залежностей
Є така проблемка в проєктах на Go, що у транзитивних залежностях може бути декілька версій однієї бібліотеки. Ну, наприклад, github.com/redis/go-redis/v9 та github.com/go-redis/redis/v8. Окрім того, що це збільшує розмір програми (та хто його лічить?) - є й ризик помилки.
(До речі, в Go предивна система версій модулів. Взагалі в Go модуль це тека. Ніякої “збірки” у .gem, .jar, чи навіть .zip немає. Назва модуля дорівнює його URL в Git. А наступна несумісна версія модуля створюється… не в теці v2, як може це здатись, а у гілці з назвою v2. При цьому назва модуля залишається такою самою. Мені доводилося це робити з нашою бібліотекою go-global, коли вона переїхала з AWS SDK v1 на AWS SDK v2, який цій системі не слідує.)
Коли ти оперуєш типами з пакета, легко помітити різницю між версіями, бо вони є несумісними. Але не всі операції потребують типів. Наприклад, константи помилок з різних версій пакета не проходитимуть перевірку на рівність. Та й інші нетипізовані константи. А ще глобальні налаштування на кшталт хуків — вони теж можуть піти не туди, куди треба.
Одним словом, коли значення, які за всією логікою мають збігатись, все ж розрізняються, перше, що спадає мені на думку — перевірити версії модулів.
Така сама тільки гірше проблема в JavaScript - бо там типи не допомагають, а залежностей експоненційно більше.
21.02.2024
Пара неочевидних ідіом у SwiftUI
Взагалі, помітив, що останнім часом багато натрапляю на прості, ідіоматичні рішення замість своїх ускладнених за браком знань. Вчусь, напевно. Бо буває, що ідіоматичний код очевидний, а буває навпаки, перелопатиш половину Інтернету, але на жаль не за тими пошуковими запитами.
-
Для відображення модального вікна вживається метод .sheet(isPresented:). (Це “модифікатор”, має схожу семантику до HOC у React, тобто надає компоненті нових властивостей, в цьому разі — можливість показати модал.) Типовий сценарій — модал редагування — потребує також вибору елементу, який зʼявиться в модалі. Тобто потрібно було мати дві змінні стану — видимість та елемент — та ще й синхронізувати їх поведінку. Дізнався, що є й інша форма цього методу: .sheet(item: $itemToEdit). Тут модал стане видимим, коли
$itemToEdit != nil, та автоматично присвоїть їйnilпри закритті вікна (Синтаксис з доларом$- це “привʼязка” - тобто спосіб передати посилання на значення, здатне на запис.) -
Є такий компонент
List, який виводить всілякі списки (Більша частина інтерфейсу iOS - списки різного роду. Форми це теж списки, до речі. ) Список може бути пласким, може — секційним. Але що, коли треба показати ієрархічний список — наприклад, файлову систему? В інтернеті знаходив багато рішень, від вкладенихListдо обчислення вірних відступів за горизонталлю. Проте сам компонентListмає режим з вкладеністю. Якщо передати аргумент children. Як це працює — бо “дітей” треба буде брати у кожного елементу, та ще й з вкладеністю? Уchildrenми передаємо шлях атрибута (key-path), за якимListзможе знайти дітей кожного конкретного елементу. Досить екзотична фішка Swift, як на мене.
20.02.2024
Операції з часом в OpenSearch
Поступово дізнаюся нові можливості OpenSearch (та ElasticSearch), які, хоч і базові, але чомусь на поверхні не лежать. Сьогодні — про те, що дати набагато гнучкіше, ніж я собі думав.
Взагалі OpenSearch зберігає дати як числа. Проте, як я розумію з коду, різниця в індексації є: як я писав, числа OpenSearch індексує деревом множин, щоб швидше шукати за діапазоном. Для дат ці множини будуються не аби як, а за одиницями часу: хвилинами, годинами й так далі: ось код. Звісно, це саме те, чого ми хочемо від пошуку за часом.
Але це таке, знати не потрібно. А що варто знати, так це те, що час можна вказати зі скругленням до потрібної одиниці. Наприклад, потрібно мені знайти записи за останній тиждень. Як вказати верхню межу? Відома проблема, що просто датою не можна — відбитки часу за останню дату будуть технічно більші за дату без часу. Тому можна вдаватись до хаків: написати 23:59:59 (і втратити відбитки за останню хвилину!), чи зробити строго менше наступної дати. Проте в OpenSearch можна просто зробити запит зі скругленням до дня:
{
"range": {
"timestamp": {
"gte": "2024-02-12||/d",
"lte": "2024-02-18||/d"
}
}
}
(Ба більше, в цьому прикладі можна було б скруглити до тижня та ще спростити собі роботу.)
Та й повертати значення дати можна відразу в такому форматі, як нам потрібно: для документів з пошуку є опція docvalue_fields (дивна назва), а для агрегацій за датою просто format. Трохи незвично таке робити в базі, бо я звик, що бібліотека-клієнт віддає обʼєкти дати, з якими вже робиш що хочеш — проте за відсутністю такої бібліотеки дуже зручно.
19.02.2024
Вебсервери на Ruby: чим вони відрізняються
Хотів в завершення теми про рівночасне програмування пройтися по серверах на Ruby та порівняти їх. Виявилось… що тема трохи застаріла. Памʼятаю часи, коли вибір сервера був гарячою темою — років з 10, а то й 15 тому. Але наразі все стабільно (та й Ruby перестав бути такою гарячою темою.) Отже.
-
Puma на цей час еталонний вибір. Заслужила місце в стандартному пакеті Rails, а це багато значить. Puma є як багатопотоковою, так і багатопроцесовою бібліотекою, тож все, про що я писав останні дні, до неї застосовується.
-
Webrick - офіційний сервер Ruby - є однопотоковим. Через це обробляє запити лише по черзі — навіть поки один з запитів чекає, скажімо, на інформацію з бази, у Webrick немає можливості взяти інший — тому процесор просто марнує час. Перевага Webrick у реалізації чисто на Ruby, а також у модульності: я використовував Webrick для інтеграції у застосунок проксі-сервера.
-
Unicorn колись був вибором №1, бо першим навчився робити декілька процесів та керувати ними — таким чином досягнувши паралелізму. Але всередині процесу був тільки один потік, тому Puma обʼєктивно виграє у швидкодії. Перевагою Unicorn є надійність, причому реальна: навіть критична помилка в коді призведе до відмови тільки в конкретному запиті, далі процес буде перезапущений. А в Puma якщо один з потоків ламається, наприклад, зависає (а таке трапляється й досі!) - то всі інші запити з цього процесу теж ламаються. Проте наразі Unicorn не підтримується, а в Puma за бажанням можна обмежити 1 потік на процес.
-
Thin - відрізняється використанням EventMachine - однопотокової бібліотеки для обробки вводу/виводу. За архітектурою вона схожа на JavaScript (який теж однопотоковий). Але, на відміну від JavaScript, для отримання переваг рівночасності наш код повинний був використовувати бібліотеки вводу/виводу з підтримкою EventMachine. В контексті вебсервера такий підхід втратив популярність, проте є багато популярних бібліотек, які й досі використовують EventMachine.
-
Практично у всіх серверів є парсер на C. Ну, бо так обʼєктивно швидше. JIT це гарно, але надійніше один раз переписати вузьке місце, ніж сподіватись на вдачу. Але, прошу помітити, всього на С ніхто не переписує.
НДНЧ: Puma - це все, що тобі потрібно.
18.02.2024
Рівночасне та паралельне програмування
Зі всіма цими написами про рівночасність та паралельність легко заплутатись. Що я не раз зустрічав у плануванні алгоритмів та підходів. (До речі, правильний український термін - рівночасність, а не одночасність, як я писав раніше. Жодний з них не є самоочевидним, на жаль.)
Рівночасність (concurrency) - це здатність програми утримувати декілька потоків виконання. Побутова аналогія: я можу “рівночасно” робити роботу та збирати Лего. Поки працюю — Лего не збирається. Потім припиняю роботу — сідаю за конструктор. Як робота, так і конструктор продовжуються з того місця, де я зупинився. Це рівночасні процеси. Але я не можу збирати в той самий час, як працюю. Це вже…
Паралелізм (parallelism) - це здатність виконувати більш ніж один потік у момент часу. Паралелізм потребує, в першу чергу, більше одного ядра процесора. Бо тут криється головний нюанс: йдеться про виконання процесором, не очікування на зовнішні ресурси. Очікування на ресурси є апріорі “паралельним”, навіть коли “паралелізм” нам недоступний — наприклад, з одним ядром процесора чи в мові з GIL.
Якщо продовжити аналогію, то поки замовлений конструктор тільки їде поштою, нічого не заважає мені працювати. А ось зібрати його в той самий час як я працюю, може тільки дружина — це вже справжній паралелізм!
Хиба в плануванні рівночасних програм полягає в відсутності розрізнення роботи процесора та вводу/виводу. Коли програма здебільшого зайнята вводом/виводом — здатність до паралелізму не важлива — ані за властивостями мови, ані за кількістю ядер процесора. А якщо потрібно прискорити обчислення — тоді треба шукати паралелізм.
17.02.2024
Навіщо потрібен GIL (глобальне блокування інтерпретатору)
Ще трохи розбирався з паралельним виконанням в Ruby. Хотів зрозуміти, чому в Ruby є GIL. Дізнався, що нічого в характері мови не потребує глобального блокування як такого. GIL це архітектурне рішення.
В будь-якій програмі будь-якою мовою з одночасністю виникають проблеми псування памʼяті через паралельний доступ. Байдуже, динамічна мова, чи інтерпретована, чи статична, чи використовує віртуальну машину. Щоб злагодити доступ, використовують різні механізми блокування. Навіть якщо код застосунку блокувань не містить, то системні підпрограми обовʼязково.
GIL - це найпростіша форма блокування. З GIL можна практично не згадувати про паралельний доступ в коді інтерпретатора. Проте, на жаль, мова втрачає всяку можливість паралельної роботи взагалі. Це не так страшно насправді оскільки головною задачею одночасного виконання є ефективна обробка вводу-виводу, а не паралельне використання ядер процесора.
Чому розробники інтерпретаторів йшли на такий компроміс? Як я розумію, причина суто історична. Інтерпретатори з GIL (Ruby MRI, CPython) зʼявилися ще у девʼяності, коли багатоядерних процесорів взагалі не існувало. Пізніше додати в код систему детальних блокувань практично нереально, тому й зробили просте глобальне блокування.
А є й популярні мови PHP та JavaScript - які взагалі не мають моделі паралельного виконання. Немає там можливості створити потік. І це їм не заважає бути одними з найпоширеніших мов на сервері.

