...

суббота, 2 мая 2020 г.

[Из песочницы] COVID-19: модель случайных процессов

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

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

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


Минутка заботы от НЛО


В мире официально объявлена пандемия COVID-19 — потенциально тяжёлой острой респираторной инфекции, вызываемой коронавирусом SARS-CoV-2 (2019-nCoV). На Хабре много информации по этой теме — всегда помните о том, что она может быть как достоверной/полезной, так и наоборот.

Мы призываем вас критично относиться к любой публикуемой информации


Официальные источники

Если вы проживаете не в России, обратитесь к аналогичным сайтам вашей страны.

Мойте руки, берегите близких, по возможности оставайтесь дома и работайте удалённо.

Читать публикации про: коронавирус | удалённую работу


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

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

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


  • S (Susceptible) — уязвимые (могут заразиться),
  • I (Infected) — инфицированные (те, кому не повезло),
  • R (Removed) — выбывшие, которые также делятся на две подгруппы:
    • Recovered — выздоровевшие и получившие иммунитет (те, кому сначала не повезло, а потом повезло)
    • Dead — умершие (те, кому сначала не повезло… и потом не повезло).


$inline$\beta$inline$ — вероятность передачи заболевания

Каждый день из рассматриваемого периода мы можем считать, сколько человек попало в каждую из трех групп, получив таким образом функции $inline$S(t)$inline$, $inline$I(t)$inline$, $inline$R(t)$inline$, зависящие от дня $inline$t$inline$.

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

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

Фиксированные параметры модели:


  1. Доля смертности из общего числа выбывших (Dead/Removed) — 12% (согласно уровню смертности по России). В данной модели не учитывается повышение доли смертности при перегрузке системы здравоохранения.
  2. Введение карантина для подтвержденных больных после 50 случаев заражения (во время карантина больных изолируют на следующий день после заражения).
  3. Введение домашней изоляции после 50 случаев заражения.
  4. Вероятность повторного заражения равна нулю (у ученых нет единого мнения на этот счет, но сделаем такое допущение для упрощения модели).

Исследуемые параметры модели:


  1. CP (contagion probability) — вероятность заражения при контакте с инфицированным: 30% — выбирается как опорный показатель, на котором построены большинство моделей (согласно мнению эпидемиологов), рассмотрены также 20% и 10%.
  2. SD (social distance) — вероятность соблюдения домашней изоляции каждым человеком. В видео заражение описывается через физический контакт, у каждой точки есть радиус заражения. Когда мы включаем SD — срабатывает запрет точке приближаться к другим на расстояние в несколько раз превышающее радиус заражения. Однако, точка может нарушить этот запрет с вероятностью равной параметру 100-SD.
    В крайнем приближении SD соответствует домашней изоляции, которая соблюдается с некоторой вероятностью. Чем меньше этот параметр, тем больше людей перемещается по городу. Для него рассматриваются следующие значения: 10%, 40%*, 75%*, 90%.
  3. IP (isolation period) — длительность домашней изоляции в днях. Берутся значения: 30 (апрельский карантин), 45 (отмена изоляции после майских праздников), 70 (продление карантина до лета), и изоляция в неограниченное число дней.
  4. AC (asymptomatic cases) — доля невыявленных** случаев заболевания — 5% (при условии массового тестирования), 40% (согласно российской статистике), 70% (согласно итальянской статистике).
  5. DT (disease time) — время болезни 14 (длительность официального карантина), 21 (согласно российским эпидемиологам), 38 дней (максимально зарегистрированное).

* согласно индексу самоизоляции Яндекса по Нижегородской области в разные периоды
** в модели тестирование не проводится, поэтому невыявленные случаи эквивалентны бессимптомным носителям

При изменении одного параметра значения остальных устанавливались следующими: CP=30% (согласно эпидемиологам), SD=75% (по индексу самоизоляции Яндекса), IP=unlimited (для более точной оценки рассматриваемого параметра), AC=40% (согласно эпидемиологам), DT=21 (согласно эпидемиологам). Желтые вертикальные линии — начало и конец домашней изоляции.


Исследование параметров модели


  1. Вероятность заражения CP

    Видео: 10%, 20%, 30%

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

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


  2. Посмотрим, как влияет на распространение вируса вероятность соблюдения домашней изоляции SD

    Видео: 10%, 40%, 75%, 90%

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


  3. Теперь рассмотрим влияние длительности домашней изоляции IP

    Видео: 30 дней, 45 дней, 70 дней, неограниченная домашняя изоляция

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


  4. Влияние доли бессимптомных носителей на динамику эпидемии AC

    Видео: 5%, 40%, 70%

    График, когда доля бессимптомных носителей 5%, эквивалентен массовому тестированию людей, так как подтвержденные случаи отправляются на карантин и больше не разносят инфекцию. А 70%, наоборот, — малому проценту тестирования.

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

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


  5. Среднее время протекания самой болезни на распространение коронавируса DT

    Видео: 14 дней, 21 день, 38 дней

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



Центры скопления людей во время эпидемии

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

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

Эпидемия развивается стремительно, заболевает большой процент сообщества. Последний заболевший человек выздоравливает примерно через 90 дней после начала эпидемии.


Хорошей иллюстрацией подобного случая является Испания

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

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

Источник


Или, например, Южная Корея

В начале эпидемии власти Южной Кореи сообщили, что сторонники апокалиптической секты ответственны почти за половину всех случаев заражения в стране. На тот момент из 100 новых заболевших 86 находились в городе Тэгу, причем практически все они принадлежали к данной секте. Предполагается, что вспышка заболеваний в Южной Корее началась с города, где большое количество членов секты с 31 января по 2 февраля посетили отпевании и похороны одного из братьев-основателей, а источником заражения стала 61-летняя женщина, у которой был выявлен коронавирус.

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


Вербное воскресенье в Дивеево

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

Источник


Дисклеймер

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


Бутовская вечеринка

Житель ЖК «Бутово парк 2» в Подмосковье организовал «балконную дискотеку», чтобы развлечь своих соседей во время режима самоизоляции, однако впоследствии люди продолжили вечеринку на улице

Источник

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


Полезные материалы

Отдельная благодарность pixml и keysloss за помощь в написании статьи и подготовке симуляций.

Let's block ads! (Why?)

[Перевод] Дэвид О’Брайен (Xirus): Метрики! Метрики! Метрики! Часть 1

