Русский
Русский
English
Статистика
Реклама

Программирование

Григорий Бакунов об электронном голосовании

06.07.2020 02:17:13 | Автор: admin

Григорий Бакунов


Директор по распространению технологий Яндекса Григорий bobuk Бакунов в эфире Точки на Эхе Москвы поделился мнением о системе голосования, которая использовалась на выборах в городскую думу в 2019 году и на голосовании по вопросу изменения конституции в 2020. Получился любопытный разбор технических деталей для неспециалистов. На Хабре уже была хорошая публикация на эту тему.


Ниже приведена стенограмма эфира, который провёл Александр plushev Плющев. Его реплики выделены полужирным.


Я не делал этого сам, но я помог другому человеку поучаствовать в этом онлайн, поэтому весь процесс я всё-таки посмотрел, и теперь я его знаю.


То есть он голосовал не тайно? Была нарушена тайна голосования? Я прошу обратить на это внимание центризбирком.


Безусловно. И это тоже хороший показатель, как мне кажется, потому что, обратите внимание, ничто не мешает при онлайн-голосовании детям помогать родителям, и родителям помогать детям.


То, что не допускается на реальных участках. Если вы зайдёте с кем-то в кабинку, то наблюдатели, да и члены избирательных комиссий, скорее всего вас выгонят. Так должно быть. Я с таким сталкивался.


Ну, если они сами предварительно туда не зашли и не помогают вам правильно проголосовать. Так, говорят, тоже бывает. В общем, я немножко понял, как устроен этот процесс в реальности. И я несколько разочарован. Потому что Илья (Илья Массух, глава штаба для наблюдения за ходом электронного голосования в Москве прим. ред.) нам рассказывал, что всё будет устроено немножко не так, как было во время голосований, которые относились к Москве. В прошлый раз это была мосгордума, кажется. К сожалению, всё практически точно так же.


Так, что ты имеешь в виду? Прежде всего?


Что это тот же самый, по большому счёту, блокчейн, который, на самом деле, довольно фиктивный. Что это процесс, в котором большая часть того, что относится к нашей безопасности, сделано, на самом деле, на совершенно фиктивном уровне. И очень жаль, что технических специалистов здесь никто не послушал. Я специально пошёл в интернет посмотреть, а были ли умные люди, которые до этого писали: Ребята, а вот как надо. И таких статей, которые рассказывают, как надо, как можно было сделать, чтобы технические специалисты во всё это поверили, очень много. Но, разумеется, это, по честному если, никому не нужно. Можно прямо разбор провести. Вот давайте с самого начала начнём. Смотрите, нам много раз в разных источниках говорили про то, что это блокчейн. А значит ничего подделать нельзя. Но это, на самом деле, чудовищный обман. В данном случае используется вообще-то хорошая, качественная реализация блокчейна. Этот блокчейн называется Exonum. Это довольно крутая разработка, очень качественная, в которую люди вложили много усилий, чтобы в ней действительно ничего нельзя было подделать. Если бы не одно но. Там такая структура, в которой есть узлы, которые записывают данные в блокчейн, а есть валидаторы. Такая система, которая каждый раз подтверждает, что то, что записано в блокчейн, записано верно. И там невероятно сложная конструкция, которая всё это валидирует. Она продумана так, что для того, чтобы убедить систему записать поддельные данные, или сказать, что какая-то часть данных поддельная, тебе нужно завладеть более, чем двумя третями плюс одним узлом. То есть если у тебя их десять, то тебе нужно, чтобы у тебя в этой системе было семь узлов, чтобы сказать, что запись, которая проведена в блокчейн, она неправильная. Но тут есть один тонкий момент. Я специально пошёл и специально проверил. Все валидаторы, на самом деле, принадлежат государству. Все до одного.


Поясни, что это значит. Чем это чревато?


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


То есть ты сейчас как эксперт заявляешь, что электронное голосование может подвергаться последующему изменению записей о нём, верно?


Оно может подвергаться изменению в процессе. Как это выглядит. Вот смотрите, в чём идея блокчейна. В том, что в каждой последующей записи есть информация о предыдущих записях. Таким образом, если ты хочешь изменить какую-то запись, например, на пять от текущего момента назад, тебе придётся изменить не только её, но и пять последующих. Так вот в текущей ситуации с текущим блокчейном сделать это довольно легко, это несложная история. Мы об этом говорили ещё и во время московских выборов. С тех пор ничего глобально не поменялось, к сожалению. При этом, смотрите, в основе этого оригинального блокчейна, который называется Exonum, в нём была даже для этого предусмотрена специальная фишка, которая называлась Anchoring. Ну, как сказать, якорение. Идея была такая: раз в какое-то время записывать контрольную сумму, то есть записывать информацию о транзакциях, которые через этот блокчейн прошли, в другой блокчейн, не зависящий от этого. Например, в блокчейн биткойна. В систему, которая показательно независима, и поэтому ты там ничего поделать не можешь. Разумеется, эта фича была выключена, выпилена и никак не использовалась. В Exonum была отдельная такая сущность, которая называлась узлы аудита. Которые не могут ничего писать, но которые могут только проверять информацию. И которые можно было выдать, не знаю, мне, например, или тебе, чтобы ты был руководителем этого узла и мог смотреть, что там происходит. Но они тоже не были предоставлены. Не было классической истории про новый блокчейн, которая называется контроль развёртывания. Это такая практика, при которой, когда разворачивается новый блокчейн на серверах, туда пускаются специальные люди, которые контролируют, что в блокчейне на момент разворачивания нет никакой подделки, нет никаких непонятных действий внутри. Вы понимаете, сейчас единственное, что мы знаем про текущий блокчейн, это текущие транзакции, которые прямо сейчас по нему проходят. Но мы не знаем, не было ли туда доложено предварительно большое количество других транзакций. Мы ничего про это не знаем. Большое количество дополнительной информации Блин, прости, что я монологом говорю.


Давай-давай, интересно.


Я покопал большое количество дополнительной информации. И вот статья, которую ты мне скинул Спасибо тебе большое за неё, потому что я бы, конечно, сам не взялся, а так я просто проверил. Действительно, выяснилось, что довольно простым набором действий можно подтвердить, что голосование было устроено Вот как сейчас, смотрите. Вы заходите на сайт. Для простоты, mos.ru. Вы получаете там, грубо говоря, разрешение или бюллетень на голосование. Там генерируется особенная строчка в вашем собственном браузере, и после этого, внимание, эта строчка отправляется на сервер, который называется elec.moscow. Я пошёл специально посмотреть по адресам, где находятся эти замечательные сервера, которые называются elec.moscow. И там, внезапно, знаете, такие странные штуки: московский минздрав, штука, которая, вот я до сих пор не знаю, что такое Moscow District Council. Ты мне можешь сказать? Я не знаю.


Районный совет?


Ну, вероятно Я не знаю, что это такое. Тем не менее, это всё московские государственные организации. То есть когда нам говорили, что вот этот сайт голосования, который будет эту промежуточную страницу выдавать, он будет на независимых узлах Ну, вот настолько они независимые. То есть они принадлежат московскому правительству. Это те узлы, по которым смог пройти я. В чём здесь фокус. В том, что вы получаете действительно уникальный идентификатор, который вроде как mos.ru не знает. Но этот промежуточный сайт, который назвается elec.moscow, он его всё равно получает. И этого в принципе достаточно по времени для того, чтобы идентифицировать и связать вас как человека, пользующегося mos.ru...


Смотри, в результате ты выяснил уже две вещи: первая вещь, что голосование можно подменить, верно?


В процессе его можно подмешивать, да. И изменять, как тебе хочется, всё так.


И второе, что его можно проконтролировать.


Можно проконтролировать, что конкретный пользователь mos.ru проголосовал. Как конкретно он проголосовал сложный вопрос, можно узнать или нет, потому что Вот какая тонкость, про которую мы рассказывали в прошлый раз. Для того чтобы узнать, что это был за голос, в пользу кого он был отдан, нужно выполнить вот какое упражнение. В тот момент, когда начиналось голосование, в самом начале, были розданы несколько ключей разным значимым лицам, в том числе, насколько я помню, одна часть ключа находится у генерального директора радиостанции, на которой мы сейчас находимся, если я ничего не путаю. В прошлый раз было так, я думаю, в этот раз точно так же.


У главного редактора, ты имеешь в виду.


У главного редактора, прости пожалуйста, а почему я сказал у генерального директора?


Не знаю.


Наверное, заволновался. Ну, ты понял меня.


Да.


Мне кажется, все поняли. И в этой конструкции есть штука, про которую мы должны исключительно верить: что разделение этого ключа не было сопровождено никакой предварительной записью этого ключа. То есть ключ раздали, но нигде его предварительно не склеили. Грубо говоря, мы должны на слово поверить, что этого ключа прямо сейчас не существует, и существовать он будет только в тот момент, когда вот эти N представителей, я не помню, по-моему, их пять человек, соберутся вместе, сложат вместе этот ключ, и тогда будет общий ключ, для того чтобы проверять информацию в блокчейне, как она записана. Я думаю, что, на самом деле, конечно же, как минимум, для надёжности, для того, чтобы быть уверенным, что всё работает, этот ключ где-нибудь сохранён. И это значит, что даже сам по себе голос с очень высокой степенью вероятности можно увидеть. И увидеть, как именно, за кого ты проголосовал. Мне кажется очень странной вся конструкция, которая была построена вокруг сайта наблюдателя, на котором можно было видеть блоки. Во-первых, произошло довольно большое количество поломок на этом сайте. Я не знаю, как у вас, а у меня много всегда вопросов, когда начинает ломаться сайт, который показывает мне информацию о том, что происходит в системе. Если ломается даже он, это значит, что в системе происходит полный хаос. Обычно так.


Подожди, что за сайт ты имеешь в виду?


Был такой сайт, который назывался observer что-то там. Я уже не очень помню. Это такой сайт формально для наблюдателей, на котором можно было посмотреть, как в блокчейн записываются транзакции. Он до сих пор работает, я могу прямо сейчас его открыть из интереса, где-то он у меня даже был записан Ну, в общем, такой сайт есть. Он публично доступен. И если вы на него посмотрите, прямо сейчас вы увидите, что транзакции, которые в нём идут Это такая традиционная история, там проходят транзакции, транзакции блокчейна. Постоянно происходят очень странные флуктуации. Вот блок блокчейна, в котором одна запись, вот ноль записей, вот одна запись, вот ноль записей, вот тридцать пять записей на ровном месте.


Ты имеешь в виду constitution.observer?


Да-да. Что-то такое там было. Я, к сожалению, не очень запоминаю...


Нет, боюсь, не то. Я, правда, не знал даже о таком.


Ты можешь посмотреть, у нас с тобой где-то в новостях была ссылка на этот сайт. Нет: observer2020.mos.ru. Я тебе сейчас пришлю ссылку, чтобы ты посмотрел на неё. Так вот, на самом деле, на этом сайте очень интересно смотреть за происходящим. Я не очень понимаю, по какой причине там возникают такие флуктуации, когда иногда ноль, иногда одна, а иногда пятьдесят записей в один блок попадает, но, допустим, что это нормально. Но когда выяснилось, что этот сайт по какой-то причине периодически падает, ломается, при том, что заходит на него пять инвалидов Это я сейчас не про конкретных людей, а в смысле пять людей, которые изредка что-то нажимают. Пять, десять, пятнадцать, несколько сотен человек. Это, в общем-то, всё мелочи для вебсайта, конечно. У меня возникает вопрос о квалификации тех людей, которые этот сайт запустили. И, конечно, когда выяснилось, что в работе этого сайта были прямо конкретные проблемы. Например, было несколько часов, когда данные по этим самым закрытым блокам вообще не показывались, то есть создавались нулевые файлы, и историю их было совершенно не посмотреть. И, насколько я знаю, эта история до сих пор не исправлена. Нам остаётся только верить в то, что в блокчейне в этот момент не происходило никаких изменений, потому что нас же не допускают к самому блокчейну, нам предоставляют только вот такой веб-интерфейс, в котором мы можем посмотреть, что в блоке номер 1452184 ноль транзакций. Или одна транзакция.


Да-да-да, есть такое дело.


В общем, всё это в целом вызывает ощущение дичайшего хаоса и Давай я тебе так скажу. Если бы кто-то из айтишников в айти-компаниях внезапно начал бы делать такой интерфейс с таким качеством, он бы недолго проработал на этой работе. Это просто ощущение сделанного левой задней ногой без какого-то желания сделать хорошо. Я, если честно, несколько удивлён.


Ты знаешь, одно дело, сделано плохо. Ну, просто в силу низкой квалификации. В конце концов, всё-таки даже можно понять, потому что конкуренция на вашем программистском рынке очень высокая, и надо всё-таки находить деньги не только на освещение города, не только на перелицовку бордюра, не только на праздники какие-то, но и на найм квалифицированных программистов. А их не хватает в силу уже вышеназванных причин. Тут, в общем, можно понять. Одно дело, когда так. Другое дело, когда это сделано в той или иной степени сознательно. Для того чтобы можно было каким-либо образом воздействовать на результаты электронного голосования. Вот что ты по этому поводу думаешь? Это разгильдяйство или заговор?


Ну, я же часто говорил, что не надо пытаться объяснить злым умыслом то, что можно объяснить банальным патриотизмом. И в данном конкретном случае я думаю ровно это же. Я не уверен в том, что там был большой заговор. Но то, что там оставлен потенциал для того, чтобы иметь возможность изменять текущие записи это совершенно точно. Он там есть. Просто по структуре того, как устроен сам проект. Означает ли это, что подделки, какие-то вбросы голосов были? Я, к сожалению, этого сказать не могу. Так же как и не могу сказать противоположного, потому что, повторюсь, у нас нет возможности посмотреть в этот блокчейн.


Да, но ты говоришь, что возможность того, чтобы эти фальсификации были, оставлена.


Я говорю вот что. Что в изначальном опенсорсном блокчейне, который используется в основе, было много сделано для того, чтобы не дать возможность фальсификации. Все эти штуки были выпилены. Я не думаю, что это было сделано для простоты. Нет, это было сделано для того, чтобы больше контролировать происходящее. Другого объяснения я не вижу.


Понятно. Слушай, ещё хотел несколько штук Для меня очень комичная ситуация, что сайт, который нам очень долго рекламировали, 2020og.ru, вот он упал утром первого дня голосования и до сих пор, главное, не вставал. Там, по сути, есть информационный сайт, и есть на его поддомене сайт, на котором проходит голосование. Вот информационный сайт упал и лежит. И не встаёт. То есть формально, если бы это было единственное место, где можно узнать о поправках в конституцию, то вы бы больше нигде не узнали, потому что всё, он лежит. Что они сделали. Они просто сделали переадресацию на голосование. Если вы заходите на 2020og.ru, то вы уходите на голосование. И вам говорят, можете вы проголосовать или нет. Всё. Всё остальное закончилось. Это ЦИКовский проект, это уже не ДИТ, это ЦИК делал, центризбирком России. И вот, видимо, это из того же ряда, о котором говорил Григорий Бакунов, насчёт высочайшей квалификации, с которой всё это сделано. Ну, потому что, что тут можно сказать. Это что, не рассчитали нагрузку и поняли, что даже если его поднимут, то лучше и не надо? Или что, объясни.


Ну, у меня нет никакого объяснения. Я думаю, что причина здесь в том, что сайт просто откровенно не выдерживал нагрузку. У программистов, вообще у технарей, которые делают сайты, есть такая традиция: мы, прежде чем запускать какой-нибудь сервис, его, есть такое хорошее слово, обстреливаем. Мы прогоняем через него большой поток ненастоящего, как будто пользовательского, трафика, проверяя, что сайт выдерживает нагрузку. В данном случае, судя по всему, этим никто не озаботился. Вот мы получили этот результат. А то, что на этом сайте с обзёрвером блоков происходило с ошибками, тебя совершенно не удивляет, да? То есть, когда выяснилось, например, что не формируются отчёты, которые вроде бы нам обещали. Что там будут прямо настоящие наблюдатели за голосованием, которые должны по отчётам смотреть...


Отчёты это то, что ты можешь скачать оттуда?


Да. Когда оно просто не формировалось. То есть, нет, не так, вру. Оно формировалось. Видимость того, что отчёты есть, была. Просто отчёты были нулевой длины.


А, это вот то, о чём писали Открытые Медиа насчёт сбоя, который Слушай, а почему произошёл этот сбой, о которых писали Открытые Медиа? Давай я просто напомню, в чём там было дело. Главное, что ДИТ Москвы признал, что сбой произошёл. Организаторы плебисцита не рассчитали размер файлов и забыли, что при многодневном голосовании следует обозначать не только время, но и дату. С вечера первого дня голосования по поправкам к конституции департамент информационных технологий Москвы больше 12 часов публиковал пустые выписки из блокчейн-системы для наблюдения за ходом электронного голосования, обнаружили Открытые медиа. Как удалось выяснить изданию, неполадка произошла из-за проблем, связанных с размером файла. Но не объясняется, почему, собственно, размер файла таковым оказался, непредсказуемым.


Я не знаю, а какой размер-то был непредсказуемым? Нечётный или что? Что такое непредсказуемый размер? Вы заранее знали, какое количество людей придёт голосовать. Было заранее известно, что больше миллиона человек. Какие там могут быть размеры файла? По утверждениям специалистов и по редким замечаниям человека, который всем этим руководил, я не знаю, можно назвать его специалистом или нет, вся проблема была в том, что...


Ты про начальника управления смарт-проектов правительства Москвы Артёма Костырко?


Да-да. В смысле, что у него была прямая речь о том, что они не успевали из блокчейна забирать все данные, которые нужно было выкладывать в отчёт. Но, прошу прощения, а проверить вы это не могли заранее? Вот опять же Я не знаю Вот была такая же история с голосованием в мосгордуму. Это уже второе такое голосование, и в нём по-прежнему просто детские ошибки. И вот, повторюсь ещё раз. У нас, у технарей, есть простое правило. Ну, давайте я на автомобильный лад что ли переведу. Если вы сидите в машине, которая должна вас провезти, я не знаю, там, тысячу километров, а у вас на приборной панели кнопочки отваливаются. Вы будете на такой машине ехать? Не знаю, как вы, а я нет, потому что я думаю, что в двигателе то же самое. Кнопочки тоже не работают. В смысле, в двигателе не работает ничего. Не дай бог взорвётся. И такая же логика у меня в отношении этого проекта. Я на него смотрю, я вижу, как он работает по внешним признакам, и думаю, что внутри там всё настолько же ужасно.


Понятно. Я хочу скзать, что Артёма Костырко мы звали сегодня. Мы подумали, что хватит уже с Ильёй Массухом при всём уважении говорить. До голосования поговорили, а теперь, когда система заработала, мы хотели бы поговорить с Артёмом Костырко. Но он не смог посетить сегодня нашу программу, в том числе и удалённо. Так что, может быть, вы опосредованно услышите его либо в других наших передачах, либо в других средствах массовой информации.


Я думаю, что просто видеофайлы, которые через Zoom проходят, они слишком большие, и они не смогли отправиться, я думаю в этом дело.


Кто-то один из нас издевается над московскими властями.


Ты знаешь, удивительно, что это сегодня не ты, да? Но просто у меня подпригорает Простите, подгорает. Я не знаю У меня довольно тёплое сидение сейчас, на котором я сижу, потому что, ну, действительно, я не ожидал, что всё настолько плохо.

Подробнее..

Что делать, если брать фронтенд-фреймворк это излишество

07.07.2020 08:21:43 | Автор: admin

Пример @@include


Современные фронтенд-фреймворки дают удивительные возможности. React, Vue, Angular и другие созданы делать то, что раньше было невозможно, веб-приложения. В 2020 скачивать и устанавливать приложения уже необязательно. Зачем, если всё можно сделать на сайте?


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


В этом вопросе я поддерживаю "консерваторов". Не нужно писать лендинги и многостранички на Create-React-App, для этого пойдет и обычная статика.


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


Что делать? Писать простыню HTML-разметки в одном файле? Хранить данные во view? Это не сделать шаг назад, это сорваться и упасть в пропасть. Это не просто неудобно, это идет вразрез с современной парадигмой фронтенд-разработки.


Во-первых, data-driven. Данные во главе угла, а внешний вид лишь их отображение. Пользователь делает действие, данные меняются, вслед меняется отображение. Так работают все современные фреймворки.


Еще один элемент современного подхода компонентность. Делим приложение на мелкие самодостаточные переиспользуемые куски. Больше переиспользуемости меньше кода. Меньше кода меньше багов.


До этого мы уже обсуждали, как реализовать data-driven минимальными усилиями. Мой выбор Alpine.js. Что же делать с компонентностью? Для простых статических сайтов я предлагаю самый простой вариант gulp-file-include.


Столько говорил о современности и парадигмах, а закончилось всё библиотекой, которой уже лет 100? Ну, на самом деле, она не такая уж и старая. 4 года с версии 1.0.0, столько же, сколько первой стабильной версии React (15). Да и зачем заново изобретать велосипед, если уже всего готово.


У библиотеки всего шестьсот звезд на Github и 6,5 тысяч скачиваний в неделю на npm, но она дает нам всё, что нужно, чтобы быстро разделить простыню HTML на небольшие удобные компоненты. И при этом не добавив ни байта дополнительного кода в конечный результат.


Единственная преграда знакомство с Gulp. Ну, на самом деле, писать даже простой лендинг без подобного инструмента сегодня не очень правильно. Поэтому, если вы не знаете Gulp, узнайте. Вот для этого статья на Хабре, вот ссылка на перевод документации. Мы на этом останавливаться не будем.


Если вы хотите не прерываться, а хотите продолжить со мной, вот ссылка на мой пресет на Github, с которым мы будем работать, там же вы можете найти мой gulpfile.


Итак, что будем делать? Вот это


Что мы будем делать


Выглядит просто. Посмотрим, как это выглядит в HTML.


Как это выглядит в HTML
<section class="text-gray-700 body-font">  <div class="container px-5 py-24 mx-auto">    <h1 class="mb-20 text-2xl font-medium text-center text-gray-900 sm:text-3xl title-font">      Проблемы, которые я решаю    </h1>    <div class="flex flex-wrap -mx-4 -mt-4 -mb-10 sm:-m-4 md:mb-10">      <div        class="flex flex-col items-center p-4 mb-6 sm:flex-row lg:w-1/3 md:mb-0 sm:items-stretch"      >        <div          class="inline-flex items-center justify-center flex-shrink-0 w-12 h-12 mb-4 text-indigo-500 bg-indigo-100 rounded-full"        >          <svg            class="w-6 h-6"            fill="none"            stroke="currentColor"            stroke-linecap="round"            stroke-linejoin="round"            stroke-width="2"            viewBox="0 0 24 24"          >            <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>          </svg>        </div>        <div class="flex-grow pl-6">          <h2 class="mb-2 text-xl font-medium text-gray-900 title-font">Оптимизация скорости</h2>          <p class="text-lg leading-relaxed">            Увеличим быстродействие системы при загрузке, уменьшим нагрузку на процессор и оперативную память, исключим из автозагрузки требовательные к ресурсам устройства программы.          </p>        </div>      </div>      <div        class="flex flex-col items-center p-4 mb-6 lg:w-1/3 md:mb-0 sm:flex-row sm:items-stretch"      >        <div          class="inline-flex items-center justify-center flex-shrink-0 w-12 h-12 mb-4 text-indigo-500 bg-indigo-100 rounded-full"        >          <svg            class="w-6 h-6"            fill="none"            stroke="currentColor"            stroke-linecap="round"            stroke-linejoin="round"            stroke-width="2"            viewBox="0 0 24 24"          >            <circle cx="6" cy="6" r="3"></circle>            <circle cx="6" cy="18" r="3"></circle>            <path d="M20 4L8.12 15.88M14.47 14.48L20 20M8.12 8.12L12 12"></path>          </svg>        </div>        <div class="flex-grow pl-6">          <h2 class="mb-2 text-xl font-medium text-gray-900 title-font">            Восстановление системных файлов          </h2>          <p class="text-lg leading-relaxed">            В случае некорректной работы системы и устройств, проведём анализ системных файлов и восстановим их, если они повреждены.          </p>        </div>      </div>      <div        class="flex flex-col items-center p-4 mb-6 lg:w-1/3 md:mb-0 sm:flex-row sm:items-stretch"      >        <div          class="inline-flex items-center justify-center flex-shrink-0 w-12 h-12 mb-4 text-indigo-500 bg-indigo-100 rounded-full"        >          <svg            class="w-6 h-6"            fill="none"            stroke="currentColor"            stroke-linecap="round"            stroke-linejoin="round"            stroke-width="2"            viewBox="0 0 24 24"          >            <path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"></path>            <circle cx="12" cy="7" r="4"></circle>          </svg>        </div>        <div class="flex-grow pl-6">          <h2 class="mb-2 text-xl font-medium text-gray-900 title-font">            Установка и обновление драйверов устройств          </h2>          <p class="text-lg leading-relaxed">            При неработоспособности какого-либо из устройств или проблемах, связанных с их некорректной работой, произведём установку, обновление или откат драйверов.          </p>        </div>      </div>    </div>  </div></section>

