Підходи проти кодової бази стають однаково повільними


11

Ми працюємо над кодом середнього розміру C ++ (10Mloc), який завдяки нашим зусиллям з оптимізації стає однаково повільним .

Ця база коду - це набір бібліотек, які ми поєднуємо, щоб привести їх у роботу. Коли розроблялися загальні рамки того, як ці бібліотеки спілкувалися, було зроблено деякий акцент на продуктивності і пізніше, коли додавалися ще частини, загальна структура не сильно змінилася. Оптимізацію проводили в міру необхідності та в міру розвитку нашого обладнання. Це зробило дороге раннє рішення очевидним лише значно пізніше. Зараз ми перебуваємо на етапі, коли подальші оптимізації значно дорожчі, оскільки потребують переписування великих частин кодової бази. Ми вважаємо, що ми наближаємось до небажаного локального мінімуму, оскільки знаємо, що в принципі код повинен бути в змозі працювати набагато швидше.

Чи існують якісь успішні методології, які допомагають вирішити, що перетворюється на еволюцію кодової бази до глобально оптимально працюючого рішення, яке не легко переплутати через прості можливості оптимізації?

EDIT

Щоб відповісти на питання, як ми зараз займаємося профілем:

У нас дійсно є лише два різних сценарії, як цей код можна використовувати, обидва бентежно паралельні. Профілювання проводиться як з усередненим часом настінного годинника на великій вибірці входів, так і з більш детальними пробіжками (витрати на інструкції, непередбачення гілок та проблеми кешування). Це добре працює, оскільки ми працюємо виключно на наших надзвичайно однорідних машинах (група з декількох тисяч однакових машин). Оскільки ми зазвичай тримаємо всі наші машини зайняті більшу частину часу швидше, значить, ми можемо переглянути додаткові нові речі. Проблема, звичайно, полягає в тому, що коли з’являться нові вхідні зміни, вони можуть отримати штраф у пізньому стані, оскільки ми усунули найбільш очевидні мікроефективність для інших випадків використання, таким чином, можливо, зменшимо кількість «оптимально працюючих» сценаріїв.


10
10Mloc - це насправді величезний проект
BЈoviћ

1
Це 10 мільйонів локальних (префікс SI), як підраховано sloc. Я назвав це "помірно розміром", тому що я не маю уявлення, що тут вважається "великим".
Бенджамін Баньє

5
майже впевнений, що 10 мільйонів принаймні великі скрізь і, мабуть, величезні більшість місць.
Рятхал

1
Дивовижне, спасибі @honk Для 10M LOC це здається, що ви оптимізуєтесь на дуже низькому рівні, майже на апаратному рівні? Традиційний OOP ("масив структур" AOS) жахливо неефективний у кешах. Ви намагалися переставити свої класи на SOA (структуру масивів), щоб точки даних, над якими працює ваш код, є когерентними в пам'яті? Завдяки тому, що на багатьох машинах ви стикаєтесь із блокуванням зв'язку чи синхронізацією. Остаточне запитання, чи маєте ви справу з великими обсягами потокової передачі даних або це здебільшого проблема складних операцій на ваших наборах даних?
Патрік Хьюз

1
Коли у вас є стільки коду, шанси перебігати від відмінних до ставок, що існують великі потенційні прискорення немісцевого виду, про які я згадав. Це не має значення, якщо є тисячі потоків / процесів. Деякі випадкові паузи або підкажуть їх вам, або докажуть, що я неправильний.
Майк Данлаве

Відповіді:


9

Я не знаю загальноприйнятого підходу до цієї проблеми, але два дещо пов'язані підходи працювали для мене в минулому: через відсутність кращих термінів я назвав їх групою і горизонтальною оптимізацією .

Підхід групи - це спроба замінити велику кількість коротких, швидких операцій однією, більш повільною, високоспеціалізованою операцією, яка в кінцевому підсумку дає однаковий результат.

