Рассмотрим на примере простой задачи оптимизацию php кода под конкретную задачу, как я её понимаю.
Вся фишка оптимизации в том, что на неё уходит много сил, а заказчики порой вообще не понимают, в чём тут сложность. Нет, конечно мы объясняем и так и эдак. Но со стороны бизнеса это выглядит как полный развод на деньги.
Код — то по факту не меняется, он по прежнему делает то же самое. А время на разработку уходит. И порой с неизвестным результатом. За что платить?
За то, что вас тайно не послали… Плохая шутка, но зато соответствует действительности.
А если серьёзно, то наши эффективные менеджеры порой даже близко не догадываются в каком пахучем месте приходится копаться для того, чтобы всё работало как надо…
Я часто встречал на работе такой случай — программист просто говорит, что занимается определённой задачей, а на самом деле переделывает код из другой, для того, чтобы сайт или приложение работало быстро. Его никто не заставляет, просто он знает, что нужно сделать хорошо. И даже не пытается объяснить, что он делает и почему на это нужно время, поскольку если он начнёт, то день будет потерян..
Да что там.. Я сам так регулярно делаю.
Цените программистов, у которых всё работает как надо. Поверьте, что порой это стоит огромных трудов. И кстати говоря, это совсем не обязательно. Можно ведь плюнуть и ваш проект станет чуть хуже, а потом ещё хуже. А потом программист найдёт себе новый, а вы будете платить реально хорошие деньги, чтобы вам кто-нибудь согласился переделать всю работу.
Цените.
Итак для упрощения будет простая задача
У нас есть матрица m*n, её нужно заполнить случайными числами без повторений.
Задача подобного рода может встретиться разве что на экзамене, но она хорошо иллюстрирует, что происходит в коде. Вместо неё можно подставить любую другую бизнес — логику. Допустим нужно пересчитывать цены в интернет-магазине в зависимости от курса либо выводить сложный отчёт, формируя его «на лету».
Итак.
Шаг 1. Решение в лоб
<?php
$m = 10;
$n = 7;
$mas = [];
$result = [];
while(count($mas) < $m*$n){
$r = rand(0, 1000);
if(!in_array($r, $mas)){
$mas[] = $r;
}
}
$i = 0;
foreach ($mas as $key => $val){
if($key % $n === 0 && $key != 0){
$i++;
}
if(empty($result[$i])){
$result[$i] = [];
}
array_push($result[$i], $val);
}
echo "<pre>";
var_dump($result);
Всё прекрасно работает.
Но что будет, если вдруг выборка будет 100 * 100? В принципе конечно фиг бы с ним, никто и не узнает. Но как — то уже виснет. А если вдруг пользователи пойдут все одновременно, что будет? Конечно, сейчас этого не ожидается, но вот через полгода — год… Может сразу сделать как положено?
Вот с таких вопросов к себе и начинается обычно оптимизация.
Шаг 2. Читаем что на эту тему думают коллеги
Поскольку все основные грабли уже давно расставлены и на них наступают не первый год, обычно можно найти готовый ответ на специальных сайтах.
Ну это не в нашем случае, поскольку допустим, что задача ранее не встречалась. Либо, как часто это бывает, ответ есть, но он нам не подходит, поскольку даже медленнее или хуже чем наш вариант. Естественно, что все найденные варианты нужно протестировать.
На это обычно уходит солидно времени — ну скажем, от одного дня до недели. И если это важный пункт в коде, то это время полностью того стоит.
А важный пункт или нет — решает опять же программист, исходя из своего опыта и совести…
Шаг 3. На следующий день, если задача не решена, — читаем увлекательную документацию, часто на английском языке
Тут происходит улучшение кода путём раскопки каких — либо уникальных функций, методов, возможностей языка.
И, о чудо — мы нашли функцию array_chunk, которая разбивает массив на части, а также генераторы, которые почти не потребляют память для генерации больших массивов. А также array_unique — которое вычисляет уникальные значения.
Все эти функции являются встроенными, значит работают наиболее быстро.
<?php
$m = 10;
$n = 7;
$mas = [];
$result = [];
while(count($mas) < $m*$n){
foreach (generate(count($mas), $m*$n) as $elem){
$mas[] = $elem;
}
$mas = array_values(array_unique($mas));
}
$result = array_chunk($mas, $n);
echo "<pre>";
var_dump($result);
//Генератор
function generate($start, $end){
for ($i = $start; $i < $end; $i++) {
yield rand(0, 1000);
}
}
Уже неплохо, но у этого кода вероятно будут ограничения из — за функции rand, да и вообще пока ещё не очень.
Шаг 4. На следующий день обдумываем возможные хитрости и нестандартные решения
Как ни странно, но часто слегка поменять условия задачи — является оптимальным решением для упрощения кода. Например мы будем генерировать не случайные, а псевдослучайные числа (то есть например последовательные). Тогда мы избавимся от проверок на уникальность, которые тоже отнимают время.
Бывает можно разделить задачу на 2 отдельных, либо вынести часть кода либо ещё-что-то. Вообщем, творчески думаем, периодически опять что-то читая.
<?php
$m = 10;
$n = 7;
$mas = [];
$result = [];
foreach (generate(count($mas), $m*$n) as $elem){
$mas[] = $elem;
}
$result = array_chunk($mas, $n);
echo "<pre>";
var_dump($result);
//Генератор
function generate($start, $end){
for ($i = $start; $i < $end; $i++) {
yield $i;
}
}
Код, как мы видим, сильно сократился. Это уже неплохо.
Шаг 5. Нагрузочное тестирование
Теперь, когда мы убедились, что скрипт работает верно и достаточно оптимально, проводим нагрузочное тестирование. Ну скажем создадим матрицу 1000 * 1000.
Мой первый скрипт вообще не смог отработать из-за особенностей функции rand. Мы ввели недостаточный диапазон значений. Это означает, что при увеличении выборки сайт ляжет. И узнает заказчик об этом только когда это случится, поскольку на малых выборках код работает стабильно.
Если же уменьшить выборку до 1000*100 и расширить $r = rand(0, 1000000000000) , то он отрабатывает за 11.97 сек.
Оптимизированный скрипт на выборке 1000 * 1000 выполнился за 0.17 сек. То есть на выборке в 10 раз большей — в 100 раз быстрее.
Конечно текущий код можно было бы ещё упростить и довести до совершенства, но я не буду сейчас этим заниматься в рамках этой статьи. Для этого нужно повторить все шаги начиная со 2.
Моя цель — показать насколько можно улучшить работу любой системы, если выделять на это время.
Речь идёт о выигрыше в 100 и более раз… Это самая важная и ответственная часть программирования.
Шаг 7. Высокая нагрузка
Как вы понимаете до этого шага доходят уже единицы. Рассматривают его в случае, если у нас действительно высокая нагрузка на систему. Скажем планируется массовая обработка банковских платежей, либо миллионы пользователей на сайте (опять же сайт банка, сайт гос органа и тд).
В этом случае всех описанных методов будет не достаточно. И решения в php подчас найти нельзя.
Все решения для высоких нагрузок связаны с применением дополнительных технологий. Соответственно программист должен либо ими владеть, либо слышать откуда-то о них, либо постоянно читать и интересоваться этой областью.
Решения в этой области часто вообще не очевидные и новаторские. На их разработку уходит порой больше времени чем на создание всего остального продукта. Приведу несколько вариантов для понимания картины бедствия:
Вариант 1. Переписать код в виде сервиса на другом, более быстром языке, сервис разместить на отдельном сервере, если для вычислений требуется много ресурсов и запрашивать у него ответ через API. Либо сделать распределённое вычисление на разных серверах, используя мощности нескольких машин одновременно.
Вариант 2. Использовать возможности мощных баз данных типа Oracle, вычисления делать на уровне базы, запрашивать результат запросом
Вариант 3. Сделать ответ асинхронным и выводить подсказку, что мы работаем, а вы ждите. Если клиент уходит — к примеру заканчивать вычисления и освобождать ресурсы. Конечно часть клиентов уйдёт ни с чем во время пиковых нагрузок, зато часть гарантированно получит ответ.
Либо возвращать пользователю когда именно он может ждать результат своего запроса
Вариант 4. Если вычисления типовые, хранить таблицы результатов в кеше и запрашивать их оттуда
Вариант 5. Пригласить математиков и совместно с ними разработать приблизительные вычисления, разделить ответ на 2 части — выдавать сначала приблизительное значение, затем точное. Либо придумать оптимальный алгоритм вычисления и реализовать его на языке низкого уровня
Вариант 6. Если проект большой, а вычисления действительно нужные — спроектировать и разработать отдельное устройство для проведения только одной операции, обращаться в дальнейшем к нему. Датчик отпечатка пальцев в телефоне — хороший пример такого устройства
Вариант 7. Сделать функцию доступной только ограниченному числу пользователей, для остальных же вводить отложенную обработку запросов
Вообщем дальше начинается чтение толстых мануалов и мозговой штурм.
В бизнесе, если у заказчика возникли ограничения, часто остаётся только ждать, когда изменится ситуация на рынке.
В программировании никому не интересно как ты запрограммируешь этот код. Он просто должен работать…
Да, за это платят больше чем остальным. Но разве мы работаем за деньги?
Скажу так — тот бизнес, который кроме денег ещё и лелеет свою команду, вникает в её трудности, неизбежно становится лидером в своей области.
К сожалению, ценить и растить программистов у нас не принято. Зато принято от них избавляться и менять на новых, как только они начинают задавать вопросы по делу. Либо объяснять, что не все вопросы решаются лишением премии…
Что ж, скупой и глупый платит дважды…
Цените тех, кто вам помогает..