Воу! Не так уж и просто, как хотелось бы. Безусловно, TailwindCSS, который здесь используется, тоже даёт о себе знать. Если бы мне пришлось разбираться в этом коде, я бы первым дизлайкнул статью, заявляющую, что TailwindCSS это новый шаг эволюции. Но нет, я её написал. А всё потому, что этот код можно красиво поделить на компоненты, где эта гора классов превратится в осмысленную картину, которая не только ухудшит, а даже улучшить наш developer-experience.


Но вернемся к плагину. Начнем с небольшой теории. Чтобы с помощью gulp-file-include вставить один HTML в другой, нам нужно написать команду @@include(<путь до файла>, <объект с переменными>).


При этом в gulpfile можно многое настроить под себя. У меня настроено примерно так:


function html() {  return src('src/*.html')    .pipe(fileinclude({ basepath: './src/partials' }))    .pipe(dest('dist'));}

Берем все HTML-файлы из src, прогоняем через наш плагин и кладем в папку dist. При этом в настройки плагина можно передать ряд опций. Быстро пройдемся по ним.


  • prefix можно изменить префикс с @@ на любой другой.
  • suffix можно добавить суффикс.
  • basepath можно настроить, как просчитываются пути в директивах. По умолчанию '@file' от HTML-файла. Есть еще '@root' от корня, или любой другой путь. В нашем случае, я создал специальную папку в src partials, где и будут лежать все наши компоненты. Важно правильно настроить Gulp, чтобы эти компоненты не попали в окончальную сборку. В примере выше, Gulp берет только файлы из корня src, не заглядывая в папки. Это то, что нам нужно.
  • filters позволяет задавать функции, которые потом можно будет запускать из разметки. Смотрите примеры в документации.
  • context "глобальные" переменные для условий @@if.

Также есть ряд директив:


  • @@include вставка HTML-файла в другой HTML.
  • @@if условия; переменные берутся из "глобального" context и/или из объекта переменных использованного @@include.
  • @@for обычный цикл по массиву из context/переменных @@include.
  • @@loop проходимся по массиву объектов, используя данные из них как переменные, и вставляем для каждого компонент. Может использовать JSON.

Можно было бы разобрать каждый из них по отдельности, но это избыточно. Проще просто перейти в документацию и посмотреть примеры для каждого случая. Там всё более-менее понятно.


В нашем случае нас больше всего интересует @@loop. Мы перенесем все данные из карточек в JSON-файл, создадим компонент и передадим ему его данные, как пропсы.


Что у нас входит в данные? Правильный ответ: заголовок карточки, текст и SVG. В переменные мы можем передавать как просто текст, так и HTML как строку. Он просто подставит её, куда мы скажем.


Вот как выглядит JSON (data.json).


[  {    "title": "Оптимизация скорости",    "text": "Увеличим быстродействие системы при загрузке, уменьшим нагрузку на процессор и оперативную память, исключим из автозагрузки требовательные к ресурсам устройства программы.",    "svg": "<path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"></path>"  },  {    "title": "Восстановление системных файлов",    "text": "В случае некорректной работы системы и устройств, проведём анализ системных файлов и восстановим их, если они повреждены.",    "svg": "<circle cx=\"6\" cy=\"6\" r=\"3\"></circle><circle cx=\"6\" cy=\"18\" r=\"3\"></circle><path d=\"M20 4L8.12 15.88M14.47 14.48L20 20M8.12 8.12L12 12\"></path>"  },  {    "title": "Установка и обновление драйверов устройств",    "text": "При неработоспособности какого-либо из устройств или проблемах, связанных с их некорректной работой, произведём установку, обновление или откат драйверов.",    "svg": "<path d=\"M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2\"></path><circle cx=\"12\" cy=\"7\" r=\"4\"></circle>"  }]

Теперь создадим компонент одной карточки (card.html). Переменные вставляем как @@<имя переменной>.


<div  class="flex flex-col items-center p-4 mb-6 sm:flex-row lg:w-1/3 md:mb-10 sm:items-stretch">  <div    class="inline-flex items-center justify-center flex-shrink-0 w-12 h-12 mb-4 text-indigo-500 bg-indigo-100 rounded-full"  >    <svg      class="w-6 h-6"      fill="none"      stroke="currentColor"      stroke-linecap="round"      stroke-linejoin="round"      stroke-width="2"      viewBox="0 0 24 24"    >      @@svg    </svg>  </div>  <div class="flex-grow pl-6">    <h2 class="mb-2 text-xl font-medium text-gray-900 title-font">@@title</h2>    <p class="text-lg leading-relaxed">@@text</p>  </div></div>

Осталось только создать файл секции (index.html).


<section class="text-gray-700 body-font">  <div class="container px-5 py-24 mx-auto">    <h1 class="mb-20 text-2xl font-medium text-center text-gray-900 sm:text-3xl title-font">      Проблемы, которые я решаю    </h1>    <div class="flex flex-wrap -mx-4 -mt-4 -mb-10 sm:-m-4">      @@loop('problems/card.html', 'partials/problems/data.json')    </div>  </div></section>

Первым параметром в @@loop передаем путь до компонента (от настроенного ранее basepath), вторым путь до JSON-файла (от src).


Структура файлов выглядит вот так:


src   index.html   main.csspartials      problems          index.html          card.html          data.json...

Теперь сам index.html я могу вставить с помощью @@include в файл основной страницы.


<!DOCTYPE html><html lang="ru">  <head>    ...  </head>  <body>    ...    @@include('problems/index.html')    ...  </body></html>

Минимальными усилиями мы получили полноценное компонентное деление. При этом, это никак не отразится на результате, будет такой же HTML, как и раньше. Конечно, не могу не заметить, что это подкрепляет также тезис про TailwindCSS, он уже не кажется таким неудобным, как прежде, не так ли? Дальше дело за малым повторить всё выше перечисленное для всех секций сайта.


На этом закончим знакомство с данным плагином. Цель данной статьи было показать, как легко можно в небольших проектах избежать использование тяжеловесных современных фреймворков, и при этом сохранить те идеи, которые они с собой несут. Конечно, это не единственный вариант, как это сделать. Возможно, вы знаете другие альтернативы, интересно будет услышать о них в комментариях.


Напоследок прошу ответить на вопрос:

Подробнее..

50200 вопросов по JavaScript

06.07.2020 10:05:56 | Автор: admin


Доброго времени суток, друзья!

Предлагаю Вашему вниманию небольшой интерактив своего рода викторину по JavaScript, на данный момент состоящую из 50 вопросов.

На мой взгляд, решение подобных задач лучший способ определить свой уровень мастерства.

Предисловие


Данная часть основана на этом репозитории. Его автор, Lydia Hallie, позиционирует свой проект как список продвинутых вопросов и, действительно, среди них есть такие, которые, как мне кажется, даже опытному JavaScript-разработчику покажутся непростыми. Однако среди этих вопросов есть и такие, для ответа на которые достаточно владеть базовыми знаниями. В репозитории имеется русский перевод, но, мягко говоря, он оставляет желать лучшего, поэтому большую часть ответов (объяснений) пришлось переводить заново.

Следует отметить, что приводимые пояснения (ответы) не всегда в полной мере раскрывают суть проблемы. Это объясняется формой проекта он представляет собой чеклист, а не учебник. Ответы, скорее, являются подсказкой для дальнейших поисков на MDN или Javascript.ru. Впрочем, многие из объяснений содержат исчерпывающие ответы.

Несмотря на то, что код неоднократно протестирован, никто не застрахован от ошибок, разумеется, кроме тех, кто ничего не делает. Поэтому при обнаружении ошибок, опечаток, неточностей, некорректности формулировок и т.п., а также при желании улучшить перевод, прошу писать в личку, буду признателен (активность на GitHub также приветствуется).

Собственно, это все, что я хотел сказать в качестве предисловия.

Правила


Правила простые: 50 вопросов, 3-4 варианта ответа, рейтинг: количество правильных и неправильных ответов, прогресс: номер и количество вопросов.

По результатам определяется процент правильных ответов и делается вывод об уровне владения JavaScript: больше 80% отлично, больше 50% неплохо, меньше 50% ну, Вы понимаете.

К каждому вопросу прилагается пояснение. При неправильном ответе данное пояснение раскрывается.

Благодаря тому, что количество правильных и неправильных ответов, а также порядковый номер вопроса записываются в локальное хранилище, у Вас есть возможность сделать паузу, передохнуть и в любое время продолжить с того места, на котором остановились.

Но довольно слов, пора переходить к делу.

Викторина



Код проекта находится здесь.

Механика


Несколько слов о том, как реализована викторина для тех, кому интересно.

Разметка выглядит так:

<head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>200+ вопросов по JavaScript</title>    <!-- шрифт -->    <link href="http://personeltest.ru/aways/fonts.googleapis.com/css2?family=Ubuntu&display=swap" rel="stylesheet">    <!-- стили -->    <link rel="stylesheet" href="style.css">    <!-- основной скрипт с типом "модуль" -->    <script type="module" src="script.js"></script></head><body></body>

Добавляем минимальные стили.
CSS:
* {    margin: 0;    padding: 0;    box-sizing: border-box;    font-family: Ubuntu, sans-serif;    font-size: 1em;    text-align: center;    letter-spacing: 1.05px;    line-height: 1.5em;    color: #111;    user-select: none;}@media (max-width: 512px) {    * {        font-size: .95em;    }}html {    position: relative;}body {    padding: 1em;    min-height: 100vh;    background: radial-gradient(circle, skyblue, steelblue);    display: flex;    flex-direction: column;    justify-content: start;    align-items: center;}h1 {    margin: .5em;    font-size: 1.05em;}output {    margin: .5em;    display: block;}.score {    font-size: 1.25em;}form {    text-align: left;}form p {    text-align: left;    white-space: pre;}form button {    position: relative;    left: 50%;    transform: translateX(-50%);}button {    margin: 2em 0;    padding: .4em .8em;    outline: none;    border: none;    background: linear-gradient(lightgreen, darkgreen);    border-radius: 6px;    box-shadow: 0 1px 2px rgba(0, 0, 0, .4);    font-size: .95em;    cursor: pointer;    transition: .2s;}button:hover {    color: #eee;}label {    cursor: pointer;}input {    margin: 0 10px 0 2em;    cursor: pointer;}details {    font-size: .95em;    position: absolute;    bottom: 0;    left: 50%;    transform: translateX(-50%);    width: 90%;    background: #eee;    border-radius: 4px;    cursor: pointer;}details h3 {    margin: .5em;}details p {    margin: .5em 1.5em;    text-align: justify;    text-indent: 1.5em;}.right {    color: green;}.wrong {    color: red;}


Исходники (assets) представляют собой массив объектов, где каждый объект имеет свойства question (вопрос), answers (ответы), rightAnswer (правильный ответ) и explanation (объяснение):

[{    question: `        function sayHi() {            console.log(name);            console.log(age);            var name = "Lydia";            let age = 21;        }        sayHi();    `,    answers: `        A: Lydia и undefined        B: Lydia и ReferenceError        C: ReferenceError и 21        D: undefined и ReferenceError    `,    rightAnswer: `D`,    explanation: `        Внутри функции мы сначала определяем переменную name с помощью ключевого слова var. Это означает, что name поднимется в начало функции. Name будет иметь значение undefined до тех пор, пока выполнение кода не дойдет до строки, где ей присваивается значение Lydia. Мы не определили значение name, когда пытаемся вывести ее в консоль, поэтому будет выведено undefined. Переменные, определенные с помощью let (и const), также поднимаются, но в отличие от var, не инициализируются. Доступ к ним до инициализации невозможен. Это называется "временной мертвой зоной". Когда мы пытаемся обратиться к переменным до их определения, JavaScript выбрасывает исключение ReferenceError.    `},...]

Основной скрипт.
JavaScript
// импортируем массив объектов - исходникиimport assets from './assets.js'// IIFE;((D, B) => {    // заголовок - вопрос    const title = D.createElement('h1')    B.append(title)    // рейтинг: количество правильных и неправильных ответов    const score = D.createElement('output')    score.className = 'score'    B.append(score)    // прогресс: порядковый номер вопроса    const progress = D.createElement('output')    progress.className = 'progress'    B.append(progress)    // контейнер для вопроса, вариантов ответа и кнопки для отправки формы    const div = D.createElement('div')    B.append(div)    // получаем значения правильных и неправильных ответов из локального хранилища    // или присваиваем переменным 0    let rightAnswers = +localStorage.getItem('rightAnswers') || 0    let wrongAnswers = +localStorage.getItem('wrongAnswers') || 0    // получаем значение счетчика из локального хранилища    // или присваиваем ему 0    let i = +localStorage.getItem('i') || 0    // рендерим вопрос    showQuestion()    // обновляем рейтинг и прогресс    updateScoreAndProgress()    function showQuestion() {        // если значение счетчика равняется количеству вопросов        // значит, игра окончена,        // показываем результат        if (i === assets.length) {            return showResult()        }        // заголовок-вопрос зависит от значения счетчика - номера вопроса        switch (i) {            case 4:                title.textContent = `Что не является валидным?`                break;            case 9:                title.textContent = `Что произойдет?`                break;            case 12:                title.textContent = `Назовите три фазы распространения событий`                break;            case 13:                title.textContent = `Все ли объекты имеют прототипы?`                break;            case 14:                title.textContent = `Каким будет результат?`                break;            case 20:                title.textContent = `Чему равно sum?`                break;            case 21:                title.textContent = `Как долго будет доступен cool_secret?`                break;            case 23:                title.textContent = `Каким будет результат?`                break;            case 25:                title.textContent = `Глобальный контекст исполнения создает две вещи: глобальный объект и this`                break;            case 27:                title.textContent = `Каким будет результат?`                break;            case 29:                title.textContent = `Каким будет результат?`                break;            case 30:                title.textContent = `Что будет в event.target после нажатия на кнопку?`                break;            case 33:                title.textContent = `Каким будет результат?`                break;            case 34:                title.textContent = `Какие из значений являются "ложными"?`                break;            case 38:                title.textContent = `Все в JavaScript это`                break;            case 39:                title.textContent = `Каким будет результат?`                break;            case 40:                title.textContent = `Каким будет результат?`                break;            case 41:                title.textContent = `Что возвращает setInterval?`                break;            case 42:                title.textContent = `Каким будет результат?`                break;            case 42:                title.textContent = `Каково значение num?`                break;            case 49:                title.textContent = `Каким будет результат?`                break;            default:                title.textContent = `Что будет выведено в консоль?`                break;        }        // поскольку каждый элемент массива - это объект,        // мы можем его деструктурировать, получив вопрос, правильный ответ и объяснение        const {            question,            rightAnswer,            explanation        } = assets[i]        // поскольку варианты ответа - это input type="radio",        // строку необходимо преобразовать в массив (критерием является перенос строки - \n)        // первый и последний элементы - пустые строки,        // избавляемся от них с помощью slice(1, -1),        // также удаляем пробелы        const answers = assets[i].answers            .split('\n')            .slice(1, -1)            .map(i => i.trim())        // HTML-шаблон        const template = `        <form action="#">            <p><em>Вопрос:</em><br> ${question}</p>            <p><em>Варианты ответов:</em></p><br>            ${answers.reduce((html, item) => html += `<label><input type="radio" name="answer" value="${item}">${item}</label><br>`, '')}            <button type="submit">Ответить</button>        </form>        <details>            <summary>Показать правильный ответ</summary>            <section>                <h3>Правильный ответ: ${rightAnswer}</h3>                <p>${explanation}</p>            </section>        </details>`        // помещаем шаблон в контейнер        div.innerHTML = template        // находим форму        const form = div.querySelector('form')        // выбираем первый инпут        form.querySelector('input').setAttribute('checked', '')        // обрабатываем отправку формы        form.addEventListener('submit', ev => {            // предотвращаем перезагрузку страницы            ev.preventDefault()            // определяем выбранный вариант ответа            const chosenAnswer = form.querySelector('input:checked').value.substr(0, 1)            // проверяем ответ            checkAnswer(chosenAnswer, rightAnswer)        })    }    function checkAnswer(chosenAnswer, rightAnswer) {        // индикатор правильного ответа        let isRight = true        // если выбранный ответ совпадает с правильным,        // увеличиваем количество правильных ответов,        // записываем количество правильных ответов в локальное хранилище,        // иначе увеличиваем количество неправильных ответов,        // записываем количество неправильных ответов в локальное хранилище        // и присваиваем индикатору false        if (chosenAnswer === rightAnswer) {            rightAnswers++            localStorage.setItem('rightAnswers', rightAnswers)        } else {            wrongAnswers++            localStorage.setItem('wrongAnswers', wrongAnswers)            isRight = false        }        // находим кнопку        const button = div.querySelector('button')        // если ответ был правильным        if (isRight) {            // сообщаем об этом            title.innerHTML = `<h1 class="right">Верно!</h1>`            // выключаем кнопку            button.disabled = true            // через секунду вызываем функции            // обновления рейтинга и прогресса и рендеринга следующего вопроса            // отключаем таймер            const timer = setTimeout(() => {                updateScoreAndProgress()                showQuestion()                clearTimeout(timer)            }, 1000)            // если ответ был неправильным        } else {            // сообщаем об этом            title.innerHTML = `<h1 class="wrong">Неверно!</h1>`            // выключаем инпуты            div.querySelectorAll('input').forEach(input => input.disabled = true)            // раскрываем объяснение            div.querySelector('details').setAttribute('open', '')            // меняем текст кнопки            button.textContent = 'Понятно'            // по клику на кнопке вызываем функции            // обновления рейтинга и прогресса и рендеринга следующего вопроса            // удаляем обработчик            button.addEventListener('click', () => {                updateScoreAndProgress()                showQuestion()            }, {                once: true            })        }        // увеличиваем значение счетчика        i++        // записываем значение счетчика в локальное хранилище        localStorage.setItem('i', i)    }    function updateScoreAndProgress() {        // обновляем рейтинг        score.innerHTML = `<span class="right">${rightAnswers}</span> - <span class="wrong">${wrongAnswers}</span>`        // обновляем прогресс        progress.innerHTML = `${i + 1} / ${assets.length}`    }    function showResult() {        // определяем процент правильных ответов        const percent = (rightAnswers / assets.length * 100).toFixed()        // объявляем переменную для результата        let result        // в зависимости от процента правильных ответов        // присваиваем result соответствующее значение        if (percent >= 80) {            result = `Отличный результат! Вы прекрасно знаете JavaScript.`        } else if (percent > 50) {            result = `Неплохой результат, но есть к чему стремиться.`        } else {            result = `Вероятно, вы только начали изучать JavaScript.`        }        // рендерим результаты        B.innerHTML = `        <h1>Ваш результат</h1>        <div>            <p>Правильных ответов: <span class="right">${rightAnswers}</span></p>            <p>Неправильных ответов: <span class="wrong">${wrongAnswers}</span></p>            <p>Процент правильных ответов: ${percent}</p>            <p>${result}</p>            <button>Заново</button>        </div>        `        // при нажатии на кнопку        // очищаем хранилище        // и перезагружаем страницу,        // удаляем обработчик        B.querySelector('button').addEventListener('click', () => {            localStorage.clear()            location.reload()        }, {            once: true        })    }})(document, document.body)


Благодарю за внимание, друзья.

Продолжение следует
Подробнее..

Перевод Учебный проект на Python интерфейс в 40 строк кода (часть 2)

03.07.2020 08:04:12 | Автор: admin
image

Демонстрация проекта Python с пользовательским интерфейсом никогда не была такой простой. С помощью Streamlit Framework вы можете создавать браузерный пользовательский интерфейс, используя только код Python. В этой статье мы будем создавать пользовательский интерфейс для программы лабиринта, подробно описанной в предыдущей статье.

Streamlit


Streamlit это веб-фреймворк, предназначенный для исследователей данных для простого развертывания моделей и визуализаций с использованием Python. Это быстро и минималистично, а также красиво и удобно. Есть встроенные виджеты для пользовательского ввода, такие как загрузка изображений, ползунки, ввод текста и другие знакомые элементы HTML, такие как флажки и переключатели. Всякий раз, когда пользователь взаимодействует с потоковым приложением, сценарий python перезапускается сверху вниз, что важно учитывать при рассмотрении различных состояний вашего приложения.
Вы можете установить Streamlit с помощью pip:

pip install streamlit


И запустите streamlit в скрипте Python:

streamlit run app.py


Варианты использования


В предыдущей статье мы создали программу на Python, которая будет проходить лабиринт, учитывая файл изображения и начальное/конечное местоположения. Мы хотели бы превратить эту программу в одностраничное веб-приложение, где пользователь может загрузить изображение лабиринта (или использовать изображение лабиринта по умолчанию), настроить начальное и конечное местоположение лабиринта и увидеть пройденный лабиринт.

Во-первых, давайте создадим пользовательский интерфейс для загрузчика изображений и возможность использовать изображение по умолчанию. Мы можем добавить вывод текста, используя такие функции, как st.write() или st.title(). Мы храним динамически загруженный файл, используя функцию st.file_uploader(). Наконец, st.checkbox() вернет логическое значение в зависимости от того, установил ли пользователь флажок.

import streamlit as stimport cv2import matplotlib.pyplot as pltimport numpy as npimport mazest.title('Maze Solver')uploaded_file = st.file_uploader("Choose an image", ["jpg","jpeg","png"]) #image uploaderst.write('Or')use_default_image = st.checkbox('Use default maze')


Результат:

image

Затем мы можем вывести наше изображение по умолчанию или загруженное изображение в пригодный для использования формат изображения OpenCV.

if use_default_image:    opencv_image = cv2.imread('maze5.jpg')elif uploaded_file is not None:    file_bytes = np.asarray(bytearray(uploaded_file.read()), dtype=np.uint8)    opencv_image = cv2.imdecode(file_bytes, 1)


Как только изображение загружено, мы хотим показать изображение, размеченное с начальной и конечной точками. Мы будем использовать ползунки, чтобы позволить пользователю переместить эти точки. Функция st.sidebar() добавляет боковую панель на страницу, а st.slider() принимает числово в пределах определенного минимума и максимума. Мы можем определить минимальное и максимальное значения слайдера динамически в зависимости от размера нашего изображения лабиринта.

if opencv_image is not None:    st.subheader('Use the sliders on the left to position the start and end points')    start_x = st.sidebar.slider("Start X", value= 24 if use_default_image  else 50, min_value=0, max_value=opencv_image.shape[1], key='sx')    start_y = st.sidebar.slider("Start Y", value= 332 if use_default_image  else 100, min_value=0, max_value=opencv_image.shape[0], key='sy')    finish_x = st.sidebar.slider("Finish X", value= 309 if use_default_image  else 100, min_value=0, max_value=opencv_image.shape[1], key='fx')    finish_y = st.sidebar.slider("Finish Y", value= 330 if use_default_image  else 100, min_value=0, max_value=opencv_image.shape[0], key='fy')    marked_image = opencv_image.copy()    circle_thickness=(marked_image.shape[0]+marked_image.shape[0])//2//100 #circle thickness based on img size    cv2.circle(marked_image, (start_x, start_y), circle_thickness, (0,255,0),-1)    cv2.circle(marked_image, (finish_x, finish_y), circle_thickness, (255,0,0),-1)    st.image(marked_image, channels="RGB", width=800)


image

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

После того, как пользователь определил начальную и конечную позиции, мы хотим кнопку, чтобы решить лабиринт и отобразить решение. Элемент st.spinner () отображается только во время работы его дочернего процесса, а вызов st.image () используется для отображения изображения.

if marked_image is not None:    if st.button('Solve Maze'):        with st.spinner('Solving your maze'):            path = maze.find_shortest_path(opencv_image,(start_x, start_y),(finish_x, finish_y))        pathed_image = opencv_image.copy()        path_thickness = (pathed_image.shape[0]+pathed_image.shape[0])//200        maze.drawPath(pathed_image, path, path_thickness)        st.image(pathed_image, channels="RGB", width=800)


image

Кнопка

image

Вывод решения

Вывод


Менее чем в 40 строк кода мы создали простой пользовательский интерфейс для приложения обработки изображений Python. Нам не нужно было писать какой-либо традиционный интерфейсный код. Помимо способности Streamlit переваривать простой код Python, Streamlit интеллектуально перезапускает необходимые части вашего скрипта сверху вниз при каждом взаимодействии пользователя со страницей или при изменении скрипта. Это обеспечивает прямой поток данных и быструю разработку.

Вы можете найти полный код на Github и первую часть, объясняющую алгоритм решения лабиринта здесь. Документация Streamlit, включая важные понятия и дополнительные виджеты находится здесь.

image

Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя платные онлайн-курсы SkillFactory:



Читать еще


Подробнее..

Восходящая сортировка кучей

03.07.2020 00:15:20 | Автор: admin

Это заключительная статья из серии про сортировки кучей. В предыдущих лекциях мы рассмотрели весьма разнообразные кучные структуры, показывающих отличные результаты по скорости. Напрашивается вопрос: а какая куча наиболее эффективна, если речь идёт о сортировке? Ответ таков: та, которую мы рассмотрим сегодня.
EDISON Software - web-development
Мы в EDISON в наших проектах используем только лучшие методологии разработки.


Когда мы дорабатывали приложения и сайты Московского ювелирного завода мы сделали полный аудит имеющихся веб-ресурсов, переписали их на Python и Django, внедрили SDK для обращения к видеосервису и рассылки SMS-оповещений, произвели интеграцию с системой электронного документооборота и API 2ГИС.

Мы работаем с ювелирной точностью ;-)
Необычные кучи, которые мы рассматривали ранее это, конечно, прекрасно, однако самая эффективная куча стандартная, но с улучшенной просейкой.

