Разработка на Perl под Raspberry PI

42
Разработка на Perl Илья Чесноков под Raspberry PI

description

Доклад о разработке (а главное - оптимизации) программы на Perl под Raspberry PI. Наглядно показывает, что в Perl есть немало возможностей, а также инструментов, которые позволяют делать программы быстрее и эффективнее - используя как преимущества самого языка, так и оптимизацию алгоритма программы.

Transcript of Разработка на Perl под Raspberry PI

Page 1: Разработка на Perl под Raspberry PI

Разработка на Perl

!Илья Чесноков

под Raspberry PI

Page 2: Разработка на Perl под Raspberry PI

Raspberry PI (model B)• CPU: 700 MHz (ARM11) с графическим ядром

• RAM: 512 Mb

• I/O: 2xUSB 2.0, Ethernet, HDMI, RCA Video, Audio, GPIO, разъем для SD карт

• Питание: 5В x 1A (з/у смартфона)

• Потребление энергии в режиме простоя: ~2 Вт

• Стоимость: $35 (без периферии)

Page 3: Разработка на Perl под Raspberry PI

Официальные дистрибутивы

• Raspbian

• Pidora

• RISC OS

• RaspBMC

• Arch

• OpenELEC

• …

Page 4: Разработка на Perl под Raspberry PI

Raspbian• Устанавливается копированием образа на флешку

• Perl v5.14.2

• Легко ставится cpanminus, perlbrew, local::lib и т.д.

• Много Perl-модулей среди пакетов системы

Page 5: Разработка на Perl под Raspberry PI

Задача• Поиск объявлений, релевантных для указанных пользователей

• Реализация на Perl

• Должно работать на Raspberry PI

• Время обработки 100 профилей и 100 объявлений не должно превышать 2 секунды

Page 6: Разработка на Perl под Raspberry PI

<?xml version="1.0" encoding="utf-8"?> <xml> <profiles> . . . <profile> <name>David Brown</name> <gender>Male</gender> <age>34</age> <likes> <like>Music</like> <like>Sports</like> <like>Movies</like> </likes> <timestamp>2013-10-25 13:34:45</timestamp> </profile> . . . </profiles> </xml>

Файл профилей

Page 7: Разработка на Perl под Raspberry PI

<?xml version="1.0" encoding="utf-8"?> <xml> <Playlist PlayerId="1000001"> <Content id="23" type="image" location="/Content/a2/1w3ewsed.jpg" ageMin="23" ageMax="35" gender="male" likes="music;movies" /> <Content id="237" type="image" location="/Content/0f/gtfrddsed.jpg" ageMax="35" gender="all" likes="sports" /> <Content id="21" type="image" location="/Content/bf/1w3ewsed.jpg" ageMin="40" gender="female" /> <Content id="33" type="video" location="/Content/9b/jiuhnnj.mp4" gender="male" likes="music;movies" /> </Playlist> </xml>

Файл объявлений

Page 8: Разработка на Perl под Raspberry PI

Алгоритм выбора объявления• Для каждого профиля:

• Если профиль устаревший, то пропускаем

• Для каждого объявления добавляем 1 очко, если в данном профиле совпадает что-то из:

• пол, возраст, предпочтения (likes)

• Выбираем объявления с максимумом очков (если их несколько, выбираем из них случайным образом)

Page 9: Разработка на Perl под Raspberry PI

Первый вариант программыuse common::sense; use local::lib; use Const::Fast; use DateTime; use Data::Dump qw(pp); use File::Temp (); use File::Copy qw(mv); use List::UtilsBy qw(max_by); use Time::HiRes qw(gettimeofday tv_interval); use XML::Rules; !process_files(@ARGV);

Page 10: Разработка на Perl под Raspberry PI

# Parse media file use XML::Rules; my $media_parser = XML::Rules->new( style => 'parser', stripspaces => 3, warnoverwrite => 1, rules => { Content => sub { my ($tag_name, $attr) = @_; ! delete @{$attr}{qw(location type)}; $attr->{likes} = [split /\s*;\s*/, lc $attr->{likes}] if exists $attr->{likes}; return $attr; }, Playlist => sub { return Playlist => { ads => $_[1]->{_content}, PlayerId => $_[1]->{PlayerId}, }; }, xml => sub { $_[1] }, }, ); my $playlist = $media_parser->parse_file($media_content_file)->{Playlist};

Парсинг XML и обработка данных

