
Тестовий патч усуває перестановку байтів при конвертації IPv4 в IPv6 у netip Go
Тестова перезапис компілятора перетворює патерн Go netip.AddrFrom16(ip.As16()) на пряме копіювання внутрішнього поля та генерує той самий короткий асемблер, що й рішення стандартної бібліотеки з 0,88 нс.
Чому патерн конвертації повільний
Go netip.Addr пропонує метод Unmap(), який повертає розгорнуту IPv4-адресу з IPv6-адреси, відображеної як IPv4. Для зворотної операції не існує методу Map() чи To6(). Супроводжувачі Go відмовилися додавати його і спрямовують користувачів до netip.AddrFrom16(ip.As16()), сподіваючись, що компілятор оптимізує це.
Всередині netip.Addr зберігає IP-адресу як 128-бітове значення і використовує додаткове поле для шифрування її сімейства та зони. Стандартна реалізація To6() в бібліотеці могла б змінити це поле для IPv4-адрес. Зовнішня ж допоміжна функція навпаки повинна перетворити адресу на 16-байтовий масив і повернути назад.
Результати бенчмарків
У бенчмарках на Go 1.27.1 у Linux на 6-ядерному процесорі AMD Ryzen 5 5600X рішення стандартної бібліотеки потребувало 0,8775 нс на операцію. Безпечна допоміжна функція потребувала 7,137 нс, а небезпечна реалізація, яка отримувала доступ до того самого внутрішнього стану через проксі, — 0,8682 нс. Тому допоміжна функція була приблизно вісім разів повільнішою.
Асемблер стандартної бібліотеки та небезпечної версії був майже ідентичним. Обидві перевіряли сімейство адреси та змінювали значення внутрішнього сімейства під час обробки IPv4. Безпечна допоміжна функція генерувала більше інструкцій, оскільки розміщувала адресу в 16-байтовому масиві, копіювала масив і знову розпаковувала його. Станом на Go 1.26.8 компілятор не усував цю послідовність.
Експеримент на рівні компілятора
Експеримент переписує саме патерн netip.AddrFrom16(ip.As16()) під час фази noding компілятора. Він замінює виклики структурою netip.Addr, яка копіює оригінальну адресу та призначає значення сімейства IPv6. Перезапис має відбуватися під час noding, оскільки більш рання фаза перевірки типів забороняє доступ до неекспортованих полів структури.
Модифікована збірка з перезаписом генерувала той самий короткий асемблер, що й пряма реалізація, і тести успішно проходили для net/netip та допоміжного пакета. Аналіз стверджує, що супроводжувачі Go навряд чи погодяться з цим підходом, оскільки він залежить від внутрішнього розташування netip.Addr і покладає більше відповідальності на noder для відповідного перекладу перевіреного синтаксичного дерева типів.
Запропонована SSA-оптимізація
Більш загальний підхід націлений на загальну фазу SSA компілятора, зокрема на прохід memcombine. Запропоновані правила перезапису перенаправляють завантаження через переміщення пам'яті, передають збережені значення до наступних завантажень та скасовують пари операцій обміну байтами.
Для кожної 64-бітової половини адреси ці перетворення усувають тимчасовий масив, його копію та зайвий доступ до пам'яті. Передача більш високої половини також можлива через операцію зберігання на сусідній, що перекривається адресі. Після спрощення кожна вихідна половина стає копією відповідної вхідної половини, що залишає дані адреси незмінними та встановлює потрібне значення сімейства.
SiTech — веброзробка з підтримкою AI
Створюємо швидкі та сучасні сайти й інтегруємо AI у бізнес-процеси. Маєте проєкт чи запитання? Із задоволенням допоможемо.