Страницы

четверг, 5 марта 2009 г.

Оптимизация,Xs,Perl и нахера это все нужно

Вообще этот пост является продолжением темы про идеальные языки.

Грубо говоря то xs это язык описания клея(интерфейся), которым вы будете связывать свою подпрограмму на perl c подпрограммой на C. Если вчитываться в доку , то вы буквально найдете перевод предыдущего предложения на английском.
Доку можно почитать здесь perlxs вернее perldoc perlxs ;). Она довольно обширная и меня не покидала мысль иногда а нахрена столько всего.Но я мужественно дочитал все до конца,а потом не не обнаружил нормального примера... Не сказать что меня это сильно расстроило но все же,учитывая сколько всего было прочитано ,стоял вопрос с какого конца надо подобраться.
Бла.бла.бла я как человек ленивый в конце концов нашел то что мне надо...
Утилита h2xs это именно то, что по-моему требуется сначала. Это утилита напишит весь код xs за вас, да еще и может сама модуль сгенерить.А ее глюки вы уж простите подправите сами,прочитав таки perlxs.

Краткий экскурс по h2xs

Допустим надо сделать модуль Neural - для нейронных сетей и реализацию его написать на С.
Начнем

h2xs -Afn Neural


Создаст пустой модуль со всеми атрибутами порядочного модуля Perl : Makefile.PL,Neural.pm и т.д.
Далее копируем например Neural.h и Neural.c в папку модуля
и выполняем следующую команду

h2xs -Oxan Neural Neural.h

Ну модуль готов с файлом xs.Выполняем perl Makefile.PL, make, make install и юзаем.За остальными вопросами обращайтесь man h2xs

А тут реальное говно,пардон 'Пример из жизни'.

Реальный пример, который много времени портил мне жизнь. В проекте тонким местом были большие объемы простых текстовых данных, есно они брались из базы данных,есно они обрабатывались и форматировались. Обратите внимание на слово форматировались. Нормальной выборкой содержала допустим 2000 записей, и в них могло содержаться от 1 до 5 числовых полей. Потребовалось форматирование по разрядам всех чисел. То есть из '1000000' сделать '1 000 000'.

RegExp!
Первое, что приходит в голову. Потом грубые прикидки и идея regexp -а для 2000 записей это от 2000 до 10000 как то мне не понравилась хотя бы из-за расходов памяти...Написал тогда я первый вариант процедуры разбития на разряды.