Page 11: Разработка на Perl под Raspberry PI

# Parse profiles file my $profile_parser = XML::Rules->new( style => ‘filter’, rules => { _default => 'raw', profile => sub { my ($tag_name, $attr, undef, undef, $parser) = @_; my $profile = extract_tags({ %{ $attr } }->{_content}); $profile->{likes} = [map { lc $_->[1]->{_content} } @{ $profile->{likes} }]; ! if (is_profile_outdated($profile->{timestamp})) { mark_as_outdated($profile); return (); } ! for my $ad (@{ $parser->{parameters}->{ads} }) { my $score = calc_score($ad, $profile); $ad->{scores}->{ $profile->{name} } = $score; $ad->{scores}->{total} += $score; $parser->{parameters}->{total_scores} += $score; } return $tag_name => $attr; }, }); $profile_parser->filterfile($profile_file, $tmp_fh->filename, $playlist);

Page 12: Разработка на Perl под Raspberry PI

Время работыtime ./parser.pl samples/profile.xml samples/media.xml Done in 30.186789 seconds!real0m30.531suser0m30.050ssys0m0.340s

Page 13: Разработка на Perl под Raspberry PI

Оптимизация

Page 14: Разработка на Perl под Raspberry PI

use DateTime; sub timed_out_at { state $old_date = DateTime->now( time_zone => DateTime::TimeZone->new(name => 'local') )->add(seconds => -$PROFILE_TIMEOUT); state $timed_out_at = $old_date->ymd('-') . ' ' . $old_date->hms(':'); return $timed_out_at; } sub is_profile_outdated { my ($timestamp) = @_; return timed_out_at() gt $timestamp; } !time ./parser.pl samples/profile.xml samples/media.xml Done in 30.186789 seconds!real0m30.531suser0m30.050ssys0m0.340s

Шаг 1. Используем «быстрые» модули

Page 15: Разработка на Perl под Raspberry PI

Шаг 1. Используем «быстрые» модулиuse Date::Calc qw(Today_and_Now Add_Delta_DHMS); { my $timed_out_at; ! sub timed_out_at { return $timed_out_at if $timed_out_at; ! my ($year, $month, $day, $hour, $min, $sec) = Add_Delta_DHMS(Today_and_Now(), 00, 00, 00, -$PROFILE_TIMEOUT); $timed_out_at = "$year-$month-$day $hour:$min:$sec"; return $timed_out_at; } } !time ./parser.pl samples/profile.xml samples/media.xml Done in 26.389202 seconds!real0m26.697suser0m26.310ssys0m0.150s

Page 16: Разработка на Perl под Raspberry PI