Недавно Дэвид О’Брайен открыл свою собственную компанию Xirus (https://xirus.com.au), сосредоточившись на облачных продуктах Microsoft Azure Stack. Они предназначены для согласованного создания и запуска гибридных приложений в центрах обработки данных, в пограничных расположениях, удаленных офисах и облаке.

Дэвид обучает отдельных лиц и компании всему, что связано с Microsoft Azure и Azure DevOps (бывшему VSTS) и до сих пор занимается практическим консультированием и инфракодированием. Он уже 5 лет является обладателем премии Microsoft MVP (Самый ценный профессионал Майкрософт), а недавно получил награду MVP Azure. Как соорганизатор Melbourne Microsoft Cloud и Datacentre Meetup, О’Брайен регулярно выступает на международных конференциях, сочетая свой интерес к путешествиям по миру со страстью делиться ИТ-историями с сообществом. Блог Дэвида находится по адресу david-obrien.net, он также публикует свои онлайн-тренинги по Pluralsight.

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

В 3 часа ночи, в воскресенье, во время сна вас внезапно будит сигнал текстового сообщения: “сверхкритическое приложение снова не отвечает”. Что же происходит? Где и в чем причина «тормозов»? Из этого доклада вы узнаете про сервисы, которые Microsoft Azure предлагает клиентам для сбора логов и, в частности, метрик ваших облачных рабочих нагрузок. Дэвид расскажет, какие метрики должны вас интересовать при работе на облачной платформе и как до них добраться. Вы узнаете об инструментах с открытым исходным кодом и построении панелей мониторинга и в результате приобретете достаточно знаний для создания своих собственных панелей.

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

Добрый день, сегодня мы будем говорить о метриках. Меня зовут Дэвид О’Брайен, я сооснователь и владелец небольшой консалтинговой австралийской компании Xirus. Еще раз благодарю за то, что пришли сюда провести со мной свое время. Итак, зачем мы здесь? Чтобы поговорить о метриках, вернее, я расскажу вам о них, и прежде чем делать какие-то вещи, начнем с теории.

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

Прежде чем начать, я попрошу поднять руки тех, кто использует Microsoft Azure. А кто работает с AWS? Я вижу, немногие. А с Google? ALI Cloud? Один человек! Отлично. Итак, что такое метрики? Официальное определение Национального института стандартов и технологий США выглядит так: «Метрика – это стандарт измерения, который описывает условия и правила выполнения измерения какого-либо свойства и служит для понимания результатов измерения». Что это значит?

Рассмотрим для примера метрику для изменения свободного пространства диска виртуальной машины. Например, нам выдается число 90, и это число означает проценты, то есть объем свободного пространства диска составляет 90%. Отмечу, что не слишком интересно читать описание определения метрик, которое занимает 40 страниц в формате pdf.

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

Во-первых, измеряем значение чего-либо, чтобы затем использовать результат измерения.

Например, мы узнали объем свободного пространства диска и теперь можем его использовать, использовать эту память и т.д. После того, как мы получили результат метрики, мы должны его интерпретировать. Например, метрика выдала результат 90. Мы должны знать, что означает это число: объем свободного пространства или объем занятого пространства диска в процентах или гигабайтах, латентность сети, равную 90 мс, и так далее, то есть нам нужно истолковать смысл значения метрики. Для того, чтобы метрики вообще имели смысл, после интерпретации одного значения метрики нам нужно обеспечить сбор множества значений. Это очень важно, поскольку многие люди не осознают необходимости сбора метрик. Microsoft сделала очень легким процесс получения метрик, но вы сами должны обеспечить их сбор. Эти метрики хранятся всего 41 день и на 42-й день исчезают. Поэтому в зависимости от свойств вашего внешнего или внутреннего оборудования вы должны озаботиться тем, как сохранять метрики более чем 41 день — в виде логов, журналов и т.д. Таким образом, после сбора вы должны разместить их в каком-то месте, позволяющем при необходимости поднять всю статистику изменения результатов метрик. Поместив их туда, вы сможете начать с ними эффективную работу.

Только после того, как вы получите значения метрик, интерпретируете их и соберете, вы сможете создать SLA – соглашение об уровне предоставления услуги. Этот SLA может не иметь особого значения для ваших клиентов, он более важен для ваших коллег, менеджеров, тех, кто обеспечивает работу системы и озабочен ее функциональностью. Метрика может измерять количество тикетов — например, вы получаете 5 тикетов в день, и в данном случае она показывает скорость реагирования на запросы пользователей и быстроту устранения неполадок. Метрика не должна просто сообщать, что ваш сайт загружается за 20 мс или скорость ответа составляет 20 мс, метрика – это больше, чем просто один технический показатель.

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

Как только мы получили метрику, то можем на 99% гарантировать рабочее состояние системы, потому что это не просто взгляд на лог-файл, в котором написано, что система работает. Гарантия 99% работоспособности означает, что, например, в 99% случаев API отвечает с нормальной скоростью 30 мс. Это именно то, что интересует ваших пользователей, ваших коллег и менеджеров. Многие наши клиенты отслеживают логи веб-серверов, при этом они не замечают в них никаких ошибок и думают, что все в порядке. Например, они видят показатель скорости сети 200 мб/с и думают: «ок, все отлично!». Но чтобы добиться этих 200, пользователям требуется скорость ответа в 30 миллисекунд, и это именно тот показатель, который не измеряется и не собирается в лог-файлах. При этом пользователи удивляются, что сайт очень медленно загружается, потому что, не располагая нужной метрикой, не знают причины подобного поведения.

Но поскольку у нас есть SLA, гарантирующий 100% работоспособности, клиенты начинают высказывать возмущение, так как в действительности сайтом очень трудно пользоваться. Поэтому для создания объективного SLA необходимо видеть полную картину процесса, создаваемую собираемыми метриками. Это является предметом моего постоянного спора с некоторыми провайдерами, которые при создании SLA не представляют, что означает термин «uptime», и в большинстве случаев не объясняют своим клиентам, как работает их API.

Если вы создали сервис, например, API для третьей особы, вы должны понимать, что означает полученная метрика 39,5 – ответ, удачный ответ, ответ на скорости 20 мс или на скорости 5 мс. Именно вы должны адаптировать их SLA к своему собственному SLA, к своим собственным метрикам.

Выяснив все это, можно приступить к созданию шикарной панели мониторинга. Скажите, кто-нибудь уже пользовался приложением для интерактивной визуализации Grafana? Отлично! Я большой фанат этого open source, потому что эта штука бесплатная и ею легко пользоваться.

Если вы еще не пользовались Grafana, я расскажу, как с ней работать. Кто родился в 80-90-х, наверное, помнит заботливых медвежат CareBears? Не знаю, насколько эти мишки были популярны в России, но в отношении метрик мы должны выступать такими же «заботливыми мишками». Как я сказал, вам нужна развернутая картина работы всей системы, и она не должна касаться только лишь вашего API, вашего веб-сайта или службы, запущенной на виртуальной машине.

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

Итак, давайте приступим к нашему облачному сервису Azure. В нем очень легко находить и организовывать сбор метрик, потому что здесь имеется Azure Monitor. Этот монитор централизует управление конфигурацией вашей системы. Каждый из элементов Azure, который вы хотите применить в своей системе, имеет множество включенных по умолчанию метрик. Это бесплатное приложение, которое работает прямо «из коробки» и не требует никаких предварительных настроек, вам не нужно ничего писать и «прикручивать» к своей системе. Мы убедимся в этом, просмотрев следующее демо.

Кроме того, имеется возможность отправлять эти метрики сторонним приложениям, таким как система хранения и анализа логов Splunk, облачное приложение по управлению логами SumoLogic, инструмент для обработки логов ELK, IBM Radar. Правда, тут имеются небольшие различия, которые зависят от используемых вами ресурсов – виртуальной машины, сетевых сервисов, баз данных Azure SQL, то есть использование метрик отличается в зависимости от функций вашей рабочей среды. Не скажу, что эти различия серьезны, но, к сожалению, они все же присутствуют, и это следует учитывать. Включение и пересылка метрик возможна несколькими способами: через Portal, CLI/Power Shell или с помощью шаблонов ARM.

Прежде чем приступить к первой демонстрации, я отвечу на имеющиеся у вас вопросы. Если вопросов нет, приступим. На экране показано, как выглядит страница Azure Monitor. Кто-нибудь из вас может сказать, что этот монитор не работает?

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

Таблица метрик – это вкладка по пути Home\Monitor\Metrics, на которую можно зайти, чтобы увидеть все имеющиеся метрики и выбрать необходимые. Но если вам нужно включить сбор метрик, нужно использовать путь каталога Home\Monitor\Diagnostic settings и проверить чекбоксы метрик Enabled/Disabled. По умолчанию практически все метрики находится во включенном состоянии, но если нужно включить что-то дополнительное, то вам понадобится изменить статус диагностики с Disabled на Enabled.

Для этого нужно кликнуть по строке выбранной метрики и на открывшейся вкладке включить режим диагностики. Если вы собираетесь анализировать выбранную метрику, то после нажатия на ссылку Turn on diagnostic нужно отметить в появившемся окне чекбокс Send to Log Analytics.

Log Analytics немного похож на Splunk, но стоит дешевле. Этот сервис позволяет собирать все ваши метрики, логи и все, что вам понадобится, и размещать их в рабочем пространстве Log Analytics. Сервис использует специальный язык обработки запросов KQL – Kusto Quarry Language, его работу мы рассмотрим в следующем демо. Пока что отмечу, что с его помощью вы можете формировать запросы относительно метрик, логов, терминов, тенденций, шаблонов и т.д. и создавать панели мониторинга.

Итак, мы отмечаем чекбокс Send to Log Analytics и чекбоксы панели LOG: DataPlaneRequests, MongoRequests и QueryRuntimeStatistics, а ниже на панели METRIC – чекбокс Requests. Затем присваиваем какое-либо имя и сохраняем настройки. В командной строке это представляет собой две строчки кода. Кстати, оболочка Azure Cloud в этом смысле напоминает Google, который тоже позволяет использовать командную строку в вашем веб-браузере. AWS не имеет ничего подобного, так что Azure в этом смысле намного удобнее.

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

Далее я использую Bash, встроенный в Azure Cloud Shell. Очень полезной штукой является встроенный в браузер IDE, облегченная версия VS Code. Далее я могу зайти в свой шаблон метрики ошибок, изменить его и настроить под свои потребности.

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

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

Для решения этой задачи Microsoft предлагает инструмент Power BI – комплексное ПО для бизнес анализа, включающее в себя визуализацию самых различных данных. Это довольно дорогой продукт, стоимость которого зависит от необходимого вам набора функций. По умолчанию он предлагает вам 48 видов обрабатываемых данных и связан с хранилищами данных SQL Azure, Azure Data Lake Storage, cлужбами машинного обучения Azure и Azure Databricks. Используя масштабируемость, вы каждые 30 минут можете получать новые данные. Этого может быть достаточно для ваших потребностей или не достаточно, если вам нужна визуализация мониторинга в режиме реального времени. В этом случае рекомендуется использовать такие приложения, как упомянутую мною Grafanа. Кроме того, в документации Microsoft описана возможность отправки метрик, логов и таблиц событий с помощью SIEM — инструментов в системы визуализации Splunk, SumoLogic, ELK и IBM radar.

23:40 мин

Продолжение будет совсем скоро…


Немного рекламы :)


