Страницы

Показаны сообщения с ярлыком оптимизация. Показать все сообщения
Показаны сообщения с ярлыком оптимизация. Показать все сообщения

вторник, 10 мая 2011 г.

Use perl или продолжаем оптимизацию

Бродил по коду проекта на новой работе и набрел на такую вот функцию :


sub translit
{
my $text = shift;
$text = "$text";

$text =~ s/А/A/g;
$text =~ s/Б/B/g;
$text =~ s/В/V/g;
$text =~ s/Г/G/g;
$text =~ s/Д/D/g;
$text =~ s/Е/E/g;
$text =~ s/Ё/E/g;
$text =~ s/Ж/ZCH/g;
$text =~ s/З/Z/g;
$text =~ s/И/I/g;
$text =~ s/Й/J/g;
$text =~ s/К/K/g;
$text =~ s/Л/L/g;
$text =~ s/М/M/g;
$text =~ s/Н/N/g;
$text =~ s/О/O/g;
$text =~ s/П/P/g;
$text =~ s/Р/R/g;
$text =~ s/С/S/g;
$text =~ s/Т/T/g;
$text =~ s/У/U/g;
$text =~ s/Ф/F/g;
$text =~ s/Х/H/g;
$text =~ s/Ц/C/g;
$text =~ s/Ч/CH/g;
$text =~ s/Ш/SH/g;
$text =~ s/Щ/SCH/g;
$text =~ s/Ь/'/g;
$text =~ s/Ъ/'/g;
$text =~ s/Ы/Y/g;
$text =~ s/Э/E/g;
$text =~ s/Ю/YU/g;
$text =~ s/Я/YA/g;
$text =~ s/а/a/g;
$text =~ s/б/b/g;
$text =~ s/в/v/g;
$text =~ s/г/g/g;
$text =~ s/д/d/g;
$text =~ s/е/e/g;
$text =~ s/ё/e/g;
$text =~ s/ж/zch/g;
$text =~ s/з/z/g;
$text =~ s/и/i/g;
$text =~ s/й/j/g;
$text =~ s/к/k/g;
$text =~ s/л/l/g;
$text =~ s/м/m/g;
$text =~ s/н/n/g;
$text =~ s/о/o/g;
$text =~ s/п/p/g;
$text =~ s/р/r/g;
$text =~ s/с/s/g;
$text =~ s/т/t/g;
$text =~ s/у/u/g;
$text =~ s/ф/f/g;
$text =~ s/х/h/g;
$text =~ s/ц/c/g;
$text =~ s/ч/ch/g;
$text =~ s/ш/sh/g;
$text =~ s/щ/sch/g;
$text =~ s/ь/'/g;
$text =~ s/ъ/'/g;
$text =~ s/ы/y/g;
$text =~ s/э/e/g;
$text =~ s/ю/yu/g;
$text =~ s/я/ya/g;
$text =~ s/і/i/g;
$text =~ s/ї/yi/g;
$text =~ s/є/e/g;

return $text;
}


Подумал про себя, чем не развертывание цикла, хоть и слегка...ммм, в общем не будем об этом.
Как вы догадались, это функция перевода в транслит текста, а теперь важный момент текста в sms сообщениях. Функция сразу выглядела ужасно, но как показали дальнейшие изыскания она имеет право на существование. Модифицируем ее