Шаг 2. Ненужная отладка profile => sub { # . . . for my $ad (@{ $parser->{parameters}->{ads} }) { my $score = calc_score($ad, $profile); $ad->{scores}->{ $profile->{name} } = $score; $ad->{scores}->{total} += $score; $parser->{parameters}->{total_scores} += $score; } return $tag_name => $attr; }, !# . . . !debug('ads with scores: ' . pp($playlist));

Page 17: Разработка на Perl под Raspberry PI

Шаг 2. Ненужная отладкаmy $DEBUG = $ENV{DEBUG} // 0; if ($DEBUG) { require Data::Dump; } !sub debug { return if !$DEBUG; ! warn map { ref $_ ? Data::Dump::pp($_) : $_ } @_; } !debug('ads with scores: ', $playlist); # Запятая!

time ./parser.pl samples/profile.xml samples/media.xml Done in 6.818755 seconds!real0m7.080suser0m6.950ssys0m0.090s

Page 18: Разработка на Perl под Raspberry PI

Файл объявлений• Читается один раз

• Не нуждается в потоковой обработке

• Можно использовать более быстрый парсер XML

Page 19: Разработка на Perl под Raspberry PI

use XML::Fast qw(xml2hash); use File::Map qw(map_file); !sub read_media_file { my $media_content_file = shift; ! map_file my $media_map, $media_content_file; my $playlist = xml2hash($media_map, attr => '')->{xml}->{Playlist}; for my $ad (@{ $playlist->{Content} }) { $ad->{likes} = [split /;/, lc $ad->{likes}] if exists $ad->{likes}; } return $playlist; }

XML::Fast + File::Map

time ./parser.pl samples/profile.xml samples/media.xml Done in 7.259239 seconds!real0m7.522suser0m7.360ssys0m0.100s

Page 20: Разработка на Perl под Raspberry PI

Шаг 3. Замеряем время работы#!/usr/bin/env perl my $t; BEGIN { use Devel::Timer; $t = Devel::Timer->new(); $t->mark('BEGIN'); } END { $t->mark('END'); $t->report(collapse => 1); } use local::lib; use common::sense; use Const::Fast; use Date::Calc qw(Today_and_Now Add_Delta_DHMS); use Data::Dump qw(pp); use File::Temp (); use File::Copy qw(mv); use List::UtilsBy qw(max_by); use XML::Rules; use XML::Fast qw(xml2hash); use File::Map qw(map_file); !$t->mark(‘loaded'); # такие отметки по всей программе

Page 21: Разработка на Perl под Raspberry PI

Шаг 3. Замеряем время работыtime ./parser.pl samples/profile.xml samples/media.xml !!Devel::Timer Report -- Total time: 7.3259 secs Count Time Percent ---------------------------------------------- 1 3.6184 49.39% created temp file -> profiles processed # подсчет очков 1 1.6455 22.46% BEGIN -> loaded # загрузка модулей 1 1.1747 16.03% loaded -> media file read # чтение файла объявлений 1 0.8662 11.82% file moved -> ad shown # get_ad_to_show() && show_ad() 1 0.0079 0.11% ad shown -> END 1 0.0060 0.08% set up profile rules -> created temp file 1 0.0036 0.05% profiles processed -> file moved 1 0.0032 0.04% media file read -> set up profile rules 1 0.0004 0.00% INIT -> BEGIN !real 0m7.584s user 0m7.370s sys 0m0.130s

Page 22: Разработка на Perl под Raspberry PI

Время загрузки модулейuse common::sense; $t->mark('loaded common::sense;'); use local::lib; $t->mark('loaded local::lib;'); use Const::Fast; $t->mark('loaded Const::Fast;'); use Date::Calc qw(Today_and_Now Add_Delta_DHMS); $t->mark('loaded Date::Calc'); use Data::Dump qw(pp); $t->mark('loaded Data::Dump'); use File::Temp (); $t->mark('loaded File::Temp'); use File::Copy qw(mv); $t->mark('loaded File::Copy'); use List::UtilsBy qw(max_by); $t->mark('loaded List::UtilsBy'); use XML::Rules; $t->mark('loaded XML::Rules;'); use XML::Fast qw(xml2hash); $t->mark('loaded XML::Fast'); use File::Map qw(map_file); $t->mark('loaded File::Map'); !$t->mark('modules loaded'); exit; !

Page 23: Разработка на Perl под Raspberry PI

Devel::Timer Report -- Total time: 1.6661 secs Interval Time Percent ---------------------------------------------- 01 -> 02 1.6611 99.70% BEGIN -> loaded common::sense; 13 -> 14 0.0023 0.14% modules loaded -> END 00 -> 01 0.0004 0.02% INIT -> BEGIN 10 -> 11 0.0003 0.02% loaded XML::Rules; -> loaded XML::Fast 02 -> 03 0.0003 0.02% loaded common::sense; -> loaded local::lib; 12 -> 13 0.0002 0.01% loaded File::Map -> modules loaded 04 -> 05 0.0002 0.01% loaded Const::Fast; -> loaded Date::Calc 11 -> 12 0.0002 0.01% loaded XML::Fast -> loaded File::Map 07 -> 08 0.0002 0.01% loaded File::Temp -> loaded File::Copy 06 -> 07 0.0002 0.01% loaded Data::Dump -> loaded File::Temp 05 -> 06 0.0002 0.01% loaded Date::Calc -> loaded Data::Dump 08 -> 09 0.0002 0.01% loaded File::Copy -> loaded List::UtilsBy 09 -> 10 0.0002 0.01% loaded List::UtilsBy -> loaded XML::Rules; 03 -> 04 0.0002 0.01% loaded local::lib; -> loaded Const::Fast;

Время загрузки модулей

Page 24: Разработка на Perl под Raspberry PI

require common::sense; $t->mark('loaded common::sense;'); require local::lib; $t->mark('loaded local::lib;'); require Const::Fast; $t->mark('loaded Const::Fast;'); require Date::Calc; $t->mark('loaded Date::Calc'); require Data::Dump; $t->mark('loaded Data::Dump'); require File::Temp; $t->mark('loaded File::Temp'); require File::Copy; $t->mark('loaded File::Copy'); require List::UtilsBy; $t->mark('loaded List::UtilsBy'); require XML::Rules; $t->mark('loaded XML::Rules;'); require XML::Fast; $t->mark('loaded XML::Fast'); require File::Map; $t->mark('loaded File::Map'); !$t->mark('modules loaded'); exit;

Время загрузки модулей

Page 25: Разработка на Perl под Raspberry PI

Devel::Timer Report -- Total time: 1.6027 secs Interval Time Percent ---------------------------------------------- 09 -> 10 0.3867 24.13% loaded List::UtilsBy -> loaded XML::Rules; 06 -> 07 0.3401 21.22% loaded Data::Dump -> loaded File::Temp 02 -> 03 0.3279 20.46% loaded common::sense; -> loaded local::lib; 10 -> 11 0.1328 8.28% loaded XML::Rules; -> loaded XML::Fast 04 -> 05 0.1080 6.74% loaded Const::Fast; -> loaded Date::Calc 05 -> 06 0.0851 5.31% loaded Date::Calc -> loaded Data::Dump 11 -> 12 0.0772 4.81% loaded XML::Fast -> loaded File::Map 03 -> 04 0.0539 3.36% loaded local::lib; -> loaded Const::Fast; 07 -> 08 0.0521 3.25% loaded File::Temp -> loaded File::Copy 08 -> 09 0.0265 1.65% loaded File::Copy -> loaded List::UtilsBy 01 -> 02 0.0095 0.59% BEGIN -> loaded common::sense; 13 -> 14 0.0024 0.15% modules loaded -> END 00 -> 01 0.0004 0.02% INIT -> BEGIN 12 -> 13 0.0003 0.02% loaded File::Map -> modules loaded

Время загрузки модулей

Page 26: Разработка на Perl под Raspberry PI

• XML::Rules -> XML::Fast (асинхронность не нужна)

• File::Temp, File::Copy, File::Map (работаем с файлами через стандартные функции Perl)

• local::lib (настраиваем пути в .bashrc)

• Const::Fast

• Data::Dump - загружаем по требованию

Выкидываем лишние модули

Page 27: Разработка на Perl под Raspberry PI

Многократно повторяющиеся элементы

- $profile->{likes} - = [map { lc $_->[1]->{_content} } @{ $profile->{likes} }]; ... + my $profiles = xml2hash(lc slurp($profile_file))->{xml}->{profiles}->{profile}; !!- for my $ad (@{ $playlist->{Content} }) { - $ad->{likes} = [split /;/, lc $ad->{likes}] ... + my $playlist = xml2hash(lc slurp($media_file), attr => '')->{xml}->{playlist};

• Проблема: изменен регистр символов на выходе - но это не критично

Page 28: Разработка на Perl под Raspberry PI

Результатtime ./parser.pl samples/profile.xml samples/media.xml !Devel::Timer Report -- Total time: 1.8889 secs Count Time Percent ---------------------------------------------- 1 1.3133 69.53% calculating scores: start -> calculating scores: end 1 0.4887 25.87% program: start -> modules loaded 1 0.0329 1.74% read profile file: start -> read profile file: end 1 0.0157 0.83% searching for ad to show: start -> searching for ad to show: end 1 0.0143 0.76% write profile file: start -> write profile file: end 1 0.0126 0.67% read media file: start -> read media file: end 1 0.0046 0.24% convert media file: start -> convert media file: end 1 0.0027 0.14% searching for ad to show: end -> program: end 1 0.0023 0.12% write profile file: end -> searching for ad to show: start !real 0m2.142s user 0m2.040s sys 0m0.070s

Page 29: Разработка на Perl под Raspberry PI

Почти хорошо!

Page 30: Разработка на Perl под Raspberry PI

Но можно лучше!

Page 31: Разработка на Perl под Raspberry PI

Пересмотрим результатыtime ./parser.pl samples/profile.xml samples/media.xml !Devel::Timer Report -- Total time: 1.8889 secs Count Time Percent ---------------------------------------------- 1 1.3133 69.53% calculating scores: start -> calculating scores: end! 1 0.4887 25.87% program: start -> modules loaded! 1 0.0329 1.74% read profile file: start -> read profile file: end 1 0.0157 0.83% searching for ad to show: start -> searching for ad to show: end 1 0.0143 0.76% write profile file: start -> write profile file: end . . . !real 0m2.142s user 0m2.040s sys 0m0.070s

• Не хватает CPU!

Page 32: Разработка на Perl под Raspberry PI

M - количество объявлений N - количество пользователей

Сложность алгоритма !

O(MxN)

Page 33: Разработка на Perl под Raspberry PI

Как уменьшить? !

(в идеале - привести к O(N))

Page 34: Разработка на Perl под Raspberry PI

Объявления• Основные параметры

• Возраст клиента

• Пол клиента

• Предпочтения клиента (их меньше, чем объявлений)

• Количество значений ограничено

• Значения известны после загрузки объявлений

• Почему бы не проиндексировать?

Page 35: Разработка на Perl под Raspberry PI

my %age; # Ads valid for given age my %gender; # Ads scored for given gender my %like; # Ads scored for given preference for my $ad (@{ $playlist->{content} }) { # Fill ages my $age_min = int($ad->{agemin}) || 0; my $age_max = int($ad->{agemax}) || 100; for my $current_age ($age_min .. $age_max) { push @{ $age{$current_age} }, $ad->{id}; } # Fill genders given ($ad->{gender}) { when ('male') { push @{ $gender{male} }, $ad->{id}; } when ('female') { push @{ $gender{female} }, $ad->{id}; } when ('all') { push @{ $gender{male} }, $ad->{id}; push @{ $gender{female} }, $ad->{id}; } } # Fill likes for my $current_like (split /;/, $ad->{likes}) { push @{ $like{$current_like} }, $ad->{id}; } }

Page 36: Разработка на Perl под Raspberry PI

Подсчет очков• Параметры профиля - те же, что и в объявлениях

• Сразу получаем нужные объявления для каждого значения параметра и добавляем им очки

Page 37: Разработка на Perl под Raspberry PI

!# Score of each ad my %score; sub calc_score { my ($profile) = @_; # Gender if (exists $profile->{gender}) { for my $ad_id (@{ $gender{$profile->{gender}} || [] }) { $score{$ad_id}++; } } # Age if (exists $profile->{age}) { for my $ad_id (@{ $age{$profile->{age}} }) { $score{$ad_id}++; } } # Likes if (ref $profile->{likes} && ref $profile->{likes}->{like}) { for my $profile_like (@{ $profile->{likes}->{like} }) { for my $ad_id (@{ $like{ $profile_like } || [] }) { $score{$ad_id}++; } } } }

Page 38: Разработка на Perl под Raspberry PI

Результатtime ./parser.pl samples/profile.xml samples/media.xml !Devel::Timer Report -- Total time: 0.8667 secs Count Time Percent ---------------------------------------------- 1 0.5066 58.45% program: start -> modules loaded 1 0.1656 19.11% calculating scores: start -> calculating scores: end 1 0.1177 13.58% index media file: start -> index media file: end 1 0.0339 3.91% read profile file: start -> read profile file: end 1 0.0152 1.75% write profile file: start -> write profile file: end 1 0.0130 1.50% read media file: start -> read media file: end !real 0m1.119s user 0m1.070s sys 0m0.020s

Page 39: Разработка на Perl под Raspberry PI

И еще кое-что• Raspberry PI поддерживает оверклокинг

• системными инструментами (raspi-config)

• безопасно (типа :))

• 700 -> 800 -> 900 -> 950 -> 1000 MHz

Page 40: Разработка на Perl под Raspberry PI

После оверклокингаtime ./parser.pl samples/profile.xml samples/media.xml !Devel::Timer Report -- Total time: 0.5798 secs Count Time Percent ---------------------------------------------- 1 0.3390 58.48% program: start -> modules loaded 1 0.1135 19.58% calculating scores: start -> calculating scores: end 1 0.0754 13.00% index media file: start -> index media file: end 1 0.0216 3.72% read profile file: start -> read profile file: end 1 0.0098 1.69% write profile file: start -> write profile file: end 1 0.0082 1.42% read media file: start -> read media file: end !real 0m0.814s user 0m0.740s sys 0m0.040s

Page 41: Разработка на Perl под Raspberry PI

Выводы• Измеряй все, что можешь - никому нельзя доверять безоговорочно.

• Выдели важное. Выбрось ненужное.

• Оптимизируй алгоритм: всегда есть другой путь.

• Выйди из зоны комфорта.

• Думай и побеждай.

Page 42: Разработка на Perl под Raspberry PI

Спасибо!

Чесноков Илья <[email protected]>