Спасибо, что остаётесь с нами. Вам нравятся наши статьи? Хотите видеть больше интересных материалов? Поддержите нас, оформив заказ или порекомендовав знакомым, облачные VPS для разработчиков от $4.99, уникальный аналог entry-level серверов, который был придуман нами для Вас:Вся правда о VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps от $19 или как правильно делить сервер? (доступны варианты с RAID1 и RAID10, до 24 ядер и до 40GB DDR4).

Dell R730xd в 2 раза дешевле в дата-центре Equinix Tier IV в Амстердаме? Только у нас 2 х Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 ТВ от $199 в Нидерландах! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — от $99! Читайте о том Как построить инфраструктуру корп. класса c применением серверов Dell R730xd Е5-2650 v4 стоимостью 9000 евро за копейки?

Let's block ads! (Why?)

[Перевод - recovery mode ] Программирование GPU на Java

Получение доступ к GPU из Java раскрывает огромную мощь. Здесь рассказывается как GPU работает и как получить доступ из Java.

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

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

Немного бекграунда


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

Суть 2D/3D обработки состоит в основном в манипулировании матрицами, это может быть управляемо с помощью распределенного подхода. Что будет эффективным подходом для обработки изображений? Ответим на это, давайте сравним стандартную архитектуру CPU (показанную на рис. 1.) и GPU.

image
Рис. 1. Блоки архитектуры CPU

В CPU, фактические элементы обработки – регистры, арифметико-логический блок(ALU) и контексты выполнения – лишь маленькие части всей системы. Для ускорения нерегулярных расчетов, поступающих в непредсказуемом порядке, здесь присутствует большой, быстрый, и дорогой кеш; различные виды сборщиков; и предсказатели ветвлений.

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

image
Рис. 2. Блоковая архитектура для простого ядра GPU

Потому что такие процессоры дешевле и обрабатываемые данные в них в параллельных кусках, это просто заставить многие из них работать параллельно. Это разработано, ссылаясь на множественные инструкции, множественные данные или MIMD (произносится «mim-dee»).

Второй подход базируется на факте, что часто одиночная инструкция, применяется к множественным частям данных. Это известно как единичная инструкция, множественные данные или SIMD (произносится «sim-dee»). В этом дизайне, единственный GPU содержит множественные ALU и контексты выполнения, маленькие области, переданные на разделяемые данные контекста, как показано на рис. 3.

image
Рис. 3. Сравнение MIMD-стиля архитектуры GPU блоков (с лево), с SIMD дизайном (с право)

Смешивая SIMD и MIMD обработку обеспечивается максимальная пропускная способность, которую я обойду стороной. В этом дизайне, вы имеете множественные SIMD процессоры, работающие параллельно, как на рис. 4.

image
Рис. 4. Работающие множественные SIMD процессоры в параллели; здесь 16 ядер с 128 ALU

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

Запуская программы на GPU


Большинство ранних графических эффектов в играх было действительно жестко запрограммированных маленьких программ выполняющихся на GPU и применялись к потокам данных из CPU.

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

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

Каждый продавец GPU имел свой собственный язык программирования и инфраструктуру для создания shaders для их архитектуры. На этом подходе было создано множество платформ.

Главные из них:

  • DirectCompute: частные shader язык/API от Microsoft, которые являются частью Direct3D, начиная с DirectX 10.
  • AMD FireStream: частные технологии ATI/Radeon, которые устарели по мнению AMD.
  • OpenACC: мультивендор-консорциум, решение для параллельных вычислений
  • С++ AMP: частная библиотека Microsoft для параллельных данных на C++
  • CUDA: частная платформа Nvidia, которая использует подмножество языка С
  • OpenСL: общий стандарт, оригинально разработанный Apple, но сейчас не управляем консорциумом Khronos Group

Большую часть времени, работа с GPU это низко уровневое программирование. Для того чтобы сделать это немного более понятным для разработчиков, для кодирования, были обеспеченны несколько абстракций. Самая известная это DirectX, от Microsoft, и OpenGL, от Khronos Group. Эти API для написания высокоуровневого кода, который затем может быть затем упрощен для GPU, более семантически, для программиста.

Насколько я знаю, не существует инфраструктуры Java для DirectX, но есть хорошее решение для OpenGL. JSR 231 запустилось в 2002 и адресовано GPU программистам, но оно было заброшено в 2008 и поддерживает только OpenGL 2.0.

Поддержка OpenGL продолжается в независимом проекте JOCL, (который также поддерживает OpenCL), и он доступен аудитории. Таким образом, знаменитая игра Minecraft была написана с использованием JOCL.

Приход GPGPU


До сих пор, Java и GPU не имели точек соприкосновения, хотя они должны быть. Java часто используется на предприятиях, в науке о данных, и в финансовом секторе, где много вычислений и где нужно много вычислительных сил. Это о том, как идея general-purpose GPU (GPGPU). Идея использование GPU по этому пути начали, когда производители видео адаптеров начали давать доступ к программному буферу кадров, давая разработчикам считывать содержимое. Некоторые хакеры определили, что они могут использовать всю силу GPU для универсальных вычислений.
Рецепт был таким:
  1. Закодировать данные как растровый массив.
  2. Написать shaders для обработки их.
  3. Отправьте их обоих на видеокарту.
  4. Получить результат из буфера кадра
  5. Декодировать данные из растрового массива.

Это очень простое объяснение. Я не уверен будет ли это работать в продакшене, но это действительно работает.

Затем начались множественные исследования от Стенфордского Института для упрощения использования GPU. В 2005 они сделали BrookGPU, которая была маленькой экосистемой, которая включала язык программирования, компилятор, и среду запуска.

BrookGPU компилировала программы написанные на языке программирования потоков Brook, который был вариантом ANSI C. Он может быть нацелен на OpenGL v1.3 +, DirectX v9 + или AMD Close to Metal для вычислительной серверной части, и он запускается на Microsoft Windows и Linux. Для отладки, BrookGPU может также симулировать виртуальную графическую карту на CPU.
Однако это не взлетело, из-за оборудования, доступного в то время. В мире GPGPU, тебе надо копировать данные на устройство (в это контексте, устройство ссылается на GPU и устройстве, на котором он расположен), ждать GPU для вычисления данных, и затем копировать данные обратно в управляющую программу. Это создает множество задержек. И в середине 2000-х, когда проект был в активной разработке, эти задержки также исключали интенсивное использование GPU для базовых вычислений.

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

Теперь, когда я вам все рассказал, давайте проверим две самые удачные технологии для вычисления на GPU – OpenCL и CUDA – смотри также как Java работает с ними.

OpenCL и Java


