Стендап Сьогодні 📢 Канал в 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 на оптимізований цикл на асемблері, де інкрементується адреса, розташована в регістрі процесора (що набагато швидше).
Не знаю, чи буде в житті ще один такий випадок, але принаймні можу в резюме написати, “професійний асемблерист”.