sub translit_new_cycle
{
my $text = shift;
my %hash_=(
'А'=>'A',
'Б'=>'B',
'В'=>'V',
'Г'=>'G',
'Д'=>'D',
'Е'=>'E',
'Ё'=>'E',
'Ж'=>'ZCH',
'З'=>'Z',
'И'=>'I',
'Й'=>'J',
'К'=>'K',
'Л'=>'L',
'М'=>'M',
'Н'=>'N',
'О'=>'O',
'П'=>'P',
'Р'=>'R',
'С'=>'S',
'Т'=>'T',
'У'=>'U',
'Ф'=>'F',
'Х'=>'H',
'Ц'=>'C',
'Ч'=>'CH',
'Ш'=>'SH',
'Щ'=>'SCH',
'Ь'=>"'",
'Ъ'=>"'",
'Ы'=>'Y',
'Э'=>'E',
'Ю'=>'YU',
'Я'=>'YA',
'а'=>'a',
'б'=>'b',
'в'=>'v',
'г'=>'g',
'д'=>'d',
'е'=>'e',
'ё'=>'e',
'ж'=>'zch',
'з'=>'z',
'и'=>'i',
'й'=>'j',
'к'=>'k',
'л'=>'l',
'м'=>'m',
'н'=>'n',
'о'=>'o',
'п'=>'p',
'р'=>'r',
'с'=>'s',
'т'=>'t',
'у'=>'u',
'ф'=>'f',
'х'=>'h',
'ц'=>'c',
'ч'=>'ch',
'ш'=>'sh',
'щ'=>'sch',
'ь'=>"'",
'ъ'=>"'",
'ы'=>'y',
'э'=>'e',
'ю'=>'yu',
'я'=>'ya',
'і'=>'i',
'ї'=>'yi',
'є'=>'e',
' '=>' ',
"\n"=>"\n"

);
my @a=split(//,$text);
foreach(@a){
$_=$hash_{$_};
}


#
return join("",@a);
}


А теперь тестируем и...

#!/usr/bin/perl
use strict;
use Benchmark qw(:all);
use Data::Dumper;
use utf8;


my @a=(
"Я родил собаку это меня убило как же все тупо Я родил собаку это меня убило как же все тупо"

);


my $count=100000;

foreach(@a){
timethese($count, {
'may be good' => "translit_new_cycle('$_')",
'may be bad' => "translit('$_')",
});
}


получаем

may be bad: 11 wallclock secs (10.24 usr + 0.00 sys = 10.24 CPU) @ 9765.62/s (n=100000)
may be good: 12 wallclock secs (10.45 usr + 0.00 sys = 10.45 CPU) @ 9569.38/s (n=100000)

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

Benchmark: timing 100000 iterations of may be bad, may be good...
may be bad: 3 wallclock secs ( 2.63 usr + 0.00 sys = 2.63 CPU) @ 38022.81/s (n=100000)
may be good: 5 wallclock secs ( 4.96 usr + 0.00 sys = 4.96 CPU) @ 20161.29/s (n=100000)


Но увеличившая разница во времени( почти в два раза) насторожила, введем очень длинную строку для тестов "Я родил собаку родил собаку это меня убило как же все тупо родил собаку это меня убило как же все тупо родил собаку это меня убило как же все тупо родил собаку это меня убило как же все тупо"

may be bad: 22 wallclock secs (18.29 usr + 0.02 sys = 18.31 CPU) @ 5461.50/s (n=100000)
may be good: 22 wallclock secs (17.58 usr + 0.01 sys = 17.59 CPU) @ 5685.05/s (n=100000)



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

my %hash_=(
'А'=>'A',
'Б'=>'B',
'В'=>'V',
'Г'=>'G',
'Д'=>'D',
'Е'=>'E',
'Ё'=>'E',
'Ж'=>'ZCH',
'З'=>'Z',
'И'=>'I',
'Й'=>'J',
'К'=>'K',
'Л'=>'L',
'М'=>'M',
'Н'=>'N',
'О'=>'O',
'П'=>'P',
'Р'=>'R',
'С'=>'S',
'Т'=>'T',
'У'=>'U',
'Ф'=>'F',
'Х'=>'H',
'Ц'=>'C',
'Ч'=>'CH',
'Ш'=>'SH',
'Щ'=>'SCH',
'Ь'=>"'",
'Ъ'=>"'",
'Ы'=>'Y',
'Э'=>'E',
'Ю'=>'YU',
'Я'=>'YA',
'а'=>'a',
'б'=>'b',
'в'=>'v',
'г'=>'g',
'д'=>'d',
'е'=>'e',
'ё'=>'e',
'ж'=>'zch',
'з'=>'z',
'и'=>'i',
'й'=>'j',
'к'=>'k',
'л'=>'l',
'м'=>'m',
'н'=>'n',
'о'=>'o',
'п'=>'p',
'р'=>'r',
'с'=>'s',
'т'=>'t',
'у'=>'u',
'ф'=>'f',
'х'=>'h',
'ц'=>'c',
'ч'=>'ch',
'ш'=>'sh',
'щ'=>'sch',
'ь'=>"'",
'ъ'=>"'",
'ы'=>'y',
'э'=>'e',
'ю'=>'yu',
'я'=>'ya',
'і'=>'i',
'ї'=>'yi',
'є'=>'e',
' '=>' ',
"\n"=>"\n"

);

sub translit_new_cycle
{
my $text = shift;

my @a=split(//,$text);
foreach(@a){
$_=$hash_{$_};
}


#
return join("",@a);
}


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


may be bad: 23 wallclock secs (18.13 usr + 0.00 sys = 18.13 CPU) @ 5515.72/s (n=100000)
may be good: 17 wallclock secs (13.86 usr + 0.02 sys = 13.88 CPU) @ 7204.61/s (n=100000)

На более коротких текстах типа SMS, наша процедура выигрывает с преимуществом в два раза.

Собственно к чему я это...Наверно к тому что панацеи не существует

четверг, 9 декабря 2010 г.

Оптимизация кода

Архитектурно правильный код - это хорошо. Он красиво выглядит, легко читается, все структурировано, а потом оказывается, что код работает долго, а все вроде бы красиво.
Так вот не всегда "правильный" с точки зрения стиля, архитектуры код самый быстрый.
Ну так получилось, так бывает, теория и практика знаете ли. А теперь идеи ускорения.

Идея 1. Назовем ее развертывание цикла.

Код :

my $ref = $dbh->selectall_arrayref(q[SELECT id FROM categories
WHERE c_status='active']);
my (@res);
my $count=10;
foreach my $tmp (@$ref){
my $ref=$dbh->selecall_hashref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;
push @r,$ref->{$_} foreach( key %$ref);
push @res,\@r;
}



То есть это простой пример вывода статей по 10 штук в каждой категорие. Он универсален, но его можно значительно ускорить потеряв при этом применимость нового алгоритма для некоторых частных случаев. "Развернув" внутренний цикл
мы получим ужасный код, но и более быстрый, избавившись в данном случае аж от 10 условных переходов (циклы это ничто иное как условные переходы) . Получим примерно следующее :
my $ref = $dbh->selectall_arrayref(q[SELECT id FROM categories
WHERE c_status='active']);
my @res;
my $count=10;
foreach my $tmp (@$ref){
my $ref=$dbh->selecall_arrayref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;
push @r,$ref->[0];
push @r,$ref->[1];
push @r,$ref->[2];
push @r,$ref->[3];
push @r,$ref->[4];
push @r,$ref->[5];
push @r,$ref->[6];
push @r,$ref->[7];
push @r,$ref->[8];
push @r,$ref->[9];
push @res,\@r;
}


Идея 2. Банальное кеширование

Не будем уходить далеко в мир высокого, а продолжим рассматривать пример выше.
Логично предположить, что новые категории добавляются не каждый день, потому от запроса на выборки списка надо избавиться путем сохранения его в памяти. Самый простой способ это Memcached, другой способ рассмотрим ниже.

sub get_cache_connection
{

require Cache::Memcached::Fast;
require Storable;
my $ref=new Cache::Memcached::Fast(
{
servers =>[{ address => '127.0.0.1:11211',weight=>2.5}],
namespace => 'sessions:',
connect_timeout => 0.2,
io_timeout => 0.5,
close_on_error => 1,
compress_threshold =>-1,
ketama_points => 150,
nowait => 1,
hash_namespace => 1,
serialize_methods => [ \&Storable::freeze, \&Storable::thaw ]
}
);

return $ref;

}

my $tabs;
my $md=get_cache_connection();
my $ref=$md->get('categories')) ;
foreach(@$ref){
###some code there

}
Кеширование применимо для данных, которые не часто меняются, при другом подходе может возникнуть проблема, что накладные расходы на работу с кешированием превысят затраты на работу с базой данных. Не забывайте, что mysql тоже кеширует запросы, и в предыдущем примере запрос на выборку категорий по сути не имеет смысла, потому что с огромной вероятностью он будет закеширован mysql. Другое дело, если вы генерируете из категорий свой особый html код, вот его можно и за кешировать.


Идея 3. Развертывание функций.


Думаю вы уже догадались в чем фишка. Рассмотрим предыдущие примеры, только добавим внутрь цикла функцию форматирования даты - ts.
my $ref = $mem_cache->get('categories')) ;
my @res;
my $count=10; foreach my $tmp (@$ref){
my $ref=$dbh->selecall_arrayref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;
format_date(\$ref->[0]->[3]);
push @r,$ref->[0];
format_date(\$ref->[1]->[3]);
push @r,$ref->[1];
format_date(\$ref->[2]->[3]);
push @r,$ref->[2];
format_date(\$ref->[3]->[3]);
push @r,$ref->[3];
format_date(\$ref->[4]->[3]);
push @r,$ref->[4];
format_date(\$ref->[5]->[3]);
push @r,$ref->[5];
format_date(\$ref->[6]->[3]);
push @r,$ref->[6];
format_date(\$ref->[7]->[3]);
push @r,$ref->[7];
format_date(\$ref->[8]->[3]);
push @r,$ref->[8];
format_date(\$ref->[9]->[3]);
push @r,$ref->[9];

push @res,\@r;
}