Что такое просейка, зачем она нужна в куче и как она работает описано в самой первой части серии статей.

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



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

Итак, как это работает, давайте посмотрим на конкретном примере.

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

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



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

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



Ну и последний шаг используя буферную переменную сдвигаем значения узлов вверх по ветке.



Теперь всё. Восходящая просейка сформировала из массива сортирующее дерево, в котором любой родитель больше чем его потомки.

Итоговая анимация:



Реализация на Python 3.7


Основной алгоритм сортировки ничем не отличается от обычной heapsort:

# Основной алгоритм сортировки кучейdef HeapSortBottomUp(data):    # Формируем первоначальное сортирующее дерево    # Для этого справа-налево перебираем элементы массива    # (у которых есть потомки) и делаем для каждого из них просейку    for start in range((len(data) - 2) // 2, -1, -1):        HeapSortBottomUp_Sift(data, start, len(data) - 1)     # Первый элемент массива всегда соответствует корню сортирующего дерева    # и поэтому является максимумом для неотсортированной части массива.    for end in range(len(data) - 1, 0, -1):         # Меняем этот максимум местами с последним         # элементом неотсортированной части массива        data[end], data[0] = data[0], data[end]        # После обмена в корне сортирующего дерева немаксимальный элемент        # Восстанавливаем сортирующее дерево        # Просейка для неотсортированной части массива        HeapSortBottomUp_Sift(data, 0, end - 1)    return data

Спуск до нижнего листа удобно/наглядно вынести в отдельную функцию:

# Спуск вниз до самого нижнего листа# Выбираем бОльших потомковdef HeapSortBottomUp_LeafSearch(data, start, end):        current = start        # Спускаемся вниз, определяя какой    # потомок (левый или правый) больше    while True:        child = current * 2 + 1 # Левый потомок        # Прерываем цикл, если правый вне массива        if child + 1 > end:             break         # Идём туда, где потомок больше        if data[child + 1] > data[child]:            current = child + 1        else:            current = child        # Возможна ситуация, если левый потомок единственный    child = current * 2 + 1 # Левый потомок    if child <= end:        current = child            return current

И самое главное просейка, сначала идущая вниз, затем выныривающая наверх:

# Восходящая просейкаdef HeapSortBottomUp_Sift(data, start, end):        # По бОльшим потомкам спускаемся до самого нижнего уровня    current = HeapSortBottomUp_LeafSearch(data, start, end)        # Поднимаемся вверх, пока не встретим узел    # больший или равный корню поддерева    while data[start] > data[current]:        current = (current - 1) // 2        # Найденный узел запоминаем,    # в этот узел кладём корень поддерева    temp = data[current]    data[current] = data[start]        # всё что выше по ветке вплоть до корня    # - сдвигаем на один уровень вниз    while current > start:        current = (current - 1) // 2        temp, data[current] = data[current], temp  

На просторах Интернета также обнаружен код на C
/*----------------------------------------------------------------------*//*                         BOTTOM-UP HEAPSORT                           *//* Written by J. Teuhola <teuhola@cs.utu.fi>; the original idea is      *//* probably due to R.W. Floyd. Thereafter it has been used by many      *//* authors, among others S. Carlsson and I. Wegener. Building the heap  *//* bottom-up is also due to R. W. Floyd: Treesort 3 (Algorithm 245),    *//* Communications of the ACM 7, p. 701, 1964.                           *//*----------------------------------------------------------------------*/#define element float/*-----------------------------------------------------------------------*//* The sift-up procedure sinks a hole from v[i] to leaf and then sifts   *//* the original v[i] element from the leaf level up. This is the main    *//* idea of bottom-up heapsort.                                           *//*-----------------------------------------------------------------------*/static void siftup(v, i, n) element v[]; int i, n; {  int j, start;  element x;  start = i;  x = v[i];  j = i << 1;  /* Leaf Search */  while(j <= n) {    if(j < n) if v[j] < v[j + 1]) j++;    v[i] = v[j];    i = j;    j = i << 1;  }  /* Siftup */  j = i >> 1;  while(j >= start) {    if(v[j] < x) {      v[i] = v[j];      i = j;      j = i >> 1;    } else break;  }  v[i] = x;} /* End of siftup *//*----------------------------------------------------------------------*//* The heapsort procedure; the original array is r[0..n-1], but here    *//* it is shifted to vector v[1..n], for convenience.                    *//*----------------------------------------------------------------------*/void bottom_up_heapsort(r, n) element r[]; int n; {  int k;   element x;  element *v;  v = r - 1; /* The address shift */  /* Build the heap bottom-up, using siftup. */  for (k = n >> 1; k > 1; k--) siftup(v, k, n);  /* The main loop of sorting follows. The root is swapped with the last  */  /* leaf after each sift-up. */  for(k = n; k > 1; k--) {    siftup(v, 1, k);    x = v[k];    v[k] = v[1];    v[1] = x;  }} /* End of bottom_up_heapsort */

Чисто эмпирически по моим замерам восходящая сортировка кучей работает в 1,5 раза быстрее, чем обычная сортировка кучей.

По некоторой информации (на странице алгоритма в Википедии, в приведённых PDF в разделе Ссылки) BottomUp HeapSort в среднем опережает даже быструю сортировку для достаточно крупных массивов размером от 16 тысяч элементов.

Ссылки


Bottom-up heapsort

A Variant of Heapsort with Almost Optimal Number of Comparisons

Building Heaps Fast

A new variant of heapsort beating, on an average, quicksort(if n is not very small)

Статьи серии:



В приложение AlgoLab добавлена сегодняшняя сортировка, кто пользуется обновите excel-файл с макросами.
Подробнее..

Перевод Введение в асинхронное программирование на Python

03.07.2020 14:20:11 | Автор: admin
Всем привет. Подготовили перевод интересной статьи в преддверии старта базового курса Разработчик Python.



Введение


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



В последние несколько лет асинхронное программирование привлекло к себе пристальное внимание, и на то есть причины. Несмотря на то, что этот вид программирования может быть сложнее традиционного последовательного выполнения, он гораздо более эффективен.

Например, вместо того, что ждать завершения HTTP-запроса перед продолжением выполнения, вы можете отправить запрос и выполнить другую работу, которая ждет своей очереди, с помощью асинхронных корутин в Python.

Асинхронность это одна из основных причин популярности выбора Node.js для реализации бэкенда. Большое количество кода, который мы пишем, особенно в приложениях с тяжелым вводом-выводом, таком как на веб-сайтах, зависит от внешних ресурсов. В нем может оказаться все, что угодно, от удаленного вызова базы данных до POST-запросов в REST-сервис. Как только вы отправите запрос в один из этих ресурсов, ваш код будет просто ожидать ответа. С асинхронным программированием вы позволяете своему коду обрабатывать другие задачи, пока ждете ответа от ресурсов.

Как Python умудряется делать несколько вещей одновременно?




1. Множественные процессы

Самый очевидный способ это использование нескольких процессов. Из терминала вы можете запустить свой скрипт два, три, четыре, десять раз, и все скрипты будут выполняться независимо и одновременно. Операционная система сама позаботится о распределении ресурсов процессора между всеми экземплярами. В качестве альтернативы вы можете воспользоваться библиотекой multiprocessing, которая умеет порождать несколько процессов, как показано в примере ниже.

from multiprocessing import Processdef print_func(continent='Asia'):    print('The name of continent is : ', continent)if __name__ == "__main__":  # confirms that the code is under main function    names = ['America', 'Europe', 'Africa']    procs = []    proc = Process(target=print_func)  # instantiating without any argument    procs.append(proc)    proc.start()    # instantiating process with arguments    for name in names:        # print(name)        proc = Process(target=print_func, args=(name,))        procs.append(proc)        proc.start()    # complete the processes    for proc in procs:        proc.join()


Вывод:

The name of continent is :  AsiaThe name of continent is :  AmericaThe name of continent is :  EuropeThe name of continent is :  Africa


2. Множественные потоки

Еще один способ запустить несколько работ параллельно это использовать потоки. Поток это очередь выполнения, которая очень похожа на процесс, однако в одном процессе вы можете иметь несколько потоков, и у всех них будет общий доступ к ресурсам. Однако из-за этого написать код потока будет сложно. Аналогично, все тяжелую работу по выделению памяти процессора сделает операционная система, но глобальная блокировка интерпретатора (GIL) позволит только одному потоку Python запускаться в одну единицу времени, даже если у вас есть многопоточный код. Так GIL на CPython предотвращает многоядерную конкурентность. То есть вы насильно можете запуститься только на одном ядре, даже если у вас их два, четыре или больше.

import threading def print_cube(num):    """    function to print cube of given num    """    print("Cube: {}".format(num * num * num)) def print_square(num):    """    function to print square of given num    """    print("Square: {}".format(num * num)) if __name__ == "__main__":    # creating thread    t1 = threading.Thread(target=print_square, args=(10,))    t2 = threading.Thread(target=print_cube, args=(10,))     # starting thread 1    t1.start()    # starting thread 2    t2.start()     # wait until thread 1 is completely executed    t1.join()    # wait until thread 2 is completely executed    t2.join()     # both threads completely executed    print("Done!")


Вывод:

Square: 100Cube: 1000Done!


3. Корутины и yield:

Корутины это обобщение подпрограмм. Они используются для кооперативной многозадачности, когда процесс добровольно отдает контроль (yield) с какой-то периодичностью или в периоды ожидания, чтобы позволить нескольким приложениям работать одновременно. Корутины похожи на генераторы, но с дополнительными методами и небольшими изменениями в том, как мы используем оператор yield. Генераторы производят данные для итерации, в то время как корутины могут еще и потреблять данные.

def print_name(prefix):    print("Searching prefix:{}".format(prefix))    try :         while True:                # yeild used to create coroutine                name = (yield)                if prefix in name:                    print(name)    except GeneratorExit:            print("Closing coroutine!!") corou = print_name("Dear")corou.__next__()corou.send("James")corou.send("Dear James")corou.close()


Вывод:

Searching prefix:DearDear JamesClosing coroutine!!


4. Асинхронное программирование

Четвертый способ это асинхронное программирование, в котором не участвует операционная система. Со стороны операционной системы у вас останется один процесс, в котором будет всего один поток, но вы все еще сможете выполнять одновременно несколько задач. Так в чем тут фокус?

Ответ: asyncio

Asyncio модуль асинхронного программирования, который был представлен в Python 3.4. Он предназначен для использования корутин и future для упрощения написания асинхронного кода и делает его почти таким же читаемым, как синхронный код, из-за отсутствия callback-ов.

Asyncio использует разные конструкции: event loop, корутины и future.

  • event loop управляет и распределяет выполнение различных задач. Он регистрирует их и обрабатывает распределение потока управления между ними.
  • Корутины (о которых мы говорили выше) это специальные функции, работа которых схожа с работой генераторов в Python, с помощью await они возвращают поток управления обратно в event loop. Запуск корутины должен быть запланирован в event loop. Запланированные корутины будут обернуты в Tasks, что является типом Future.
  • Future отражает результат таска, который может или не может быть выполнен. Результатом может быть exception.


С помощью asyncio вы можете структурировать свой код так, чтобы подзадачи определялись как корутины и позволяли планировать их запуск так, как вам заблагорассудится, в том числе и одновременно. Корутины содержат точки yield, в которых мы определяем возможные точки переключения контекста. В случае, если в очереди ожидания есть задачи, то контекст будет переключен, в противном случае нет.

Переключение контекста в asyncio представляет собой event loop, который передает поток управления от одной корутины к другой.

В следующем примере, мы запускаем 3 асинхронных таска, которые по-отдельности делают запросы к Reddit, извлекают и выводят содержимое JSON. Мы используем aiohttp клиентскую библиотеку http, которая гарантирует, что даже HTTP-запрос будет выполнен асинхронно.

import signal  import sys  import asyncio  import aiohttp  import jsonloop = asyncio.get_event_loop()  client = aiohttp.ClientSession(loop=loop)async def get_json(client, url):      async with client.get(url) as response:        assert response.status == 200        return await response.read()async def get_reddit_top(subreddit, client):      data1 = await get_json(client, 'https://www.reddit.com/r/' + subreddit + '/top.json?sort=top&t=day&limit=5')    j = json.loads(data1.decode('utf-8'))    for i in j['data']['children']:        score = i['data']['score']        title = i['data']['title']        link = i['data']['url']        print(str(score) + ': ' + title + ' (' + link + ')')    print('DONE:', subreddit + '\n')def signal_handler(signal, frame):      loop.stop()    client.close()    sys.exit(0)signal.signal(signal.SIGINT, signal_handler)asyncio.ensure_future(get_reddit_top('python', client))  asyncio.ensure_future(get_reddit_top('programming', client))  asyncio.ensure_future(get_reddit_top('compsci', client))  loop.run_forever()


Вывод:

