Thursday, August 26, 2010

Немного об оптимизации веб-сайтов. часть 1.

Большую часть работы я трачу на поддержку и развитие уже написанных сайтов. Немалую долю времени при поддержке приходится тратить на оптимизацию. Именное ей и будет посвящен набор статей виде отдельных историй об одном абстрактном программисте.


Этап 1 - становление.


Опыт 1.


Начинающий веб программист получает заказ на небольшой сайт. На удивление сайт завершен за 2 недели до сроков, даже с учетом затребованных изменений. Программист понимает что код его далек от идеала, многие вещи реализованы не оптимально. Принято решение заняться оптимизацией сайта. Сказал - сделал. Время генерации сайта до оптимизации 0.02 секунды, время генерации сайта после оптимизации 0.015 секунды. Т.е. страница генерируется на 25% быстрее. Итак по мнению программиста сайт обслужит на 25% больше пользователей.


Проверка.


Пусть следующий скрипт эмулирует сайт до оптимизации.



$echo “<?php usleep(20000);” > /var/www/site_before.php

А следующий после:



$echo “<?php usleep(15000);?>” > /var/www/site_after.php

Запускаем проверку.



$ ab -q -n 1000 -c 150 http://127.0.0.1/site_before.php | grep "Requests per second"
Requests per second: 64, 3[#/sec] (mean) (усреднено по трем замерам)


$ ab -q -n 1000 -c 150 http://127.0.0.1/site_after.php | grep "Requests per second"
Requests per second: 64 [#/sec] (mean) (усреднено по трем замерам)

Результат.


Прироста в 25% нет. Есть падение производительности в рамках погрешности.


Предположение.


При фиксированных параметрах веб серверва и физических ресурсах сервера есть некое пороговое значение времени выполнения процесса ниже которого оптимизация теряет смысл.


Подтверждение



$echo “<?php ” > /var/www/site_null.php
$ ab -q -n 1000 -c 150 http://127.0.0.1/site_null.php | grep "Requests per second"
Requests per second: 67 [#/sec] (mean) (усреднено по трем замерам)

Т.е. даже если убрать весь код из нашего проекта мы получим лишь 4% прирост производительности вебсервера.


Объяснение.


Пороговая производительность веб сервера при выполнении какой либо задачи напрямую связана с типом задачи. В нашем примере скорее всего значительную часть времени было потрачено на запуск интерпретатора, что составляло настолько большую долю в процессе отдачи страницы, что временем выполнения скрипта вообще можно было пренебречь.


Резюме.


При выполнении оптимизации вы должны быть четко уверены в том что она нужна.
Перед оптимизацией кода будьте уверены что проблема именно в нем. Есть много способов оптимизации сайта, которые не затрагивают исходные коды.


Мое мнение.


Вообще вывод тривиален, да, согласен. Но я склонен считать что этой ошибке подвержены все начинающие веб программисты. В целом нет ничего плохого в том, что делал наш абстрактный программист, он изучал возможности языка и это очень полезное качество. Вопрос тут в другом. Нужно правильно расставлять акценты. Не нужно подменять понятия. В своей жизни я видел задержки проектов по срокам, из-за подобной деятельности, при этом человек занявшись "оптимизацией" (на самом деле просто игрался), начинал сам верить что оптимизирует сайт и даже обижался когда ему указывали на срыв сроков. И начинал заговариваться утверждая, что его действия необходимы для поддержки и развития проекта. Начинающий программист должен понимать, что если он занимается оптимизацией, а никаких оснований для этого нет, то он просто изучает язык, приемы, но никак не улучшает конечный продукт.

Tuesday, June 22, 2010

Смена @author в eclipse|zend docblock

eclipse постоянно подставлял в phpdoc или скажем в pydoc напротив тэга @author имя текущего пользователя в системе, как я сильно от этого мучался. В итоге я потратил время на поиск решения и даже нашел:

- решение 1.

Запускать со следующими параметрами :

./eclipse -vmargs -Duser.name="User name"

- Решение 2.

Вручную заменив во всех шаблонах переменную ${user} на ваше имя.

Взято отсюда

Thursday, May 27, 2010

php type hinting

Навеяно http://habrahabr.ru/blogs/php/94714/


В статье обсуждаются предлагаемые в будущих реализациях механизмы проверки типов, в комментариях задевается текущая. А я хочу рассказать о том как я боролся с проверкой типов, в том числе и примитивных, в php средствами php.



Идеология. Проверка типов должна быть легко отключаема, или поведение на несоотвестие типов должно легко подменяться.



Набросок использования предполагаемого решения для проверки типов данных на примере Yii framework. (В принципе от yii тут только component, что например в Zend может быть сделано на Registry, если там ничего не появилось нового, или на Toolkit в limb, ну или уж в крайнем случае singleton, глобальные переменные)



class AnyClass
{
/**
* anyFunction description
*
* @param integer $arg1
* @param string $arg2
*/
public function anyFunction($arg1, $arg2)
{
Yii::app()->typeHintingComponent->assertFunctionArgs($this,'anyFunction' get_funciton_args());
//orYii::app()->typeHintingComponent->assertFunctionArgs(__CLASS__,__METHOD__, get_funciton_args());
}


Ожидаемое поведение : в случае если тип $arg1 не integer или тип $arg2 не string или полученное количество аргументов не совпадает, мы получаем например RuntimeException.



Реализация :


  • По классу и имени метода получаем Reflection Method

  • У Reflection Method получаем docsting

  • Из dostring вытаскиваем типы данных

  • Проверяем

  • Делаем что душа пожелает, тут можно использовать либо стратегию (оно же вызывать call_back) , либо наследников с переопределением обработки ошибок, чтобы менять поведение.




В моем примере поведение меняется заданием component в конфиге.



В результате получаем отключаемую проверку типов, и можем управлять поведением.



Основным недостатком мне видится необходимость каждый раз явно указывать объект, метод, передавать полученные аргументы

Мое решение это написать функцию обертку что берет эти данные из debug_backtrace


//docstring
public function func($arg1, $arg2)
{
Yii::app()->typeHintingComponent->assertFunctionArgs();
}

//...
public function assertFunctionArgs()
{

$backTrace = debug_backtrace();
if ( ! isset($backTrace[1]['object'], $backTrace[1]['function'], $backtrace[1]['args']))
throw new RCAssertException(/* Details */);

$obj = $backTrace[1]['object'];
$function = $backTrace[1]['function'];
$args = $backTrace[1]['args'];
//далее по предыдущему алгоритму

}



Еще один недостаток, на этапе разработки можно получить несоотвествие документации и ождаемых параметров, лично я это к недостаткам не отношу, т.к. позволит поддерживать актуальность документации как минимум.



Ну а самый большой недостаток такой реализации, это все же необходимость в методе постоянно вызывать ....->assertFunctionArgs(), если этого не делать, то и схема получится не работающая, это уже нужно насаждать на уровне review кода.

Зато самое большое преимущество это возможность проверять даже не существующие типы

/**
* @param mixed $var ну и нафига проверка типов если используются такие переменные? :)
* @param string|integer $var2 тоже смешно, но на промежуточном этапе рефакторинга может пригодится
*/



На практике у меня это не прижилось т.к. пытался насадить это вместе с кучей других требований и это не было приоритетным, возможно теперь перейдя на yii framework и реализовав это в виде компоненты у меня получится это сделать со второй попытки. Но тем не менее мне пришлось при рефакторинге одного запутанного кода использовать эту наработку активно, просто в том проекте одни и те же функции/методы использовались для разных целей, и соотвественно передаваемые типы данных были непредсказуемыми.

Friday, March 26, 2010

Забавный php5.3

В debian testing неожиданно для меня появился 5.3, спустя час после этого "счастья", LAMP заработал как и раньше. Захотелось экспериментов, вот что в итоге получилось, на мой взгляд забавно для php.

<?php

class BaseObject extends stdClass
{
    public function 
__call($method$args = array())
    {
        return 
call_user_func_array($this->$method$args); //call closure
    
}
}

/**
 * Create className instance add getIncrementedAge, getName behaviours
 * 
 * @param string $className
 * @param integer $age
 * @param string $name
 * @return object same type as $className 
 */
function Type() 
{
    
    
$props func_get_args();
    
$className array_shift($props);//classname should be first 
    
    
$refFunction = new ReflectionFunction('Type');
    
$props array_combine(// make dictionary from function arg names (from phpdoc) and function arguments
        
array_map(//extract variable names from phpdoc @param line
            
function ($docString) { //search variable name and return it 
                
preg_match('/\$(\w+)/',$docString$data);
                return 
$data[1]; 
            },
            
array_filter//make lines array from phpdoc, filtering all except strings with @param and skip string with $className 
                
explode("\n"$refFunction->getDocComment()), 
                function (
$line
                { 
                        return 
strpos($line'param') > 0  && strpos($line'$className' ) < 1  
                } 
            ) 
        ), 
        
$props 
    
);
    
    
$ob = new $className(); 

    foreach ( 
$props as $name => $value
        
$ob->$name $value
        
    
$ob->getName = function () use($ob)  
    {
        return 
$ob->name
    };
    
    
$ob->getIncrementedAge = function ($inc =1) use($ob)  
    {
        return 
$ob->age $inc
    };
    
    return 
$ob;  
}

$cd Type('BaseObject'10,'cd');
$stas Type('BaseObject'20'stas');

var_dump($cd->getName());
var_dump($cd->getIncrementedAge(2));
var_dump($stas->getName());
var_dump($stas->getIncrementedAge(3));





Результат

php test.php
string(2) "cd"
int(12)
string(4) "stas"
int(23)

Monday, March 22, 2010

Так документировать код нельзя.

Сижу поздним ночером, хочу спать, нужно чуть изменить функционал модуля. Открываю, вижу

 
/**
* Returns the data model based on the primary key given in the GET variable.
* If the data model is not found, an HTTP exception will be raised.
* @param integer the primary key value. Defaults to null, meaning using the 'id' GET variable
*/
public function loadUser()
{
if($this->_model===null)
{
if(Yii::app()->user->id)
$this->_model=User::model()->findbyPk(Yii::app()->user->id);
if($this->_model===null)
$this->redirect(Yii::app()->controller->module->loginUrl);
}
return $this->_model;
}


Ну и как это вообще называется? Я бы убивал таких документаторов.

Где exception?
Где GET?
Где param int ?
Что return ?

Я уже промолчу про шедевральный

Yii::app()->controller->module->loginUrl

В корне поменявший поведение по умолчанию.


P.S.

Понял откуда это взялось, эту документацию и метод yii генерирует в контроллере при использовании crud команды. (За исключением @param int )
 
Каталог сайтов, Добавить сайт