Как и другие инфраструктурные пакеты, OpenCL обеспечивает базовую имплементацию на C. Это технически доступно с помощью Java Native Interface (JNI) или Java Native Access (JNA), но такой подход будет слишком тяжёлым для большинства разработчиков.

К счастью, эта работа была уже сделана несколькими библиотеками: JOCL, JogAmp, и JavaCL. К несчастью, JavaCL стал мертвым проектом. Но проект JOCL жив и очень даже адаптирован. Я буду использовать его для следующих примеров.

Но сначала я должен объяснить, что из себя представляет OpenCL. Я упоминал ранее, что OpenCL обеспечивает очень базовую модель, подходящую для программирования всех видов устройств – не только GPU и CPU, но даже DSP процессоры и FPGA.

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

image
Рис. 5. Сложение элементов двух массивов и сохранение суммы в результирующий массив

Как вы можете видеть, операция очень согласованная и тем не менее распределяемая. Вы можете запихнуть каждую операцию сложения в разные GPU ядра. Это значит, что, если вы имеете 2048 ядер, как на Nvidia 1080, вы можете выполнить 2048 операций сложения одновременно. Это означает что здесь потенциальные терафлопсы компьютерной мощи ждут вас. Этот код для массива из 10 миллионов чисел взят с сайта JOCL:

public class ArrayGPU {
    /**
     * The source code of the OpenCL program 
     */
    private static String programSource =
        "__kernel void "+
        "sampleKernel(__global const float *a,"+
        "             __global const float *b,"+
        "             __global float *c)"+
        "{"+
        "    int gid = get_global_id(0);"+
        "    c[gid] = a[gid] + b[gid];"+
        "}";
    
    public static void main(String args[])
    {
        int n = 10_000_000;
        float srcArrayA[] = new float[n];
        float srcArrayB[] = new float[n];
        float dstArray[] = new float[n];
        for (int i=0; i<n; i++)
        {
            srcArrayA[i] = i;
            srcArrayB[i] = i;
        }
        Pointer srcA = Pointer.to(srcArrayA);
        Pointer srcB = Pointer.to(srcArrayB);
        Pointer dst = Pointer.to(dstArray);


        // The platform, device type and device number
        // that will be used
        final int platformIndex = 0;
        final long deviceType = CL.CL_DEVICE_TYPE_ALL;
        final int deviceIndex = 0;

        // Enable exceptions and subsequently omit error checks in this sample
        CL.setExceptionsEnabled(true);

        // Obtain the number of platforms
        int numPlatformsArray[] = new int[1];
        CL.clGetPlatformIDs(0, null, numPlatformsArray);
        int numPlatforms = numPlatformsArray[0];

        // Obtain a platform ID
        cl_platform_id platforms[] = new cl_platform_id[numPlatforms];
        CL.clGetPlatformIDs(platforms.length, platforms, null);
        cl_platform_id platform = platforms[platformIndex];

        // Initialize the context properties
        cl_context_properties contextProperties = new cl_context_properties();
        contextProperties.addProperty(CL.CL_CONTEXT_PLATFORM, platform);
        
        // Obtain the number of devices for the platform
        int numDevicesArray[] = new int[1];
        CL.clGetDeviceIDs(platform, deviceType, 0, null, numDevicesArray);
        int numDevices = numDevicesArray[0];
        
        // Obtain a device ID 
        cl_device_id devices[] = new cl_device_id[numDevices];
        CL.clGetDeviceIDs(platform, deviceType, numDevices, devices, null);
        cl_device_id device = devices[deviceIndex];

        // Create a context for the selected device
        cl_context context = CL.clCreateContext(
            contextProperties, 1, new cl_device_id[]{device}, 
            null, null, null);
        
        // Create a command-queue for the selected device
        cl_command_queue commandQueue = 
            CL.clCreateCommandQueue(context, device, 0, null);

        // Allocate the memory objects for the input and output data
        cl_mem memObjects[] = new cl_mem[3];
        memObjects[0] = CL.clCreateBuffer(context,
            CL.CL_MEM_READ_ONLY | CL.CL_MEM_COPY_HOST_PTR,
            Sizeof.cl_float * n, srcA, null);
        memObjects[1] = CL.clCreateBuffer(context,
            CL.CL_MEM_READ_ONLY | CL.CL_MEM_COPY_HOST_PTR,
            Sizeof.cl_float * n, srcB, null);
        memObjects[2] = CL.clCreateBuffer(context,
            CL.CL_MEM_READ_WRITE,
            Sizeof.cl_float * n, null, null);
        
        // Create the program from the source code
        cl_program program = CL.clCreateProgramWithSource(context,
            1, new String[]{ programSource }, null, null);
        
        // Build the program
        CL.clBuildProgram(program, 0, null, null, null, null);
        
        // Create the kernel
        cl_kernel kernel = CL.clCreateKernel(program, "sampleKernel", null);
        
        // Set the arguments for the kernel
        CL.clSetKernelArg(kernel, 0,
            Sizeof.cl_mem, Pointer.to(memObjects[0]));
        CL.clSetKernelArg(kernel, 1,
            Sizeof.cl_mem, Pointer.to(memObjects[1]));
        CL.clSetKernelArg(kernel, 2,
            Sizeof.cl_mem, Pointer.to(memObjects[2]));
        
        // Set the work-item dimensions
        long global_work_size[] = new long[]{n};
        long local_work_size[] = new long[]{1};
        
        // Execute the kernel
        CL.clEnqueueNDRangeKernel(commandQueue, kernel, 1, null,
            global_work_size, local_work_size, 0, null, null);
        
        // Read the output data
        CL.clEnqueueReadBuffer(commandQueue, memObjects[2], CL.CL_TRUE, 0,
            n * Sizeof.cl_float, dst, 0, null, null);
        
        // Release kernel, program, and memory objects
        CL.clReleaseMemObject(memObjects[0]);
        CL.clReleaseMemObject(memObjects[1]);
        CL.clReleaseMemObject(memObjects[2]);
        CL.clReleaseKernel(kernel);
        CL.clReleaseProgram(program);
        CL.clReleaseCommandQueue(commandQueue);
        CL.clReleaseContext(context);

    }

    private static String getString(cl_device_id device, int paramName) {
        // Obtain the length of the string that will be queried
        long size[] = new long[1];
        CL.clGetDeviceInfo(device, paramName, 0, null, size);

        // Create a buffer of the appropriate size and fill it with the info
        byte buffer[] = new byte[(int)size[0]];
        CL.clGetDeviceInfo(device, paramName, buffer.length, Pointer.to(buffer), null);

        // Create a string from the buffer (excluding the trailing \0 byte)
        return new String(buffer, 0, buffer.length-1);
    }
}

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

Код будет документированным, но давайте сделаем маленькое прохождение. Как вы можете видеть, код очень похож на код на С. Это нормально, потому что JOCL – это всего лишь OpenCL. В начале, здесь некоторый код в строке, и этот код самая важная часть: Она компилируется с помощью OpenCL и затем отправляется на видео карту, где и выполняется. Этот код называется Kernel. Не путайте этот термин с OC Kernel; это код устройства. Этот код написан на подмножестве C.

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

Подведем итог: тут «хостовый код», что обычно является языковой привязкой (в нашем случае на Java), и «код устройства». Вы всегда выделяете, что будет работать на хосте и что должно работать на устройстве, потому что хост контролирует устройство.

Предшествующий код должен показать GPU эквивалент «Hello World!». Как вы видите, большая его часть огромна.

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

__kernel void MatrixMul_kernel_basic(int dim,
                  __global float *A,
                  __global float *B,
                  __global float *C){

    int iCol = get_global_id(0);
    int iRow = get_global_id(1);
    float result = 0.0;
    for(int i=0; i< dim; ++i)
    {
          result +=
          A[iRow*dim + i]*B[i*dim + iCol];
    }
    C[iRow*dim + iCol] = result;
}

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

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

