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

🤖🚫 Контент вільний від AI. Цей пост на 100% написаний людиною, як і все на моєму блозі. Насолоджуйтесь!

12.09.2023

Використання StringScanner для прискорення парсера на Ruby

Вже писав, як я прискорив бібліотеку для Ruby css_parser прибиранням регулярних виразів. Проте це було не єдине покращення того сезону. Нещодавня стаття Аарона Патерсона про клас StringScanner нагадала, що я теж залучав StringScanner, щоб прискорити парсер на 80%.

Зміни можна подивитись у цьому коміті. Як бачиш, вони мінімальні: просто замість методу String#scan використовую клас StringScanner. Цей клас існує ще з Ruby 1.8 - саме час його вивчити. :)

Бо мій канал не для того, щоб просто сказати, який клас хороший. Розберімося, чому. Перший ключ в тому, що він цілком написаний на C. Проте так само і String#scan. Взагалі в Ruby багато низькорівневих методів реалізовані на C, що трохи розвіює міф про те, що Ruby обовʼязково має бути повільніше за мови, що компілюються.

На жаль, профілювання коду Ruby не пояснить нам різницю між цими реалізаціями. Зате ось gist з бенчмарком, який показує, що на тривіальному прикладі StringScanner навіть на 170% швидше. Тобто різниця полягає не в виконанні регулярного виразу (тут тривіалізованому), а в тому, що відбувається окрім нього. Єдине, що обидва методи роблять окрім регулярки — це повертають знайдений рядок.

Я здогадуюсь, що різниця у виділенні памʼяті. Якщо пропустити мій бенчмарк через benchmark-memory, то виявиться, що String#scan виділяє в 5 разів більше памʼяті. Здається (без подальшого профілювання), що String#scan (тобто rb_str_scan) містить більш узагальнений код, а StringScanner#scan (тобто strscan_scan) - зоптимізований рівно під повертання результату з початкової позиції. (Так, до речі, є різниця в тому, що StringScanner шукає тільки на початку рядка — але зазвичай це саме те, що нам потрібно.)

Висновок тут, як і в попередній історії, такий, що виділення памʼяті всередині циклу може бути вузьким місцем та впливати не тільки на витрати ОЗУ, але також й на швидкість виконання.