Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
23.03.2024
Як лягати спати раніше?
🐈 Я переконаний, що рано вставати — ознака здорового, продуктивного та щасливого життя. (Моя кішка — теж.) Проте… якщо в мене виходить вставати рано, то принаймні в комбо з нестачею сну.
Тому всі, хто не встає рано, не люблять цього робити. Рано вставати з недосипом безглуздо; якісний сон — теж засада гарного життя. Виходить, математично еквівалентно розглядати натомість, як рано лягати спати. Принаймні, з такою постановкою задачі ми здатні щось змінити, а не тільки поставити будильник гучніше.
Особисто в мене проблеми з тим, що я відкладаю низку задач на кінець дня. Потім підключається прокрастинація. Відкладання задач на найостанніший момент дня для мене виглядає (зовнішнє) саме як поведінка “сови”; проте я, для себе, не бачу нічого приємного або природного в такій поведінці.
В інші періоди життя я залишався не спати, щоб відібрати у дня час на себе, коли обовʼязки закінчились. Для такої поведінки вже є окрема назва - reverse bedtime procrastination. Тут можна звернути увагу, що час вранці так само належить собі, тільки вранці ти вже виспаний. (Якби ж ще воно допомагало сбе переконати.)
Я ніякого рішення запропонувати не можу, проте пропоную запитати себе: чи тобі не подобається прокидатись рано, або не висинатись?
22.03.2024
Клас Fiber в Ruby - кооперативна рівночасність
🧶 Готую тут матеріал про засоби рівночасності в Ruby, та відкрив для себе клас Fiber. Він дуже схожий на Thread, але насправді корисний зовсім в інших умовах. (Якщо ці назви класів перекладати, то все стає ще заплутаніше, тому я не буду.)
Fiber, на відміну від Thread, не є рівночасними в типовому розумінні. Натомість виконання передається від Fiber до Fiber явно, командами resume / yield. До виклику такої команди буде виконуватись тільки поточний Fiber - навіть у разі очікування на IO. Тобто можна сказати, що Fiber гарантовано не будуть паралельними.
Навіщо таке взагалі потрібно? Явно не для пришвидшення програми — його ми тут не побачимо. А для спрощення алгоритмів звернемо увагу, що кожний Fiber має власний стек. Це відкриває чудові можливості кооперації рекурсивних процесів.
Наприклад: нескладно написати обхід дерева. Але що якщо потрібно обійти одночасно два дерева? (Може, ми хочемо знайти між ними різницю.) Тут вже традиційна рекурсія не влаштовує. Я в такій ситуації зазвичай перетворюю рекурсію на цикл з “емуляцією” стеку — тобто положення в дереві відстежую у власній структурі даних.
Звісно, такий код буде складніше, ніж просто рекурсія. Тому я відразу побачив перевагу Fiber - з ними можна запустити дві рекурсії паралельно та взагалі не перейматись про внутрішню форму алгоритму. Це чудовий підхід для роз’єднання деталей виконання, які зазвичай тісно поєднані. Більше — в майбутній статті.
21.03.2024
Генерація CSV: насправді це дуже просто
Коли попереднього разу робив експорт в CSV (на Swift), то згадав, що зробив генерацію самотужки; тоді я просто обʼєднував поля в рядок комами.
Інтуїтивно здається, що то надто ненадійно та треба брати спеціальну бібліотеку. Це не так: щоб зробити вірний CSV, достатньо дотримуватись єдиного правила:
Якщо поле містить лапки, кому, або новий рядок (так, навіть це дозволено), то його потрібно взяти в лапки. При цьому лапки всередині поля замінити на подвійні лапки ("").
Навіть це перетворення не обовʼязково робити з кожним полем, а тільки з тими, що можуть містити ці символи; в першу чергу це користувацький текст. З іншими полями заздалегідь відомо, що брати в лапки потрібно: наприклад, дробові числа по українському правопису (тобто з комою.) А решту полів, значення яких ми контролюємо, можна залишити як є.
Так, можна всі ці нюанси залишити на бібліотеку (якщо вона є), я нічого проти не маю. Тільки варто також знати, що для такої задачі, серед інших, можна все зробити самому — тоді ми будемо впевнено генерувати CSV хоч з Баш-скрипту, хоч в SQL… хтозна, де доведеться!
20.03.2024
1BRC на Ruby: YJIT, потоки, знову Apple Silicon
В одному з обговорень згадали, що ще в Ruby є YJIT, а я згадав, що попередні вимірювання робив без нього. (YJIT оптимізує код через фіксовану адресацію там, де вона можлива.)
Отже, перевірив, що робить YJIT з найшвидшим з моїх рішень. Прискорення у 20% - з одного боку, до кратних прискорень паралелізації далеко, але з іншого, для зміни в один рядок коду (RubyVM::YJIT.enable з Ruby 3.3.0) - дуже приємно. Для досягнення такого результату вручну потрібна ретельна оптимізація коду, яка часто ще й ускладнює його читання.
Спробував також зробити розвʼязок з пулом потоків (Thread); хоч вочевидь це не дасть паралелізму, принаймні можна було б провести з користю час очікування на дані з диску. На жаль, оскільки час читання менше за час обробки, а сам пул має ненульову ціну, то розвʼязок з пулом виходить в кращому випадку не повільніше ніж без пула. Зате навчився синхронізувати виконання потоків, про пізніше можна окремо написати.
Нарешті, так і не вийшло виправити segmentation fault в моєму розвʼязку з пулом ракторів (Linux, Ruby 3.2.2 та 3.3.0.) Причому як з StringScanner, так і з StringIO помилка зʼявлялася, хоча в різних місцях. Це на fly.io; локально ж на Apple M2 як рактори, так і процеси дають прискорення у 2.5 разів при пулі з 8 потоків на 8 ядрах. Треба буде колись детально роздивитись, що ж не так з Apple Silicon; ну добре, повільні ядра — повільні, але хіба не має бути прискорення принаймні в 4+ рази?
19.03.2024
GC в Go посеред функції
Сьогодні розвідав дуже несподівану особливість Go: невикористана змінна може бути вивільнена ще до виходу з (теоретичної) області видимості.
Як це було. Є такий випадково хибний тест на обмеження кількості підключень. Він створює декілька одночасних підключень, і нарешті на N-те сервер повинен відмовити. Але на практиці час від часу не відмовляє.
Спочатку думав, що то проблеми синхронізації, чи затримки. Розставив логи. Виявилося, що в хибному випадку клієнти закривають підключення ще до останньої перевірки — тому під час перевірки вже є вільні місця. Шукав налаштування клієнта, які можуть змусити його відключитися. Не знайшов — та й за лічені секунди ніякі клієнти не відключаються.
Нарешті, випадково помітив, що якщо звернутися до масиву клієнтів після перевірки (хоч би для логування), то тест не хибиться! Виявилося, що це Go бачить, що я не звертаюся до масиву, та вважає його вільним для збірки сміття. Причому все це в межах однієї функції! Там, де візуально змінна “ще могла б знадобитися.”
Зробив демонстраційний скрипт, який ще раз підтверджує цю поведінку. (До речі, щоб побачити запуски GC, можна запустити з опцією GODEBUG=gogc=1)
Виходить, не дарма Go не дозволяє мати в коді невикористані змінні — бо життєвий цикл змінної вимірюється по використанню, а не по області видимості.
18.03.2024
Відкритий код Дії
Якщо ще не чули, то вихідний код “Дії"опублікували на GitHub. Ось мої думки без поглибленого, поки, аналізу.
-
Взагалі, з коду зрозуміло, що “Дія” - це сервіс-посередник для інших систем, та вона не зберігає всередині дані. Як сховище можна помітити MongoDB, проте там зберігаються тільки сервісні обʼєкти на кшталт налаштувань чи категорій.
-
Все побудовано на мікросервісах, що є найбільш правильною топологією, коли потрібно розділити доступ до даних. В когось це примха, а в для державних документів — можна очікувати найвищий можливий рівень ізоляції.
-
Головна мова бекенду - TypeScript. Я, звісно, великий прихильник TS, тож вибір ніби гарний. Можливо, TypeScript дає вірний баланс між гарантіями типізації та легкістю розробки (та доступністю розробників!). З іншого боку, я б очікував бачити щось компільоване та з ще кращою системою статичної перевірки. (Цікавий факт: Haskell стоїть вище за TypeScript в TIOBE Index. Ні, не думаю що це гарна ідея.)
-
Також, особливо лячна загроза вкладених залежностей в екосистемі JavaScript; як знають всі причетні, проєкти схильні непомітно підтягнути тисячі залежностей, кожний з яких можна використати як вектор атаки. Проте, цього можна уникнути ретельним аудитом, який, я дуже сподіваюсь, в “Дії” роблять.
-
Код чистий, лінтери є, тести є, типи правильні (без всяких там
any.) Для крутого опен-сорсу не вистачає коментарів. Але, окрім того, код читати приємно. Може, пізніше спробую розгорнути все локально та роздивитися уважніше.
17.03.2024
Найкраща у світі печена картопля
🥔☘️ Сьогодні раптом рецепт моєї улюбленої печеної картоплі (через день Святого Патрика, може? А насправді це чистий збіг.) Рецепт я побачив у Kenji Lopez-Alt та трохи адаптував для простоти.
-
Чистимо та нарізаємо повну каструлю картоплі. Шматок має бути сантиметр-два завтовшки. Можна товстими брусочками чи напівскибочками.
-
Відварюємо до напівготовності. Не забуваємо посолити! Зупинитись, коли картоплю легко буде проткнути ножем, але вона ще не розпадатиметься. Від закипання може хвилин 7. Зливаємо воду.
-
Прямо до каструлі додаємо: олію (я беру оливкову, але в гарячу картоплю можна й масло, й навіть курячий чи качиний жир, якщо такий залишився); спеції (в мене орегано, перець, але можна все що гарно з картоплею — часник, паприку, і так далі.)
-
Тепер — магія: закриваємо каструлю кришкою та інтенсивно трусимо пів хвилинки. Від того шматки мають вкритися шаром “пюрешки”; це пюре запечеться в найкращу у світі скоринку. Тут головне, щоб картопля не була надто розварена, бо геть розсиплеться.
-
Висипаємо все на лист, вкритий пергаментом. Розділяти шматки не потрібно; достатньо розрівняти, щоб все було одним шаром. Під час випікання вони зменшаться та запечуться кожний окремо (ще одна перевага цього методу.)
-
Печемо за температури 230, бажано з конвекцією. (Ще можна, мабуть, в аерогрилі, але я не пробував.) Час близько 40 хвилин. Краще дивитися за кольором картоплі та зупинитись на свій смак — бо всередині вона вже готова, ми тільки створюємо скоринку. Перегортати, за моїм досвідом, не потрібно, але можна десь о 30 хвилин перевірити та перегорнути, якщо знизу не запікається.
-
Картопля виходить мʼяка всередині та хрумка та обʼємна ззовні. Насолоджуємося з улюбленим соусом. Смачного!
16.03.2024
Запуск одноразових скриптів на Fly.io
Люблю хостинг Fly.io за його простоту (а ще за те, що до $5 на місяць вони не беруть оплату.) Тому, коли довелося запустити бенчмарк десь у хмарах, то саме Fly.io й взяв, хоч це не типове його використання.
Чому цей хостинг? Бо він побудований на Docker, але також підтримує стале сховище, доступ по SSH, та швидке масштабування, включаючи до нуля.
Отже, з чого почати: нам буде потрібний “застосунок” - він триматиме всі дані. flyctl app init. Застосунок матиме конфігураційний файл, також Dockerfile, та повинний запускати хоч якийсь “сервіс” - я бачив tail -f /dev/null як мінімальне рішення. Потім запускаємо: flyctl app deploy.
А далі flyctl ssh console відкриває звичайнісінький ssh, в якому будемо запускати бенчмарк чи інший скрипт. Для масштабування — команда flyctl scale вміє як змінити кількість ядер та памʼяті, так і кількість віртуальних машин.Нам потрібна лише одна, але дуже корисно масштабувати в нуль — так можна недорого запускати бенчмарки до 16 ядер та 128 Гб.
Залишається питання великих файлів. Пакувати гігабайти в образ Docker буде дуже повільно, та ще й відбуватиметься на кожну збірку. Тут є два шляхи: загальний — завантажити файл кудись на S3 та забирати звідти. Але краще — приєднати до машини том сталого сховища, та розмістити файли там — flyctl volumes create. Та, на відміну від, наприклад, AWS, до томів є легкий доступ — або через ssh, або навіть через SFTP командою flyctl ssh sftp shell.
…Я вже звик до того, що в AWS все робиться через шари бюрократії та непрямостей, тож радію, коли за 10 хвилин можна від чистого аркуша дійти до рішення.
15.03.2024
Місяць зі стохастичним таймтрекером
Мій таймтрекер живий та просувається до релізу. Ось такий прогрес за останні пару тижнів:
1: Повністю переробив інтерфейс вводу тегів. Початкова версія була примітивна та складалася з двох списків: використаних тегів та всіх інших; більшість роботи ж лягало на рядок пошуку, бо знайти в переліку з сотні тегів потрібний — це нереально.
Поточна версія пропонує ймовірні теги. Це або теги з попередніх записів, або ті, що вже зустрічалися в такий самий день тижня та годину. Такі теги додаються одним натиском. Можливість знайти тег з загального списку залишається, але як додаткова; там же ж можна й створити тег, якщо його ще не існує.
2: Шукаю нові шляхи аналізу тегів. Моя мета (на цей час) - допомогти краще використовувати свій час. Для цього в мене вже є графіки часу за тегом, проте їх не має сенсу робити детальніше, ніж по тижнях. Таких графіків достатньо для оцінки змін, але не для розуміння, що саме потрібно міняти.
Щоб побачити нагоди для змін, розробив аналіз тегів на суміжність. Наприклад: відкриваю тег “робота” та бачу що за ним часто слідує тег “в телефоні”. Що значить, напевно, що телефон під час роботи варто тримати якомога далі та не відвертатися на нього (нагадаю, що в цій системі трекінгу навіть якщо на “хвилинку” взяв телефон та у цей час прийшов пінг, то він заноситься як “в телефоні”, а не “робота”. ) Така аналітика вже допоможе виправляти свою поведінку.
Також додав таку метрику, як середню безперервну тривалість тегу. Так можна виявити заняття, які особливо затягують (або навпаки, затягують недостатньо.) В мене ось знайшлося, що Baldur’s Gate 3 затягує. Висновок: не сідати грати, аж допоки 1) не закінчиш всі задачі та 2) не підготуєш відомої точки зупинки.
14.03.2024
Особливості процесорів Apple Silicon
Як писав, вимірювання швидкості паралельних програм на Apple Silicon має неочевидні аспекти, через які я б радив взагалі не вимірювати нічого локально, а використовувати хмарну віртуальну машину.
Нюанс головний: в процесорі Apple Silicon є два типи ядер: швидкі та економні. Вони відрізняються швидкістю, розміром кеша, та, звісно, витратами енергії. Економні ядра — чудова інновація, бо вони дозволяють заощадити батарею під час виконання фонових, не вибагливих до швидкості процесів. Але… процеси чи потоки, що потрапляють на економні ядра, будуть виконуватись повільніше.
Причому примусово використовувати тільки швидкі ядра ми не можемо. Процеси по ядрах розподіляє операційна система. Для того в процесу є пріоритет (клас QoS); найменший пріоритет мають фонові задачі, які завжди виконуються на економних ядрах, а найбільший — графічні програми, робота яких нам буквально видна. Пріоритет можна побачити, зокрема, в htop - це стовпчик PRI. (Взагалі, htop - найкращий монітор процесів і на Apple Silicon теж.)
Все це гарно для користування, проте шкодить бенчмаркінгу. Зверх того, треба мати на увазі стандартні обмеження — запускати тести тільки при живленні від мережі, зупинити зайві процеси, перевірити охолодження (до речі: в macOS є процес kernel_task, який буквально відбирає час CPU, коли той перегрівається. Бачиш високу активність kernel_task - шукай причину перегріву.)
…При всьому цьому, мені так і не вдалося зробити розвʼязок з ракторами, який не був би у 2 рази повільніше за процеси — попри те, що процеси в мене комунікують рядками JSON, а рактори безпосередньо передають значення в памʼяті. Експериментальна технологія експериментальна.
(О, а ще — і я це перевіряв — запущена в фоні база даних має нижчий пріоритет, ніж запущена з термінала, та це впливає на швидкість тестів, які її використовують.)
Раджу ось цю низку статей, де в дрібних деталях розписано про поведінку Apple Silicon.