#define VECTOR_SIZE 4    
__kernel void MatrixMul_kernel_basic_vector4(
    size_t dim, // dimension is in single floats
    const float4 *A,
    const float4 *B,
    float4 *C)
{
    size_t globalIdx = get_global_id(0);
    size_t globalIdy = get_global_id(1);
    float4 resultVec = (float4){ 0, 0, 0, 0 };
    size_t dimVec = dim / 4;
    for(size_t i = 0; i < dimVec; ++i) {
        float4 Avector = A[dimVec * globalIdy + i];
        float4 Bvector[4];
        Bvector[0] = B[dimVec * (i * 4 + 0) + globalIdx];
        Bvector[1] = B[dimVec * (i * 4 + 1) + globalIdx];
        Bvector[2] = B[dimVec * (i * 4 + 2) + globalIdx];
        Bvector[3] = B[dimVec * (i * 4 + 3) + globalIdx];
        resultVec += Avector[0] * Bvector[0];
        resultVec += Avector[1] * Bvector[1];
        resultVec += Avector[2] * Bvector[2];
        resultVec += Avector[3] * Bvector[3];
    }

    C[dimVec * globalIdy + globalIdx] = resultVec;
}

С этим кодом вы можете удвоить производительность.

Круто. Вы только что открыли GPU для Java мира! Но будучи Java разработчиком, неужели вы хотите делать всю эту грязную работу, с С кодом, и работы с столь низкоуровневыми деталями? Я не хочу. Но сейчас, когда вы имеете некоторые знания о том, как GPU используется, давайте посмотрим на другое решение отличающееся от JOCL кода, который я только что представил.

CUDA и Java


CUDA это решение Nvidia на этот вопрос программирования. CUDA обеспечивает много больше готовых к использованию библиотек для стандарта GPU операций, такие как матрицы, гистограммы, и даже глубокие нейронные сети. Появился список библиотек уже имеющих кучу готовых решений. Это все из проекта JCuda:
  • JCublas: все для матриц
  • JCufft: быстрое преобразование Фурье
  • JCurand: все для случайных чисел
  • JCusparse: редкие матрицы
  • JCusolver: факторизация чисел
  • JNvgraph: все для графов
  • JCudpp: CUDA библиотека примитивных параллельных данных и некоторые алгоритмы сортировки
  • JNpp: обработка картинок на GPU
  • JCudnn: библиотека глубоких нейронных сетей

Я рассматриваю использование JCurand, которая генерирует случайные числа. Вы можете использовать это из Java кода без другого специального языка Kernel. Для примера:
...
int n = 100;
curandGenerator generator = new curandGenerator();
float hostData[] = new float[n];
Pointer deviceData = new Pointer();
cudaMalloc(deviceData, n * Sizeof.FLOAT);
curandCreateGenerator(generator, CURAND_RNG_PSEUDO_DEFAULT); 
curandSetPseudoRandomGeneratorSeed(generator, 1234);
curandGenerateUniform(generator, deviceData, n);
cudaMemcpy(Pointer.to(hostData), deviceData, 
        n * Sizeof.FLOAT, cudaMemcpyDeviceToHost);
System.out.println(Arrays.toString(hostData));
curandDestroyGenerator(generator);
cudaFree(deviceData);
...

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

В JCuda вы можете также написать общий CUDA код и вызвать это из Java с помощью вызова некоторого JAR файла в вашем classpath. Смотрите документацию JCuda для больших примеров.

Оставаться выше низкоуровневого кода


Это все выглядит замечательным, но тут слишком много кода, слишком много установки, слишком много различных языков для запуска всего этого. Есть ли способ использовать GPU хотя бы частично?

Что если вы не хотите задумываться о всей этой OpenCL, CUDA, и других ненужных вещей? Что если вы хотите только программировать на Java и не думать обо всем не очевидном? Aparapi проект может помочь. Aparapi базируется на «параллельном API». Я думаю об этом как об какой-то части Hibernate для GPU программирования, которая использует OpenCL под капотом. Давайте взглянем на пример сложения векторов.

public static void main(String[] _args) {
    final int size = 512;
    final float[] a = new float[size];
    final float[] b = new float[size];

    /* fill the arrays with random values */
    for (int i = 0; i < size; i++){
        a[i] = (float) (Math.random() * 100);
        b[i] = (float) (Math.random() * 100);
    }
    final float[] sum = new float[size];

    Kernel kernel = new Kernel(){
        @Override public void run() {
I           int gid = getGlobalId();
            sum[gid] = a[gid] + b[gid];
        }
    };

    kernel.execute(Range.create(size));
    for(int i = 0; i < size; i++) {
        System.out.printf("%6.2f + %6.2f = %8.2f\n", a[i], b[i], sum[i])
    }
    kernel.dispose();
}

Здесь чистый Java код (взятый из документации Aparapi), также здесь и там, вы можете видеть некий термин Kernel и getGlobalId. Вам все еще нужно понимать, как программировать GPU, но вы можете использовать подход GPGPU в более близкой к Java манере. Более того, Aparapi обеспечивает простой путь к использованию OpenGL контекста к слою OpenCL – тем самым позволяя данным полностью оставаться на видеокарте — и тем самым избежать проблем с задержкой памяти.

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

В добавок, есть некоторый проект под названием TornadoVM — он автоматически переносит подходящие вычисления из CPU в GPU, таким образом, обеспечивая массовую оптимизацию из коробки.

Выводы


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

Ссылка на источник

Let's block ads! (Why?)

Raspberry Pi выпустила модуль камеры с поддержкой сменных объективов

image

Британская Raspberry Pi представила усовершенствованный модуль камеры «High Quality Camera». Для него подготовили несколько совместимых объективов.

Стоимость модуля составляет $50. Он включает 12,3-мегапиксельный сенсор Sony IMX477 с задней подсветкой. Модуль поддерживает установку объективов с креплениями стандартов C- и CS-mount, используемых на промышленных и 16-мм пленочных камерах. Пока известно, что он будет поддерживать 6-миллиметровый объектив с CS-креплением стоимостью $15 и более качественный 16-миллиметровый объектив с С-креплением стоимостью $50 от реселлеров. 16-миллиметровое крепление можно адаптировать к современному объективу APS-C или даже к полнокадровому объективу.


Новый модуль крупнее предшественника от 2016 года, Camera Module V2, при этом он оснащен кольцом регулировки зума и креплением для штатива.

Как отметили в Raspberry Pi, с новым модулем качество съемки станет выше, в том числе в ночное время. Компания утверждает, что оно будет лучше, чем у камер смартфонов.

В комплект для Raspberry Pi, помимо модуля, входят пылезащитный колпачок, адаптер для креплений C-CS, штатив и кабель для подключения к мини-компьютеру. Сам модуль обладает совместимостью с фирменными одноплатными компьютерами начиная Raspberry Pi 1 Model B.

image

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

Компания также выпустила книгу «Official Raspberry Pi Camera Guide» с инструкций о том, как из компонентов Raspberry Pi собрать видеокамеру с поддержкой режима замедленной съёмки, а также скрытую камеру для слежения за животными и «умный» дверной звонок с дисплеем.

В феврале цена на двухгигабайтную модель Raspberry Pi 4 опустилась до $35 в честь восьмилетия с момента выхода первой версии и из-за общего снижения цен на оперативную память. Разработчики тогда заявили, что с момента запуска Model B производительность процессора компьютера возросла в 40 раз, объём ОЗУ — в 8 раз, пропускная способность — в 10 раз. Raspberry Pi 4 начали продавать в июне прошлого года.

См. также:

Let's block ads! (Why?)

[Из песочницы] Неироничная ненависть к JavaScript

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

Ответьте на вопрос, если вы разрабатываете на JS, то что именно? Может быть вы работаете на FrontEnd-е, может вы разрабатываете небольшое BackEnd приложение на nodejs, а может вы являетесь разработчиком в банке где все ПО написано на нем? Если третий случай это про вас, то скорее всего вы где-то ошиблись.

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

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

Первым делом хотелось бы привести в пример следующий отрывок с кодом:

const x={
  i: 1,
  toString: function(){
    return this.i++;
  }
}

if(x==1 && x==2 && x==3){
  document.write("This will be printed!")
}

Действительно, условие будет истинным. В таких случаях у меня возникает два вопроса — «Зачем?!» и «Как так получилось?». Дать ответ на первый вопрос я к сожалению не могу. Но вот попытаться ответить на второй вполне. Как известно JavaScript — язык с динамической типизацией. В нем есть два способа сравнения — строгое (===) и с преобразованием типов (==). Если бы в коде было использовано строгое сравнение, то никаких проблем бы не возникло. Но во втором случае интерпретатор пытается привести оба операнда к одному типу (строке) и сравнить их значения. Но почему-то истинность условия `x == 1 && x == 2 && x == 3` кому-то кажется смешной, несмотря на то, что нечто подобное можно реализовать и в других языках:

