Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!

06.09.2023

Про те, як я колись переписував код на асемблері

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

Мова йде знову про бітові мапи. Одна з ключових операцій з ними — підрахунок кількості “одиничних” бітів в числі (а точніше, в масиві чисел довжиною в мегабайт або більше.) Окрім очевидного підходу “в циклі зсувати число та перевіряти нижній біт” є набагато більш ефективна формула ваги Гемінга. Але максимально ефективно буде використати інструкцію процесора, яка робить саме те, що нам потрібно: POPCNT.

На той момент (2016 рік) готового рішення для використання її з Go не було (А зараз є, наприклад, модуль go-popcount.) Проте інструкція була настільки смачна, а операція настільки важлива для нашого проєкту, що я вирішив власноруч її інтегрувати.

Go підтримує код на асемблері без додаткових засобів. До модуля можна просто додати файл .s з реалізацією однієї або декількох функцій. Звісно, такий файл буде обмежений однією з архітектур, тому можна також написати реалізацію за замовчуванням - на Go.

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

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

Не знаю, чи буде в житті ще один такий випадок, але принаймні можу в резюме написати, “професійний асемблерист”.