50: Undershoot: Parsing theory in 1965 (http://personeltest.ru/away/jeffreykegler.github.io/Ocean-of-Awareness-blog/individual/2018/07/knuth_1965_2.html)12: Question about best-prefix/failure function/primal match table in kmp algorithm (http://personeltest.ru/aways/www.reddit.com/r/compsci/comments/8xd3m2/question_about_bestprefixfailure_functionprimal/)1: Question regarding calculating the probability of failure of a RAID system (http://personeltest.ru/aways/www.reddit.com/r/compsci/comments/8xbkk2/question_regarding_calculating_the_probability_of/)DONE: compsci336: /r/thanosdidnothingwrong -- banning people with python (http://personeltest.ru/aways/clips.twitch.tv/AstutePluckyCocoaLitty)175: PythonRobotics: Python sample codes for robotics algorithms (http://personeltest.ru/aways/atsushisakai.github.io/PythonRobotics/)23: Python and Flask Tutorial in VS Code (http://personeltest.ru/aways/code.visualstudio.com/docs/python/tutorial-flask)17: Started a new blog on Celery - what would you like to read about? (http://personeltest.ru/aways/www.python-celery.com)14: A Simple Anomaly Detection Algorithm in Python (http://personeltest.ru/aways/medium.com/@mathmare_/pyng-a-simple-anomaly-detection-algorithm-2f355d7dc054)DONE: python1360: git bundle (http://personeltest.ru/aways/dev.to/gabeguz/git-bundle-2l5o)1191: Which hashing algorithm is best for uniqueness and speed? Ian Boyd's answer (top voted) is one of the best comments I've seen on Stackexchange. (http://personeltest.ru/aways/softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed)430: ARM launches Facts campaign against RISC-V (http://personeltest.ru/aways/riscv-basics.com/)244: Choice of search engine on Android nuked by Anonymous Coward (2009) (http://personeltest.ru/aways/android.googlesource.com/platform/packages/apps/GlobalSearch/+/592150ac00086400415afe936d96f04d3be3ba0c)209: Exploiting freely accessible WhatsApp data or Why does WhatsApp web know my phones battery level? (http://personeltest.ru/aways/medium.com/@juan_cortes/exploiting-freely-accessible-whatsapp-data-or-why-does-whatsapp-know-my-battery-level-ddac224041b4)DONE: programming


Использование Redis и Redis Queue RQ


Использование asyncio и aiohttp не всегда хорошая идея, особенно если вы пользуетесь более старыми версиями Python. К тому же, бывают моменты, когда вам нужно распределить задачи по разным серверам. В этом случае можно использовать RQ (Redis Queue). Это обычная библиотека Python для добавления работ в очередь и обработки их воркерами в фоновом режиме. Для организации очереди используется Redis база данных ключей/значений.

В примере ниже мы добавили в очередь простую функцию count_words_at_url с помощью Redis.

from mymodule import count_words_at_urlfrom redis import Redisfrom rq import Queueq = Queue(connection=Redis())job = q.enqueue(count_words_at_url, 'http://nvie.com')******mymodule.py******import requestsdef count_words_at_url(url):    """Just an example function that's called async."""    resp = requests.get(url)    print( len(resp.text.split()))    return( len(resp.text.split()))


Вывод:

15:10:45 RQ worker 'rq:worker:EMPID18030.9865' started, version 0.11.015:10:45 *** Listening on default...15:10:45 Cleaning registries for queue: default15:10:50 default: mymodule.count_words_at_url('http://nvie.com') (a2b7451e-731f-4f31-9232-2b7e3549051f)32215:10:51 default: Job OK (a2b7451e-731f-4f31-9232-2b7e3549051f)15:10:51 Result is kept for 500 seconds


Заключение


В качестве примера возьмем шахматную выставку, где один из лучших шахматистов соревнуется с большим количеством людей. У нас есть 24 игры и 24 человека, с которыми можно сыграть, и, если шахматист будет играть с ними синхронно, это займет не менее 12 часов (при условии, что средняя игра занимает 30 ходов, шахматист продумывает ход в течение 5 секунд, а противник примерно 55 секунд.) Однако в асинхронном режиме шахматист сможет делать ход и оставлять противнику время на раздумья, тем временем переходя к следующему противнику и деля ход. Таким образом, сделать ход во всех 24 играх можно за 2 минуты, и выиграны они все могут быть всего за один час.

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

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

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



Узнать о курсе подробнее.


Подробнее..

SOLID ООП?

03.07.2020 16:05:03 | Автор: admin

Наверное я не ошибусь, если скажу, что чаще всего на собеседованиях спрашивают о SOLID принципах. Технологии, языки и фреймворки разные, но принципы написания кода в целом похожи: SOLID, KISS, DRY, YAGNI, GRASP и подобные стоит знать всем.


В современной индустрии уже много десятков лет доминирует парадигма ООП и у многих разработчиков складывается впечатление, что она лучшая или и того хуже единственная. На эту тему есть прекрасное видео Why Isn't Functional Programming the Norm? про развитие языков/парадигм и корни их популярности.


SOLID изначально были описаны Робертом Мартином для ООП и многими воспринимаются как относящиеся только к ООП, даже википедия говорит нам об этом, давайте же рассмотрим так ли эти принципы привязаны к ООП?


Single Responsibility


Давайте пользоваться пониманием SOLID от Uncle Bob:


This principle was described in the work of Tom DeMarco and Meilir Page-Jones. They called it cohesion. They defined cohesion as the functional relatedness of the elements of a module. In this chapter well shift that meaning a bit, and relate cohesion to the forces that cause a module, or a class, to change.

Каждый модуль должен иметь одну причину для изменений (а вовсе не делать одну вещь, как многие отвечают) и как объяснял сам автор в одном из видео это означает, что изменения должны исходить от одной группы/роли людей, например модуль должен меняться только по запросам бизнес-аналитика, дизайнера, DBA специалиста, бухгалтера или юриста.


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


Open Closed


SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION
Bertrand Meyer

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


При этом функция это одна из лучших абстракций (исходя из принципа сегрегации интерфейсов, о котором позже). Использование функций для обеспечения этого принципа настолько удобно, что подход уже прочно перекочевал из функциональных языков во все основные ООП языки. Для примера можно взять функции map, filter, reduce, которые позволяют менять свой функционал прямой передачей кода в виде функции. Более того, весь этот функционал можно получить используя только одну функцию foldLeft без изменения ее кода!


def map(xs: Seq[Int], f: Int => Int) =   xs.foldLeft(Seq.empty) { (acc, x) => acc :+ f(x) }def filter(xs: Seq[Int], f: Int => Boolean) =   xs.foldLeft(Seq.empty) { (acc, x) => if (f(x)) acc :+ x else acc }def reduce(xs: Seq[Int], init: Int, f: (Int, Int) => Int) =  xs.foldLeft(init) { (acc, x) => f(acc, x) }

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


Liskov Substitution


Обратимся к самой Барбаре:


If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

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


Как видите тут речь хоть и идет об "объектах", но ни слова о классах нет, "объект" тут это просто значение типа. Многие говорят о том, что этот принцип регламентирует наследование в ООП и они правы! Но принцип шире и может быть даже использован с другими видами полиморфизма, вот пример (немного утрированный конечно), который без всякого наследования нарушает этот принцип:


static <T> T increment(T number) {  if (number instanceof Integer) return (T) (Object) (((Integer) number) + 1);  if (number instanceof Double) return (T) (Object) (((Double) number) + 1);  throw new IllegalArgumentException("Unexpected value "+ number);}

Тут мы объявляем, что функция принимает тип T, не ограничивая его, что делает все типы его "подтипом" (т.е. компилятор позволяет передать в функцию объект любого типа), при этом функция ведет себя не так, как объявлена работает не для всех типов.


Вообще люди, привыкли считать, что "полиморфизм" это один из принципов ООП, а значит про наследование, но это не так. Полиморфизм это способность кода работать с разными типами данных, потенциально неизвестными на момент написания кода, в данном случае это параметрический полиморфизм (собственно ошибочное его использование), в ООП используется полиморфизм включения, а существует еще и специальный (ad hoc) полиморфизм. И во всех случаях этот принцип может быть полезен.


Interface Segregation


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


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


Например вместо интерфейса Comparable в Java есть type class Ord в haskell (пусть слово class не вводит вас в заблуждение haskell чисто функциональный язык):


// упрощенноclass Ord a where    compare :: a -> a -> Ordering

Это "протокол", сообщающий, что существуют типы, для которые есть функция сравнения compare (практически как интерфейс Comparable). Для таких классов типов принцип сегрегации прекрасно применим.


Dependency Inversion


Depend on abstractions, not on concretions.

Этот принцип часто путают с Dependency Injection, но этот принцип о другом он требует использования абстракций где это возможно, причем абстракций любого рода:


int first(ArrayList<Integer> xs) // ArrayList это деталь реализации -> int first(Collection<Integer> xs) // Collection это абстракция -> <T> T first(Collection<T> xs) // но и тип элемента коллекции это только деталь реализации

В этом функциональные языки пошли гораздо дальше чем ООП: они смогли абстрагироваться даже от эффектов (например асинхронности):


def sum[F[_]: Monad](xs: Seq[F[Int]]): F[Int] =  if (xs.isEmpty) 0.pure  else for (head <- xs.head; tail <- all(xs.tail)) yield head + tailsum[Id](Seq(1, 2, 3)) -> 6sum[Future](Seq(queryService1(), queryService2())) -> Future(6)

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




Вот и выходит, что принципы SOLID более общие, чем ООП в сегодняшнем нашем его понимании. Не забывайте оглядываться по сторонам, докапываться до смысла и узнавать новое!

Подробнее..

Как реализовать свою идею и не сойти с ума на самоизоляции

03.07.2020 18:07:41 | Автор: admin
Во время тотальной самоизоляции разработчики стали ещё активнее интересоваться онлайн-ивентами, где можно поучиться и попробовать силы в конкурсах. На этой волне мы запустили грантово-образовательный проект для нашего комьюнити. Не мелочась, заложили призовой фонд в 15 миллионов рублей. В этой статье расскажем, почему конкурс не сбавляет оборотов даже сейчас, когда никто уже не сидит дома, что ждёт VK Fresh Code дальше и какие приложения победили в первом этапе.

image


Денис Марков, деврел VK Mini Apps:
Мини-приложения открывают большие возможности перед теми, кто интересуется веб-разработкой. Используя привычные веб-технологии и уникальные возможности ВКонтакте, вы можете быстро сделать продукт, который привлечёт внимание тысяч, а то и миллионов пользователей.



Привет от команды VK Mini Apps! Мы расскажем, как помогли независимым разработчикам с пользой скоротать время на самоизоляции и что из этого получилось.

Почему всем был нужен VK Fresh Code


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

Сервисы известных брендов увеличили свою и без того обширную аудиторию. Ещё больше людей стали подбирать недвижимость в мини-аппе Этажей, покупать товары с AliExpress, читать свежие выпуски Cosmopolitan не выходя из привычной соцсети и ничего не скачивая. У крупных бизнесов достаточно ресурсов, чтобы привлечь пользователей и удержать их преимуществами своих сервисов.

Но мы точно знали: сейчас, когда аудитория платформы резко возросла, так же выстрелить могут и мини-приложения от небольших студий и независимых разработчиков. И придумали, как им помочь.

Что мы сделали


Организовали грантовый конкурс VK Fresh Code. Он замешан на свободной фантазии и энтузиазме разработчиков, у которых есть реальная, понятная мотивация: крутой опыт, перспектива для их проектов и денежный приз.

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

Ещё обсудили тонкости модерации: на платформе она довольно жёсткая и обязательная для всех сервисов. Чтобы попасть в каталог, мини-аппы проходят через дотошных модераторов, а потом попадают в систему Testpool, где тысячи тестировщиков создают уйму репортов в первый же день. Но это всё идёт сервисам на пользу: из модерации они выходят крепкими, без багов и артефактов. Разработчики получают бесплатно полное тестирование продукта и уверенность, что пользователи придут в чистое приложение.

За время первого этапа конкурса, с 28 апреля по 8 июня, через всё это прошли 62 полноценных мини-аппа. Но из них только 30 соответствовали заявленной тематике волны она была посвящена образованию.

Познакомим вас с десятью победителями разработчики этих сервисов поровну разделили 3 миллиона рублей.

Итог первого этапа VK Fresh Code | Тематика: образование



Решу ЕГЭ


Готовиться к ЕГЭ проще, если для этого уже подобраны материалы, есть где потренироваться и у кого спросить совета. Такой набор возможностей пригодится выпускникам в любой точке страны: 14 предметов, тестовые вопросы, режим экзамена и сохранение личных достижений. А благодаря мини-аппу теперь всё это умещается в смартфоне.

image

Вадим Трегубенко, full-stack developer и автор проекта:
В 11-м классе портал Решу ЕГЭ был моим лучшим другом вот я и предложил его руководству разработать для них мини-приложение. Мне кажется, что благодаря моему проекту подготовка к экзаменам может стать ещё более удобной и структурированной.


Brain Defense


Интеллектуальный тренажёр в жанре Tower Defense. Чтобы защищаться от монстров, нужно решать математические примеры, разбираться в правописании и быстро переводить слова с английского. Скоро добавятся ещё вопросы по географии и химии.

image

Денис Пешехонов, бэкенд-разработчик:
При реализации ставил задачу закончить проект, наработки по которому появились намного раньше. Конкурсы я уже выигрывал, а тут появилась мотивация хоть что-то довести до каталога и логического завершения. ВКонтакте самое популярное российское мобильное приложение. Если кому-то и проверять гипотезу супераппа, то именно им. Интересно заскочить в этот поезд как можно раньше. Вообще я заметил крутую тенденцию: на хакатонах для реализации проектов всё чаще используют именно VK Mini Apps, потому что так быстрее.


Мозгополия


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



Александр Белов, геймдизайнер:
Идея Мозгополии появилась спонтанно, когда мы с друзьями-программистами проводили вечер за настольной игрой. Мы увидели новость о начале первого этапа VK Fresh Code и подумали: А почему бы не перенести в онлайн то, во что мы играем здесь и сейчас, но сделать упор на образование?.


GeoPuzzle


Это игра на интерактивной карте Земли, помогает изучить политическую географию. Можно подтянуть знания о странах и регионах: вспомнить их расположение, флаги, столицы и многое другое.



Виктор Тыщенко, бэкенд-разработчик и автор проекта:
Наверное, из всех победивших разработчиков мне было проще всего. Я разрабатываю и улучшаю аналогичный сервис уже три года, но на другой площадке. Фактически мне потребовалось только понять, как перенести свои наработки на платформу VK Mini Apps. В итоге всё получилось. Этот опыт дал мне новую аудиторию, и прямо сейчас в моём приложении она влюбляется в географию
.

Эко Просто


Мини-приложение доступно рассказывает про раздельный сбор мусора и объясняет, как обращаться с разными группами бытовых и промышленных отходов. А ещё помогает оценить ваш личный вклад в сохранение окружающей среды.



Александр Алибутаев, фронтенд-разработчик и автор проекта:
Идея пришла почти случайно, хотя вся наша команда давно придерживается жизненных принципов, которые помогают сберечь природу. Мы решили попробовать затронуть чувства людей, а заодно придумали для себя непростую миссию объяснить пользователям, что планета у нас пока только одна. В итоге вышло очень даже неплохо: пришедшая аудитория оказалась действительно понимающей, и мы получили много сообщений с благодарностями. Приятные бонусы победа в конкурсе и поток желающих безвозмездно помочь проекту.


SMM Посты


Внутри этого сервиса много интересного для SMM-специалистов: генератор тем для контента, формулы продающих текстов, галерея баннеров, трекер постинга и даже целая академия с уроками копирайтинга.

image

Анастасия Югова, дипломированный маркетолог, автор приложения и сообщества о SMM:
У меня есть экспертный опыт в продвижении сообществ ВКонтакте. Ещё полгода назад я задумала создать бесплатный сервис, который поможет админам вести коммерческие страницы. Провела анкетирование, в котором подробно опросила 566 респондентов о проблемах, с которыми они сталкиваются в этой работе. Все ответы структурировала и выявила главные боли.

Образовательный модуль и возможность питчинга перед экспертами стали хорошим триггером, чтобы прокачать свои скилы и выпустить продукт на голову выше. Давно я не проводила столько бессонных ночей над проектом, чтобы успеть зафиналить работу в срок. Но оно того стоило: помимо победы, я получила позитивный фидбэк от тысяч пользователей и виральный охват, который позитивно влияет на показатели моего бизнеса.


QURAGA



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



Евгений Завалишин, бэкенд-разработчик проекта:
Фактически это упрощённая реинкарнация приложения Клевер, только без онлайн-ведущего и с более кулуарным режимом игры. Как мне кажется, наш сервис поможет людям в игровой форме узнать новые факты из разных областей: химии, физики, математики, географии, биологии. Пусть это не самые объёмные знания, но чем больше занимательных мелочей ты знаешь, тем интереснее ты сам.


Детский



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



Андрей Комаров, разработчик и автор проекта:
Я участвовал в обоих конкурсах мини-приложений от платформы VK Mini Apps. И в том и в другом занимал вторые места и брал награду за лучшее техническое решение. Потом случился Домашний разраб и очередное второе место по баллам в своей волне. Так что у меня уже четыре призовых места! Если бы у всех была такая же настойчивость и вера в себя, как у меня, думаю, мир был бы лучше.


Словоглот ридер



Приложение для чтения книг и текстов на иностранном языке. Сервис предлагает интерактивы, которые помогают запоминать новые фразы и пополнять словарный запас.



Константин Барышев, full-stack developer и автор проекта:
До сих пор скептически отношусь к монетизации на платформе, хоть в последнее время всё и становится намного лучше. Продолжаю совершенствовать своё приложение Полиглот оно давно в каталоге. Когда узнал про грант, решил создать сиквел, но с иностранными словами. Грела душу перспектива получить 300 тысяч рублей и всё получилось! Очень помогли ранние наработки и опыт, так что конкурс для меня прошёл чётко и выверено. Переживал только за то, чтобы попасть в тематику этапа. Спасибо за питчинг это мотивировало не забросить задуманное.


Грамотей



Бесконечная викторина по грамматике. Сервис помогает скоротать время, расширяя свой словарный запас, и радует приятным дизайном.



Александр Шарипов, full-stack developer и автор проекта:
У меня всё достаточно прозаично: идея в голове сидела давно, а делать её просто ради интереса было как-то неактуально. Можно сказать, что звёзды сошлись. Нас ограничили только общей тематикой в неё моя идея попадала идеально и пообещали приятный бонус за победу. Считаю, что не зря выжидал и вынашивал проект. Он дождался своего часа.



До конца года у нас состоятся ещё четыре этапа конкурса VK Fresh Code, включая вторую волну, которая уже идёт. В каждом из них авторы 10 лучших приложений получат грантовую поддержку 300 тысяч рублей. Половина суммы поступит на счёт в рекламном кабинете VK чтобы новый сервис получил мощное продвижение.

Участникам нужно пройти модерацию и бета-тестирование, чтобы попасть в каталог мини-приложений платформы VK Mini Apps. У каждого этапа свои сроки и тематика за ними можно следить в официальной группе платформы: vk.com/vkappsdev

Помните: не всегда побеждают большие и важные зачастую удача улыбается быстрым и дерзким. Ждём вас на VK Fresh Code поможем пройти путь от поиска идеи до релиза!
Подробнее..

Перевод О нет! Моя Data Science ржавеет

04.07.2020 14:10:42 | Автор: admin
Привет, Хабр!

Предлагаем вашему вниманию перевод интереснейшего исследования от компании Crowdstrike. Материал посвящен использованию языка Rust в области Data Science (применительно к malware analysis) и демонстрирует, в чем Rust на таком поле может посоперничать даже с NumPy и SciPy, не говоря уж о чистом Python.


Приятного чтения!

Python один из самых популярных языков программирования для работы с data science, и неслучайно. В индексе пакетов Python (PyPI) найдется огромное множество впечатляющих библиотек для работы с data science, в частности, NumPy, SciPy, Natural Language Toolkit, Pandas и Matplotlib. Благодаря изобилию высококачественных аналитических библиотек в доступе и обширному сообществу разработчиков, Python очевидный выбор для многих исследователей данных.

Многие из этих библиотек реализованы на C и C++ из соображений производительности, но предоставляют интерфейсы внешних функций (FFI) или привязки Python, так, чтобы из функции можно было вызывать из Python. Эти реализации на более низкоуровневых языках призваны смягчить наиболее заметные недостатки Python, связанные, в частности, с длительностью выполнения и потреблением памяти. Если удается ограничить время выполнения и потребление памяти, то сильно упрощается масштабируемость, что критически важно для сокращения расходов. Если мы сможем писать высокопроизводительный код, решающий задачи data science, то интеграция такого кода с Python станет серьезным преимуществом.

При работе на стыке data science и анализа вредоносного ПО требуется не только скоростное выполнение, но и эффективное использование разделяемых ресурсов, опять же, для масштабирования. Проблема масштабирования является одной из ключевых в области больших данных, как, например, эффективная обработка миллионов исполняемых файлов на множестве платформ. Для достижения хорошей производительности на современных процессорах требуется параллелизм, обычно реализуемый при помощи многопоточности; но также необходимо повышать эффективность выполнения кода и расхода памяти. При решении подобных задач бывает сложно сбалансировать ресурсы локальной системы, а правильно реализовать многопоточные системы даже сложнее. Суть C и C++ такова, что потокобезопасность в них не предоставляется. Да, существуют внешние платформо-специфичные библиотеки, но обеспечение потокобезопасности, это, очевидно, долг разработчика.

Парсинг вредоносного ПО это в принципе опасное занятие. Вредоносное ПО часто непредусмотренным образом обращается со структурами данных, касающимися формата файлов, таким образом выводя из строя аналитические утилиты. Относительно распространенная западня, поджидающая нас в Python, связана с отсутствием хорошей типобезопасности. Python, великодушно принимающий значения None, когда на месте них ожидался bytearray, может скатиться в полный бедлам, избежать которого можно, лишь нашпиговав код проверками на None. Такие допущения, связанные с утиной типизацией часто приводят к обвалам.

Но есть Rust. Язык Rust во многом позиционируется как идеальное решение всех потенциальных проблем, обрисованных выше: время выполнения и потребление памяти сравнимы с C и C++, а также предоставляется обширная типобезопасность. Также в языке Rust предоставляются дополнительные приятности, в частности, серьезные гарантии безопасности памяти и никаких издержек времени исполнения. Поскольку таких издержек нет, упрощается интеграция кода Rust с кодом других языков, в частности, Python. В этой статье мы сделаем небольшую экскурсию по Rust, чтобы понять, достоин ли он связанного с ним хайпа.

Пример приложения для Data Science


Data science очень широкая предметная область со множеством прикладных аспектов, и обсудить их все в одной статье невозможно. Простая задача для data science вычисление информационной энтропии для байтовых последовательностей. Общая формула для расчета энтропии в битах приводится в Википедии:



Чтобы рассчитать энтропию для случайной переменной X, мы сначала считаем, сколько раз встречается каждое возможное байтовое значение , а потом делим это число на общее количество встречающихся элементов, чтобы вычислить вероятность встретить конкретное значение , соответственно . Затем считаем отрицательное значение от взвешенной суммы вероятностей конкретного значения xi, встречающегося , а также так называемую собственную информацию . Поскольку мы вычисляем энтропию в битах, здесь используется (обратите внимание на основание 2 для бит).

Давайте испробуем Rust и посмотрим, как он справляется с вычислением энтропии по сравнению с чистым Python, а также с некоторыми популярнейшими библиотеками Python, упомянутыми выше. Это упрощенная оценка потенциальной производительности Rust в области data science; данный эксперимент не является критикой Python или отличных библиотек, имеющихся в нем. В этих примерах мы сгенерируем собственную библиотеку C из кода Rust, который сможем импортировать из Python. Все тесты проводились на Ubuntu 18.04.

Чистый Python


Начнем с простой функции на чистом Python (в entropy.py) для расчета энтропии bytearray, воспользуемся при этом только математическим модулем из стандартной библиотеки. Эта функция не оптимизирована, возьмем ее в качестве отправной точки для модификаций и измерения производительности.

import mathdef compute_entropy_pure_python(data):    """Compute entropy on bytearray `data`."""    counts = [0] * 256    entropy = 0.0    length = len(data)    for byte in data:        counts[byte] += 1    for count in counts:        if count != 0:            probability = float(count) / length            entropy -= probability * math.log(probability, 2)    return entropy

Python с NumPy и SciPy


Неудивительно, что в SciPy предоставляется функция для расчета энтропии. Но сначала мы воспользуемся функцией unique() из NumPy для расчета частот байтов. Сравнивать производительность энтропийной функции SciPy с другими реализациями немного нечестно, так как в реализации из SciPy есть дополнительный функционал для расчета относительной энтропии (расстояния Кульбака-Лейблера). Опять же, мы собираемся провести (надеюсь, не слишком медленный) тест-драйв, чтобы посмотреть, какова будет производительность скомпилированных библиотек Rust, импортированных из Python. Будем придерживаться реализации из SciPy, включенной в наш скрипт entropy.py.

import numpy as npfrom scipy.stats import entropy as scipy_entropydef compute_entropy_scipy_numpy(data):    """Вычисляем энтропию bytearray `data` с SciPy и NumPy."""    counts = np.bincount(bytearray(data), minlength=256)    return scipy_entropy(counts, base=2)

Python с Rust


Далее мы несколько подробнее исследуем нашу реализацию на Rust, по сравнению с предыдущими реализациями, ради основательности и закрепления. Начнем с принимаемого по умолчанию библиотечного пакета, сгенерированного при помощи Cargo. В следующих разделах показано, как мы модифицировали пакет Rust.

cargo new --lib rust_entropyCargo.toml

Начинаем с обязательного файла манифеста Cargo.toml, в котором определяем пакет Cargo и указываем имя библиотеки, rust_entropy_lib. Используем общедоступный контейнер cpython (v0.4.1), доступный на сайте crates.io, в реестре пакетов Rust Package Registry. В статье мы используем Rust v1.42.0, новейшую стабильную версию, доступную на момент написания.

[package] name = "rust-entropy"version = "0.1.0"authors = ["Nobody <nobody@nowhere.com>"] edition = "2018"[lib] name = "rust_entropy_lib"crate-type = ["dylib"][dependencies.cpython] version = "0.4.1"features = ["extension-module"]

lib.rs


Реализация библиотеки Rust весьма проста. Как и в случае с нашей реализацией на чистом Python, мы инициализируем массив counts для каждого возможного значения байт и перебираем данные для наполнения counts. Для завершения операции вычисляем и возвращаем отрицательную сумму вероятностей, умноженную на вероятностей.

use cpython::{py_fn, py_module_initializer, PyResult, Python};/// вычисляем энтропию массива байтfn compute_entropy_pure_rust(data: &[u8]) -> f64 {    let mut counts = [0; 256];    let mut entropy = 0_f64;    let length = data.len() as f64;    // collect byte counts    for &byte in data.iter() {        counts[usize::from(byte)] += 1;    }    // вычисление энтропии    for &count in counts.iter() {        if count != 0 {            let probability = f64::from(count) / length;            entropy -= probability * probability.log2();        }    }    entropy}

Все, что нам остается взять из lib.rs это механизм для вызова чистой функции Rust из Python. Мы включаем в lib.rs функцию, приспособленную к работе с CPython (compute_entropy_cpython()) для вызова нашей чистой функции Rust (compute_entropy_pure_rust()). Поступая таким образом, мы только выигрываем, так как будем поддерживать единственную чистую реализацию Rust, а также предоставим обертку, удобную для работы с CPython.

/// Функция Rust для работы с CPython fn compute_entropy_cpython(_: Python, data: &[u8]) -> PyResult<f64> {    let _gil = Python::acquire_gil();    let entropy = compute_entropy_pure_rust(data);    Ok(entropy)}// инициализируем модуль Python и добавляем функцию Rust для работы с CPython py_module_initializer!(    librust_entropy_lib,    initlibrust_entropy_lib,    PyInit_rust_entropy_lib,    |py, m | {        m.add(py, "__doc__", "Entropy module implemented in Rust")?;        m.add(            py,            "compute_entropy_cpython",            py_fn!(py, compute_entropy_cpython(data: &[u8])            )        )?;        Ok(())    });

Вызов кода Rust из Python


Наконец, вызываем реализацию Rust из Python (опять же, из entropy.py). Для этого сначала импортируем нашу собственную динамическую системную библиотеку, скомпилированную из Rust. Затем просто вызываем предоставленную библиотечную функцию, которую ранее указали при инициализации модуля Python с использованием макроса py_module_initializer! в нашем коде Rust. На данном этапе у нас всего один модуль Python (entropy.py), включающий функции для вызова всех реализаций расчета энтропии.

import rust_entropy_libdef compute_entropy_rust_from_python(data):    ""Вычисляем энтропию bytearray `data` при помощи Rust."""    return rust_entropy_lib.compute_entropy_cpython(data)

Мы собираем вышеприведенный библиотечный пакет Rust на Ubuntu 18.04 при помощи Cargo. (Эта ссылка может пригодиться пользователям OS X).

cargo build --release

Закончив со сборкой, мы переименовываем полученную библиотеку и копируем ее в тот каталог, где находятся наши модули Python, так, чтобы ее можно было импортировать из сценариев. Созданная при помощи Cargo библиотека называется librust_entropy_lib.so, но ее потребуется переименовать в rust_entropy_lib.so, чтобы успешно импортировать в рамках этих тестов.

Проверка производительности: результаты


Мы измеряли производительность каждой реализации функции при помощи контрольных точек pytest, рассчитав энтропию более чем для 1 миллиона случайно выбранных байт. Все реализации показаны на одних и тех же данных. Эталонные тесты (также включенные в entropy.py) показаны ниже.

# ### КОНТРОЛЬНЕ ТОЧКИ #### генерируем случайные байты для тестирования w/ NumPyNUM = 1000000VAL = np.random.randint(0, 256, size=(NUM, ), dtype=np.uint8)def test_pure_python(benchmark):    """тестируем чистый Python."""    benchmark(compute_entropy_pure_python, VAL)def test_python_scipy_numpy(benchmark):    """тестируем чистый Python со SciPy."""    benchmark(compute_entropy_scipy_numpy, VAL)def test_rust(benchmark):    """тестируем реализацию Rust, вызываемую из Python."""    benchmark(compute_entropy_rust_from_python, VAL)

Наконец, делаем отдельные простые драйверные скрипты для каждого метода, нужного для расчета энтропии. Далее идет репрезентативный драйверный скрипт для тестирования реализации на чистом Python. В файле testdata.bin 1 000 000 случайных байт, используемых для тестирования всех методов. Каждый из методов повторяет вычисления по 100 раз, чтобы упростить захват данных об использовании памяти.

import entropywith open('testdata.bin', 'rb') as f:    DATA = f.read()for _ in range(100):    entropy.compute_entropy_pure_python(DATA)

Реализации как для SciPy/NumPy, так и для Rust показали хорошую производительность, легко обставив неоптимизированную реализацию на чистом Python более чем в 100 раз. Версия на Rust показала себя лишь немного лучше, чем версия на SciPy/NumPy, но результаты подтвердили наши ожидания: чистый Python гораздо медленнее скомпилированных языков, а расширения, написанные на Rust, могут весьма успешно конкурировать с аналогами на C (побеждая их даже в таком микротестировании).

Также существуют и другие методы повышения производительности. Мы могли бы использовать модули ctypes или cffi. Могли бы добавить подсказки типов и воспользоваться Cython для генерации библиотеки, которую могли бы импортировать из Python. При всех этих вариантах требуется учитывать компромиссы, специфичные для каждого конкретного решения.



Мы также измерили расход памяти для каждой реализации функции при помощи приложения GNU time (не путайте со встроенной командой оболочки time). В частности, мы измерили максимальный размер резидентной части памяти (resident set size).

Тогда как в реализациях на чистом Python и Rust максимальные размеры этой части весьма схожи, реализация SciPy/NumPy потребляет ощутимо больше памяти по данному контрольному показателю. Предположительно это связано с дополнительными возможностями, загружаемыми в память при импорте. Как бы то ни было, вызов кода Rust из Python, по-видимому, не привносит серьезных накладных расходов памяти.



Итоги


Мы крайне впечатлены производительностью, достигаемой при вызове Rust из Python. В ходе нашей откровенно краткой оценки реализация на Rust смогла потягаться в производительности с базовой реализацией на C из пакетов SciPy и NumPy. Rust, по-видимому, отлично подходит для эффективной крупномасштабной обработки.

Rust показал не только отличное время выполнения; следует отметить, что и накладные расходы памяти в этих тестах также оказались минимальными. Такие характеристики времени выполнения и использования памяти представляются идеальными для целей масштабирования. Производительность реализаций SciPy и NumPy C FFI определенно сопоставима, но с Rust мы получаем дополнительные плюсы, которых не дают нам C и C++. Гарантии по безопасности памяти и потокобезопасности это очень привлекательное преимущество.

Тогда как C обеспечивает сопоставимое с Rust время выполнения, C как таковой не предоставляет потокобезопасности. Существуют внешние библиотеки, обеспечивающие такой функционал для C, но за правильность их использования полностью отвечает разработчик. Rust следит за проблемами потокобезопасности, например, за возникновением гонок, во время компиляции благодаря реализованной в нем модели владения а стандартная библиотека предоставляет комплект механизмов конкурентности, среди которых каналы, блокировки и умные указатели с подсчетом ссылок.

Мы не призываем портировать SciPy или NumPy на Rust, так как эти библиотеки Python уже хорошо оптимизированы и поддерживаются классными сообществами разработчиков. С другой стороны, мы настоятельно рекомендуем портировать с чистого Python на Rust такой код, который не предоставляется в высокопроизводительных библиотеках. В контексте приложений для data science, используемых для анализа безопасности, Rust представляется конкурентоспособной альтернативой для Python, учитывая его скорость и гарантии безопасности.
Подробнее..

Дипфейки, реверс-инжиниринг, муз. программирование и аудиоинтерфейсы за рулем что почитать аудиоману

04.07.2020 16:09:21 | Автор: admin
Это подборка материалов по теме программирования, компьютерного железа и ПО для автомобильных аудиоинтерфейсов. Всех заинтересовавшихся темой, приглашаем под кат.


Фото Jefferson Santos / Unsplash

Как программируют музыку. Прежде чем взять и влиться в тему музыкального программирования, стоит осмотреться. Предлагаем вашему вниманию компактный обзор специализированных инструментов. Первый из них называется Csound наследник семейства MUSIC-N, прямиком из Bell Labs 1960-х годов. В середине 80-х его доработал специалист из MIT, а теперь на Csound играет легендарный BT. Расскажем о его особенностях, приведем примеры использования, плюс обсудим SuperCollider и Pure Data, принадлежащих все к тому же языковому семейству MUSIC-N.

Кто занимается дипфейк-аудио. Обсуждаем Neural Voice Puppetry разработку сотрудников Мюнхенского технического университета и Института информатики Общества Макса Планка. По записи голоса и фотографии она синтезирует речь и моделирует мимику. Аналогичное решение сингапурских коллег может нативно прикрутить реплики одного человека к видеозаписи с совершенно другим лицом. Рассказываем, где применяют такие наработки (конечно, не там, где вы подумали) и вспоминаем другие проекты по теме быстрого редактирования аудио.

Реверс-инжиниринг-пост. Вслед за материалами о Sound Blaster 1.0 и Innovation SSI-2001 наш свежий разбор того, что удалось выяснить энтузиастам об усилителе в Nintendo Game Boy Color.



В статье мы напоминаем, чем известна эта консоль, показываем структуру чипа (схема выше), объясняем особенности транзисторов, использованных резисторов и конденсаторов, а еще даем компактный обзор других реверс-инжиниринг-проектов по теме легендарного Game Boy.

Если дэт-метал, то нейросетевой. Заменят ли алгоритмы музыкантов? Это вопрос, к которому мы возвращаемся в очередной раз. Обсуждаем возможности разработок по теме синтезатор NSynth Super, систему ИИ Dadabots, тематический стартап Jukedeck и инструментарий OpenAI.

Аудиобиблиотеки для машинного обучения. Чтобы реализовать что-то похожее на вышеописанные проекты, потребуется собрать солидный массив записей например, для обучения собственных нейронок. Рассказываем про четыре библиотеки, распространяющих аудиоконтент по лицензии Creative Commons. Это значит, что его можно использовать даже в коммерческих проектах.

Не отвлекаться за рулем. В одном из недавних материалов мы разобрались, как и почему автомобильные инфотейнмент-системы провоцируют ДТП. На этот раз обсуждаем альтернативу в виде голосовых ассистентов. Говорим о примерах их внедрения на стороне автопроизводителей, исследованиях и потенциальных рисках, от которых будет не так и просто избавиться.


Фото Miguel ngel Hernndez / Unsplash

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

Начать неделю правильно. Бонус-пост из прошлого. В нем мы обсудили аудиогаджеты, призванные защитить от шума и внешних раздражителей. Тогда речь шла об офисной работе, но многие решения могут быть применимы и в новых условиях. Например, специальные шлемы и маски. Что касается наушников они остаются актуальными в любые времена и незаменимыми на удаленке.



Дополнительные материалы:

Домашнее аудио от песенных вечеров до механических проигрывателей
Как домашнее аудио становилось действительно массовым
Как развивалось домашнее аудио: эра винила


Подробнее..

Как сменить свою специальность на программиста?

04.07.2020 18:11:54 | Автор: admin
Вам надоела ваша работа? Нет перспектив? Возможно, только начинаете свой путь?
В данной статье мы рассмотрим какой путь надо пройти, сколько времени, сил и денег надо затратить чтобы с нуля дойти до уровня, когда вам начнут присылать приглашения на работу за рубежом на очень вкусных условиях.



Здравствуйте, меня зовут Александр Зеленин. Я программист с более чем 15 годами опыта. За это время удалось поработать на позициях от веб-разработчика до CTO, в компаниях разного уровня (от 5 человек, до 2000+, стартапах, корпорациях), в разных странах и городах. Так же собеседовал более тысячи человек за всё время, вёл образовательные курсы, менторил и так далее.

* Данная статья является исключительно моим мнением. Не претендует на полность, абсолютную корректность, отсутствие ошибок, неточностей. Используйте здравый смысл при принятии любых решений и несите ответственность за них лично.

Обязательно ли релевантное высшее образование?


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

Но, всё же, с высшим образованием проще?


Да, так как многие из тем так или иначе затрагивались при обучении. Даже если всё забылось, то восстановить знания выйдет быстрее. То если оно есть хорошо, нет ну и фиг с ним.

Я интроверт. Программист это же самый круто вариант для интровертов, да?


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

Сложно учиться?


Да, очень. Конечно, это всё индивидуально, и кому-то это даётся очень легко (мне лично это всё казалось очень лёгким, но и путь был долгий), но, в основном, все кто переучивался говорят, что это очень сложно.

Основная проблема в том, что начинаешь в новом для себя поле (особенно когда был экспертом в другом) с самых низов. Постоянное ощущение что тупой, знаешь недостаточно, медленно решаешь и так далее. Это серьёзный вызов. Намного серьёзнее чем кажется на первый взгляд.

Оно того стоит вообще?


Это зависит от вашего видения мира, текущих условий и кучи других факторов специфичных для вас.

Специалист высокого уровня может рассчитывать на следующее (на момент написания этой статьи):

  • Для РФ: зарплата в районе 200 000 рублей белая, ДМС (на семью), печеньки, свободный график, оплата образования/садика детей, поездки на профильные конференции, высокую стабильность, интересные задачи, полный пакет для переезда (квартира на месяц, билеты, перевозка грузов, помощь с поиском жилья и т.п.)
  • Для Европы: зарплата в районе 5000 евро, и в принципе всё что и выше. В пакет для переезда часто ещё включают единоразовую выплату в районе 7000 евро. Часто ещё местная пенсия начинает капать и есть возможность позже получить вид на жительство / гражданство
  • США/Канада/Швейцария/Мидл Ист: зарплата в районе 10000$, и все плюшки выше

Что значит специалист высокого уровня?


Принято условно делить специалистов на 3 уровня. Часто очень разнятся требования, но в общем случае они следующие:

  1. Junior может эффективно решать задачи, но требуется помощь более опытных коллег при столкновении с неопределенностями (не понятна задача, не ясно как решать, не ясно кого спросить и так далее)
  2. Middle самостоятельная единица, которая может и задачу решить очень хорошо, и всю информацию собрать сам из нужных источников
  3. Senior эксперт, который хорошо знает не только программную часть, но и понимает бизнес, в котором работает и может предлагать решения на совершенно разных уровнях

Окей, и сколько времени учиться на каждый из уровней?


Самый первый этап, пожалуй, самый сложный психологически. На Juniorа вам потребуется около 1500 часов чистого времени (это полгода по 8 часов каждый день).

После чего джуниором проработаете около 1.5 лет.

После чего мидлом ешё года два.

Если не меняли предметную область, то добро пожаловать в сеньёры (4-5 лет суммарно).

Погоди, погоди Я вот видел курсы, там 3 раза в неделю по 2 часа и через два месяца мне обещают зарплату в 100 000 в месяц!


Ну если обещают, значит так и будет (нет).

Я вёл несколько подобных курсов исключительно из личного интереса. Даже увеличив нагрузку на учащихся по часам в 2.5 раза (по 5 часов), к концу второго месяца получался хороший обзор технологий.

Конкурентоспособность после таких курсов примерно никакая. В зачёт опыта такое не идёт. Ну т.е. вы можете в резюме написать хоть 5 лет опыта, но опыта это не даст.

Окей, окей, а вообще польза от курсов платных есть?


Вообще есть, если не сидеть и не ждать у моря погоды, а активно использовать возможность общения с ментором и при условии, что помимо этих курсов (которые вроде 6 часов в неделю) вы ещё вкладываете свои 34 часа сверху.

Какие курсы порекомендуешь?


Бесплатные. Я серьёзно. Главная ценность курсов общение с ментором. На этом их польза заканчивается. Проблема многих подобных платформ в том, что: на них много участников и внимание ментора рассеивается, менторы не квалифицированы.

Часто платформы предлагают какие-то сущие копейки за их проведение, что практически сразу исключает появление хороших специалистов готовых их вести (кроме идейных, которых мало и фиг найдёшь).

Эээ, так как тогда учиться?


  1. 1. Обязательно нужно найти личного ментора.
    Без этого весь процесс будет либо замедлен в разы, либо уйдёте, не туда потеряв кучу времени. Как найти ментора? Лучший способ это среди друзей и знакомых, кто либо уже программист, либо учится (хотя бы полгода). Обычно программисты не против делиться знаниями и помогать.
    Другой способ это нанять ментора. Минус этого способа в том, что он стоит денег. Плюс в том, что можно более точно подобрать под свои нужды. Тут надо учитывать зарплату хороших спецов (см в предыдущих вопросах) и понимать, что даже несколько часов в неделю обойдутся прилично.
  2. 2. Определиться, где вы хотите работать и что делать
    Это нужно сразу, чтобы подобрать релевантные технологии для изучения и быть полезным этой компании через полгода
  3. 3. Составить план и поставить сроки
    Составить план поможет ментор. Самому это нереально, потому что ты не знаешь то, что ты не знаешь

Ментор, ментор что, совсем никак без него?


Можно и без него. Но тогда надо намного больше читать, делать, пытаться ориентироваться во всём и перепроверять себя постоянно.

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

А потом найти ментора. Даже если ты уже сеньёр, всегда круто получить консультацию от более опытного специалиста.

Статья, вроде, про то как сменить специальность, а ты говоришь что нужно пол года по 8 часов. Как совмещать с работой то? Может год по 4 часа?


Подловили. Это очень сложный вопрос. По предыдущему опыту сокращение количества часов увеличивает срок обучения непропорционально (т.е. по 4 часа будет не ожидаемый год, а, скажем, 1.5-2). Так же, в зависимости от текущей работы, усвоение сложного материала может просто не идти и всё, то есть совмещать может не получится. Это всё зависит, всё индивидуально.
Один из способов это накопить средств и уделить полгода на такой переход. Это может быть очень сложным, когда уже есть семья или другие требования в виде ипотек и подобного. А ещё может получиться что с ходу сложно найти работу или какие-либо непредвиденные обстоятельства типа короновируса.

Другой способ это ну фигачить по полной. А вы чего хотели? :-D

У меня друг закончил курсы за два месяца и начал получать 5000$ в месяц сразу после


Несколько вариантов: либо он врёт, либо он гений (в этом случае курс тут не причём, совпало), либо ему повезло (знакомый нанял по-хитрому и т.п.), либо это что-то прям супер узкоспециализированное и он нашел лазейку. Пусть расскажет как. Ещё бывает, когда стартап не знает куда деньги от инвестора тратить и переплачивает явно. В первой же компании с адекватной разработкой картина будет совсем иная.

Где искать работу?


В столицах или, минимум, городах-миллионниках. Если вы не в Москве, Киеве, Минске или где вы там готовьтесь к переезду. Локальные рынки слишком малы и не могут предоставить достаточно вакансий (особенно для начинающих специалистов), возможности для роста малы и так далее. Вообще если удалить всю статью, и оставить только эту рекомендацию это будет одним из лучших решений для карьеры (!, но может быть не лучшим для других ваших факторов).

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

Можно ли устроиться сразу на удалёнку?


Можно, это будет сложнее, в первую очередь, для вас. Не потому, что сама работа отличается, а потому что ответы на вопросы будут с задержкой, не будет возможности что бы подошел человек и пальцем показал, где что и почему. Да, есть куча инструментов сейчас (тивью, чаты, скайпы, доски разные), но в плане комфорта и скорости коммуникации оно отличается. Если целить на удалёнку, то надо быть готовым фигачить х2 от нормы, что бы хотя бы поспевать за адекватным темпом.

Мне 45. Возьмут ли меня Juniurом без живого опыта?


Да, возьмут. Возраст вам даже в плюс сыграет, потому что много опыта из жизни хорошо перекладывается на технологии. С большой вероятностью пройти путь до сеньёра у вас выйдет намного быстрее, именно благодаря житейскому опыту (который начинает играть большую роль со стадии мидла, чем технические навыки). С релокациями в некоторые страны может быть сложнее если и возраст большой, и вышки нет, но это тоже всё решаемо.

Какой язык учить?


Зависит от того, чем вы хотите заниматься. Если не хотите играть в лотерейку и пойти по надёжному пути Java ваш выбор. Во всяком случае он позволит пройти весь путь без сильной технологической смены и с очень хорошей оплатой.

Если у вас есть друг, который обещает устроить вас PHP разработчиком уже через полгода это может быть также хорошим кандидатом. Потому что язык не важен.

Я думал ты пошутишь про английский. Надо его учить?


Не на старте. Несмотря на то, что куча документации, информации и статей идут на английском, это всё можно поглощать через переводчики. Английский понадобится, когда решите переезжать. Хорошим моментом для начала изучения может быть через пару месяцев после устройства на работу Juniorом. И да, обязательно нужно будет общение с носителями языка будьте готовы в это также вложить денег.

Про переводчик мини-пример: буквально недавно пришлось столкнуться с библиотекой, актуальная документация для которой существует только на китайском (MyBatis-Plus) гугл транслейт помог мне разобраться что к чему без особых приключений.

Погоди, ты сказал язык не важен что?


Это не совсем так. Он важен, конечно, но после определённого этапа (senior) вам будет всё равно на каком языке писать. Концепты, на которых строится программа, переносятся из языка в язык и именно их изучение занимает намного большее время.

Условно говоря, это можно представить, что профессиональный писатель решил написать рассказ на не родном языке рассказ получится не такой выразительный и красивый, но сама история будет крутой, потому что он уже знает, как писать крутую историю. Язык это лишь инструмент донесения.

Окей, можешь накидать мне план что делать то?


  1. Определитесь реально оно вам нужно. Стоит ли оно того?
  2. Решите чем хотите заниматься. В идеале составить список компаний, в которых хотели бы работать. В идеале выбрать, где хотите быть через полгода, через два и через пять.
  3. Напишите в компании, где хотите через полгода работать. Узнайте условия найма, что нужно выучить и так далее.
  4. Составьте план тем, которые надо изучить (из описания вакансий и информации из пунктов 2 и 3)
  5. Учите, практикуйте. Каждый день. Каждый. Обязательно. Без пауз.
  6. Решайте задачки на платформах типа Leetcode и Hackerrank. Минимум 1 в день, хотя бы easy уровень.
  7. По мере продвижения можно контактировать с рекрутерами из компаний и искать возможность чтобы вас наняли. Чем раньше, тем лучше. Для увеличения шансов можно попробовать сделать какое-нибудь мини-решение полезное для компании, чтобы заинтересовать их.
  8. Найдите ментора.

Вне зависимости от языка список тем, которые я рекомендую включить в план: контроль версий (git, github), структуры и алгоритмы (знать все распространённые, big O, знать что что-то такое есть и уметь найти быстро), IDE (зачем, как настроить), отладка и профилирование (как искать ошибки, точки остановки и прочее), тестирование (написание хотя бы простейших тестов и понимание что включать в них), базы данных (реляционные, сетевые, документо-ориентированные), документирование (как писать, зачем), планирование и приоритизация (сколько займёт, что делать и когда), изучить Style Gudie по вашему языку, linux (базовое понимание что там, зачем и куда, умение запустить свой код там), пакетные менеджеры (как пользоваться, зачем, как поддерживать), семантичное версирование (зачем оно, как следовать), фреймворки (на нужном языке, хотя бы для ознакомления несколько), инструменты для сборки и автоматизации, криптография (базово что существует, а не как сделать), авторизация и аутентификация (что есть что и что для этого существует).

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

Я видел, что компания, которая мне интересна обучает с нуля и нанимает. В чём подвох?


В низкой зарплате первые год-два. Но вообще это довольно крутой вариант, так как при должной мотивации это гарантированная работа, живой опыт и заинтересованные менторы, которые будут вас учить именно тому, что требуется компании. Теоретически это может снизить вариации после или удлинить срок роста, но это очень зависит от конкретных компаний и предложений. Если компания из списка вашего интереса имеет подобные стажировки с нуля стоит к ним присмотреться.

Жесть, всё сложно.


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

Из песочницы Обнаружение столкновений и теорема о разделяющей оси

04.07.2020 20:20:46 | Автор: admin
В наше время компьютеры представляют собой мощные вычислительные машины,
способные выполнять миллионы операций в секунду. И естественно не обойтись без симуляции реального или игрового мира. Одна из задач компьютерного моделирования и симуляции состоит в определении столкновения двух объектов, одно из решений которой реализуется теоремой о разделяющей оси.



Примечание. В статье будет приведен пример с 2 параллелепипедами(далее кубы), но идея для других выпуклых объектов будет сохранена.
Примечание. Вся реализация будет выполнена в Unity.

Акт 0. Общая теория


Для начала нужно познакомиться с "теоремой о разделяющей гиперплоскости".Именно она будет лежать в основе алгоритма.

Теорема. Две выпуклые геометрии не пересекаются, тогда и только тогда, когда между ними существует гиперплоскость, которая их разделяет. Ось ортогональная разделяющей
гиперплоскости называется разделяющей осью, а проекции фигур на нее не пересекаются.


Разделяющая ось (двумерный случай)


Разделяющая ось (трехмерный случай)
Можно заметить, что проекции на разделяющую ось не пересекаются.

Свойство. Потенциальная разделяющая ось будет находиться в следующих множествах:
  • Нормы плоскостей каждого куба(красные)
  • Векторное произведение ребер кубов $\{[\vec{x},\vec{y}] : xX, yY\}$,

где X ребра первого куба (зеленые), а Y второго (синие).



Каждый куб мы можем описать следующими входными данными:
  • Координаты центра куба
  • Размеры куба (высота, ширина, глубина)
  • Кватернион куба

Создадим для этого дополнительный класс, экземпляры которого будут предоставлять информацию о кубе.
public class Box{    public Vector3 Center;    public Vector3 Size;    public Quaternion Quaternion;    public Box(Vector3 center, Vector3 size, Quaternion quaternion)    {       this.Center = center;       this.Size = size;       this.Quaternion = quaternion;    }    // дополнительный конструктор, который    // позволяет сгенерировать объект на основе GameObject    public Box(GameObject obj)    {        Center = obj.transform.position;        Size = obj.transform.lossyScale;        Quaternion = obj.transform.rotation;    }}

Акт 1. Кватернионы


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

Кватернион это гиперкомплексное число, которое определяет вращение объекта в пространстве.

$w+xi+yj+zk$


Мнимая часть(x,y,z) представляет вектор, который определяет направление вращения
Вещественная часть(w) определяет угол, на который будет совершено вращение.

Его основное отличие от всем привычных углов Эйлера в том, что нам достаточно иметь один вектор, который будет определять направление вращения, чем три линейно независимых вектора, которые вращают объект в 3 подпространствах.

Рекомендую две статьи, в которых подробно рассказывается о кватернионах:

Раз
Два

Теперь, когда у нас есть минимальные представления о кватернионах, давайте поймем, как вращать вектор, и опишем функцию вращение вектора кватернионом.

Формула вращения вектора

$\vec{v}' = q * \vec{v} * \overline{q}$


$\vec{v}'$ искомый вектор
$\vec{v}$ исходный вектор
$q$ кватернион
$\overline{q}$ обратный кватернион

Для начала, дадим понятие обратного кватерниона в ортонормированном базисе это кватернион с противоположной по знаку мнимой частью.

$q = w+xi+yj+zk$
$\overline{q} = w-xi-yj-zk$

Посчитаем $\vec{v} * \overline{q}$

$ M = \vec{v}*\overline{q} = (0 + v_xi + v_yj + v_zk)(q_w - q_xi - q_yj - q_zk) = $
$=\color{red}{v_xq_wi} + \color{purple}{v_xq_x} - \color{blue}{v_xq_yk} + \color{green}{v_xq_zj} +$
$+\color{green}{v_yq_wj} + \color{blue}{v_yq_xk} + \color{purple}{v_yq_y} - \color{red}{v_yq_zi} +$
$+\color{blue}{v_zq_wk} - \color{green}{v_zq_xj} + \color{red}{v_zq_yi} + \color{purple}{v_zq_z}$

Теперь выпишем отдельные компоненты и из этого произведения соберем новый кватернион
$M = u_w+u_xi+u_yj+u_zk$
$u_w = \color{purple}{v_xq_x + v_yq_y + v_zq_z}$
$u_xi = \color{red}{(v_xq_w - v_yq_z + v_zq_y)i}$
$u_yj = \color{green}{(v_xq_z + v_yq_w - v_zq_x)j}$
$u_zk = \color{blue}{(-v_xq_y + v_yq_x + v_zq_w)k}$

Посчитаем оставшуюся часть, т.е. $ q*M $ и получим искомый вектор.

Примечание. Чтобы не загромождать вычисления, приведем только мнимую(векторную) часть этого произведения. Ведь именно она характеризует искомый вектор.

$\vec{v}' = q*M = (q_w + q_xi + q_yj + q_zk)(u_w + u_xi + u_yj + u_zk) =$
$= \color{red}{q_wu_xi} + \color{green}{q_wu_yj} + \color{blue}{q_wu_zk} + $
$ +\color{red}{q_xu_wi} + \color{blue}{q_xu_yk} - \color{green}{q_xu_zj} +$
$+\color{green}{q_yu_wj} - \color{blue}{q_yu_xk} + \color{red}{q_yu_zi} +$
$+\color{blue}{q_zu_wk} +\color{green}{q_zu_xj} -\color{red}{q_zu_yi}$

Соберем компоненты вектора

$v_x' = \color{red}{q_wu_x + q_xu_w + q_yu_z - q_zu_y}$
$v_y' = \color{green}{q_wu_y - q_xu_z + q_yu_w + q_zu_x}$
$v_z' = \color{blue}{q_wu_z + q_xu_y - q_yu_x + q_zu_w}$

$v' = (v_x', v_y', v_z')$
Таким образом необходимый вектор получен

Напишем код:

private static Vector3 QuanRotation(Vector3 v,Quaternion q){        float u0 = v.x * q.x + v.y * q.y + v.z * q.z;        float u1 = v.x * q.w - v.y * q.z + v.z * q.y;        float u2 = v.x * q.z + v.y * q.w - v.z * q.x;        float u3 = -v.x * q.y + v.y * q.x + v.z * q.w;        Quaternion M = new Quaternion(u1,u2,u3,u0);                Vector3 resultVector;        resultVector.x = q.w * M.x + q.x * M.w + q.y * M.z - q.z * M.y;          resultVector.y = q.w * M.y - q.x * M.z + q.y * M.w + q.z * M.x;        resultVector.z = q.w * M.z + q.x * M.y - q.y * M.x + q.z * M.w;                return resultVector;}

Акт 2. Нахождение вершин куба


Зная как вращать вектор кватернионом, не составит труда найти все вершины куба.

Перейдем к функцию нахождении вершин куба. Определим базовые переменные.

private static Vector3[] GetPoint(Box box){        //Тут будут храниться координаты вершин        Vector3[] point = new Vector3[8];        //получаем координаты        //....        return point;}

Далее необходимо найти такую точку(опорную точку), от которой будет легче всего найти другие вершины.

Из центра покоординатно вычитаем половину размерности куба.Потом к опорной точке прибавляем по одной размерности куба.

//...        //первые четыре вершины        point[0] = box.Center - box.Size/2;        point[1] = point[0] + new Vector3(box.Size.x , 0, 0);        point[2] = point[0] + new Vector3(0, box.Size.y, 0);        point[3] = point[0] + new Vector3(0, 0, box.Size.z);//таким же образом находим оставшееся точки        point[4] = box.Center + box.Size / 2;        point[5] = point[4] - new Vector3(box.Size.x, 0, 0);        point[6] = point[4] - new Vector3(0, box.Size.y, 0);        point[7] = point[4] - new Vector3(0, 0, box.Size.z);//...


Можем видеть, как сформированы точки

После нахождения координат вершин, необходимо повернуть каждый вектор на соответствующий кватернион.

//...        for (int i = 0; i < 8; i++)        {            point[i] -= box.Center;//перенос центра в начало координат            point[i] = QuanRotation(point[i], box.Quaternion);//поворот            point[i] += box.Center;//обратный перенос        }//...

полный код получения вершин
private static Vector3[] GetPoint(Box box){        Vector3[] point = new Vector3[8];                //получаем координаты вершин        point[0] = box.Center - box.Size/2;        point[1] = point[0] + new Vector3(box.Size.x , 0, 0);        point[2] = point[0] + new Vector3(0, box.Size.y, 0);        point[3] = point[0] + new Vector3(0, 0, box.Size.z);//таким же образом находим оставшееся точки        point[4] = box.Center + box.Size / 2;        point[5] = point[4] - new Vector3(box.Size.x, 0, 0);        point[6] = point[4] - new Vector3(0, box.Size.y, 0);        point[7] = point[4] - new Vector3(0, 0, box.Size.z);        //поворачиваем вершины кватернионом        for (int i = 0; i < 8; i++)        {            point[i] -= box.Center;//перенос центра в начало координат            point[i] = QuanRotation(point[i], box.Quaternion);//поворот            point[i] += box.Center;//обратный перенос        }                return point;}


Перейдем к проекциям.

Акт 3. Поиск разделяющих осей


Следующим шагом необходимо найти множество осей, претендующих на разделяющую.
Вспомним, что ее можно найти в следующих множествах:

  • Нормали плоскостей каждого куба(красные)
  • Векторное произведение ребер кубов $\{[\vec{x},\vec{y}[ : xX, yY\}$, где X ребра первого куба (зеленые), а Y второго (синие).



Для того, чтобы получить необходимые оси, достаточно иметь четыре вершины куба, которые формируют ортогональную систему векторов. Эти вершины находятся в первых четырех ячейках массива точек, которые мы сформировали во втором акте.



Необходимо найти нормали плоскостей, порожденные векторами:

  • $\vec{a}$ и $\vec{b}$
  • $\vec{b}$ и $\vec{c}$
  • $\vec{a}$ и $\vec{c}$

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

такой код позволяет получить эти вектора и найти нормали к плоскостям для двух кубов(понятный вариант)
private static List<Vector3> GetAxis(Vector3[] a, Vector3[] b){        //ребра        Vector3 A;        Vector3 B;        //потенциальные разделяющие оси        List<Vector3> Axis = new List<Vector3>();        //нормали плоскостей первого куба        A = a[1] - a[0];        B = a[2] - a[0];        Axis.Add(Vector3.Cross(A,B).normalized);                A = a[2] - a[0];        B = a[3] - a[0];        Axis.Add(Vector3.Cross(A,B).normalized);                A = a[1] - a[0];        B = a[3] - a[0];        Axis.Add(Vector3.Cross(A,B).normalized);                //нормали второго куба        A = b[1] - b[0];        B = b[2] - b[0];        Axis.Add(Vector3.Cross(A,B).normalized);                A = b[1] - b[0];        B = b[3] - b[0];        Axis.Add(Vector3.Cross(A,B).normalized);                A = b[2] - b[0];        B = b[3] - b[0];        Axis.Add(Vector3.Cross(A,B).normalized);        //...}


Но можно сделать проще:
private static List<Vector3> GetAxis(Vector3[] a, Vector3[] b){        //ребра        Vector3 A;        Vector3 B;//потенциальные разделяющие оси        List<Vector3> Axis = new List<Vector3>();//нормали плоскостей первого куба        for (int i = 1; i < 4; i++)        {            A = a[i] - a[0];            B = a[(i+1)%3+1] - a[0];            Axis.Add(Vector3.Cross(A,B).normalized);        }//нормали второго куба        for (int i = 1; i < 4; i++)        {            A = b[i] - b[0];            B = b[(i+1)%3+1] - b[0];            Axis.Add(Vector3.Cross(A,B).normalized);        }        //...}

Еще мы должны найти все векторные произведения ребер кубов. Это можно организовать простым перебором:

private static List<Vector3> GetAxis(Vector3[] a, Vector3[] b){        //...        //получение нормалей        //...        //Теперь добавляем все векторные произведения        for (int i = 1; i < 4; i++)        {            A = a[i] - a[0];            for (int j = 1; j < 4; j++)            {                B = b[j] - b[0];                if (Vector3.Cross(A,B).magnitude != 0)                {                    Axis.Add(Vector3.Cross(A,B).normalized);                }            }        }        return Axis;}

Полный код
private static List<Vector3> GetAxis(Vector3[] a, Vector3[] b){//ребра        Vector3 A;        Vector3 B;//потенциальные разделяющие оси        List<Vector3> Axis = new List<Vector3>();//нормали плоскостей первого куба        for (int i = 1; i < 4; i++)        {            A = a[i] - a[0];            B = a[(i+1)%3+1] - a[0];            Axis.Add(Vector3.Cross(A,B).normalized);        }//нормали второго куба        for (int i = 1; i < 4; i++)        {            A = b[i] - b[0];            B = b[(i+1)%3+1] - b[0];            Axis.Add(Vector3.Cross(A,B).normalized);        }        //Теперь добавляем все векторные произведения        for (int i = 1; i < 4; i++)        {            A = a[i] - a[0];            for (int j = 1; j < 4; j++)            {                B = b[j] - b[0];                if (Vector3.Cross(A,B).magnitude != 0)                {                    Axis.Add(Vector3.Cross(A,B).normalized);                }            }        }        return Axis;}


Акт 4. Проекции на оси


Мы подошли к самому главному моменту. Здесь мы должны найти проекции кубов на все потенциальные разделяющие оси. У теоремы есть одно важное следствие: если объекты пересекаются, то ось на которую величины пересечения проекции кубов минимальна является направлением(нормалью) коллизии, а длинна отрезка пересечения глубиной проникновения.

Но для начала напомним формулу скалярной проекции вектора v на единичный вектор a:

$proj_\left\langle \vec{a} \right\rangle \vec{v} = \frac{(\vec{v},\vec{a})}{| \vec{a} |}$



private static float ProjVector3(Vector3 v, Vector3 a){        a = a.normalized;        return Vector3.Dot(v, a) / a.magnitude;        }

Теперь опишем функцию, которая будет определять пересечение проекций на оси-кандидаты.

На вход подаем вершины двух кубов, и список потенциальных разделяющих осей:

private static Vector3 IntersectionOfProj(Vector3[] a, Vector3[] b, List<Vector3> Axis){        for (int j = 0; j < Axis.Count; j++)        {           //в этом цикле проверяем каждую ось           //будем определять пересечение проекций на разделяющие оси из списка кандидатов        }        //Если мы в цикле не нашли разделяющие оси, то кубы пересекаются, и нам нужно         //определить глубину и нормаль пересечения.}

Проекция на ось задается двумя точками, которые имеют максимальные и минимальные значения на самой оси:



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

private static void ProjAxis(out float min, out float max, Vector3[] points, Vector3 Axis){        max = ProjVector3(points[0], Axis);        min = ProjVector3(points[0], Axis);        for (int i = 1; i < points.Length; i++)        {            float tmp = ProjVector3(points[i], Axis);            if (tmp > max)            {                max = tmp;            }            if (tmp < min)            {                min= tmp;            }        }}

Итого, применив данную функцию( ProjAxis ), получим проекционные точки каждого куба.

private static Vector3 IntersectionOfProj(Vector3[] a, Vector3[] b, List<Vector3> Axis){        for (int j = 0; j < Axis.Count; j++)        {            //проекции куба a            float max_a;            float min_a;            ProjAxis(out min_a,out max_a,a,Axis[j]);            //проекции куба b            float max_b;            float min_b;            ProjAxis(out min_b,out max_b,b,Axis[j]);                        //...        }        //...}

Далее, на основе проекционных вершин определяем пересечение проекций:



Для этого давайте поместим наши точки в массив и отсортируем его, такой способ поможет нам определить не только пересечение, но и глубину пересечения.

            float[] points = {min_a, max_a, min_b, max_b};            Array.Sort(points);

Заметим следующее свойство:

1) Если отрезки не пересекаются, то сумма отрезков будет меньше, чем отрезок сформированными крайними точками:



2) Если отрезки пересекаются, то сумма отрезков будет больше, чем отрезок сформированными крайними точками:



Вот таким простым условием мы проверили пересечение и непересечение отрезков.

Если пересечения нет, то глубина пересечения будет равна нулю:

            //...            //Сумма отрезков            float sum = (max_b - min_b) + (max_a - min_a);            //Длина крайних точек            float len = Math.Abs(p[3] - p[0]);                        if (sum <= len)            {                //разделяющая ось существует                // объекты непересекаются                return Vector3.zero;            }            //Предполагаем, что кубы пересекаются и рассматриваем текущую ось далее            //....

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

В случае пересечения кубов, все немного интереснее: проекции кубов на все вектора будет пересекаться, и мы должны определить вектор с минимальным пересечением.

Создадим этот вектор перед циклом, и будем хранить в нем вектор с минимальной длинной. Тем самым в конце работы цикла получим искомый вектор.

private static Vector3 IntersectionOfProj(Vector3[] a, Vector3[] b, List<Vector3> Axis){        Vector3 norm = new Vector3(10000,10000,10000);        for (int j = 0; j < Axis.Count; j++)        {                //...        }        //в случае, когда нашелся вектор с минимальным пересечением, возвращаем его        return norm;{

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

Так же я добавил определение ориентации нормали по отношению первого куба.

//...if (sum <= len){   //разделяющая ось существует   // объекты не пересекаются   return new Vector3(0,0,0);}//Предполагаем, что кубы пересекаются и рассматриваем текущий вектор далее//пересечение проекций - это расстояние между 2 и 1 элементом в нашем массиве//(см. рисунок на котором изображен случай пересечения отрезков)float dl = Math.Abs(points[2] - points[1]);if (dl < norm.magnitude){   norm = Axis[j] * dl;   //ориентация нормали   if(points[0] != min_a)   norm = -norm;}//...

Весь код
private static Vector3 IntersectionOfProj(Vector3[] a, Vector3[] b, List<Vector3> Axis){        Vector3 norm = new Vector3(10000,10000,10000);        for (int j = 0; j < Axis.Count; j++)        {            //проекции куба a            float max_a;            float min_a;            ProjAxis(out min_a,out max_a,a,Axis[j]);            //проекции куба b            float max_b;            float min_b;            ProjAxis(out min_b,out max_b,b,Axis[j]);            float[] points = {min_a, max_a, min_b, max_b};            Array.Sort(points);            float sum = (max_b - min_b) + (max_a - min_a);            float len = Math.Abs(points[3] - points[0]);                        if (sum <= len)            {                //разделяющая ось существует                // объекты не пересекаются                return new Vector3(0,0,0);            }            float dl = Math.Abs(points[2] - points[1]);            if (dl < norm.magnitude)            {                norm = Axis[j] * dl;                //ориентация нормы                if(points[0] != min_a)                    norm = -norm;            }        }        return norm;}


Заключение


Проект с реализацией и примером загружен на GitHub, и ознакомиться можно с ним здесь.

Моей целью было поделиться своим опытом в решение задач связанных с определением пересечений двух выпуклых объектов. А так же доступно и понятно рассказать о данной теореме.
Подробнее..

Перевод Понимаем JIT в PHP 8

05.07.2020 04:21:30 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса Backend-разработчик на PHP



TL;DR


Компилятор Just In Time в PHP 8 реализован как часть расширения Opcache и призван компилировать операционный код в инструкции процессора в рантайме.

Это означает, что с JIT некоторые операционные коды не должны интерпретироваться Zend VM, такие инструкции будут выполняться непосредственно как инструкции уровня процессора.

JIT в PHP 8


Одной из наиболее комментируемых фич PHP 8 является компилятор Just In Time (JIT). Он на слуху во множестве блогов и сообществ вокруг него много шума, но пока я нашел не очень много подробностей о работе JIT в деталях.

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

Упрощая вещи: когда JIT работает должным образом, ваш код не будет выполняться через Zend VM, вместо этого он будет выполняться непосредственно как набор инструкций уровня процессора.

В этом вся идея.

Но чтобы лучше это понять, нам нужно подумать о том, как php работает внутри. Это не очень сложно, но требует некоторого введения.

Я уже писал статью с кратким обзором того, как работает php. Если вам покажется, что эта статья становится чересчур сложной, просто прочитайте ее предшественницу и возвращайтесь. Это должно немного облегчить ситуацию.

Как выполняется PHP-код?


Мы все знаем, что php интерпретируемый язык. Но что это на самом деле означает?

Всякий раз, когда вы хотите выполнить код PHP, будь то фрагмент или целое веб-приложение, вам придется пройти через интерпретатор php. Наиболее часто используемые из них PHP FPM и интерпретатор CLI. Их работа очень проста: получить код php, интерпретировать его и выдать обратно результат.

Это обычная картина для каждого интерпретируемого языка. Некоторые шаги могут варьироваться, но общая идея та же самая. В PHP это происходит так:

  1. Код PHP читается и преобразуется в набор ключевых слов, известных как токены (Tokens). Этот процесс позволяет интерпретатору понять, в какой части программы написан каждый фрагмент кода. Этот первый шаг называется лексирование (Lexing) или токенизация (Tokenizing).
  2. Имея на руках токены, интерпретатор PHP проанализирует эту коллекцию токенов и постарается найти в них смысл. В результате абстрактное синтаксическое дерево (Abstract Syntax Tree AST) генерируется с помощью процесса, называемого синтаксическим анализом (parsing). AST представляет собой набор узлов, указывающих, какие операции должны быть выполнены. Например, echo 1 + 1 должно фактически означать вывести результат 1 + 1 или, более реалистично, вывести операцию, операция 1 + 1.
  3. Имея AST, например, гораздо проще понять операции и их приоритет. Преобразование этого дерева во что-то, что может быть выполнено, требует промежуточного представления (Intermediate Representation IR), которое в PHP мы называем операционный код (Opcode). Процесс преобразования AST в операционный код называется компиляцией.
  4. Теперь, когда у нас есть опкоды, происходит самое интересное: выполнение кода! PHP имеет движок под названием Zend VM, который способен получать список опкодов и выполнять их. После выполнения всех опкодов программа завершается.


Чтобы сделать это немного нагляднее, я составил диаграмму:


Упрощенная схема процесса интерпретации PHP.

Достаточно прямолинейно, как вы можете заметить. Но здесь есть и узкое место: какой смысл лексировать и парсить код каждый раз, когда вы его выполняете, если ваш php-код может даже не меняется так часто?

В конце концов, нас интересуют только опкоды, верно? Правильно! Вот зачем существует расширение Opcache.

Расширение Opcache


Расширение Opcache поставляется с PHP, и, как правило, нет особых причин его деактивировать. Если вы используете PHP, вам, вероятно, следует включить Opcache.

Что он делает, так это добавляет слой оперативного общего кэша для опкодов. Его задача состоит в том, чтобы извлекать опкоды, недавно сгенерированные из нашего AST, и кэшировать их, чтобы при дальнейших выполнениях можно было легко пропустить фазы лексирования и синтаксического анализа.

Вот схема того же процесса с учетом расширения Opcache:


Поток интерпретации PHP с Opcache. Если файл уже был проанализирован, php извлекает для него кэшированный операционный код, а не анализирует его заново.

Это просто завораживает, как красиво пропускаются шаги лексирования, синтаксического анализа и компиляции.
Примечание: именно здесь лучше всего себя проявляет функция предварительной загрузки PHP 7.4! Это позволяет вам сказать PHP FPM анализировать вашу кодовую базу, преобразовывать ее в опкоды и кэшировать их даже до того, как вы что-либо выполните.

Вы можете начать задумываться, а куда сюда можно прилепить JIT, верно?! По крайней мере я на это надеюсь, именно поэтому я и пишу эту статью

Что делает компилятор Just In Time?


Прослушав объяснение Зива в эпизоде подкастов PHP и JIT от PHP Internals News, мне удалось получить некоторое представление о том, что на самом деле должен делать JIT

Если Opcache позволяет быстрее получать операционный код, чтобы он мог переходить непосредственно к Zend VM, JIT предназначить заставить его работать вообще без Zend VM.

Zend VM это программа, написанная на C, которая действует как слой между операционным кодом и самим процессором. JIT генерирует скомпилированный код во время выполнения, поэтому php может пропустить Zend VM и перейти непосредственно к процессору. Теоретически мы должны выиграть в производительности от этого.

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

Реализация JIT в PHP использует библиотеку DynASM (Dynamic Assembler), которая отображает набор команд ЦП в конкретном формате в код сборки для многих различных типов ЦП. Таким образом, компилятор Just In Time преобразует операционный код в машинный код для конкретной архитектуры, используя DynASM.

Хотя одна мысль все-таки не давала мне покоя

Если предварительная загрузка способна парсить php-код в операционный перед выполнением, а DynASM может компилировать операционный код в машинный (компиляция Just In Time), почему мы, черт возьми, не компилируем PHP сразу же на месте, используя Ahead of Time компиляцию?!

Одна из мыслей, на которые меня натолкнул эпизода подкаста, заключалась в том, что PHP слабо типизирован, то есть часто PHP не знает, какой тип имеет переменная, пока Zend VM не попытается выполнить определенный опкод.

Это можно понять, посмотрев на тип объединения zend_value, который имеет много указателей на различные представления типов для переменной. Всякий раз, когда виртуальная машина Zend пытается извлечь значение из zend_value, она использует макросы, подобные ZSTR_VAL, которые пытаются получить доступ к указателю строки из объединения значений.

Например, этот обработчик Zend VM должен обрабатывать выражение меньше или равно (<=). Посмотрите, как он разветвляется на множество различных путей кода, чтобы угадать типы операндов.

Дублирование такой логики вывода типов с помощью машинного кода неосуществимо и потенциально может сделать работу еще медленнее.

Финальная компиляция после того, как типы были оценены, также не является хорошим вариантом, потому что компиляция в машинный код является трудоемкой задачей ЦП. Так что компиляция ВСЕГО во время выполнения плохая идея.

Как ведет себя компилятор Just In Time?


Теперь мы знаем, что не можем вывести типы, чтобы генерировать достаточно хорошую опережающую компиляцию. Мы также знаем, что компиляция во время выполнения стоит дорого. Чем же может быть полезен JIT для PHP?

Чтобы сбалансировать это уравнение, JIT PHP пытается скомпилировать только несколько опкодов, которые, по его мнению, того стоят. Для этого он профилирует коды операций, выполняемые виртуальной машиной Zend, и проверяет, какие из них имеет смысл компилировать. (в зависимости от вашей конфигурации).

Когда определенный опкод компилируется, он затем делегирует выполнение этому скомпилированному коду вместо делегирования на Zend VM. Это выглядит как на диаграмме ниже:


Поток интерпретации PHP с JIT. Если они уже скомпилированы, опкоды не выполняются через Zend VM.

Таким образом, в расширении Opcache есть пара инструкций, определяющих, должен ли определенный операционный код быть скомпилирован или нет. Если да, то компилятор преобразует его в машинный код с помощью DynASM и выполняет этот новый сгенерированный машинный код.

Интересно, что, поскольку в текущей реализации есть ограничение в мегабайтах для скомпилированного кода (также настраиваемое), выполнение кода должно иметь возможность беспрепятственного переключения между JIT и интерпретируемым кодом.

Кстати, эта беседа Бенуа Жакемона о JIT от php ОЧЕНЬ помогла мне разобраться во всем этом.

Я до сих пор не уверен в том, в каких конкретных случаях происходит компиляция, но я думаю, что пока не очень хочу это знать.

Так что, вероятно, ваш прирост производительности не будет колоссальным


Я надеюсь, что сейчас гораздо понятнее, ПОЧЕМУ все говорят, что большинство приложений php не получат больших преимуществ в производительности от использования компилятора Just In Time. И почему рекомендация Зива для профилирования и эксперимента с различными конфигурациями JIT для вашего приложения лучший путь.

Скомпилированные опкоды обычно будут распределены между несколькими запросами, если вы используете PHP FPM, но это все равно не изменит правила игры.

Это потому, что JIT оптимизирует операции с процессором, а в настоящее время большинство php-приложений в большей степени завязаны на вводе/выводе, нежели на чем-либо еще. Не имеет значения, скомпилированы ли операции обработки, если вам все равно придется обращаться к диску или сети. Тайминги будут очень похожи.

Если только...

Вы делаете что-то не завязанное на ввод/вывод, например, обработку изображений или машинное обучение. Все, что не касается ввода/вывода, получит пользу от компилятора Just In Time. Это также причина, по которой люди сейчас говорят, что они склоняются больше к написанию нативных функций PHP, написанных на PHP, а не на C. Накладные расходы не будут разительно отличаться, если такие функции будут скомпилированы в любом случае.

Интересное время быть программистом PHP

Я надеюсь, что эта статья была полезна для вас, и вам удалось лучше разобраться, что такое JIT в PHP 8. Не стесняйтесь связываться со мной в твиттере, если вы хотите добавить что-то, что я мог забыть здесь, и не забудьте поделиться этим со своими коллегами-разработчиками, это, несомненно, добавит немного пользы вашим беседам!-- @nawarian



PHP: статические анализаторы кода


Подробнее..

Сортировка выбором

06.07.2020 02:17:13 | Автор: admin
Всем привет. Эту статью я написал специально к запуску курса Алгоритмы и структуры данных от OTUS.



Введение


Сортировка массива является одной из первых серьезных задач, изучаемых в классическом курсе Алгоритмы и структуры данных дисциплины computer science. В связи с этим задачи на написание сортировок и соответствующие вопросы часто встречаются на собеседованиях на позиции стажера или junior разработчика.

Постановка задачи


Традиционно стоит начать изложение решений задачи с ее постановки. Обычно задача сортировки предполагает упорядочивание некоторого массива целых чисел по возрастанию. Но на самом деле, это является некоторым упрощением. Излагаемые в этом разделе алгоритмы можно применять для упорядочивания массива любых объектов, между которыми установлено отношение порядка (то есть про любые два элемента можно сказать: первый больше второго, второй больше первого или они равны). Упорядочивать можно как по возрастанию, так и по убыванию. Мы же воспользуемся стандартным упрощением.

Сортировка выбором


Одной из простейших сортировок является сортировка выбором.

Описание алгоритма


Сортировка массива выбором осуществляется так: массив делится на две части. Одна из частей называется отсортированной, а другая неотсортированной. Алгоритм предполагает проход по всему массиву с тем, чтобы длина отсортированной части стала равна длине всего массива. В рамках каждой итерации мы находим минимум в неотсортированной части массива и меняем местами этот минимум с первым элементом неотсортированной части массива. После чего мы увеличиваем длину отсортированной части массива на единицу. Пример осуществления одной итерации представлен ниже:
1 3 5 | 8 9 6 -> 1 3 5 6 | 9 8

Реализация


Предлагаю посмотреть на реализацию данного алгоритма на языке C:
void choiseSortFunction(double A[], size_t N){    for(size_t tmp_min_index = 0; tmp_min_index < N;                                  tmp_min_index++) {        //ищем минимум        for(size_t k = tmp_min_index + 1; k < N; k++) {            if (A[k] < A[tmp_min_index]) {                double min_value = A[k];                A[k] = A[tmp_min_index];                A[tmp_min_index] = min_value;            }        }    }}


Анализ


Предлагаю проанализировать данный алгоритм. Тело внутреннего цикла само по себе выполняется за O(1), то есть не зависит от размера сортируемого массива. Это означает, что для понимания асимптотики алгоритма необходимо посчитать сколько раз выполняется это тело. На первой итерации внешнего цикла имеют место (n 1) итераций внутреннего цикла. На второй итерации внешнего цикла (n 2) итерации внутреннего цикла. Продолжая это рассуждение и далее, мы приходим к следующему:

$T(n) = (n - 1) * O(1) + (n - 2) * O(1) + ... + O(1) = O((n - 1) + (n - 2) + ... + 1) = O((n - 1) * n / 2) = O(n ^ 2)$



В процессе вычислений, мы воспользовались сначала свойствами О нотации, а затем формулой для вычисления суммы арифметической прогрессии.

Для порядка необходимо оценить еще и дополнительную память, которая требуется для выполнения алгоритма. Здесь все гораздо проще: мы выделяли память только для счетчиков цикла и переменной буфера, позволяющей обменивать 2 элемента массива местами. Поэтому:

$M(n) = O(1)$



В результате анализа мы пришли к тому, что временная сложность алгоритма зависит от размера входного массива квадратично. Поэтому данную сортировку относят к классу квадратичных сортировок. Результат анализа не зависит от содержания массива: он верен в лучшем, в худшем и в среднем случае.

Также важно заметить, что сортировка выбором в такой реализации является устойчивой. Позволю себе напомнить, что сортировка называется устойчивой, если при ее выполнении порядок следования равных элементов не меняется. Это свойство не очень принципиально для такой учебной задачи, как сортировка массива чисел, но если бы мы сортировали какие-то более сложные объекты с установленным отношением порядка это могло бы быть важно. Подобный пример мы можем рассмотреть как-нибудь в следующий раз, когда будем говорить о поразрядной сортировке.

Двусторонняя сортировка выбором


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

Рекурсивная сортировка выбором


В качестве упражнения можно попробовать написать алгоритм не с использованием цикла, а с использованием рекурсии. На java это может выглядеть следующим образом:
public static int findMin(int[] array, int index){    int min = index - 1;    if(index < array.length - 1) min = findMin(array, index + 1);    if(array[index] < array[min]) min = index;    return min;}  public static void selectionSort(int[] array){    selectionSort(array, 0);}  public static void selectionSort(int[] array, int left){    if (left < array.length - 1) {        swap(array, left, findMin(array, left));        selectionSort(array, left+1);    }}  public static void swap(int[] array, int index1, int index2) {    int temp = array[index1];    array[index1] = array[index2];    array[index2] = temp;}


Итоги


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



Если вы хотите подробнее узнать о курсе, приглашаю всех на бесплатный вебинар, который пройдет уже 10 июля.


Подробнее..

Перевод Flutter. Разбираемся, как рисовать различные фигуры с помощью CustomClipper

06.07.2020 10:05:56 | Автор: admin


Flutter предлагает различные виджеты для работы с определенным набором фигур, например, ClipRect, ClipRRect, ClipOval. Но также есть ClipPath, с помощью которого мы можем создавать любые типы фигур.


В данной статье мы сосредоточимся на том, что можно сделать, используя ClipPath и CustomClipper. Поехали!


Содержание:



ClipPath


Благодаря ClipPath мы можем создавать очень сложные и необычные фигуры. В этом нам поможет свойство clipper у ClipPath


@overrideWidget build(BuildContext context) {  return Scaffold(    backgroundColor: Colors.grey,    body: Center(      child: ClipPath(        clipper: MyCustomClipper(), // <--        child: Container(          width: 200,          height: 200,          color: Colors.pink,        ),      ),    ),  );}

В качестве значения для clipper необходимо указать экземпляр класса, который наследуют CustomClipper<Path> и переопределяет два метода.


class MyCustomClipper extends CustomClipper<Path> {  @override  Path getClip(Size size) {    Path path = Path();    return path;  }  @override  bool shouldReclip(CustomClipper<Path> oldClipper) => false}

Метод getClip вызывается каждый раз, когда требуется обновить нашу описанную фигуру. В качестве параметра метод получает Size, который содержит значения высоты и ширины виджета, переданного в сhild у ClipPath.


shouldReclip вызывается, когда в clipper передается новый экземпляр класса. Если новый экземпляр отличается от старого, то метод должен возвращать true, в противном случае false.


Нам нужно описать фигуру внутри CustomClipper. Это не очень сложно, но сначала надо разобраться с основами графики.



Как показано на приведенном выше рисунке, каждая точка на графике описывается через координату (x,y). x представляет собой горизонтальную ось, а y вертикальную ось. Построение фигуры начинается с верхнего левого угла, чья координата (0, 0).


Давайте рассмотрим доступные методы для построения фигур. С помощью этих методов вы можете создавать собственные фигуры.


lineTo


Данный метод используется для построения отрезка от текущей точки до заданной.



Как показано выше на рисунке (a), путь по умолчанию начинается с точки p1(0, 0). Теперь добавим новый отрезок к p2(0, h), а затем p3(w, h). Нам не нужно определять линию от конечной точки p3 до начальной p1, она будет нарисована по умолчанию.


Результат можно увидеть на рисунке (b) с треугольником розового цвета.


@overridePath getClip(Size size) {  Path path = Path()    ..lineTo(0, size.height) // Добавить отрезок p1p2    ..lineTo(size.width, size.height) // Добавить отрезок p2p3    ..close();  return path;}

moveTo


Этот метод нужен для перемещения точки отрисовки.



Как показано на рисунке выше, начальная точка перемещена из (0, 0) в точку p1(w/2, 0).


@overridePath getClip(Size size) {  Path path = Path() // Начальная точка в (0, 0)    ..moveTo(size.width/2, 0) // передвигаем точку в (width/2, 0)    ..lineTo(0, size.width)    ..lineTo(size.width, size.height)    ..close();  return path;}

quadraticBezierTo


Этот метод используется для построения квадратичной кривой Безье.



Источник: Wikipedia


Как показано на приведенном выше рисунке, мы можем нарисовать квадратичную кривую Безье, используя контрольную и конечную точки. P0 это начальная точка, P1 контрольная точка, а P2 конечная точка.



Как показано выше на рисунке (а), кривая рисуется от точки p2(0, h) до p3(w, h) с использованием контрольной точки c(w/2, h/2).


@overridePath getClip(Size size) {  // Эта переменная определена для лучшего понимания, какое значение указать в методе quadraticBezierTo  var controlPoint = Offset(size.width / 2, size.height / 2);  var endPoint = Offset(size.width, size.height);  Path path = Path()    ..moveTo(size.width / 2, 0)    ..lineTo(0, size.height)    ..quadraticBezierTo(        controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy)    ..close();  return path;}

cubicTo


Данный метод используется для построения кубической кривой путем указания 2 контрольных и конечной точек.



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



Как показано на рисунке (a), кубическая кривая рисуется между начальной точкой p2 и конечной точкой p3 с использованием контрольных точек c1 и c2.


@overridePath getClip(Size size) {  var controlPoint1 = Offset(50, size.height - 100);  var controlPoint2 = Offset(size.width - 50, size.height);  var endPoint = Offset(size.width, size.height - 50);  Path path = Path()    ..moveTo(size.width / 2, 0)    ..lineTo(0, size.height - 50)    ..cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,        controlPoint2.dy, endPoint.dx, endPoint.dy)    ..close();  return path;}

arcToPoint


Данный метод нужен для рисования дуги от начальной точки до указанной точки. Мы можем настроить дугу, установив радиус, указав направление (по часовой стрелке / против часовой стрелки).



Существуют эллиптический и круговой типы радиуса для построения дуги. Как показано на приведенном выше рисунке, эллиптический радиус рисуется с использованием значения (x, y), а круговой радиус радиуса R.



Как показано на рисунке (a) выше, построение фигуры начинается с точки p1. Первая дуга рисуется от точки p2 до точки p3, при этом радиус не задан, поэтому по умолчанию равен нулю, соответственно наша дуна выглядит, как прямая. Вторая дуга тянется от начальной точки p4 до конечной точки p5 с использованием кругового радиуса и направления по часовой стрелке (прим. по часовой направление по умолчанию). Третья дуга тянется от точки p6 до точки p7, используя круговой радиус и направление против часовой стрелки. Четвертая дуга проходит от начальной точки p8 до конечной точки p1, используя эллиптический радиус.


@overridePath getClip(Size size) {  double radius = 20;  Path path = Path()      ..moveTo(radius, 0)    ..lineTo(size.width-radius, 0)    ..arcToPoint(Offset(size.width, radius))    ..lineTo(size.width, size.height - radius)    ..arcToPoint(Offset(size.width - radius, size.height),radius: Radius.circular(radius))    ..lineTo(radius, size.height)    ..arcToPoint(Offset(0, size.height - radius), radius: Radius.circular(radius), clockwise: false)    ..lineTo(0, radius)    ..arcToPoint(Offset(radius, 0), radius: Radius.elliptical(40, 20))    ..close();  return path;}

arcTo


Этот метод используется, чтобы нарисовать дугу, задав в качестве значения в радианах Rect, начальный угол (startAngle) и конечный угол (sweepAngle).



Приведенное выше изображение предназначено для предоставления основной информации об углах в радианах. Минимальный угол равен 0 PI (значение PI равно ~3.14), полный 2 PI.



Существует несколько способов построения Rect, как с помощью точек, окружности, LTRB (Left, Top, Right, Bottom) и LTWH (Left, Top, Width, Height). На вышеприведенном рисунке (a) все типы дуг нарисованы с разным начальным углом.


@overridePath getClip(Size size) {  double radius = 50;  Path path = Path()    ..lineTo(size.width - radius, 0)    ..arcTo(        Rect.fromPoints(            Offset(size.width - radius, 0), Offset(size.width, radius)), // Rect        1.5 * pi,   // начальный угол        0.5 * pi,  // конечный угол        true)  // направление по часовой стрелке    ..lineTo(size.width, size.height - radius)    ..arcTo(Rect.fromCircle(center: Offset(size.width - radius, size.height - radius), radius: radius), 0, 0.5 * pi, false)    ..lineTo(radius, size.height)    ..arcTo(Rect.fromLTRB(0, size.height - radius, radius, size.height), 0.5 * pi, 0.5 * pi, false)    ..lineTo(0, radius)    ..arcTo(Rect.fromLTWH(0, 0, 70, 100), 1 * pi, 0.5 * pi, false)    ..close();  return path;}

addRect


Данный метод нужен для построения прямоугольников. Есть несколько различных методов для создания Rect: fromPoints, fromLTWH, fromCircle, fromLTRB и fromCircle.



@overridePath getClip(Size size) {  Path path = Path()    ..addRect(Rect.fromPoints(Offset(0, 0), Offset(60, 60)))    ..addRect(Rect.fromLTWH(0, size.height - 50, 50, 50))    ..addRect(Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: 20))    ..close();  return path;}

addRRect


Этот метод используется для добавления прямоугольника с закругленными углами. Можно скруглить, как все углы сразу, так и один.



@overridePath getClip(Size size) {    double radius = 10;    Path path = Path()      ..addRRect(RRect.fromLTRBR(0, 0, 60, 60, Radius.circular(radius)))      ..addRRect(RRect.fromRectAndRadius(          Rect.fromLTWH(0, size.height - 50, 50, 50), Radius.circular(radius)))      ..addRRect(RRect.fromRectAndCorners(          Rect.fromCircle(              center: Offset(size.width / 2, size.height / 2), radius: 30          ),          topLeft: Radius.circular(radius)))      ..close();    return path;}

addOval


Данный метод используется для описания овала. Как и для addRect, параметр типа Rect является обязательным.



@overridePath getClip(Size size) {    Path path = Path()      ..addOval(Rect.fromPoints(Offset(0, 0), Offset(60, 60)))      ..addOval(Rect.fromLTWH(0, size.height - 50, 100, 50))      ..addOval(Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: 20))      ..close();    return path;}

addPolygon


Этот метод используется для добавления многоугольника путем определения нескольких точек.



@overridePath getClip(Size size) {    var points = [      Offset(size.width / 2, 0), // точка p1      Offset(0, size.height / 2), // точка p2      Offset(size.width / 2, size.height), // точка p3      Offset(size.width, size.height / 2) // точка p4    ];     Path path = Path()      ..addPolygon(points, false)      ..close();    return path;}

addPath


Данный метод пригодится, если надо добавить ещё одну фигуру на отображение к уже имеющейся. Для этого необходимо указать описание фигуры и сдвиг относительно начальной позиции точки для вычислениям координат новой фигуры.



Как показано на рисунке (a), есть две фигуры (path 1 и path 2), path 1 является основной, а path 2 добавляется в path 1. path 2 строится в соответсвии со сдвигом (w/2, 0), поэтому начало координат (0, 0) и все остальные точки вычисляются с учетом указанного смещения.


@overridePath getClip(Size size) {    Path path1 = Path()      ..lineTo(0, size.height)      ..lineTo(size.width/2, size.height)      ..lineTo(0, 0);    Path path2 = Path()      ..lineTo(size.width/2, size.height)      ..lineTo(size.width/2, 0)      ..lineTo(0, 0);    path1.addPath(path2, Offset(size.width/2,0));    return path1;}

relativeLineTo


Этот метод аналогичен методу lineTo, но конечная точка линии задается не точной координатой, а смещением из начальной.



На рисунке (a) линия p1p2 рисуется с помощью relativeLineTo, поэтому координата точки p2 вычисляется относительно p1. Можно записать в виде формулы p2(x, y) = (p1.x + 50, p1.y + h)


@overridePath getClip(Size size) {    Path path = Path()      ..moveTo(size.width/2, 0)      ..relativeLineTo(50, size.height)      ..lineTo(size.width , size.height)      ..close();    return path;}

Примечание: relativeMoveTo, relativeQuadraticBezierTo, relativeArcToPoint, relativeCubicTo будут работать по сравнению с quadraticBezierTo, arcToPoint, cubicTo по тому же принципу, что и relativeLineTo по отношению к lineTo.



Надеюсь, что данный материал будет вам полезен и поможет создавать новые классные фигуры.




Подробнее..

Анонс Code Challenge недельное соревнование для настоящих разработчиков

06.07.2020 16:22:04 | Автор: admin

image


У нас в Контуре живёт дух соревновательного программирования, и мы хотим разделить его с вами. Давайте отвлечёмся от рабочих задач и попрограммируем что-нибудь необычное. Освобождайте вечера, собирайте команду, пишите ботов и отправляйте их на сражения в межгалактические бои.


Когда: 6-12 июля
Где: онлайн


Что случилось?


Code Challenge первое открытое онлайн-соревнование от Контура для хардкорных инженеров. Уже сейчас открыта песочница, а сам контест начнётся вечером, в понедельник 6 июля, и продлится 7 суток. Вместе мы попытаемся прорваться к центру вселенной через вражеские корабли и планеты древних цивилизаций. Конечно, с помощью кода и квестов.


Если вы уже слышали про соревнования ICFP Contest, CodinGame или Google Code Jam, вам точно понравится то, что мы приготовили.


А в чём суть контеста?


В каждом уголке галактики непрерывно идут бои за первенство. В боях участвуют флоты под управлением тактических ботов. Мы будем программировать своего бота (на C#, JS, Python или 1С %)), управлять космическими кораблями, побеждать врагов и захватывать новые планеты. Прокачать тактического бота помогут артефакты древних цивилизаций, но найти их будет непросто: все они спрятаны и зашифрованы.


Почему это интересно?


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


Кто может участвовать?


Соревноваться могут все, кто так или иначе связан с разработкой: программисты, тестировщики, менеджеры, аналитики, дизайнеры и т.д. Сражаться с вражескими цивилизациями и конкурирующими отрядами в одиночку возможно, но сложно лучше собрать команду. Максимальное количество участников в команде 7 человек.


Как стартануть?


Капитан должен зарегистрировать команду. Сделать это можно не только до начала соревнования, но и во время него. Мы организовали битву таким образом, что играть будет интересно, даже если начинать сильно позже остальных. Никому, кроме капитана, регистрироваться не нужно: достаточно договориться об участии между собой и сообщить нам размер команды.


Вечером 6 июля мы откроем портал для боевых отрядов и начнём Межгалактический бой. Ссылка на игру и ключи от песочницы прилетят капитанам команд на почту заранее. Будет время изучить правила и запустить свои корабли в первые тестовые полеты.


А если победим?


Командам, обыгравших всех остальных, мы подарим сертификаты на Ozon.


Герои меча Брезенхэма и магии Чебышёва, в бой!

Подробнее..

PVS-Studio теперь в Compiler Explorer

06.07.2020 16:22:04 | Автор: admin
image1.png

Совсем недавно произошло знаменательное событие: PVS-Studio появился в Compiler Explorer! Теперь вы можете быстро и легко проанализировать код на наличие ошибок прямо на сайте godbolt.org (Compiler Explorer). Это нововведение открывает большое количество новых возможностей от утоления любопытства по поводу способностей анализатора до возможности быстро поделиться результатом проверки с другом. О том, как использовать эти возможности, и пойдёт речь в этой статье. Осторожно большие гифки!

Если вам хочется сразу перейти к делу и проанализировать код здесь и сейчас, то вот вам полностью готовая к использованию ссылка. Рекомендую добавить её себе в закладки еще пригодится ;)

Теперь давайте обо всём по порядку. Compiler Explorer интерактивный онлайн-сервис для исследования компиляторов. Здесь вы можете прямо на сайте писать код и сразу видеть, какой ассемблерный вывод сгенерирует для него тот или иной компилятор:

image2.gif

Помимо этого, на сайте существует еще целый набор возможностей, и сегодня мы рассмотрим возможность проведения анализа с помощью PVS-Studio. Для того, чтобы провести анализ, необходимо сделать следующее:

  1. Зайти на сайт godbolt.org,
  2. Во вкладке с выводом компилятора нажать "Add tool...",
  3. В выпадающем списке выбрать "PVS-Studio".

Пример такой последовательности действий вы можете увидеть на анимации ниже:

image3.gif

Готово! Теперь в появившемся окне будут автоматически выводиться все предупреждения, которые выдаются анализатором на ваш код. Анализировать можно как собственноручно написанный код, так и исходные файлы, загруженные по отдельности. Естественно, файлы должны быть самодостаточными и не использовать include на файлы, не входящие в стандартную библиотеку. В противном случае файл не получится ни скомпилировать, ни проверить.

На данный момент анализ с помощью PVS-Studio доступен на сайте для всех версий GCC и Clang под платформы x86 и x64. Мы планируем расширить возможности сайта и на другие поддерживаемые нами компиляторы (например, MSVC или компиляторы для ARM), если на это будет спрос.

Сейчас на сайте включены только General-диагностики уровней error, warning и note. Мы специально не стали включать остальные режимы (Optimization, 64-bit, Custom и MISRA), чтобы в выводе остались только самые важные предупреждения. Также, в отличие от самого PVS-Studio, Compiler Explorer пока не поддерживает языки C# и Java мы планируем запустить анализ кода на этих языках, как только они там появятся :)

Compiler Explorer имеет весьма умную систему окон, поэтому вы можете двигать их или, например, накладывать друг на друга. Если сейчас вас не интересует вывод компилятора, его можно "спрятать". Вот так:

image4.gif

Вы можете как сразу писать код в окне Compiler Explorer, так и загружать отдельные файлы. Для этого нужно нажать "Save/Load" и в открывшейся вкладке выбрать "File system". Также можно "скачать" написанный вами код на компьютер, нажав Ctrl + S.

image5.gif

На сайте нельзя анализировать проекты целиком можно только загружать файлы по отдельности.

Если вам хочется увидеть вывод вашей программы, то можно открыть окно выполнения, нажав "Add new > Execution only" в окне для написания кода (не в окне с компилятором). На гифке ниже вы можете увидеть вывод лабораторной работы, взятой из нашей страницы про бесплатное использование PVS-Studio студентами и преподавателями.

image6.gif

Кстати, вы заметили, что при переходе по ссылкам на godbolt у вас открывается уже заранее вписанный код в заранее расставленных окнах? Да, вы можете генерировать постоянные ссылки, полностью сохраняющие состояние страницы в момент генерации! Для этого вам нужно нажать на кнопку "Share" в правом верхнем углу экрана.

image7.gif

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

Мы планируем использовать эту возможность для работы с клиентами в техподдержке: зачастую использование Compiler Explorer является очень удобным для составления минимально воспроизводимых примеров, которые можно быстро взять и отправить по почте.

Также в выпадающей вкладке "Share" есть пункт создания Embedded-ссылки, с помощью которой можно встроить окно с Compiler Explorer на какой-нибудь другой сайт.

В Compiler Explorer всегда находится актуальная версия PVS-Studio, поэтому после каждого нашего релиза на сайте можно будет найти всё больше и больше ошибок. Тем не менее, использование PVS-Studio на godbolt.org не дает полного представления о его возможностях, ведь PVS-Studio это не только диагностики, но и развитая инфраструктура:

  • Анализ кода на языках C, C++, C# и Java для куда большего количества платформ и компиляторов;
  • Плагины для Visual Studio 2010-2019, JetBrains Rider, IntelliJ IDEA;
  • Возможность интеграции в TeamCity, PlatformIO, Azure DevOps, Travis CI, CircleCI, GitLab CI/CD, Jenkins, SonarQube и т.д.
  • Утилита мониторинга компиляции для проведения анализа независимо от IDE или сборочной системы;
  • И многое, многое другое.

В конце концов, только скачав анализатор вы сможете полноценно проанализировать проект, состоящий из нескольких файлов. Поэтому самый лучший способ получить правильное представление о PVS-Studio скачать анализатор и попробовать его на своём проекте.

Чтобы всегда быть в курсе, следите за нашими новостями. Также читайте наш блог: там мы публикуем не только новости и статьи о поиске багов в реальных проектах, но и различные интересные моменты, связанные с C, C++, C# и Java.

Наши соцсети:



Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: George Gribkov. PVS-Studio is now in Compiler Explorer!.
Подробнее..

Из песочницы Преимущества интерфейсов в GO

06.07.2020 20:09:26 | Автор: admin

Преимущества интерфейсов в GO


В языке GO интерфейсы отличаются от других языков. Они немного лучше чем в других популярных языках с точки зрения дизайна. В этой статье я постараюсь объяснить почему.
Я расскажу о преимуществах, приведу примеры и рассмотрю некоторые вопросы, которые могут возникнуть при использовании интерфейсов.


В чем особенность интерфейсов в GO?


В большинстве языков вы описываете один интерфейс, и реализуете их в других местах явно, указывая, что вы реализуете именно их. Но в GO это не так. Вам не нужно указывать явно, что вы реализуете его в своей структуре. Если у структуры есть все функции и они имеют одинаковую сигнатуру с интерфейсом, то она уже его реализует.


Какие преимущество дает эта особенность?


Приватный интерфейс


Интерфейсы удобно описывать внутри модуля. Объясню почему это хорошо. Предположим вы пишите пакет. По-хорошему он должен минимально зависеть от других пакетов, и для этого вы отгораживаетесь от них интерфейсом. Это позволит вам тестировать ваш пакет изолированно и в случае необходимости подменить внешнюю библиотеку. Но этот интерфейс может быть приватным, т.е. другие пакеты о нем ничего не знают.


В конкретный момент нужны конкретные методы от какого-то внешнего объекта, и для этого достаточно иметь интерфейс только с этими конкретными методами. Вам не нужно искать подходящие интерфейсы под создаваемый модуль из внешних или общих пакетов.


Рассмотрим пример.


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


package authimport (   "gitlab.com/excercice_detection/backend")type userRepository interface {   FindUserByEmail(email string) (backend.User, error)   AddUser(backend.User) (userID int, err error)   AddToken(userID int, token string) error   TokenExists(userID int, token string) bool}// Auth сервис авторизацииtype Auth struct {   repository userRepository   logger     backend.Logger}// NewAuth создает объект авторизацииfunc NewAuth(repository userRepository, logger backend.Logger) *Auth {   return &Auth{repository, logger}}// Autentificate Проверяет существование токена пользователяfunc (auth Auth) Autentificate(userID int, token string) bool {   return auth.repository.TokenExists(userID, token)}

Для примера я показал как используется один из методов, на самом деле они используются все.


В главном методе main создается и используется объект авторизации:


package mainimport (   "gitlab.com/excercice_detection/backend/auth"   "gitlab.com/excercice_detection/backend/mysql")func main() {    logger := newLogger()    userRepository := mysql.NewUserRepository(logger)    err := userRepository.Connect()    authService := auth.NewAuth(userRepository, logger)...

При создании объекта авторизации достаточно передать userRepository, у которого реализованы все методы, которые есть в интерфейсе, а пакет mysql при этом ничего не знает об интерфейсе, описанном в сервисе авторизации. Он и не должен об этом знать. Нет лишних зависимостей. Код остается чистым.


В других языках программирования вам бы пришлось описывать интерфейс в общем модуле. И указывать в классе репозитория, что он реализует этот интерфейс. А в классе авторизации использовать его. Хотя на самом деле об этом интерфейсе достаточно знать только модулю авторизации, потому что он нужен только ему.


Если вы передадите объект, который не реализует нужный интерфейс, вы получите ошибку на этапе компиляции.


Такой интерфейс удобно расширять


Если вам в будущем пригодятся другие методы из репозитория, вы можете просто добавить их в этот приватный интерфейс. Он немного усложниться, но только внутри этого модуля. Вам не нужно докидывать их в неком общем интерфейсе, а затем описывать методы везде, где он реализуется.


Тесты


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


Пример мока:


type userRepositoryMock struct {   user         backend.User   findUserErr  error   addUserError error   addUserID    int   addTokenErr  error   tokenExists  bool}func (repository userRepositoryMock) FindUserByEmail(email string) (backend.User, error) {   return repository.user, repository.findUserErr}func (repository userRepositoryMock) AddUser(backend.User) (userID int, err error) {   return repository.addUserID, repository.addUserError}func (repository userRepositoryMock) AddToken(userID int, token string) error {   return repository.addTokenErr}func (repository userRepositoryMock) TokenExists(userID int, token string) bool {   return repository.tokenExists}

Далее, в тестах, userRepositoryMock можно подсунуть вместо обычного userRepositorу, подставляя нужные значения, которые должна вернуть функции.


Как понять, кто на самом деле реализует метод, используемый из интерфейса?


Кажется, что закрываясь интерфейсом и не указывая явно кто его реализует, мы теряем знание о том, как на самом деле работает нужная функция. Но, поскольку GO является строго типизированным языком, то узнать кто реализует метод достаточно просто. Например IDE GoLand умеет так делать.


Чтобы убедиться, что структура реализует интерфейс, достаточно запустить компилятор. Он покажет, каких методов не хватает.


Как найти места, где используются реализованные методы, если они закрыты интерфейсом?


Ответ тот же самый. Это тоже легко поддается статическому анализу. Если ваша IDE не может найти реализации, то это её недостаток, а не интерфейсов. Если IDE развивается, то в ближайшем будущем эта функция должна появиться.


Заключение


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


Язык GO кажется подходящим инструментом для развития больших проектов. И интерфейсы это одна из тех фич, которая сделана удачно.

Подробнее..

Контур стал организатором ICFPC 2020

07.07.2020 10:10:24 | Автор: admin

Ничего не планируйте с 17 по 20 июля, потому что в это время пройдет ежегодное международное соревнование ICFPC 2020. Собирайте команду и трое суток решайте секретную задачу от Контура. Чтобы быть в курсе всех новостей, получать подсказки и не пропустить регистрацию, подписывайтесь на Твиттер.


15 лет команда Контура участвовала в соревновании, а в этом году нас пригласили провести ICFPC 2020. Мы первая команда из России, которой доверили организацию, и это очень круто! Какую задачу мы приготовили пока секрет. Все участники узнают ее условия одновременно 17 июля, но уже сейчас в Твиттере можно увидеть некоторые спойлеры.




Почему нужно участвовать в ICFPC 2020


ICFPC командное соревнование. Соревнований для одиночек много: например, Facebook Hacker Cup и Google Code Jam. Если вам нравятся AI для игр, то codingame.com проводят отличные челенджи раз в 2-3 месяца. В одиночных соревнованиях топ обычно забит какими-то гениями, а в командных можно хорошо выступить за счет упорства и хорошей организации.


Команда может быть любого размера. В рамках одной задачи есть чем заняться всем: можно реализовывать хардкорные алгоритмы, заниматься инфраструктурой, писать код, который окружает хардкорные алгоритмы.


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


Задачи небанальные. Организатор соревнования меняется каждый год и старается превзойти предыдущего, поэтому задачи год от года становятся все интереснее. Обычно это университет, поэтому они добавляют в задачи отсылки к научным проблемам и классике computer science. Кроме того, ICFPC приурочен к научной конференции по функциональному программированию ICFP, и это влияет на задачи. Через раз приходится читать описания на функциональном псевдоязыке (не бойтесь, понятном для обывателей!), а потом программировать виртуальные машины и компиляторы.


Задача одна! За 72 часа команда неограниченного размера должна решить всего одну задачу. Но многогранную и трудную. Её нельзя решить оптимально, но можно решить лучше других команд. Самыми необычными и яркими задачами считаются задачи 2006 и 2007 годов, в которых балом правили виртуальные машины внутри виртуальных машин и а также реверс-инжениринг виртуальных машин.


Про некоторые задачи прошлых лет мы уже писали на Хабре. Если вы участвуете впервые, обязательно посмотрите текст green_hippo. Там Игорь рассказывает, как подготовиться, что посмотреть и послушать, чтобы набраться опыта и вдохновиться.


Подробности соревнования появляются в Твиттере. Там же скоро появится ссылка на регистрацию.


Желаем удачи!

Подробнее..

Применение CQRS amp Event Sourcing в создании платформы для проведения онлайн-аукционов

07.07.2020 14:13:48 | Автор: admin
Коллеги, добрый день! Меня зовут Миша, я работаю программистом.

В настоящей статье я хочу рассказать о том, как наша команда решила применить подход CQRS & Event Sourcing в проекте, представляющем собой площадку для проведения онлайн-аукционов. А также о том, что из этого получилось, какие из нашего опыта можно сделать выводы и на какие грабли важно не наступить тем, кто отправится путем CQRS & ES.
image


Прелюдия


Для начала немного истории и бизнесового бэкграунда. К нам пришел заказчик с платформой для проведения так называемых timed-аукционов, которая была уже в продакшене и по которой было собрано некоторое количество фидбэка. Заказчик хотел, чтоб мы сделали ему платформу для live-аукционов.

Теперь чуть-чуть терминологии. Аукцион это когда продаются некие предметы лоты (lots), а покупатели (bidders) делают ставки (bids). Обладателем лота становится покупатель, предложивший самую большую ставку. Timed-аукцион это когда у каждого лота заранее определен момент его закрытия. Покупатели делают ставки, в какой-то момент лот закрывается. Похоже на ebay.

Timed-платформа была сделана классически, с применением CRUD. Лоты закрывало отдельное приложение, запускаясь по расписанию. Работало все это не слишком надежно: какие-то ставки терялись, какие-то делались как будто бы от лица не того покупателя, лоты не закрывались или закрывались по несколько раз.

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

Обе платформы в принципе работают в реальном времени, но если в случае timed все покупатели находятся в равном положении, то в случае live крайне важно, чтоб онлайн-покупатели могли успешно соревноваться с находящимися в комнате. То есть система должна быть очень быстрой и надежной. Печальный опыт timed-платформы недвусмысленно говорил нам, что классический CRUD нам не подходит.

Своего опыта работы с CQRS & ES у нас не было, так что мы посовещались с коллегами, у которых он был (компания у нас большая), презентовали им наши бизнес-реалии и совместно пришли к заключению, что CQRS & ES должен нам подойти.

Какая еще есть специфика работы онлайн-аукционов:

  • Много пользователей одновременно пытаются воздействовать на один и тот же объект в системе текущий лот. Покупатели делают свои ставки, клерк вводит в систему ставки из комнаты, закрывает лот, открывает следующий. В каждый момент времени в системе можно сделать ставку только одной величины например, 5 рублей. И только один пользователь сможет сделать эту ставку.
  • Нужно хранить всю историю действий над объектами системы, чтобы в случае необходимости можно было посмотреть, кто какую ставку сделал.
  • Время отклика системы должно быть очень маленьким ход онлайн-версии аукциона не должен отставать от офлайн, пользователям должно быть понятно, к чему привели их попытки сделать ставку успешны они или нет.
  • Пользователи должны оперативно узнавать обо всех изменениях в ходе аукциона, а не только о результатах своих действий.
  • Решение должно быть масштабируемым несколько аукционов могут проходить одновременно.

Краткий обзор подхода CQRS & ES


Не буду подробно останавливаться на рассмотрении подхода CQRS & ES, материалы об этом есть в интернете и в частности на Хабре (например, вот: Введение в CQRS + Event Sourcing). Однако кратко все же напомню основные моменты:

  • Самое главное в event sourcing: система хранит не данные, а историю их изменения, то есть события. Текущее состояние системы получается последовательным применением событий.
  • Доменная модель делится на сущности, называемые агрегатами. Агрегат имеет версию. События применяются к агрегатам. Применение события к агрегату инкрементирует его версию.
  • События хранятся в write-базе. В одной и той же таблице хранятся события всех агрегатов системы в том порядке, в котором они произошли.
  • Изменения в системе инициируются командами. Команда применяется к одному агрегату. Команда применяется к последней, то есть текущей, версии агрегата. Агрегат для этого выстраивается последовательным применением всех своих событий. Этот процесс называется регидратацией.
  • Для того, чтобы не регидрировать каждый раз с самого начала, какие-то версии агрегата (обычно каждая N-я версия) можно хранить в системе в готовом виде. Такие снимки агрегата называются снапшотами. Тогда для получения агрегата последней версии при регидратации к самому свежему снапшоту агрегата применяются события, случившиеся после его создания.
  • Команда обрабатывается бизнес-логикой системы, в результате чего получается, в общем случае, несколько событий, которые сохраняются в write-базу.
  • Кроме write-базы, в системе может еще быть read-база, которая хранит данные в форме, в которой их удобно получать клиентам системы. Сущности read-базы не обязаны соответствовать один к одному агрегатам системы. Read-база обновляется обработчиками событий.
  • Таким образом, у нас получается разделение команд и запросов к системе Command Query Responsibility Segregation (CQRS): команды, изменяющие состояние системы, обрабатываются write-частью; запросы, не изменяющие состояние, обращаются к read-части.



Реализация. Тонкости и сложности.


Выбор фреймворка


В целях экономии времени, а также в силу отсутствия специфического опыта мы решили, что нужно использовать какой-то фреймворк для CQRS & ES.

В целом наш технологический стек это Microsoft, то есть .NET и C#. База данных Microsoft SQL Server. Хостится все в Azure. На этом стеке была сделана timed-платформа, логично было и live-платформу делать на нем.

На тот момент, как сейчас помнится, Chinchilla была чуть ли не единственным вариантом, подходящим нам по технологическому стеку. Так что мы взяли ее.

Зачем вообще нужен фреймворк CQRS & ES? Он может из коробки решать такие задачи и поддерживать такие аспекты реализации как:

  • Сущности агрегата, команды, события, версионирование агрегатов, регидратация, механизм снапшотов.
  • Интерфейсы для работы с разными СУБД. Сохранение/загрузка событий и снапшотов агрегатов в/из write-базы (event store).
  • Интерфейсы для работы с очередями отправка в соответствующие очереди команд и событий, чтение команд и событий из очереди.
  • Интерфейс для работы с веб-сокетами.

Таким образом, с учетом использования Chinchilla, к нашему стеку добавились:

  • Azure Service Bus в качестве шины команд и событий, Chinchilla поддерживает его из коробки;
  • Write- и read-базы Microsoft SQL Server, то есть обе они SQL-базы. Не скажу, что это является результатом осознанного выбора, скорее по историческим причинам.

Да, фронтенд сделан на Angular.

Как я уже говорил, одно из требований к системе чтобы пользователи максимально быстро узнавали о результатах своих действий и действий других пользователей это относится и к покупателям, и к клерку. Поэтому мы используем SignalR и веб-сокеты для оперативного обновления данных на фронтенде. Chinchilla поддерживает интеграцию с SignalR.

Выбор агрегатов


Одной из первых вещей, которую надо сделать при реализации подхода CQRS & ES это определить, как доменная модель будет делиться на агрегаты.

В нашем случае доменная модель состоит из нескольких основных сущностей, примерно таких:

public class Auction{     public AuctionState State { get; private set; }     public Guid? CurrentLotId { get; private set; }     public List<Guid> Lots { get; }}public class Lot{     public Guid? AuctionId { get; private set; }     public LotState State { get; private set; }     public decimal NextBid { get; private set; }     public Stack<Bid> Bids { get; }} public class Bid{     public decimal Amount { get; set; }     public Guid? BidderId { get; set; }}


У нас получилось два агрегата: Auction и Lot (с Bidами). В общем, логично, но мы не учли одного того, что при таком делении состояние системы у нас размазалось по двум агрегатам, и в ряде случаев для сохранения консистентности мы должны вносить изменения в оба агрегата, а не в один. Например, аукцион можно поставить на паузу. Если аукцион на паузе, то нельзя делать ставки на лот. Можно было бы ставить на паузу сам лот, но аукциону на паузе тоже нельзя обрабатывать никаких команд, кроме как снять с паузы.

В качестве альтернативного варианта можно было сделать только один агрегат, Auction, со всеми лотами и ставками внутри. Но такой объект будет довольно тяжелым, потому что лотов в аукционе может быть до нескольких тысяч и ставок на один лот может быть несколько десятков. За время жизни аукциона у такого агрегата будет очень много версий, и регидратация такого агрегата (последовательное применение к агрегату всех событий), если не делать снапшотов агрегатов, будет занимать довольно продолжительное время. Что для нашей ситуации неприемлемо. Если же использовать снапшоты (мы их используем), то сами снапшоты будут весить очень много.

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

Подобные обстоятельства нужно учитывать, разбивая доменную модель на агрегаты.

Мы на данном этапе эволюции проекта живем с двумя агрегатами, Auction и Lot, и нарушаем архитектуру, меняя в рамках некоторых команд оба агрегата.

Применение команды к определенной версии агрегата


Если несколько покупателей одновременно делают ставку на один и тот же лот, то есть отправляют в систему команду сделать ставку, успешно пройдет только одна из ставок. Лот это агрегат, у него есть версия. При обработке команды создаются события, каждое из которых инкрементирует версию агрегата. Можно пойти двумя путями:

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

Мы используем второй вариант. Так у команд больше шансов выполниться. Потому что в той части приложения, которая отправляет команды (в нашем случае это фронтенд), текущая версия агрегата с некоторой вероятностью будет отставать от реальной версии на бэкенде. Особенно в условиях, когда команд отправляется много, и верcия агрегата меняется часто.

Ошибки при выполнении команды с использованием очереди


В нашей реализации, в большой степени обусловленной использованием Chinchilla, обработчик команд читает команды из очереди (Microsoft Azure Service Bus). Мы у себя явно разделяем ситуации, когда команда зафейлилась по техническим причинам (таймауты, ошибки подключения к очереди/базе) и когда по бизнесовым (попытка сделать на лот ставку той же величины, что уже была принята, и проч.). В первом случае попытка выполнить команду повторяется, пока не выйдет заданное в настройках очереди число повторений, после чего команда отправляется в Dead Letter Queue (отдельный топик для необработанных сообщений в Azure Service Bus). В случае бизнесового эксепшена команда отправляется в Dead Letter Queue сразу.



Ошибки при обработке событий с использованием очереди


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

Однако, в отличие от ситуации с невыполненной командой, здесь все хуже может получиться так, что команда выполнилась и события в write-базу записались, но обработка их обработчиками зафейлилась. И если один из этих обработчиков обновляет read-базу, то read-база не обновится. То есть окажется в неконсистентном состоянии. Благодаря наличию механизма повторных попыток обработки события read-база почти всегда, в конечном итоге, обновляется, однако вероятность того, что после всех попыток она останется поломанной, все-таки остается.



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

В итоге, в качестве временной меры мы отказались от использования Azure Service Bus для передачи событий из write-части приложения в read-часть. Вместо нее используется так называемая In-Memory Bus, что позволяет обрабатывать команду и события в одной транзакции и в случае неудачи откатить все целиком.



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

Отправка команды в качестве реакции на событие


Такое в принципе уместно, но только в случае, когда невыполнение этой второй команды не ломает состояние системы.

Обработка множества событий одной команды


В общем случае в результате выполнения одной команды получается несколько событий. Бывает, что на каждое из событий нам нужно сделать какое-то изменение в read-базе. Бывает также, что последовательность событий тоже важна, и в неправильной последовательности обработка событий не отработает как надо. Все это означает, что мы не можем читать из очереди и обрабатывать события одной команды независимо, например, разными экземплярами кода, который читает сообщения из очереди. Плюс к этому нам нужна гарантия того, что события из очереди будут прочитаны в той же последовательности, в которой они были туда отправлены. Либо нам нужно быть готовыми к тому, что не все события команды будут успешно обработаны с первой попытки.



Обработка одного события несколькими обработчиками


Если в качестве реакции на одно событие системе нужно выполнить несколько разных действий, обычно делаются несколько обработчиков этого события. Они могут отрабатывать параллельно или последовательно. В случае последовательного запуска при неуспешном выполнении одного из обработчиков вся последовательность запускается заново (в Chinchilla это так). При такой реализации важно, чтобы обработчики были идемпотентными, чтобы второй запуск один раз успешно отработавшего обработчика не свалился. В противном случае при падении второго обработчика из цепочки она, цепочка, уже точно не отработает целиком, потому что во вторую (и последующие) попытку упадет первый обработчик.

Например, обработчик события в read-базе добавляет ставку на лот величиной 5 рублей. Первая попытка сделать это будет успешной, а вторую не даст выполнить constraint в базе.



Выводы/Заключение


Сейчас наш проект находится в стадии, когда, как нам кажется, мы наступили уже на бОльшую часть существующих граблей, актуальных для нашей бизнес-специфики. В целом мы считаем свой опыт довольно успешным, CQRS & ES хорошо подходит для нашей предметной области. Дальнейшее развитие проекта видится в отказе от Chinchilla в пользу другого фреймворка, дающего больше гибкости. Впрочем, возможен и вариант отказа от использования фреймворка вообще. Также вероятно будут какие-то изменения в направлении поиска баланса между надежностью с одной стороны и быстротой и масштабируемостью решения с другой.

Что касается бизнес-составляющей, то и здесь некоторые вопросы по-прежнему остаются открытыми например, деление доменной модели на агрегаты.

Хочется надеяться, что наш опыт окажется для кого-то полезным, поможет сэкономить время и избежать граблей. Спасибо за внимание.
Подробнее..

Категории

Последние комментарии

© 2006-2020, personeltest.ru