sub format_float
{
my $f=shift;
$f=~/([\-])?(\d+)\.(\d{1,2})$|([\-])?(\d+)/;

my $d='';
my $mines='';

unless($3)
{
$f=$5;
$mines=$4;
}else
{
$mines=$1;
$d=".$3";
$f=$2;
}
my @ar=split(//,$f);
my $size=@ar;
my $i=0 ;
my $res;
my $j=0;
$res.="$d";
for($i=$size-1;$i>=0;$i--)
{
unless($j)
{
$res=$ar[$i].$res;
$j++;
}else
{
$res=' '.$res unless($j%3);
$res=$ar[$i].$res;
$j++;
}

}
return $mines.$res;
}

За это долго сам себя буду пинать ногами,но надо было что-то сделать,а сверху пинали требовали заняться другим и т.п. Когда все утихло ,я вернулся к этому куску.Меня дико не устраивало,что обработка выборки занимала для любимых 2000 от 1 до 1.5 секунд.Сделаем regexp к слову это вариант перлиста коммуниста

sub regexp_format_float
{
my $f=shift;
$f=normal_prec($f);

# $f=~s/(\d{1,3})(?=\d{3})/$1 /g;
$f=~s/(\d{1,3})(?=((\d{3})+)\D?)/$1 /g;
return $f;
}

Причем при тесте производительности чисто лишь процедур в среднем вариант c reg-exp-ом был раза в три быстрее первого.Фигасе но при реальном тестирование такого прироста не наблюдалось.Вот тут вспомним про С и XS. Напишем format_float.h , где определим

char * C_format_float(double);

и format_float.c


#include
#include
#include



char * C_format_float(double num)
{
char buffer[60];
char *str,*str1;
char * str_out;
int i,size,j,z,ext;
char tmp;

sprintf(buffer, "%f", num);

size=strlen(buffer);

str=strtok(buffer,".");

size=strlen(str);
ext=(int)size/3;
str_out = malloc(100);


j=0;//
size--;
z=1;//reverse index
for(i=size;i>=0;z++,i--)
{

tmp=str[i];
str_out[i-j+ext]=tmp;
if((z%3)==0)
{
j++;
tmp=' ';
str_out[i-j+ext]=tmp;
}


}
str_out[size+ext+1]='.';
//str_out[size+ext+2]='\0';
str=strtok(NULL,".");
if(str==NULL) return str_out;
str_out[size+ext+2]=str[0];
str_out[size+ext+3]=str[1];
str_out[size+ext+4]='\0';
//strcat(str_out, str);
return str_out;
}

Потом проведем операции из первого абзаца:

bash # h2xs -Afn Format
bash # cp /home_dir/xs/format_float.* /home_dir/Format/
bash # h2xs -Oxan Format format_float.h

Готово можно подключать...Далее могу сказать, что здесь я остановился в оптимизации данного куска кода. В данном случае я говорю о функции char * format_float(double num).Ибо написав скрипт , чтоб посмотреть разницу во времени для всех реализаций

#!/usr/bin/perl
use lib q(.);
use SiteCommon;#содержит процедуры формаирования на Perl
use Format;# C -реализация
use Benchmark qw(:all) ;

timethese(100000, {
'format_float without regexp' => sub { SiteCommon::format_float(1000000.00) },
'format_float on C' => sub { Format::C_format_float(1000000.00) },
'format_float with regexp' => sub { SiteCommon::regexp_format_float(1000000.00) },

});

timethese(100000, {
'format_float without regexp' => sub { SiteCommon::format_float(10000.00) },
'format_float on C' => sub { Format::C_format_float(10000.00) },
'format_float with regexp' => sub { SiteCommon::regexp_format_float(10000.00) },

});
timethese(100000, {
'format_float without regexp' => sub { SiteCommon::format_float(11.00) },
'format_float on C' => sub { Format::C_format_float(10.00) },
'format_float with regexp' => sub { SiteCommon::regexp_format_float(13.00) },

});

Получили примерно следующие результаты


Benchmark: timing 100000 iterations of format_float on C, format_float with regexp, format_float without regexp...
format_float on C: 1 wallclock secs ( 0.31 usr + 0.03 sys = 0.34 CPU) @ 294117.65/s (n=100000)
(warning: too few iterations for a reliable count)
format_float with regexp: 2 wallclock secs ( 2.12 usr + 0.09 sys = 2.21 CPU) @ 45248.87/s (n=100000)
format_float without regexp: 5 wallclock secs ( 5.58 usr + 0.10 sys = 5.68 CPU) @ 17605.63/s (n=100000)
Benchmark: timing 100000 iterations of format_float on C, format_float with regexp, format_float without regexp...
format_float on C: 0 wallclock secs ( 0.30 usr + 0.01 sys = 0.31 CPU) @ 322580.65/s (n=100000)
(warning: too few iterations for a reliable count)
format_float with regexp: 2 wallclock secs ( 1.84 usr + 0.08 sys = 1.92 CPU) @ 52083.33/s (n=100000)
format_float without regexp: 7 wallclock secs ( 4.81 usr + 0.18 sys = 4.99 CPU) @ 20040.08/s (n=100000)
Benchmark: timing 100000 iterations of format_float on C, format_float with regexp, format_float without regexp...
format_float on C: 0 wallclock secs ( 0.27 usr + 0.01 sys = 0.28 CPU) @ 357142.86/s (n=100000)
(warning: too few iterations for a reliable count)
format_float with regexp: 2 wallclock secs ( 0.70 usr + 0.00 sys = 0.70 CPU) @ 142857.14/s (n=100000)
format_float without regexp: 4 wallclock secs ( 3.76 usr + 0.11 sys = 3.87 CPU) @ 25839.79/s (n=100000)

Чем больше число для форматирование тем больше выигрыш в скорости,правда не удивительно, вот только разрыв между реализацией на С и regexp -ом оказался в 8 раз для самого большого тестируемого числа.

2 комментария:

Анонимный комментирует...

что за время wallclock?

Богдан комментирует...

вообще wallclock переводится как настенные часы,подозреваю это так обозначаются целые секунды затраченные ,но я ориентируюсь обычно на другие параметры