Розбираємося з проблемою мертвого коду та інклудами

У цій статті ми поговоримо про деякі аспекти, що іноді втрачаються розробниками аспектів, що впливають на загальну продуктивність веб-програми. Зокрема розглянемо як впливає на продуктивність множинні підключення зовнішніх файлів, наявність «мертвого» коду, акселерація шляхом кешерів опкоду і FastCGI для PHP.

Якоюсь мірою стаття є наслідком топіка «Зберігання коду в бд або збираємо код по цеглинках», так як саме після нього виникли множинні питання про продуктивність (до речі не так давно запустився ще один проект побудований на зазначеній технології зберігання всього коду в бд). Однією з переваг розглянутого підходу була можливість отримання на виході чистого монолітного коду, що не містить «мертвих» ділянок і з'єднань зовнішніх файлів (наприклад, за допомогою include або require. Далі для простоти називаємо це просто «інклуди»). Для тих, хто не знайомий з тією топікою, поясню, що «мертвим» називається код, який свідомо не буде виконаний на сторінці.

Трохи науки. Ось що розповідає про мертвий код директор Інституту системного проектування РАН член-кореспондент РАН один авторитетний дядько Віктор Іванніков:

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

Цією проблемою зацікавилися на початку 50-х років. Відповідні теорема Райса і теорема Успенського були доведені одночасно і незалежно, тільки у Володимира Андрійовича вона формулюється більш узагальнено. Теореми фактично свідчать, що розпізнавання будь-якої нетривіальної властивості алгоритму є нерозв'язною проблемою ".

Від себе додам, що у веб програмуванні кількість мертвого коду зазвичай на порядок більша. При своповому підключенні його частка легко перевищує поріг і в 80-90%. Тобто тільки кожен 20 рядок коду буде реально виконаний. Особливо це добре видно при застосуванні «важких» фреймворків.

Тут же введемо пару визначень, які будемо використовувати нижче:

моноліт - код в якому не використовуються з'єднання зовнішніх файлів (інклудів)

чистий моноліт - моноліт, в якому немає «мертвого» коду.

Як було заявлено, чистий монолітний код значно збільшує загальну продуктивність додатка, що викликало чимало суперечок і підозр у коментарях. Справедливо виникали питання, наскільки «мертвий» код та інклуди взагалі гальмують додатки? А в якості вирішення проблем з продуктивністю і чистотою коду згадувалися кешери опкоду, «магічні» функції автопідключення класів і методів (далі для простоти будемо називати це просто autoload), а також fastCGI.

Що ж, спробуємо поглиблюватися в поставленому питанні в пошуках істини. Отже, поїхали.

А чи є проблема?

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

Почнемо з мертвого коду. Створюємо сторінки на 2000, 5000, 10000 рядків коду і нацьковуємо на них apacheBenchmark. Отримуємо такі результати:

кількість рядків

запитів/сек

середній час на запит

частка мертвого коду

2000

72.24

0.013

0

5000

32.20

0.031

0.6

10000

15.97

0.062

0.75

Тут і далі зазначено час, отриманий на старенькому ноутбуці FS Amilo 1718. Результати контрольних тестів, проведених на різних системах з різними ОС І ФС пропорційно не відрізнялися

Як бачимо, час витрачений на обробку скрипту фактично прямо пропорційно кількості рядків у ньому (що цілком логічно, адже оскільки ми в скрипті нічого не робимо, то основний час йде тільки на його парсинг. Більше рядків - відповідно більше час парсингу). Як великий цей час? Воно суттєве. Погодьтеся, навіть в ідеалі за відсутності мертвого коду 13 тисячних - це багато (не кажучи вже про 62, як у третьому тесті). За цей час можна віддати готову сторінку, виконавши на ній підключення до бд, запити та інше. У тут тільки парсинг...

Тепер розберемося з інклудами. Будемо імітувати 4 ситуації:

- «моноліт» (без інклудів і мертвого коду)

- «своп» (Імітує безумовне підключення всіх бібліотек, які зазвичай об'єднані у великі файли. Тягне мало інклудів великих файлів. Багато мертвого коду)

- «autoload» (Імітує підключення класів/методів/функцій за потреби (зазвичай у момент звернення до неіснуючого класу/методу/функції). Тягне багато інклудів маленьких файлів. Майже немає мертвого коду)

- Пару проміжних сосотояний між свопом і autoload. Just for fun Для повноти картини:)

Результати відображено у наступній таблиці (у порядку продуктивності):

кількість рядків

інклудів

запитів/сек

середній час на запит

частка мертвого коду

тип імітації

2000

0

72.24

0.013

0

чистий моноліт

2000

60

35.12

0.028

0

autoload

5000

7

25.63

0.039

0.6

10500

14

13.2

0.076

0.81

15000

5

9.78

0.1

0.87

своп

Як і очікувалося, на першому місці йде моноліт, на другому з майже дворазовим відставанням autoload. Ну а своп нервово курить в сторонці:(Правда це тільки поки, потім він ще надере дупу автолоаду. Читаємо статтю далі). Але час на обробку запиту все ще дуже великий...

До речі, ця таблиця якраз підтверджує зазначену мною цифру в отриманому виграші в 600% при переведенні системи на чистий моноліт.

Є проблема - є рішення

Основним засобом вирішення проблеми і збільшення продуктивності програми за рахунок скорочення витрат на парсинг сторінок є кешери байт коду. Найвідоміші системи кешування (PHP) - eAccelerator, xCache і APC. Для тестування було обрано eAccelerator, як найпоширеніший і найшвидший в умовах нашого завдання. Досить детально про порівняння систем кешування можна почитати тут. Ось результати з використанням кешу опкоду (в порядку продуктивності):

кількість рядків

інклудів

запитів/сек

(без кеша опкоду)

запитів/сек

з кешем опкоду

коефіцієнт акселерації

тип імітації

2000

0

72.24

412.86

5.71

чистий моноліт

15000

5

9.78

204.6

20.92

своп

10500

14

13.2

192.2

14.56

2000

60

35.12

112.11

3.19

autoload

І ось тут ми спостерігаємо дуже цікаві факти. При використанні кеша опкоду, незважаючи на величезну кількість мертвого коду свопове підключення виявляється майже в 2 рази ефективніше autoload! І все завдяки шаленому коефіцієнту акселерації в 21x. А ось у autoload коефіцієнт акселерації виявився найнижчим - кешер опкоду всього в 3 рази знизив витрати. Звідси робимо нескладний висновок: кеш опкоду практично вирішує проблему «мертвого» коду, але не вирішує проблему інклудів. Кешери також не люблять велику кількість дрібних файлів, показуючи на них найменший коефіцієнт приросту продуктивності.

А як же FastCGI?

На думку багатьох, ще одним способом прискорення скриптів і зменшення витрат на «підвантаження» скрипту є запуск PHP в режимі FastCGI. Це не так! Чому fastCGI не прискорює PHP ви можете прочитати у Котерова тут. До речі там же проведені його тести eAccelerator на прикладі Zend Framework. Результати отримані Дмитром Котеровом дуже близькі до наведених у цій топіці.

Ув'язнення

Метою статті ставився докладний розгляд впливу мертвого коду і підключення зовнішніх файлів у коді на кінцеву продуктивність програми. Як бачите вплив цих факторів досить істотний.

Стаття написана Napolsky, з відомих причин він не зміг її опублікувати. Все + йому в карму;)