public class JavaScript {
        public static void main(String[] args) {
                AnonymousObject x = new AnonymousObject(1);

                if (x.equals(new AnonymousObject(1)) && x.equals(new AnonymousObject(2)) && x.equals(new AnonymousObject(3))) {
                        System.out.println("JavaScript == Java // true");
                }
        }
}

class AnonymousObject {
        public int i;

        public AnonymousObject(int i) {
                this.i = i;
        }

        public boolean equals(AnonymousObject that) {
                return this.i++ == that.i;
        }
}

Кто-то скажет что тут использована функция .equals(), а там .toString(), что они предназначены для разных целей, что имплементация функции .toString() не должна влиять на сравнение, да и вообще это другое. Но если использовать строгие сравнения в JS то проблемы не будет, а вот в Java данное условие всегда будет истинно.

Ещё есть претензия к функции .sort(), мол она сортирует лексикографически:

[-2, -7, 0.0000001, 0.0000000006, 6, 10].sort()
// [-2, -7, 10, 1e-7, 6, 6e-10]

Но передадим мы функцию для сравнения и все внезапно работает как надо:

[-2, -7, 0.0000001, 0.0000000006, 6, 10].sort((a, b) => a - b)
// [-7, -2, 6e-10, 1e-7, 6, 10]

Кто-то снова возразит, сказав что в <название ЯП> все работает как надо и что вы, когда переходите с одного языка на другой хотите чтобы все было как раньше. Но знаете, я начал изучение программирования с Паскаля, там оператором сравнения был один знак равно. Представляете как мне не хотелось переходить на современные языки из-за того что в них два, а то и три знака?!

Под конец хотелось бы обсудить многопоточность в JS. Причина, по которой в JavaScript-е нет нормальной реализации многопоточности не в том, что язык какой-то не такой и он просто не способен на что-то такое, а в сфере его применения — web разработка. Ведь язык программирования это всего лишь спецификация, а её реализация лежит на компиляторе / трансляторе / интерпретаторе. В веб-разработке вам в принципе не должна понадобиться возможность исполнять ваш код в несколько потоков. Если вам часто приходится прибегать к ней, то скорее всего вы делаете что-то не так. Если вам нужна многопоточность на nodejs то как и написано в исходной статье для этого есть разные библиотеки: comlink, указанная там или paralleljs, которая на мой взгляд удобнее в использовании. А если вам не устраивает дополнительная зависимость, то скорее всего вам нужен другой ЯП. Получить все и сразу попросту не выйдет.

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


Есть всего два типа языков программирования: те, на которые люди всё время ругаются, и те, которые никто не использует.

Let's block ads! (Why?)

Valve прекращает поддержку SteamVR для macOS

В начале мая 2020 года Valve объявила, что прекращает поддержку своей платформы виртуальной реальности SteamVR для пользователей macOS.

Это произошло спустя три года после того, как Apple анонсировала поддержку устройств и платформ виртуальной реальности для macOS на конференции разработчиков WWDC 2017. Именно тогда VR SDK от Valve, Unity и Unreal Engine начали становиться совместимыми с Mac.
Valve пояснила, что по ее данным в настоящее время около 96 % пользователей платформы Steam используют Windows и Linux, а лишь менее 4 % пользователей работают со Steam из macOS. Также в компании фиксируют рост количества пользователей, так в марте 2020 года у Steam стало 100 млн активных пользователей, что на 10 млн больше, чем было годом раньше. Сейчас около 1.3 % пользователей Steam используют устройства виртуальной реальности и платформу SteamVR. Поэтому разработчики Valve решили сфокусировать свое внимание на Windows и Linux. Вдобавок, оказалось, что Linux предпочитают разработчики, и он также необходим для развертывания виртуальных систем для решения специальных корпоративных или государственных задач.

Тем не менее, пользователи macOS могут работать далее с платформой SteamVR, запуская ее приложения в Windows через Parallels Desktop или VMware Fusion. Также в режиме бета-тестирования пользователи macOS могут использовать устаревшие версии сборок платформы SteamVR, в которых уже не будет новых изменений от Valve.

Ранее в конце марта 2020 года стало известно, что Apple работает над собственными устройствами виртуальной реальности. Ожидается, что в 2021 или 2022 году некоторые разработчики вернутся на эту платформу и будут использовать уже Apple SDK, а в 2023 году Apple может выпустить свой шлем виртуальной реальности. Новые устройства виртуальной реальности уже имеют кодовые имена в компании, например, N301 и N421.

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

Let's block ads! (Why?)

Релиз Windows 10 May 2020 Update откладывается до конца мая из-за необходимости залатать уязвимость нулевого дня

Microsoft откладывает до конца мая 2020 начало развертывания большого майского обновления Windows 10 May 2020 Update (версия 2004) для обычных пользователей. Ранее компания планировала выпустить это обновление 12 мая 2020 года. Теперь этот срок сдвинут по соображениям безопасности еще на две недели из-за необходимости исправить выявленную в последний момент уязвимость нулевого дня в Windows 10.
В апреле 2020 года предварительная версия обновления Windows 10 May 2020 Update стала доступна участникам программы Windows Insider. Однако, перед выдачей сборки Windows 10 версии 2004 OEM-производителям и поставщикам ПК, специалистами Microsoft в системе безопасности Windows 10 была обнаружена еще одна критическая уязвимость. В компании не раскрыли детали по этому инциденту и приступили к выпуску патча, который будет включен в Windows 10 May 2020 Update.

Обновленные даты выпуска Windows 10 May 2020 Update (версии 2004):

  • выпуск сборки для OEM-производителей / производителей ПК: 5 мая 2020 года;
  • доступность обновления для разработчиков: 12 мая 2020 года;
  • доступность обновления для всех пользователей Windows 10: 28 мая 2020 года.

Ранее 14 апреля 2020 года компания Microsoft выпустила в рамках вторника патчей (Patch Tuesday) обновления безопасности для Windows 10 версий 1607, 1909, 1903 и 1809, Windows 8.1 и Windows 7, а также Windows Server 2008/2012/2016 и 2019. Этот апрельский комплект патчей безопасности устранил 113 уязвимостей в одиннадцати программных продуктах компании. Среди них: 15 критических уязвимостей, включая три нулевого дня, 93 серьезные, три со средним уровнем угроз и две уязвимости с низким уровнем влияния на систему. Причем две закрытые уязвимости нулевого дня ранее уже некоторое время эксплуатировались злоумышленниками.

24 марта 2020 года Microsoft сообщила, что в компании решили начиная с мая 2020 года приостановить выпуск всех необязательных обновлений (так называемые обновления C и D), которые не связаны с безопасностью программных продуктов, для всех поддерживаемых в настоящее время клиентских и серверных версий ОС Windows. Таким образом, Microsoft устанавливает максимальный приоритет на безопасности, чтобы в текущей ситуации обеспечить стабильность, надежность и продуктивность всех версий ОС Windows, установленных у пользователей.

Let's block ads! (Why?)

C++ быстрее и безопаснее Rust, Yandex сделала замеры

Недавно я пытался заманить коллегу, сишника из соседнего отдела, на Тёмную сторону Rust. Но мой разговор с коллегой не задался. Потому что, цитата:


В 2019 году я был на конференции C++ CoreHard, слушал доклад Антона antoshkka Полухина о незаменимом C++. По словам Антона, Rust еще молодой, не очень быстрый и вообще не такой безопасный.

Антон Полухин является представителем России в ISO на международных заседаниях рабочей группы по стандартизации C++, автором нескольких принятых предложений к стандарту языка C++. Антон действительно крутой и авторитетный человек в вопросах по C++. Но доклад содержит несколько серьёзных фактических ошибок в отношении Rust. Давайте их разберём.

Речь идет об этом докладе с 13:00 по 22:35.


Для примера сравнения ассемблерного выхлопа Антон взял функцию возведения в квадрат(link:godbolt):