Приклад: Після профілювання одного особливо повільного функціонування нашого редактора візуальних правил ми не виявили "плодів з низьким рівнем звисання": не було жодної операції, яка забирала б більше 2% часу виконання, але ця операція в цілому відчувала себе млявою. Однак ми виявили, що редактор надсилає на сервер велику кількість невеликих запитів. Незважаючи на те, що редактор швидко обробляв окремі відповіді, кількість взаємодій запит / відповідь мала мультипликативний ефект, тому загальний час роботи тривав кілька секунд. Після ретельної каталогізації взаємодій редактора під час цієї тривалої операції ми додали нову команду до серверного інтерфейсу. Додаткова команда була більш спеціалізованою, оскільки приймала дані, необхідні для виконання підмножини коротких операцій, досліджував залежності даних, щоб визначити кінцевий набір даних для повернення, і надав відповідь, що містить інформацію, необхідну для виконання всіх окремих невеликих операцій за одну поїздку на сервер. Це не скоротило час обробки нашого коду, але скоротило дуже значну кількість затримок за рахунок видалення декількох дорогих туристських поїздок клієнт-сервер.

Горизонтальна оптимізація - це споріднена техніка, коли ви усуваєте "повільність", яка тонко розподіляється між декількома компонентами вашої системи, використовуючи певну особливість вашого середовища виконання.

Приклад: Профілювавши тривалу операцію, ми виявили, що ми здійснюємо багато дзвінків через межу домену додатків (це дуже специфічно для .NET). Ми не змогли усунути жоден з дзвінків, і ми не змогли зібрати їх разом: вони надходили в різний час із дуже різних розділів нашої системи, а запитувані ними речі залежали від результатів, отриманих від попередніх запитів. Кожен виклик вимагав серіалізації та десеріалізації порівняно невеликого обсягу даних. Знову ж таки, окремі дзвінки були невеликими за тривалістю, але дуже великими. Ми закінчили розробити схему, яка майже повністю уникала серіалізації, замінивши її перенесенням покажчика через межу домену додатка. Це було великим виграшем, оскільки багато запитів з абсолютно неспоріднених класів миттєво стали набагато швидшими в результаті застосування синглугоризонтальне рішення.


Дякуємо, що поділилися своїм досвідом. Це корисні оптимізації, які потрібно пам’ятати. Крім того, оскільки вони піднімуть проблемні частини на чільне місце, в майбутньому їх буде набагато краще контролювати. У певному сенсі вони ставлять на місце те, що повинно було статися в першу чергу, тепер лише за допомогою жорстких даних.
Бенджамін Баньє

3

Це зробило дороге раннє рішення очевидним лише значно пізніше. Зараз ми перебуваємо на етапі, коли подальші оптимізації значно дорожчі, оскільки потребують переписування великих частин кодової бази.

Коли ви починаєте це перезапис, вам потрібно зробити кілька справ по-різному.

Спочатку. І найголовніше. Перестаньте "оптимізувати". "Оптимізація" взагалі не має великого значення. Як ви бачили, лише оптом перепишіть справи.

Тому.

Друге. Зрозумійте наслідки кожної структури даних та вибору алгоритму.

Третя. Зробіть фактичний вибір структури даних та алгоритму питанням "пізньої прив'язки". Проектні інтерфейси, які можуть мати будь-яку з декількох реалізацій, що використовуються за інтерфейсом.

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


1
Дякую за вашу відповідь. Хоча нам ще потрібно оптимізувати (що підпадає під 1. і 2. для мене), мені дуже подобається організоване мислення за 3. Створюючи структуру даних, алгоритм та доступ, визначені пізно і явно, треба мати можливість отримати обробку для багатьох проблеми, з якими ми стикаємось. Дякуємо, що виклали це на цілісну мову.
Бенджамін Баньє

Насправді вам не потрібно оптимізувати. Після правильної структури даних оптимізація виявиться марною витратою зусиль. Профілювання покаже, де у вас неправильна структура даних та неправильний алгоритм. Дурення з різницею в продуктивності між ++і +=1буде нерелевантним і майже незмінним. Це те, що ти триваєш .
S.Lott

1
Не всі погані алгоритми можна знайти чистими міркуваннями. Час від часу потрібно сідати і займатися профілем. Це єдиний спосіб з'ясувати, чи була початкова здогадка правильною. Це єдиний спосіб оцінити реальну вартість (BigO + const).
Бенджамін Баньє