Заметка кстати, профайлинг Perl показал, что быстрее передавать параметры в функцию по значению, а не по указателю( то есть не так как здесь ;) ). И заменяем все format_date на их код.

my $ref = $mem_cache->get('categories')) ;
my @res;
my $count=10;
foreach my $tmp (@$ref){
my $ref=$dbh->selecall_arrayref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;

$ref->[0]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[0]->[3]="$3.$2.$1";
push @r,$ref->[0];
$ref->[1]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[3]="$3.$2.$1";


push @r,$ref->[1];
$ref->[2]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[2]->[3]="$3.$2.$1";
push @r,$ref->[2];
$ref->[3]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[3]->[3]="$3.$2.$1";
push @r,$ref->[3];
$ref->[4]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[4]->[3]="$3.$2.$1";
push @r,$ref->[4];
$ref->[5]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[5]->[3]="$3.$2.$1";
push @r,$ref->[5];
$ref->[6]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[6]->[3]="$3.$2.$1";
push @r,$ref->[6];
$ref->[7]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[7]->[3]="$3.$2.$1";
push @r,$ref->[7];
$ref->[1]->[8]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[8]="$3.$2.$1";
push @r,$ref->[8];
$ref->[1]->[9]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[9]="$3.$2.$1";
push @r,$ref->[9];
push @res,\@r;
}

