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

698 views

Published on

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

Published in: Technology, Education
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
698
On SlideShare
0
From Embeds
0
Number of Embeds
5
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Разработка на 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>

×