Цитата (13:35):


Получаем одинаковый ассемблерный выхлоп. Отлично! У нас есть базовая линия. Пока что C++ и Rust выдает одно и тоже.

В самом деле, ассемблерный листинг арифметического умножения в обоих случаях выглядит одинаковым, но это только до поры до времени. Дело в том, что с точки зрения семантики языков, код делает разные вещи. Этот код определяет функции возведения числа в квадрат, но в случае Rust область определения [-2147483648, 2147483647], а в случае C++ это [-46340, 46340]. Как такое может быть? Магия?

Магические константы -46340 и 46340 — это максимальные по модулю аргументы, квадрат которых умещается в std::int32_t. Все что выше будет давать неопределенное поведение из-за signed overflow. Если не верите мне, послушайте PVS-Studio. И если вы достаточно удачливы, чтобы работать в командах, которые настроили себе CI с проверкой кода на определенное поведение, вы получите такое сообщение:

runtime error: signed integer overflow: 46341 * 46341 cannot be represented in type 'int'
runtime error: signed integer overflow: -46341 * -46341 cannot be represented in type 'int'

В Rust такая ситуация с неопределенным поведением в арифметике невозможна в принципе.

Давайте послушаем, что об этом думает Антон (13:58):


Неопределенное поведение заключается в том что у нас тут знаковое число, и компилятор C++ считает что переполнения знаковых чисел не должно происходить в программе. Это неопределенное поведение. За счет этого компилятор C++ делает множество хитрых оптимизаций. В компиляторе Rust'а это задокументированное поведение, но от этого вам легче не станет. Ассемблерный код у вас получается тот же самый. В Rust'е это задокументированное поведение, и при умножении двух больших положительных чисел положительных вы получите отрицательное число, что скорее всего не то, что вы ожидали. При этом за счет того что они документируют это поведение Rust теряет возможность делать многие оптимизации. Они у них прям где-то на сайте написаны.

Я бы почитал, какие оптимизации не умеет Rust, особенно с учётом того, что в основе Rust лежит LLVM — тот же самый бэкенд, что и у Clang. Соответственно, Rust «бесплатно» получил и разделяет с C++ большую часть независящих от языка трансформаций кода и оптимизаций. И хотя в представленном примере мы и получили одинаковый ассемблер, на самом деле, это случайность. Хитрые оптимизации и наличие неопределённого поведения при переполнении знакового в языке C++ могут приводить к веселью и порой порождают такие статьи. Рассмотрим эту статью подробнее.

Дан код функции, вычисляющей полиномиальный хеш от строки с переполнением int'a:

unsigned MAX_INT = 2147483647;

int hash_code(std::string x) {
    int h = 13;
    for (unsigned i = 0; i < 3; i++) {
        h += h * 27752 + x[i];
    }
    if (h < 0) h += MAX_INT;
    return h;
}

На некоторых строках, в частности, на строке «bye», и только на сервере (что интересно, на своем компьютере все было в порядке) функция возвращала отрицательное число. Но как же так, ведь в случае, если число отрицательное, к нему прибавится MAX_INT и оно должно стать положительным.

Как подсказывает PVS-Studio, неопределенное поведение действительно не определено. Если посчитать 27752 в 3 степени, можно понять, почему хэш от двух букв считается нормально, а от трех уже с какими-то странными результатами.

Аналогичный код на Rust будет вести себя корректно(link:playground):

fn hash_code(x: String) -> i32 {
    let mut h = 13i32;
    for i in 0..3 {
        h += h * 27752 + x.as_bytes()[i] as i32;
    }
    if h < 0 {
        h += i32::max_value();
    }
    return h;
}

fn main() {
    let h = hash_code("bye".to_string());
    println!("hash: {}", h);
}

Выполнение этого кода отличается в Debug и Release по понятным причинам, а для унификации поведения можно воспользоваться семейством функций: wrapping*, saturating*, overflowing* и checked*.

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

Вычисление квадрата числа — это отличный пример того, как можно выстрелить себе в ногу с помощью C++ в трех строчках кода. Зато быстро и с оптимизациями. Если от обращения к неинициализированной памяти ещё можно откреститься вдумчивым взглядом, то проблема с арифметикой в том, что беда может прийти совершенно внезапно и на «голом» арифметическом коде, где ломаться на первый взгляд нечему.

В качестве примера приводится следующий код(link:godbolt):

Антон (15:15):


Компилятор Rust'а и компилятор C++ скомпилировали оба этих приложения, и функция bar ничего не делает. При этом оба компилятора выдали сообщения-предупреждения, что возможно здесь что-то не то. К чему я все это говорю… Когда вы слышите, что Rust супер замечательный безопасный язык, то его безопасность заключается только в анализе времени жизни объектов, UB — либо документированное поведение, которое вы не очень ожидаете, по-прежнему в нем есть. Компилятор по-прежнему компилирует код, который явно делает какую-то чушь. И-и-и так уж получается.

Здесь мы наблюдаем бесконечную рекурсию. Опять-таки код компилируется в одинаковый ассемблерный выхлоп, то есть NOP для функции bar как в C++, так и в Rust. Но это баг LLVM.

Если вывести LLVM IR кода с бесконечной рекурсией, то мы увидим(link:godbolt):

ret i32 undef — и есть ошибка, сгенерированная LLVM.

В самом LLVM бага живет с 2006 года. И это важный вопрос, ведь необходимо иметь возможность пометить бесконечные цикл или рекурсию так, чтобы LLVM не мог оптимизировать это в ноль. К счастью, есть прогресс. В LLVM 6 добавили интринсик llvm.sideeffect, а в 2019 году в rustc был добавлен флаг -Z insert-sideeffect, который добавляет llvm.sideeffect в бесконечные циклы и рекурсии. И бесконечная рекурсия становится действительно бесконечной(link:godbolt). Надеюсь, что в скором времени этот флаг перейдет и в stable rustc по-умолчанию.

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

Итак, после того, как мы разобрались с ошибкой LLVM, давайте перейдем к главному заявлению: "его безопасность заключается только в анализе времени жизни объектов". Это заявление ложно, так как безопасное подмножество Rust защищает от ошибок, связанных с многопоточностью, гонками данных и выстрелам по памяти.

Антон (16:00):


Посмотрим более сложные функции. Что с ними делает Rust. Поправили нашу функцию bar и теперь она вызывает функцию foo. Мы видим, что Rust сгенерировал две лишних инструкции: одна инструкция сохраняет что-то в стек, другая инструкция в конце вытаскивает со стека. В C++ этого нету. Rust два раза потрогал память. Как-то уже не очень.

Вот этот пример(link:godbolt):

Вывод ассемблера для Rust длинный, но мы разберемся в причинах такой разницы. В этом примере Антон использует флаги -ftrapv для C++ и -C overflow-checks=on для Rust, чтобы включить проверку на переполнение знаковых. При переполнении C++ прыгает на инструкцию ud2, которая приводит к "Illegal instruction (core dumped)", а Rust прыгает на вызов функции core::panicking::panic, подготовка к которой занимает половину ассемблерного кода. В случае переполнения core::panicking::panic дает нам красивое объяснение падения:

$ ./signed_overflow 
thread 'main' panicked at 'attempt to multiply with overflow', signed_overflow.rs:6:12
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Так откуда взялись эти "лишние" инструкции, которые трогают память? Соглашение о вызове функции x86-64 требует, чтобы стек был выравнен до 16 байт, инструкция call кладёт 8-байтовый адрес возврата на стек, что ломает выравнивание. Чтобы это исправить, компиляторы кладут всякие инструкции типа push rax. И так делает не только Rust, но и C++(link:godbolt):

И C++, и Rust сгенерировали одинавый выхлоп ассемблера, оба добавили push rbx для выравнивания стека. Q.E.D.

Самое интересное заключается в том, что именно C++ нуждается в деоптимизации кода путём добавления аргумента -ftrapv, чтобы ловить неопределенное поведение при переполнении знаковых. Выше я уже показал, что Rust будет вести себя корректно даже без флага -C overflow-checks=on, так что можете сравнить сами(link:godbolt) стоимость корректного кода на C++, либо почитайте статью на эту тему. К тому же -ftrapv в gcc сломан с 2008 года.