Профілювання виявить погані алгоритми. Цілком правильно. Це ще не "оптимізація". Це все-таки виправлення фундаментальної вади дизайну, яка змінила дизайн. Оптимізація (налаштування, точне налаштування тощо) рідко буде помітна для профілювання.
S.Lott

3

Приємний практичний трюк - використовувати свій тестовий набір одиниць як набір тестів про ефективність .

Наступний підхід добре працював у моїх кодових базах:

  1. Переконайтесь, що покриття тесту вашої одиниці добре (ви вже робили це, правда?)
  2. Переконайтесь, що ваш тестовий запуск фреймворку звітує про час виконання кожного окремого тесту . Це важливо, тому що ви хочете знайти, де відбувається повільна продуктивність.
  3. Якщо тест працює повільно, використовуйте це як спосіб зануритися та оптимізувати ціль у цій зоні . Оптимізація перед виявленням проблеми ефективності може вважатися передчасною, тому важливим у цьому підході є те, що ви спочатку отримуєте конкретні докази низької ефективності. Якщо потрібно, розбийте тест на більш дрібні тести, які орієнтують різні аспекти, щоб ви могли визначити, де знаходиться коренева проблема.
  4. Якщо тест запускається надзвичайно швидко, це зазвичай добре, хоча ви можете розглянути можливість виконання тесту в циклі з різними параметрами. Це робить його кращим тестом на працездатність, а також збільшує тестове покриття простору параметрів.
  5. Напишіть кілька додаткових тестів, які конкретно орієнтуються на ефективність, наприклад, час трансакції або час завершення до кінця або час для завершення 1000 програм. Якщо у вас є специфічні нефункціональні вимоги до продуктивності (наприклад, <300 мс час відгуку), то зробіть тест невдалим, якщо це займе занадто довго.

Якщо ви все це робите, то з часом середня продуктивність вашої кодової бази повинна органічно покращуватися.

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


Мені подобається техніка - розумний - ховвре, це мікрооптимізація і покращиться до локального мінімуму - це не вирішить архітектурних проблем, які дозволять тобі досягти глобальних мінімумів
Jasonk

@jasonk - ви абсолютно праві. Хоча я додам, що іноді це може дати вам необхідні докази, щоб продемонструвати, чому певна архітектурна зміна виправдана .....
mikera

1

Відповідь @dasblinkenlight вказує на дуже поширене питання, особливо з великими базами коду (на мій досвід). Можуть виникнути серйозні проблеми з роботою, але вони не локалізовані . Якщо ви запускаєте профайлер, підпрограми не займають достатньо часу, щоб привернути увагу. (Якщо припустити, що ви дивитесь на включений відсоток часу, що включає виклики. Навіть не турбуйтеся про "час самозабезпечення".)

Насправді в такому випадку справжню проблему виявили не шляхом профілювання, а вдалого розуміння.

У мене є тематичний випадок, для якого є короткий слайд-шоу у форматі PDF , який детально ілюструє цю проблему та як її впоратися. Основний момент полягає в тому, що код набагато повільніше, ніж міг би бути, це означає (за визначенням) зайвий час витрачається на те, що можна було видалити.

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

Це не "гаряча точка".

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

(У такому підході часто виникає критика, що кількість вибірок є занадто малою для статистичної валідності. На це відповідає відповідь на слайді 13 PDF. Коротко - так, існує велика невизначеність при "вимірюванні" потенційних заощаджень, але 1) очікувана цінність потенційної економії істотно не впливає, і 2) коли потенційна економія $ x $ переводиться на коефіцієнт прискорення на $ 1 / (1-x) $, вона сильно перекошена до високих (вигідних) факторів.)


Дякую за вашу відповідь. Ми не віримо в статистичну вибірку і використовуємо прилади з вальгрингом. Це дає нам хороші оцінки як «самості», так і «включно» вартості більшості речей, які ми робимо.
Бенджамін Баньє

@honk: Правильно. Але, на жаль, інструментарій все ще несе в собі думку про те, що проблеми з продуктивністю локалізовані, і тому їх можна знайти, вимірявши частину часу, проведеного в підпрограмах і т.д. .
Майк Данлаве
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.