В итоге мы види ужасный неудобоворимый код, который вызывает рвотные рефлексы при одном своем виде. Дальше агрументов много
1) А что делать, если полей в таблице дофига, и все надо форматировать, это ж "каша"
2) А если надо увеличить/уменьшить количество статей, снова лезть в код!
И так далее.. И так далее...Все эти вопросы можно решить при помощи "админки". А админка есть у любого уважающего себя сайта статусом выше, персональная страничка.
На нужно при изменение нужных вам параметров сделать генерацию нужного вам кода.
Например добавить изменение количества выводимых статей с 10 на 5( вполне обычная функция для администраторского интерфейса). Помещаем функцию обработки вывода статей в отдельный модуль например "/lib/Article.pm". Даем права на работу с этим файлом пользователю от которого работает наш скрипт
( ну или обычные авось chmod 777 /lib/Artcle.pm).
ну и в функцию изменения количества статей пишем примерно следующее:
open(Fl,">document_root/lib/Article");

print FL, q{
package lib::Artcle;
use strict;
use base qw[Exporter];
use SiteDB;##connecting to the database $dbh
our @EXPORT = qw(
list
);





sub list{
my $ref = $mem_cache->get('categories')) ;
my @res;
my $count=}.$new_count_value.q{;

foreach my $tmp (@$ref){
my $ref=$dbh->selecall_arrayref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;

$ref->[0]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[0]->[3]="$3.$2.$1";
push @r,$ref->[0];
$ref->[1]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[3]="$3.$2.$1";


push @r,$ref->[1];
$ref->[2]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[2]->[3]="$3.$2.$1";
push @r,$ref->[2];
$ref->[3]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[3]->[3]="$3.$2.$1";
push @r,$ref->[3];
$ref->[4]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[4]->[3]="$3.$2.$1";
push @r,$ref->[4];
$ref->[5]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[5]->[3]="$3.$2.$1";
push @r,$ref->[5];
$ref->[6]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[6]->[3]="$3.$2.$1";
push @r,$ref->[6];
$ref->[7]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[7]->[3]="$3.$2.$1";
push @r,$ref->[7];
$ref->[1]->[8]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[8]="$3.$2.$1";
push @r,$ref->[8];
$ref->[1]->[9]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[9]="$3.$2.$1";
push @r,$ref->[9];
push @res,\@r;
\}

return \@res;
\}

1;
};
close(FL);
Ну и далее можем использовать этот модуль у себя в скрипте обычным способом :



sub some_sub{
my $self=shift;
require lib::Article;
my $ref=lib::Article::list();
###some code there
####
}


Всем удачи.

p.s
При помощи генерации кода можно избавиться вообще от последнего цикла обхода категорий.

понедельник, 26 июля 2010 г.

MySql и leftmost

Если индекс составной (a, b), то операция сравнения идёт слева направо. Тогда индекс будет справделив для вариантов
WHERE a =?
WHERE a =? AND b =?
Но индекс не будет использоваться в случае
WHERE b =?