Антон (18:10):


Чуть медленнее Rust плюсов...

На протяжении всего доклада Антон выбирает примеры, написанные на Rust'е, которые компилируются в чуть больший ассемблер. Не только примеры выше, которые "трогают" память, но и пример на 17:30(link:godbolt):

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

В 2019 на конференции CppCon был интересный доклад There Are No Zero-cost Abstractions от Chandler Carruth. Вот он там на 17:30 сильно страдает из-за того, что std::unique_ptr стоит дороже сырых указателей (link:godbolt). И чтобы хоть как-то приблизиться к ассемблерному выхлопу кода на сырых указателях ему приходится добавлять noexcept, rvalue ссылки, и использовать std::move. А на Rust всё будет работать без дополнительных усилий. Давайте сравним два кода и ассемблер. В примере на Rust мне пришлось дополнительно извратиться с extern "Rust" и unsafe, чтобы компилятор не заинлайнил вызовы (link:godbolt):

При меньших трудозатратах Rust генерирует меньше ассемблера. И не нужны подсказки компилятору в виде noexcept, rvalue ссылок и std::move. В сравнениях языков нужны нормальные бенчмарки. Нельзя вытащить понравившийся пример, и утвержать, что один язык медленнее другого.

В декабре 2019 Rust превосходил по производительности C++ согласно результатам Benchmarks Game. С тех пор C++ немного укрепил свои позиции. Но на таких синтетических бенчмарках языки будут раз за разом обходить друг друга. Я бы не отказался посмотреть нормальные бенчмарки.

Антон (18:30):


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

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

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

К счастью, у языка Rust есть пакетный менеджер cargo, который позволяет один раз сгенерировать объявления и поделиться ими со всем миром. Как вы понимаете, люди делятся не только сырыми объявлениями, но и безопасными и идиоматичными обёртками. На 2020 год в реестре пакетов crates.io находится около 40 000 крейтов.

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

# Cargo.toml
[dependencies]
flate2 = "1.0"

Всю работу по компиляции и линковке с учетом версий зависимостей cargo выполнит автоматически. Пример с flate2 примечателен тем, что в начале своего существования этот крейт использовал сишную библиотеку miniz, написанную на C, но со временем сообщество переписало сишный код на Rust. И flate2 стал работать быстрее.

Антон (19:14):


Внутри блока unsafe отключаются все проверки Rust'а, он там ничего не проверяет, и целиком полагается на то, что вы в этом месте написали все правильно.

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

Увы, мнение об отключении всех проверок в unsafe — это типичное заблуждение, потому что в документации к языку Rust сказано, что unsafe позволяет:


  1. Разыменовывать сырой указатель;
  2. Вызывать и объявлять unsafe функции;
  3. Читать или измененять статическую изменяемую переменную;
  4. Реализовывать и объявлять unsafe типаж;
  5. Получать доступ к полям union.

Ни о каких отключениях всех проверок Rust здесь и речи не идет. Если у вас ошибка с lifetime-ами, то просто добавление unsafe не поможет коду скомпилироваться. Внутри этого блока компилятор продолжает проверять код на соответствие системы типов, отслеживать время жизни переменных, корректность на потокобезопасность и многое-многое другое. Подробнее можно прочитать в статье You can’t "turn off the borrow checker" in Rust.

К unsafe не стоит относиться как "я делаю, что хочу". Это указание компилятору, что вы берете на себя ответственность за вполне конкретный набор инвариантов, которые компилятор самостоятельно проверить не может. Например, разыменование сырого указателя. Это мы с вами знаем, что сишный malloc возвращает NULL или указатель на аллоцированный кусок неинициализированной памяти, а компилятор Rust об этой семантике ничего не знает. Поэтому для работы с сырым указателем, который вернул, к примеру, malloc, вы должны сказать компилятору: "я знаю, что делаю; я проверил, там не нулл, память правильно выравнена для этого типа данных". Вы берете на себя ответственность за этот указатель в блоке unsafe.

Антон (19:25):


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

По статистике Microsoft, 70% уязвимостей связаны с нарушениями безопасности доступа к памяти и с другими классами ошибок, от которых Rust предотвращает ещё на этапе компиляции. Это ошибки, которые физически невозможно совершить в безопасном подмножестве Rust.

С другой стороны, существует и unsafe подмножество Rust, которое позволяет разыменовывать сырые указатели, вызывать сишные функции… и прочие небезопасные вещи, которые могут сломать вашу программу, если ими пользоваться неправильно. В общем, именно то, что делает Rust системным языком программирования.

И, казалось бы, можно поймать себя на мысли, что если в Rust и в C++ надо следить за корректностью вызовов сишных функций, то Rust ничуть не выигрывает. Но особенностью Rust является возможность разграничения кода на безопасный и потенциально опасный с последующей инкапсуляцией последнего. А если на текущем уровне гарантировать корректность семантики не удаётся, то unsafe надо делегировать вызывающему коду.

На практике делегация unsafe наверх выглядит вот так:

// Warning: Calling this method with an out-of-bounds index is undefined behavior.
unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
    *elems.get_unchecked(index)
}

slice::get_unchecked — это стандартная unsafe функция, которая получает элемент по индексу без проверок индекса на выход за границы. Так как в нашей функции get_elem_by_index мы тоже не проверяем индекс, а передаем его как есть, то наша функция потенциально опасна. И любое обращение к такой функции требует явного указания unsafe(link:playground):

// Warning: Calling this method with an out-of-bounds index is undefined behavior.
unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
    *elems.get_unchecked(index)
}

fn main() {
    let elems = &[42];
    let elem = unsafe { unchecked_get_elem_by_index(elems, 0) };
    dbg!(elem);
}

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

Тем не менее, с помощью этой unsafe функции мы можем построить безопасную версию(link:playground):

// Warning: Calling this method with an out-of-bounds index is undefined behavior.
unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
    *elems.get_unchecked(index)
}

fn get_elem_by_index(elems: &[u8], index: usize) -> Option<u8> {
    if index < elems.len() {
        let elem = unsafe { unchecked_get_elem_by_index(elems, index) };
        Some(elem)
    } else {
        None
    }
}

fn main() {
    let elems = &[42];
    let elem = get_elem_by_index(elems, 0);
    dbg!(&elem);
}

И эта безопасная версия никогда не выстрелит по памяти, какие бы аргументы вы туда не передали. Если что, я не призываю вас писать подобный код на Rust (есть функция slice::get), я показываю, как можно перейти из unsafe подмножества Rust в безопасное подмножество с сохранением гарантий безопасности. На месте нашей unchecked_get_elem_by_index могла быть аналогичная функция, написанная на C.

Благодаря межъязыковой LTO вызов сишной функции может быть абсолютно бесплатен:

Я выложил проект с флагами компилятора на гитхаб. Результирующий выхлоп ассемблера аналогичен коду, написанному на чистом C(link:godbolt), но имеет гарантии кода, написанного на Rust.

Антон (20:38):


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

В 2018 году доказали, что система типов Rust, механизмы заимствования, владения, времён жизни и многопоточности корректны. Так же было доказано, что если мы используем семантически правильный код из библиотек внутри unsafe и смешаем это с синтаксически правильным safe кодом, мы получим семантически правильный код, который не позволяет стрелять по памяти или делать гонки данных.

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

В качестве практического применения своей модели авторы доказали корректность некоторых примитивов стандартной библиотеки, включая Mutex, RwLock, thread::spawn. А они используют сишные функции. Таким образом, в Rust невозможно случайно расшарить переменную между потоков без примитивов синхронизации; а, используя Mutex из стандартной библиотеки, доступ к переменной всегда будет корректен, несмотря на то, что их реализация опирается на сишные функции. Круто? Круто.

Объективно обсуждать относительные преимещества того или иного языка сложно, особенно если вам сильно нравится один язык и не нравится другой. Весьма часто новый апологет очередного "новоявленного языка-убийцы C++" делает громкие заявления, не разобравшись толком с C++, за что ожидаемо получает по рукам.

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

Большое спасибо Дмитрию Кашицыну и Алексею Кладову за ревью статьи.

Let's block ads! (Why?)