Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni
🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!13.09.2023
Профілювання С-коду всередині Ruby
String#scan витрачає половину часу на створення обʼєктів MatchData
Докопався до правди по вчорашньому питанню. Для цього треба було знайти, як профілювати не Ruby-код, а C-код, який складає методи, які ми порівнюємо.
Вдалося це зробити за допомогою цієї статті. Потрібно встановити інструменти профілювання GPerfTools - вони є у Homebrew. А потім скомпілювати Ruby з додаванням профілювання. На моєму Macbook M2 для цього достатньо увімкнути дві опції:
env RUBY_CFLAGS='-fno-omit-frame-pointer' LIBS="-lprofiler" asdf install ruby 3.2.1
# Потім:
env CPUPROFILE="out.profile" ruby payload.rb
pprof $(asdf which ruby) out.profile --svg > out.svg
Результати можна побачити на ілюстрації зверху. Обидві реалізації витрачають більшість часу у виділенні памʼяті. (Решта — це пошук регулярного виразу, який відбувається практично однаково.) Але там, де StringScanner
просто бере довжину збігу та копіює від початку, String#scan
викликає код, що створює обʼєкти MatchData
, а з них вже отримує розташування та довжину збігів. Та, профілювання ясно показує, що саме виділення памʼяті для MatchData
становить додаткові витрати часу, які ми бачили вчора.
Чому String#scan
потрібні обʼєкти MatchData
? Тому, що цей метод шукає фрагменти не тільки на початку, а й в будь-якому місці рядка. До того ж він підтримує регулярні вирази з підгрупами. А StringScanner
розрахований на єдиний випадок використання. Маємо висновок: у програмуванні спеціалізація коду веде до його оптимізації.