Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

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

Related Books

Free with a 30 day trial from Scribd

See all
  • Be the first to comment

  • Be the first to like this

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

  1. 1. Разработка на Perl под Raspberry PI ! Илья Чесноков
  2. 2. 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 (без периферии)
  3. 3. Официальные дистрибутивы • Raspbian • Pidora • RISC OS • RaspBMC • Arch • OpenELEC • …
  4. 4. Raspbian • Устанавливается копированием образа на флешку • Perl v5.14.2 • Легко ставится cpanminus, perlbrew, local::lib и т.д. • Много Perl-модулей среди пакетов системы
  5. 5. Задача • Поиск объявлений, релевантных для указанных пользователей • Реализация на Perl • Должно работать на Raspberry PI • Время обработки 100 профилей и 100 объявлений не должно превышать 2 секунды
  6. 6. Файл профилей <?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>
  7. 7. Файл объявлений <?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>
  8. 8. Алгоритм выбора объявления • Для каждого профиля: • Если профиль устаревший, то пропускаем • Для каждого объявления добавляем 1 очко, если в данном профиле совпадает что-то из: • • пол, возраст, предпочтения (likes) Выбираем объявления с максимумом очков (если их несколько, выбираем из них случайным образом)
  9. 9. Первый вариант программы use use use use use use use use use use ! common::sense; local::lib; Const::Fast; DateTime; Data::Dump qw(pp); File::Temp (); File::Copy qw(mv); List::UtilsBy qw(max_by); Time::HiRes qw(gettimeofday tv_interval); XML::Rules; process_files(@ARGV);
  10. 10. Парсинг XML и обработка данных # 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};
  11. 11. # 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);
  12. 12. Время работы time ./parser.pl samples/profile.xml samples/media.xml Done in 30.186789 seconds ! real 0m30.531s user 0m30.050s sys 0m0.340s
  13. 13. Оптимизация
  14. 14. Шаг 1. Используем «быстрые» модули 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 ! real 0m30.531s user 0m30.050s sys 0m0.340s
  15. 15. Шаг 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 ! real 0m26.697s user 0m26.310s sys 0m0.150s
  16. 16. Шаг 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));
  17. 17. Шаг 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 ! real 0m7.080s user 0m6.950s sys 0m0.090s
  18. 18. Файл объявлений • Читается один раз • Не нуждается в потоковой обработке • Можно использовать более быстрый парсер XML
  19. 19. XML::Fast + File::Map 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; } time ./parser.pl samples/profile.xml samples/media.xml Done in 7.259239 seconds ! real 0m7.522s user 0m7.360s sys 0m0.100s
  20. 20. Шаг 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'); # такие отметки по всей программе
  21. 21. Шаг 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 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 # подсчет очков # загрузка модулей # чтение файла объявлений # get_ad_to_show() && show_ad()
  22. 22. Время загрузки модулей use use use use use use use use use use use ! common::sense; $t->mark('loaded local::lib; $t->mark('loaded Const::Fast; $t->mark('loaded Date::Calc qw(Today_and_Now Add_Delta_DHMS); $t->mark('loaded Date::Calc'); Data::Dump qw(pp); $t->mark('loaded File::Temp (); $t->mark('loaded File::Copy qw(mv); $t->mark('loaded List::UtilsBy qw(max_by); $t->mark('loaded XML::Rules; $t->mark('loaded XML::Fast qw(xml2hash); $t->mark('loaded File::Map qw(map_file); $t->mark('loaded $t->mark('modules loaded'); exit; ! common::sense;'); local::lib;'); Const::Fast;'); Data::Dump'); File::Temp'); File::Copy'); List::UtilsBy'); XML::Rules;'); XML::Fast'); File::Map');
  23. 23. Время загрузки модулей 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;
  24. 24. Время загрузки модулей require require require require require require require require require require require ! common::sense; local::lib; Const::Fast; Date::Calc; Data::Dump; File::Temp; File::Copy; List::UtilsBy; XML::Rules; XML::Fast; File::Map; $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('loaded $t->mark('modules loaded'); exit; common::sense;'); local::lib;'); Const::Fast;'); Date::Calc'); Data::Dump'); File::Temp'); File::Copy'); List::UtilsBy'); XML::Rules;'); XML::Fast'); File::Map');
  25. 25. Время загрузки модулей 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
  26. 26. Выкидываем лишние модули • XML::Rules -> XML::Fast (асинхронность не нужна) • File::Temp, File::Copy, File::Map (работаем с файлами через стандартные функции Perl) • local::lib (настраиваем пути в .bashrc) • Const::Fast • Data::Dump - загружаем по требованию
  27. 27. Многократно повторяющиеся элементы $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}; • Проблема: изменен регистр символов на выходе - но это не критично
  28. 28. Результат 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
  29. 29. Почти хорошо!
  30. 30. Но можно лучше!
  31. 31. Пересмотрим результаты 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!
  32. 32. Сложность алгоритма ! O(MxN) M - количество объявлений N - количество пользователей
  33. 33. Как уменьшить? ! (в идеале - привести к O(N))
  34. 34. Объявления • Основные параметры • Возраст клиента • Пол клиента • Предпочтения клиента (их меньше, чем объявлений) • Количество значений ограничено • Значения известны после загрузки объявлений • Почему бы не проиндексировать?
  35. 35. 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}; } }
  36. 36. Подсчет очков • Параметры профиля - те же, что и в объявлениях • Сразу получаем нужные объявления для каждого значения параметра и добавляем им очки
  37. 37. ! # 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}++; } } } }
  38. 38. Результат 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
  39. 39. И еще кое-что • Raspberry PI поддерживает оверклокинг • системными инструментами (raspi-config) • безопасно (типа :)) • 700 -> 800 -> 900 -> 950 -> 1000 MHz
  40. 40. После оверклокинга 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
  41. 41. Выводы • Измеряй все, что можешь - никому нельзя доверять безоговорочно. • Выдели важное. Выбрось ненужное. • Оптимизируй алгоритм: всегда есть другой путь. • Выйди из зоны комфорта. • Думай и побеждай.
  42. 42. Спасибо! Чесноков Илья <chesnokov.ilya@gmail.com>

    Be the first to comment

    Login to see the comments

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

Views

Total views

804

On Slideshare

0

From embeds

0

Number of embeds

5

Actions

Downloads

2

Shares

0

Comments

0

Likes

0

×