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

Ruvds_перевод

Перевод 10 ведущих технических трендов 2021 года, на которые стоит обратить внимание программистам

02.06.2021 12:04:43 | Автор: admin
Для индустрии разработки программного обеспечения и для программистов 2020 год стал значительным годом больших прорывов во многих областях. Пандемия значительно ускорила перевод самых разных процессов в цифровую среду, в результате тренды, о которых мы сегодня поговорим, будут представлять собой более масштабные явления, чем нечто подобное в прошлом году.

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



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

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

1. Мультиоблачные среды


Если говорить о компаниях, поддерживающих облачные сервисы для публичного использования, то совершенно очевидно то, какие именно компании являются лидерами рынка. По данным Statista в четвёртом квартале 2020 года лидером рынка облачных услуг с долей в 32% стала платформа Amazon Web Services. Microsoft Azure досталось 20% рынка, а Google Cloud 9%. В 2021 году, вероятно, эти три ведущих платформы сохранят свои позиции.


Ведущие облачные платформы в 4 квартале 2020 года

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

2. Блокчейн-технологии


Блокчейн-технологии появились сравнительно недавно. Уже сейчас понятно, что они способны изменить мир. Они используются, например, в криптовалютах. Но эти технологии могут серьёзно трансформировать всю IT-индустрию. Ресурс PR Newswire прогнозирует, что к 2027 году рынок блокчейн-технологий достигнет размеров в 30,7 миллиардов долларов при совокупном среднегодовом темпе роста в 43%. Весьма вероятно то, что в 2021 году эти технологии, в виде механизма смарт-контрактов, будут использоваться в самых разных областях.

3. Квантовые вычисления


Квантовые вычисления это, без сомнения, самая реформистская технология нынешней эпохи. Эта технология, скорее всего, повлияет на все отрасли экономики. И, по сведениям, опубликованным в IBM Research Blog, в 2023 году компания выпустит процессор IBM Quantum Condor на 1121 кубита.

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

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

4. Инструменты для глубокого обучения


Globe News Wire даёт прогноз, в соответствии с которым рынок технологий глубокого обучения достигнет в 2028 году 93,34 миллиарда долларов, при стабильном совокупном среднегодовом темпе роста в 39,1%. Наиболее заметными глобальными фигурами на этом рынке являются Facebook и Google. По данным исследования, проведённого Stack Overflow среди разработчиков, оказалось, что фреймворк Google TensorFlow 2.0 популярнее, чем Facebook PyTorch. Причиной этого является тот факт, что фреймворк TensorFlow обладает всеми возможностями PyTorch, но при этом отлично работает в среде Google Colab.


TensorFlow популярнее PyTorch

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


С PyTorch работать комфортнее, чем с TensorFlow

Не стоит и говорить о том, что в 2021 году и PyTorch, и TensorFlow 2.0. станут теми самыми инструментами, которые, в зависимости от нужд конкретного проекта, чаще всего будут использоваться там, где нужны технологии глубокого обучения.

5. Вычисления, требующие переработки большого количества данных


Несколько лет назад в сфере распределённой пакетной обработки данных, или при проведении вычислений, требующих переработки большого количества данных, стандартным инструментом была платформа Hadoop. Но сейчас, с появлением платформы Apache Spark, её, в большинстве случаев, используют вместо Hadoop. В публикации из блога Towards Data Science сказано, что основное отличие двух этих платформ заключается в производительности. А именно, если речь идёт об обработке данных, хранящихся на дисках, то Spark стабильно показывает производительность, в 10 раз превышающую производительность Hadoop. Если же данные хранятся в памяти речь идёт о 100-кратном повышении производительности. Более того платформа Spark создавалась с прицелом на исправление недостатков Hadoop. В результате тренд отказа от Hadoop и перехода на Spark, весьма вероятно, продолжится и в этом году.

6. Быстрая разработка приложений


Недавняя публикация ресурса PR Newswire позволяет говорить о том, что к 2027 году рынок быстрой разработки приложений (Low Code/No-Code, LCNC) достигнет 65,15 миллиардов долларов, при этом совокупный среднегодовой темп роста превысит показатель в 26,1%. Low Code/No-Code-возможности в сфере веб-разработки поддерживает несколько платформ. Среди них Microsoft Power Apps, Bubble, OutSystems и Appian.

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

7. JavaScript, Python и Java


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


Языки программирования

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

8. React популярная библиотека для разработки пользовательских интерфейсов


Если взглянуть на фреймворки и библиотеки, используемые в веб-разработке, то оказывается, что тут первое место всё ещё принадлежит jQuery, но эта библиотека довольно скоро может уступить первенство React и Angular. Кроме того, React это, в соответствии с результатами исследования Stack Overflow, библиотека для фронтенд-разработки, которая опережает другие подобные инструменты по количеству загрузок и по уровню использования. Разработчики выбирают её для создания интерфейсов чаще других подобных средств.


Библиотеки и фреймворки для фронтенд-разработки

9. Контейнеризация


В IT-индустрии, изначально ориентированной на облачные среды, контейнеризацию можно признать одной из ключевых технологий. Платформа Kubernetes, по сведениям Globe Newswire, занимает 48% рынка. Эта платформа стала ведущим инструментом для оркестрации контейнеров и для управления ими. Причём, это относится и к частным, и к общедоступным облачным системам. Более того, все ведущие поставщики облачных услуг, такие, как Amazon, Microsoft и Google, предоставляют своим клиентам возможность пользоваться Kubernetes.

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

10. Пограничные вычисления


Пограничные вычисления представлены распределённой вычислительной инфраструктурой, расположенной как можно ближе к конечному пользователю, которая объединяет системы хранения данных и вычислительные мощности. По некоторым оценкам глобальный рынок пограничных вычислений к 2028 году может дорасти до 61,14 миллионов долларов. Учитывая рост использования IoT-устройств, транспортных средств с выходом в интернет, технологий искусственного интеллекта и машинного обучения, в этом и в следующих годах рынок пограничных вычислений будет бурно расти. Поэтому нам стоит быть готовыми к инновациям в этой области и к появлению в этой области новых стандартов.

Итоги


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

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


Подробнее..

Перевод Управление зависимостями в Node.js

04.06.2021 16:20:58 | Автор: admin
Управление зависимостями это часть повседневной работы Node.js-программиста. Сегодня мы поговорим о разных подходах к работе с зависимостями в Node.js, и о том, как система загружает и обрабатывает зависимости.

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



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

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

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

Я исхожу из предположения о том, что вы уже владеете основами Node.js. Если нужно можете, прежде чем продолжать читать этот материал, посмотреть мою статью, посвящённую основам Node.js.

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


Начнём с простых вещей. Я создал директорию для проекта и, используя команду npm init, инициализировал проект. Затем я создал два JavaScript-файла: app.js и appMsgs.js. Ниже показан внешний вид структуры проекта в VS Code. Этот проект мы будем использовать в роли отправной точки наших экспериментов. Вы можете, прорабатывая этот материал, делать всё сами, а можете упростить себе работу, воспользовавшись готовым кодом. Его можно найти в репозитории, ссылку на который я приведу в конце статьи.

Структура базового проекта

В данный момент оба .js-файла пусты. Внесём в файл appMsgs.js следующий код:


Экспорт значений простых типов и объектов в appMsgs.js

Тут можно видеть конструкцию module.exports. Она используется для того, чтобы вывести во внешний мир некие сущности, описанные в файле (они могут быть представлены простыми типами, объектами, функциями), которыми потом можно воспользоваться в других файлах. В нашем случае мы кое-что экспортируем из файла appMsgs.js, а пользоваться этим собираемся в app.js.

В app.js воспользоваться тем, что экспортировано из appMsgs.js, можно, прибегнув к команде require:


Импорт модуля appMsgs.js в app.js

Система, выполнив команду require, вернёт объект, который будет представлять обособленный фрагмент кода, описанный в файле appMsgs.js. Мы назначаем этот объект переменной appMsgs, а затем просто пользуемся свойствами этого объекта в вызовах console.log. Ниже показан результат выполнения кода app.js.


Выполнение app.js

Команда require выполняет код файла appMsgs.js и конструирует объект, дающий нам доступ к функционалу, экспортируемому файлом appMsgs.js.

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

В результате оказывается, что мы, пользуясь конструкциями require и module.exports, можем создавать модульные приложения.

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

Выше мы рассматривали экспорт объектов и значений простых типов. Посмотрим теперь на то, как экспортировать функции и как потом этими функциями пользоваться. Уберём из appMsgs.js старый код и введём в него следующее:


Экспорт функции из appMsgs.js

Теперь мы экспортируем из appMsgs.js функцию. Код этой функции выполняется каждый раз, когда код, импортировавший её, её вызывает.

Попробуем воспользоваться этой функцией в app.js, приведя код этого файла к следующему виду:


Использование импортированной функции в app.js

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

Вот результат запуска этого кода:


Выполнение app.js

Мы рассмотрели два подхода к использованию module.exports. Ещё одним способом применения module.exports является экспорт функций-конструкторов, используемых, с ключевым словом new, для создания объектов. Рассмотрим пример:


Экспорт функции-конструктора из appMsgs.js

А вот обновлённый код app.js, в котором используется импортированная функция-конструктор:

Использование в app.js функции-конструктора, импортированной из appMsgs.js

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

Вот что получится, если выполнить новый вариант app.js:


Выполнение app.js

Я добавил в проект файл userRepo.js и внёс в него следующий код:


Файл userRepo.js

Вот файл app.js, в котором используется то, что экспортировано из userRepo.js:


Использование в app.js того, что экспортировано из userRepo.js

Запустим app.js:


Выполнение app.js

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

Импорт директорий


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

var appMsgs = require("./appMsgs")

Node.js, выполняя эту команду, будет искать файл appMsgs.js, но систему будет интересовать и директория appMsgs. То, что она найдёт первым, она и импортирует.

Теперь давайте посмотрим на код, в котором используется эта возможность.

Я создал папку logger, а в ней файл index.js. В этот файл я поместил следующий код:


Код файла index.js из папки logger

А вот файл app.js, в котором команда require используется для импорта этого модуля:


Файл app.js, в котором require передаётся не имя файла, а имя папки

В данном случае можно было бы воспользоваться такой командой:

var logger = require("./logger/index.js")

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

var logger = require("./logger")

Так как система не может обнаружить файл logger.js, она ищет соответствующую папку. По умолчанию импортируется файл index.js, являющейся точкой входа в модуль. Именно поэтому я и дал .js-файлу, находящемуся в папке, имя index.js.

Попробуем теперь выполнить код app.js:


Выполнение app.js

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

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

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

Npm


Мы кратко поговорим и о ещё одном аспекте работы с зависимостями в Node.js. Это npm (Node Package Manager, менеджер пакетов Node.js). Вы, вероятно, уже знакомы с npm. Если кратко описать его суть, то окажется, что он даёт разработчикам простой механизм для включения в их проекты необходимого им функционала, оформленного в виде npm-пакетов.

Установить нужную зависимость с помощью npm (в данном случае библиотеку underscore) можно так:

npm install underscore

Потом эту библиотеку можно подключить в коде с помощью require:


Импорт библиотеки, установленной с помощью npm

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


Пример использования underscore в app.js

Выполним этот код.


Выполнение app.js

Итоги


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

Применяете ли вы модульный подход при работе над своими Node.js-проектами?


Подробнее..

Перевод Советский реактор РБМК 35 лет после Чернобыльской катастрофы

04.06.2021 20:14:20 | Автор: admin
Тридцать пять лет назад на АЭС Форсмарк в Швеции сработала система предупреждения о радиационной опасности. После расследования было установлено, что источником радиации была не сама электростанция, а нечто, находящееся за её пределами. В итоге, с учётом направления господствующих ветров, было выяснено, что радиация пришла с советской территории. Советское правительство, после некоторых политических распрей, признало, что источником радиационного заражения была Чернобыльская атомная электростанция, на которой произошла авария.

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



В пользу этой идеи говорит тот факт, что оставшиеся реакторы серии РБМК, включая три установки на Чернобыльской АЭС, функционировали без заметных проблем с 1986 года, а девять из них работают до сих пор. В ходе международного расследования причин возникновения Чернобыльской катастрофы в соответствующих отчётах МКГЯБ постоянно говорится о недостаточном уровне культуры безопасности.

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

Анатомия катастрофы


За два года до аварии на Чернобыльской АЭС, в ночь на 3 декабря 1984 года, в индийском городе Бхопал погибло более двух тысяч человек. Тогда на близлежащем химическом заводе компании Union Carbide India Ltd случился выброс смертельно опасного вещества метилизоцианата. В последующие годы умерло ещё более тысячи человек, а общее число пострадавших составило около полумиллиона.

Резервуар E610 источник смертоносного газа

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

К катастрофе в Бхопале привели низкий уровень технического обслуживания оборудования, неисправные средства защиты, а так же отсутствие культуры безопасности. Всё это вместе позволило воде проникнуть через неисправные вентили в резервуар с метилизоцианатом, что привело, в результате экзотермической реакции, к образованию смертоносного газа. Американская компания-владелец завода (теперь она называется The Dow Chemical Company) не очистила место аварии после закрытия завода в 1986 году. Теперь эта задача возложена на местные власти.

Катастрофа 1986 года в Чернобыле во многом похожа на аварию в Бхопале. В частности недостаточным уровнем культуры безопасности. Всё началось ещё на этапе проектирования реактора РБМК (реактор большой мощности канального типа), когда, ради экономии, было решено использовать природный уран, а не обогащённый уран-235. Это означало увеличение размеров реактора, что привело к принятию решения о том, что в конструкции реактора не нужен корпус, который имеется у реакторов других типов (например у корпусных водо-водяных энергетических реакторов, ВВЭР). Корпус РБМК оказался бы слишком большим и слишком дорогим.

В РБМК имеется множество систем обеспечения безопасности, включая применение независимых петель контура охлаждения реактора, использования системы аварийного охлаждения реактора (САОР) и системы аварийного отключения реактора (SCRAM). Но там не было чего-то такого, что не дало бы операторам реактора по собственному усмотрению отключить все эти системы безопасности. В результате то, что должно было стать простым испытанием турбогенератора в режиме выбега (что предусматривало использование кинетической энергии, запасённой во вращающемся роторе турбогенератора, для выработки электроэнергии, необходимой для питания циркуляционных насосов в аварийной ситуации), превратилось в катастрофу.

Игры с реактивностью реактора


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

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

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

Верхняя часть реактора РБМК Ленинградской АЭС

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

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

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

Закон Мёрфи в действии


Когда было запланировано отключение четвёртого энергоблока Чернобыльской АЭС для обслуживания, было решено провести на нём эксперимент с турбогенератором, для чего была отключена САОР. Но, прямо перед тем, как было запланировано начать эксперимент, решено было оставить реактор в работающем состоянии ещё на 11 часов, так как энергосеть нуждалась в энергии, вырабатываемой энергоблоком. Эта задержка привела к тому, что персонал дневной смены, который и должен был проводить эксперимент, сменился сотрудниками вечерней смены. Им, как результат, из-за отключённой САОР, пришлось вручную регулировать вентили гидравлической системы реактора.

Когда на службу пришли работники ночной смены, ожидающие, что им придётся иметь дело с остановленным и остывающим реактором, им сообщили о том, что эксперимент должны проводить они. Это означало, что мощность реактора нужно было снизить, перейти с полной мощности к 700 1000 МВт (тепловых), а потом прекратить подачу пара на турбину.
Схема контуров охлаждения РБМК

У реактора РБМК есть одна особенность, которая выражается в том, что он крайне нестабилен и сложен в управлении на низких уровнях мощности. Учитывая положительный паровой коэффициент реактивности, несовершенство конструкции управляющих стержней и образование, в качестве побочного продукта работы реактора, ксенона-135, поглощающего много нейтронов, мощность реактора упала менее чем до 100 МВт. Это привело к тому, что операторы начали убирать всё больше и больше управляющих стержней (включая стержни, имеющие отношение к автоматической системе управления) в попытке увеличить реактивность реактора. Это позволило реактивности медленно вырасти и дойти до уровней, близких к тем, которые требовались для проведения эксперимента.

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

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

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

Конец эпохи РБМК


В наши дни всё ещё работают девять реакторов РБМК. Все они расположены в России. А три реактора, оставшиеся на Чернобыльской АЭС, были постепенно выведены из эксплуатации. Работающие реакторы РБМК усовершенствовали, учтя опыт катастрофы. А именно, речь идёт о следующих улучшениях:

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

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

Учитывая то, что реакторы типа РБМК и подобные им в наши дни совершенно не пользуются поддержкой общественности, в России будущее атомной электроэнергетики строится на реакторах типа ВВЭР. В частности, речь идёт о реакторе ВВЭР-1200, относящемуся к поколению 3+. В таких реакторах обычная вода используется для замедления нейтронов, для охлаждения реактора, а так же для поглощения нейтронов. Такие реакторы, при создании которых соблюдаются международные стандарты безопасности, заменят в будущие годы оставшиеся на российских атомных электростанциях реакторы РБМК.

Всё дело в культуре безопасности


Интересным противопоставлением идее о том, что реактор РБМК так опасен из-за положительного парового коэффициента реактивности, являются принципы, по которым построены реакторы CANDU (Canada Deuterium Uranium тяжеловодные водо-водяные ядерные реакторы производства Канады). Эти реакторы привлекают к себе так мало внимания, что обычные люди, не являющиеся гражданами Канады, обычно не знают о том, что в Канаде есть атомная промышленность, и о том, что Канада экспортирует эти реакторы во многие страны.

При этом в реакторах CANDU изначально использовался природный уран и они отличаются положительным паровым коэффициентом реактивности. Но, несмотря на это, активные и пассивные системы защиты таких реакторов способны предотвратить нечто вроде тех ошибок персонала, которые были совершены в Чернобыле, или что-то вроде частичного расплавления активной зоны реактора (при отрицательном паровом коэффициенте реактивности) при аварии на АЭС Три-Майл-Айленд. В последнем случае оператор взял на себя управление системой безопасности, в результате события развивались по сценарию, напоминающему неудачный эксперимент в Чернобыле.

Похожие причины лежат в основе аварии на АЭС Фукусима-1, которая произошла в 2011 году в Японии. Об этом говорится в отчёте Национального парламента Японии. Низкий уровень культуры безопасности и широкое распространение коррупции, доходящей до высших правительственных кругов, привело к тому, что системы безопасности электростанции не поддерживались в актуальном состоянии. АЭС не вполне соответствовала стандартам устойчивости к землетрясениям. Она не была модернизирована в соответствии с рекомендациями американской регулирующей организации.
Разлив смеси угольной золы и воды из отстойника (угольная электростанция в Кингстоне, аэрофотоснимок)

Но, даже учитывая вышесказанное, происшествия на атомных электростанциях чрезвычайно редки, благодаря чему атомная энергетика входит в число самых безопасных форм генерирования электроэнергии (с учётом количества выработанной энергии). Пожалуй, даже большее беспокойство, чем отдельные инциденты, вызывает то, что низкая культура безопасности характерна не только для атомной промышленности. Похожая ситуация наблюдается и во многих других сферах, о чём красноречиво говорят авария в Бхопале и другие крупные техногенные катастрофы. В США за расследование происшествий в сфере химической промышленности отвечает Совет по химической безопасности и расследованию угроз (Chemical Safety and Hazard Investigation Board, CSB).

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

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

В безопасности нет такого понятия, как Я


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

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

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

Как вы оцениваете уровень культуры безопасности, сложившийся в той сфере, в которой вы работаете?


Подробнее..

Перевод Ptpython улучшенный REPL для Python

05.06.2021 14:06:41 | Автор: admin
Возникало ли у вас когда-нибудь желание быстро испытать какую-нибудь свежую идею, прибегнув к интерфейсу командной строки Python, к REPL? Вероятно, если речь идёт об эксперименте буквально с несколькими строками кода, вам просто не захочется создавать для этого новый блокнот Jupyter.

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

Что если можно было бы превратить довольно-таки скучную командную строку Python в многофункциональный инструмент, вроде того, запись работы с которым показана ниже?


Продвинутая командная строка Python

Собственно, именно на тех, у кого возникает подобное желание, и ориентирован проект ptpython.

Что такое ptpython?


Ptpython можно назвать улучшенным интерфейсом командной строки Python. Установить его можно так:

pip install ptpython

А для того чтобы его запустить достаточно воспользоваться следующей командой:

ptpython

Возможности по вводу данных


Проверка вводимых данных


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


Ошибка, допущенная в обычной командной строке Python

Но ptpython позволяет проверить то, что введено с клавиатуры, ещё до нажатия на Enter. На следующем анимированном изображении показано, что пропущенная закрывающая скобка вызывает появление сообщения об ошибке. Эту ошибку можно тут же исправить.


Исправление ошибки при работе в ptpython

Автодополнение ввода, основанное на исторических данных


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


Автодополнение ввода, основанное на исторических данных

Но эта возможность ptpython, по умолчанию, не включена. Правда, для того чтобы включить её, достаточно, воспользовавшись клавишей F2, вызвать меню, в котором, пользуясь клавишами-стрелками, надо найти опцию Auto suggestion и перевести её в состояние on. Для закрытия меню надо нажать на Enter.


Включение автодополнения ввода

После включения опции Auto suggestion у вас должно заработать автодополнение ввода, основанное на истории. Для того чтобы воспользоваться тем, что предлагает ptpython, достаточно нажать клавишу-стрелку Вправо.

Использование подсказок при вводе кода


Если при работе с объектом ввести точку будет выведен список его свойств и методов.


Подсказки, выводимые после ввода точки

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

Вставка данных из истории команд


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

После того, как выбор нужного участка кода завершён, достаточно нажать на Enter и соответствующий код будет вставлен в рабочую область.


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

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

Режим вставки


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


Работа в обычной командной строке Python

А ptpython позволяет редактировать вставленный код, доводя его до нужного состояния.


Редактирование вставленного кода в ptpython

Для того чтобы включить режим вставки достаточно нажать на клавишу F6. Когда этот режим активирован код, при нажатии на Enter, выполняться не будет. А после того, как код будет готов к выполнению нужно снова нажать F6 для выключения режима вставки, а потом дважды нажать на Enter.

Возможности по выводу данных


Просмотр сигнатур функций и документационных строк


Ptpython позволяет просматривать сведения о параметрах функций и конструкторов.


Просмотр сведений о конструкторе DataFrame

Ещё можно смотреть документационные строки классов и функций. Для включения этой возможности нужно открыть меню (F2), а потом включить опцию Show docstring.


Включение вывода документационных строк

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


Вывод документации

Выделение парных скобок


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


Выделение парных скобок

Добавление пустой строки после введённых или выведенных данных


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


Улучшение читабельности кода за счёт пустых строк

Для того чтобы включить эту возможность нужно, вызвав меню клавишей F2, включить опции Blank line after input и Blank line after output.


Включение опций Blank line after input и Blank line after output

Выделение синтаксических конструкций


Ptpython, кроме прочего, поддерживает подсветку синтаксиса.


Подсветка синтаксиса

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

В системе имеется 39 тем. Если, например, вам хочется выбрать такую же цветовую схему, которая используется в Sublime Text знайте, что она имеет код monokai. Этот код нужно ввести в опции меню Code, которую можно найти в разделе Colors.


Настройка темы в меню

Магические команды IPython


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


Возможности IPython


Возможности IPython

Настройка ptpython


Те изменения, которые вносят в настройки ptpython во время работы, исчезают после окончания сеанса работы с программой.

Настройки, которые используются в каждом сеансе, должны быть описаны в файле $XDG_CONFIG_HOME/ptpython/config.py. В Linux путь к нему выглядит как ~/.config/ptpython/config.py.

Вот файл, который содержит все те полезные настройки программы, о которых мы говорили выше.

Итоги


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

Планируете ли вы пользоваться ptpython?


Подробнее..

Перевод Тест пропускной способности ASP.NET Core 5.0 в Kestrel, IIS, Nginx и Caddy

05.06.2021 18:10:05 | Автор: admin
Начиная с версии 2.2. ASP.NET Core поддерживает режим внутрипроцессного размещения приложения (InProcess) в IIS, направленный на улучшение производительности кода. Рик Страл написал статью, в которой подробно исследовал эту тему. С тех пор прошло три года, теперь платформа ASP.NET Core добралась до версии 5.0. Как это повлияло на производительность ASP.NET Core-проектов на различных серверах?



Результаты исследования Рика Страла


Рик Страл в своей статье занимался тестирование ASP.NET Core-кода на Windows в Kestrel и в IIS (в режимах InProcess и OutOfProcess). Его интересовало количество запросов в секунду, обрабатываемых системой. В результате он пришёл к выводу о том, что первое место по производительности получает использование IIS в режиме InProcess, второе Kestrel, третье IIS в режиме OutOfProcess.

Обзор эксперимента


Рик не провёл испытания, позволяющие выявить различия в выполнении ASP.NET Core-кода на Windows- и на Linux-серверах. А вопрос о том, что в 2021 году лучше выбрать для проектов, основанных на ASP.NET Core 5.0, интересует многих из тех, кого я знаю. Поэтому я решил, используя подход к тестированию, похожий на тот, которым пользовался Рик, узнать о том, сколько запросов в секунду может обработать ASP.NET Core 5.0-приложение на Windows и на Linux.

Тестовое окружение


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

Windows-сервер:


  • Провайдер: Microsoft Azure East Asia Region.
  • ОС: Windows Server 2019 Data Center.
  • Характеристики системы: B2S / 2 vCPU, 4GB RAM, Premium SSD.
  • Окружение: IIS с поддержкой статического и динамического сжатия контента, отсутствие интеграции с ASP.NET 3.5 или 4.x, на сервере установлена платформа ASP.NET Core 5.0.2 Runtime.


Сведения о Windows-сервере

Linux-сервер


  • Провайдер: Microsoft Azure East Asia Region.
  • ОС: Ubuntu Server 20.04 LTS.
  • Характеристики системы: B2S / 2 vCPU, 4GB RAM, Premium SSD.
  • Окружение: включено использование BBR, установлены Nginx, Caddy и ASP.NET Core 5.0.2 Runtime.


Сведения о Linux-сервере

Инструменты для проведения тестов


Рик пользовался West Wind Web Surge, но этот инструмент доступен только на платформе Windows, а это нас не устроит. Я решил воспользоваться опенсорсным кросс-платформенным инструментом bombardier, о котором однажды писали в официальном .NET-блоге Microsoft.

Тестовое приложение


Я создал новый проект ASP.NET Core 5.0 Web API, в котором имеется лишь один метод:

[ApiController][Route("[controller]")]public class TestController : ControllerBase{[HttpGet]public string Get(){return $"Test {DateTime.UtcNow}";}}

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

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

"LogLevel": {"Default": "Information","Microsoft": "Warning","Microsoft.Hosting.Lifetime": "Information"}

Методика тестирования


Я запускал проект, используя следующие конфигурации:

  • Kestrel.
  • IIS в режиме InProcess.
  • IIS в режиме OutOfProcess.
  • Обратный прокси Nginx.
  • Обратный прокси Caddy.

Затем я применял bombardier. В течение 10 секунд, по 2 соединениям, велась работа с конечной точкой, доступной на localhost. После прогревочного раунда испытаний я, друг за другом, проводил ещё 3 раунда и на основе полученных данных вычислял показатель количества запросов, обработанных исследуемой системой за секунду (Request per Second, RPS).

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

Результаты тестов


Windows + Kestrel


Средний RPS: 18808


Windows + Kestrel

Windows + IIS в режиме InProcess


Средний RPS: 10089


Windows + IIS в режиме InProcess

Windows + IIS в режиме OutOfProcess


Средний RPS: 2820


Windows + IIS в режиме OutOfProcess

Linux + Kestrel


Средний RPS: 10667


Linux + Kestrel

Linux + Nginx


Средний RPS: 3509


Linux + Nginx

Linux + Caddy


Средний RPS: 3485


Linux + Caddy

Итоги


Вот как выглядят результаты тестирования (от самой быстрой комбинации ОС и серверного ПО до самой медленной):

  • Windows + Kestrel (18808)
  • Linux + Kestrel (10667)
  • Windows + IIS в режиме InProcess (10089)
  • Linux + Nginx (3509)
  • Linux + Caddy (3485)
  • Windows + IIS в режиме OutOfProcess (2820)

Мои результаты отличаются от тех, что получил Рик, тестируя ASP.NET Core 2.2-проект. В его тестах производительность IIS в режиме InProcess оказывалась выше, чем производительность Kestrel. Но сейчас Kestrel оказывается быстрее IIS в режиме InProcess, и это кажется вполне логичным и ожидаемым.

А вот неожиданностью для меня стало то, что производительность Windows-серверов c Kestrel оказалась выше производительности аналогичных Linux-серверов. Это меня удивило.

В режиме обратного прокси-сервера Nginx и Caddy, в общем-то, показывают одинаковую производительность. И та и другая конфигурации обходят IIS в режиме OutOfProcess.

Конечно, мой простой тест, в ходе которого сервер возвращает обычную строку, не позволяет проверить все нюансы производительности ASP.NET Core 5.0 и исследовать возможности каждого сервера. В реальных проектах присутствует множество факторов, которые воздействуют на производительность. План моего эксперимента не перекрывает все возможные сценарии использования ASP.NET Core 5.0, в нём, наверное, имеются какие-то недочёты. Если вы что-то такое заметите дайте мне знать.

Дополнение


Мне сообщили, что в .NET 5 у DateTime.UtcNow могут быть проблемы, касающиеся производительности. Я провёл повторные испытания, заменив эту конструкцию на Activity.Current?.Id ?? HttpContext.TraceIdentifier. Получившиеся у меня результаты были выражены немного более высокими показателями, но общая картина осталась почти такой же.

А если увеличить число подключений с 2 до 125 сервер Kestrel, и на Windows, и на Linux, способен дать гораздо более высокую пропускную способность.

Каким серверным ПО вы пользуетесь в своих проектах?


Подробнее..

Перевод Разработка REST-серверов на Go. Часть 2 применение маршрутизатора gorillamux

06.06.2021 14:21:06 | Автор: admin
Перед вами второй материал из серии статей, посвящённой разработке REST-серверов на Go. В первом материале этой серии мы создали простой сервер, пользуясь стандартными средствами Go, а после этого отрефакторили код формирования JSON-данных, вынеся его во вспомогательную функцию. Это позволило нам выйти на достаточно компактный код обработчиков маршрутов.

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



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

Улучшенная система маршрутизации


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

Перед переходом к практическому примеру вспомним о том, как устроен API нашего сервера:

POST  /task/       : создаёт задачу и возвращает её IDGET  /task/<taskid>   : возвращает одну задачу по её IDGET  /task/       : возвращает все задачиDELETE /task/<taskid>   : удаляет задачу по IDGET  /tag/<tagname>   : возвращает список задач с заданным тегомGET  /due/<yy>/<mm>/<dd> : возвращает список задач, запланированных на указанную дату

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

  1. Можно создать механизм, позволяющий задавать отдельные обработчики для разных методов одного и того же маршрута. Например запрос POST /task/ должен обрабатываться одним обработчиком, а запрос GET /task/ другим.
  2. Можно сделать так, чтобы обработчик маршрута выбирался бы на основе более глубокого, чем сейчас, анализа запросов. То есть, например, у нас при таком подходе должна быть возможность указать, что один обработчик обрабатывает запрос к /task/, а другой обработчик обрабатывает запрос к /task/<taskid> с числовым ID.
  3. При этом система обработки маршрутов должна просто извлекать числовой ID из /task/<taskid> и передавать его обработчику каким-нибудь удобным для нас способом.

Написание собственного маршрутизатора на Go это очень просто. Это так из-за того, что организовывать работу с HTTP-обработчиками можно, используя компоновку. Но тут я не стану потакать своему желанию написать всё самому. Вместо этого предлагаю поговорить о том, как организовать систему маршрутизации с использованием одного из самых популярных маршрутизаторов, который называется gorilla/mux.

Сервер приложения для управления задачами, использующий gorilla/mux


Пакет gorilla/mux представляет собой один из самых старых и самых популярных HTTP-маршрутизаторов для Go. Слово mux, в соответствии с документацией к пакету, расшифровывается как HTTP request multiplexer (Мультиплексор HTTP-запросов) (такое же значение mux имеет и в стандартной библиотеке).

Так как это пакет, нацеленный на решение единственной узкоспециализированной задачи, пользоваться им очень просто. Вариант нашего сервера, в котором для маршрутизации используется gorilla/mux, можно найти здесь. Вот код определения маршрутов:

router := mux.NewRouter()router.StrictSlash(true)server := NewTaskServer()router.HandleFunc("/task/", server.createTaskHandler).Methods("POST")router.HandleFunc("/task/", server.getAllTasksHandler).Methods("GET")router.HandleFunc("/task/", server.deleteAllTasksHandler).Methods("DELETE")router.HandleFunc("/task/{id:[0-9]+}/", server.getTaskHandler).Methods("GET")router.HandleFunc("/task/{id:[0-9]+}/", server.deleteTaskHandler).Methods("DELETE")router.HandleFunc("/tag/{tag}/", server.tagHandler).Methods("GET")router.HandleFunc("/due/{year:[0-9]+}/{month:[0-9]+}/{day:[0-9]+}/", server.dueHandler).Methods("GET")

Обратите внимание на то, что одни только эти определения тут же закрывают первые два пункта вышеприведённого списка задач, которые надо решить для повышения удобства работы с маршрутами. Благодаря тому, что в описании маршрутов используются вызовы Methods, мы можем с лёгкостью назначать в одном маршруте разные методы для разных обработчиков. Поиск совпадений с шаблонами (с использованием регулярных выражений) в путях позволяет нам легко различать /task/ и /task/<taskid> на самом верхнем уровне описания маршрутов.

Для того чтобы разобраться с задачей, которая имеется в третьем пункте нашего списка, посмотрим на использование getTaskHandler:

func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) {log.Printf("handling get task at %s\n", req.URL.Path)// Тут и в других местах мы не проверяем ошибку Atoi, так как маршрутизатор// принимает лишь данные, проверенные регулярным выражением [0-9]+.id, _ := strconv.Atoi(mux.Vars(req)["id"])ts.Lock()task, err := ts.store.GetTask(id)ts.Unlock()if err != nil {http.Error(w, err.Error(), http.StatusNotFound)return}renderJSON(w, task)}

В определении маршрутов маршрут /task/{id:[0-9]+}/ описывает регулярное выражение, используемое для разбора пути и назначает идентификатор переменной id. К этой переменной можно обратиться, вызвав функцию mux.Vars с передачей ей req (эту переменную gorilla/mux хранит в контексте каждого запроса, а mux.Vars представляет собой удобную вспомогательную функцию для работы с ней).

Сравнение различных подходов к организации маршрутизации


Вот как выглядит последовательность чтения кода, применяемая в исходном варианте сервера тем, кто хочет разобраться в том, как обрабатывается маршрут GET /task/<taskid>.


А вот что нужно прочитать тому, кто хочет понять код, в котором применяется gorilla/mux:


При использовании gorilla/mux придётся не только меньше прыгать по тексту программы. Тут, кроме того, читать придётся гораздо меньший объём кода. По моему скромному мнению это очень хорошо с точки зрения улучшения читабельности кода. Описание путей при использовании gorilla/mux это простая задача, при решении которой нужно написать лишь небольшой объём кода. И тому, кто читает этот код, сразу понятно то, как этот код работает. Ещё одно преимущество такого подхода заключается в том, что все маршруты можно увидеть буквально раз взглянув на код, расположенный в одном месте. И, на самом деле, код настройки маршрутов выглядит теперь очень похожим на описание нашего REST API, выполненное в произвольной форме.

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

Какой маршрутизатор вы использовали бы при разработке REST-сервера на Go?


Подробнее..

Перевод Будущее веба станет ли рендеринг в ltcanvasgt заменой DOM?

07.06.2021 20:11:26 | Автор: admin
В последнее время было немало горестных рассуждений о последствиях решения Google использовать HTML-элемент <canvas> для рендеринга всего, что видно на экране при работе с Google Docs. И то, что это многих беспокоит, вполне понятно. Когда-то веб был задуман как система для работы с тщательно структурированной информацией, полной осмысленных метаданных и рассчитанной на совместное её использование многими людьми. Но, вместо этого, тот веб, который мы видим сегодня, представляет собой довольно сложно и запутанно устроенные приложения, которые работают в браузерных песочницах.


Решение Google, которое заключается в том, чтобы перейти от вывода на страницы HTML-элементов к рисованию пикселей на <canvas>, нельзя назвать чем-то таким, чего раньше никто не видел и не пробовал. Другие передовые веб-приложения уже вышли далеко за пределы традиционных схем работы с HTML-элементами. Так, в Google Maps вывод данных на <canvas> используется уже многие годы. В VS Code для отрисовки идеального интерфейса терминала тоже используется <canvas>. А в подающем надежды наборе инструментов Google Flutter, который позволяет создавать кросс-платформенные интерфейсы, в веб-браузере, по умолчанию, используется рендеринг с использованием <canvas>.

Но в этот раз происходящее вызывает несколько иные ощущения. А именно, появляется такое чувство, что рендеринг в <canvas> и другие современные технологии, вроде WebAssembly, увели нас за точку невозврата. Все привыкли к схеме работы, когда страница загружает, в виде обычного текста, JavaScript-код, который выполняется, взаимодействуя с HTML-элементами, видимыми в инструментах разработчика. Сейчас возникает такое впечатление, что это лишь небольшой этап на пути постоянно развивающихся технологий веб-разработки.

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

Что же сейчас происходит?

Подход к разработке веб-приложений с использованием рендеринга в <canvas> будет распространяться


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

Лет 15 назад Google была первопроходцем в деле использования асинхронных JavaScript-вызовов (это потом назвали AJAX). Компания использовала какие-то новейшие для своего времени технологии в Gmail и Google Maps, а позже эти технологии превращались в фундаментальные основы веб-разработки. Теперь, благодаря переходу Google к выводу интерфейсов на <canvas>, этот подход к формированию UI начинает выглядеть совершенно приемлемым в глазах веб-разработчиков нового поколения.

Прямо сейчас перед тем, кто хочет использовать <canvas> для каких-то серьёзных дел, стоят довольно-таки высокие барьеры. В ходе работы над Google Docs специалисты компании сделали новые реализации целой кучи механизмов, которые многие программисты обычно воспринимают как нечто само собой разумеющееся. Это, например, точные инструменты для создания макетов, средства для выделения текста, системы проверки правописания, механизмы оптимизированной перерисовки экранных элементов. В наши дни в мире существует всего несколько компаний, которые способны провести работы такого масштаба лишь ради надежды на возможное улучшение производительности некоего решения.

И самой сложной и интересной задачей в этом проекте является обеспечение доступности сайтов и приложений для людей с ограниченными возможностями. Для того чтобы приложение выполняло бы указания нормативных документов по доступности, оно должно соответствовать определённым требованиям. Это нужно для компаний, которые должны соответствовать требованиям, необходимым для участия в правительственных контрактах, вроде Google, и, кроме того, важно для тех компаний, которые стремятся быть хорошими жителями веба. Версия Google Docs, основанная на <canvas>, несмотря ни на что, должна иметь отличную поддержку средств для чтения с экрана и инструментов для увеличения фрагментов экрана. Она должна поддерживать работу в режиме высокой контрастности, должна содержать механизмы, помогающие работать с ней людям с ограниченными двигательными возможностями. Один из способов реализации этих возможностей заключается в создании невидимой DOM, которая воспроизводила бы реальные данные, выводимые в <canvas>, но существовала бы только ради обеспечения правильной работы ассистивных технологий. И, конечно, две эти модели должны быть идеально синхронизированы друг с другом.

В наши дни нет единого, стандартного решения, которым разработчики могли бы воспользоваться для реализации ассистивных возможностей приложения, которое использует <canvas> для формирования интерфейса. Но по мере того, как этот подход к созданию интерфейсов будет набирать популярность, ситуация изменится в лучшую сторону. И никто не знает о том, насколько быстро произойдут эти изменения. То, что Google использует <canvas> для создания интерфейсов, привлечёт к соответствующим вопросам внимание, этим заинтересуется множество разработчиков, будут развиваться сопутствующие технологии. Потом появятся библиотеки, а ещё через некоторое время, возможно, будут созданы соответствующие стандарты и API. К закону Этвуда можно сделать дополнение, с которым он будет выглядеть так:

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

Семантический веб мёртв


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

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

В соответствии с HTML5 в понятие веб-стандарт входит всё что угодно, по поводу чего пришли к согласию производители разных браузеров. В частности, речь идёт о множестве JavaScript-API, рассчитанных на решение различных практических задач. Из этих API можно, как из кирпичей, строить функционал веб-страниц. Среди этих API можно отметить, например Geolocation, Web Storage, WebSockets, Web Workers. В HTML5, конечно, появились и некоторые новые семантические описательные элементы, но единственным по-настоящему многообещающим новшеством стандарта, связанным с встраиванием информации в веб-страницы, была поддержка микроданных (microdata). Её, правда, довольно быстро из стандарта убрали (в основном из-за того, что Google и Apple не были заинтересованы в реализации этого новшества).


Что скрывается за маской HTML5?

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

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

Будущее это WebAssembly и бинарные блобы


Если вам кажется, что широкое распространение рендеринга в <canvas> это большое событие, то знайте, что оно выглядит не таким уж и значительным в сравнении с другими тенденциями веб-разработки. И самой крупной из таких тенденций, без сомнения, является применение WebAssembly, то есть низкоуровневого бинарного формата инструкций, который понятен всем современным браузерам.

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

В наши дни WebAssembly-программам для доступа к DOM нужен корявый промежуточный JavaScript-слой. Но вспомним о проекте WebGPU, об оптимизированном последователе теперь заброшенного WebGL. У WebGPU и WebGL есть одна важная общая черта: и тот и другой проекты дают возможность оптимизированного рендеринга на <canvas>. Если объединить это с аппаратным ускорением графики, которое реализовано в браузерах, то получится, что в нашем распоряжении оказался низкоуровневый механизм вывода графики, который можно использовать для создания веб-приложений нового поколения. Или, что больше похоже на правду, нового поколения библиотек и фреймворков, которые лягут в основу новых веб-приложений. Учитывая то, как много внимания к себе привлекает WebAssembly, сложно представить себе будущее веб-разработки, на которое не повлияют WebAssembly и WebGPU.

И почему нет? В конце концов, та модель, которую до сих пор использует Google Docs, это, определённо, не что-то такое, что здравомыслящий веб-разработчик выбрал бы, если бы ему нужно было с нуля написать текстовый редактор. Речь идёт о движке для формирования макетов документов, который основан на движке для формирования HTML-макетов, причём связаны эти движки с помощью абстракции, состоящей из кусков JavaScript-кода. Но Google Docs это реальное приложение, которым, просто потому что это возможно, пользуется невообразимое количество людей. С точки зрения архитектуры программного обеспечения это куча кода, которая работает лишь каким-то чудом, а не аккуратная, образцовая программа.

Итоги


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

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

Как вы относитесь к веб-приложениям, интерфейс которых основан на <canvas>?


Подробнее..

Перевод Дамп прошивки беспроводной Logitech K360 с помощью GreatFET

08.06.2021 16:18:29 | Автор: admin


Недавно чисто ради спортивного интереса я решил хакнуть клавиатуру, и в качестве первой подопытной выбрал самую дешевую беспроводную модель Logitech K360. Она уже несколько устарела, а ее основная микросхема также, как и используемый протокол беспроводной связи, ранее подробно разбирались. Можете посмотреть презентацию Mousejack Марка Ньюлина, почитать работу Трэвиса Гудспида по сниффингу nRF24, а также ознакомиться с упоминаемой в этих материалах разработкой KeyKeriki.

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

Разборка и первый осмотр


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


Лицевая сторона платы


Обратная сторона платы

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

Подключение к GreatFET и пробное тестирование


Чтобы прозондировать интерфейс SPI активно я подключил эти площадки к гребенке с помощью эмальпровода, а с ними и контакты TP5 и TP7. Согласно спецификации nRF24LE1, TP7 ведет к контакту PROG, используемому для активации программирования флэш-памяти, а TP5 к RESET (подписан сразу рядом с TP5). Reset в данном случае активируется сигналом низкого уровня.


Схема распиновки nRF24LE1

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


Провода SPI

Я подключил площадки SPI к SPI-контактам на GreatFET, GND к одному из контактов земли и привязал контакт ввода/вывода к площадке RESET. При этом я также задействовал на GreatFET контакт 3V3 для прямой подачи питания к микросхеме nRF24 через площадку VMCU.

gf = GreatFET()reset_pin = gf.gpio.get_pin('J1_P4')reset_pin.high()

Далее я попробовал отправить простые тестовые команды при помощи встроенного кода SPI, но в таком состоянии в ответ ничего не получил:

In [6]: gf.spi.transmit([0x05], receive_length=1)Out[6]: b'\xff'In [7]: gf.spi.transmit([0x03, 0x00, 0x00], receive_length=4)Out[7]: b'\xff\xff\xff\xff'

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

В этом мне помог мультиметр, но вы также можете проследить дорожки на фото самой платы. К счастью, оказалось, что к этим контактам (P1.2, P1.5, P1.6 и P2.0) ведут те самые оставленные мной изначально TP10-TP13. Так как потребовалось задействовать всего четыре новых площадки, я не стал паять, а снова использовал щупы PCBite.


Проверка интерфейса флэш-программирования

Для активации SPI нужно удерживать контакт PROG на высоком уровне и сбросить устройство подачей на RESET сигнала низкого уровня. Я настроил отдельный контакт ввода/вывода GreatFET для PROG, а затем добавил в скрипт функцию для отправки на RESET сигнала низкого уровня.

#!/usr/bin/env python3import hexdumpimport timefrom greatfet import GreatFETdef reset(gf, reset_pin):    reset_pin.low()    time.sleep(0.001)    reset_pin.high()    time.sleep(0.001)def main():    gf = GreatFET()    reset_pin = gf.gpio.get_pin('J1_P4')    prog_pin = gf.gpio.get_pin('J1_P6')    # Reset is active low    reset_pin.high()    # Enter prog mode    prog_pin.high()    time.sleep(0.01)    reset(gf, reset_pin)    # ...if __name__ == '__main__':    main()

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

fsr = ord(gf.spi.transmit([0x05], receive_length=1))fpcr = ord(gf.spi.transmit([0x89], receive_length=1))print(f'flash status register: {fsr:#02x}')print(f'flash protect register: {fpcr:#02x}')# test readprint('test read:')data = gf.spi.transmit([0x03, 0x00, 0x00], receive_length=256)hexdump.hexdump(data)

Но и теперь я по-прежнему не получал никакого ответа (лишь все ту же последовательность байтов 0xFF). В итоге, вооружившись осциллографом, я взялся проверять работоспособность всех контактов. С помощью простого скрипта для GreatFET я продолжал сбрасывать устройство и проверял осциллографом контакт RESET, возвращающийся на высокий уровень, чтобы увидеть состояние сигнала Reset на щупе после загрузки.

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


Сброс при низком уровне PROG


Сброс при высоком уровне PROG

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

$ ./test.pyflash status register: 0x80flash protect register: 0x0test read:00000000: 80 A3 A3 02 00 03 78 FF  E4 F6 D8 FD 90 00 00 7F  ......x.........00000010: 00 7E 04 E4 F0 A3 DF FC  DE FA 75 81 7E 02 07 82  .~........u.~...00000020: FC 00 FF D9 00 11 01 FF  E0 FF E0 00 01 02 FF E1  ................00000030: FF E1 00 01 02 FF E2 FF  E6 00 01 02 FF E7 FF EB  ................00000040: 00 01 02 FF EC FF EF 00  04 02 FF F0 FF FF 00 01  ................00000050: 00 57 69 72 65 6C 65 73  73 20 4B 65 79 62 6F 61  .Wireless Keyboa00000060: 72 64 20 00 34 D9 1D F0  40 01 00 00 00 61 02 20  rd .4...@....a....

Обратите внимание на строку с Wireless keyboard. Затем я выполнил одно считывание 18432 байтов допустимый максимум, если верить спецификации. На выводе получился толковый дамп флэш-памяти программы. В nRF24LE1 используется набор инструкций от 8051, поэтому для подтверждения, что это код, я загрузил его в Ghidra в виде BLOB-объекта 8051. В начале объекта находится подпрограмма инициализации, значит это действительно код.


Тестовое дизассемблирование в Ghidra

Чтобы проверить настройки активации аппаратной отладки и защиты от эхосчитывания флэш-памяти я также решил считать упомянутую в спецификации InfoPage:


Раздел InfoPage спецификации nRF24LE1

Для считывания InfoPage необходимо установить бит INFEN в регистре состояния флэш-памяти, для чего достаточно было отправить перед считыванием команду write flash status register:

def read_fsr(gf):    fsr = gf.spi.transmit([0x05], receive_length=1)    return ord(fsr)def write_fsr(gf, fsr):    fsr &= 0xff    gf.spi.transmit([0x01, fsr])def read_flash(gf, address, count):    command = struct.pack('>BH', 0x03, address)    data = gf.spi.transmit(command, receive_length=count)    return datadef get_infoblock(gf):    flash_stat_reg = read_fsr(gf)    # INFEN is bit 3 (2^3)    write_fsr(gf, flash_stat_reg | 8)    time.sleep(0.001)    infoblock = read_flash(gf, 0, 512)    # Unset INFEN bit    write_fsr(gf, flash_stat_reg & (~8 & 0xff))    return infoblock

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

При этом в код достаточно внести всего несколько простых изменений и его можно будет использовать для дампирования памяти программы и/или InfoPage в файлы:

$ ./k360_spi.py --dump flashdump.binflash status register: 0x80flash protect register: 0x0InfoBlock content:00000000: 00 A3 A3 48 31 57 54 79  70 14 0A 12 FF FF 98 04  ...H1WTyp.......00000010: 79 7C 88 23 B1 50 0F 05  FF FF FF FF 82 79 FF FF  y|.#.P.......y..00000020: FF FF FF FF FF FF FF FF  FF FF FF FF FF FF FF FF  ................00000030: FF FF FF 4C 45 31 4F FF  FF FF FF FF FF FF FF FF  ...LE1O............000001F0: FF FF FF FF FF FF FF FF  FF FF FF FF FF FF FF FF  ................Flash readback protection: False (ff)HW debug enabled: False (ff)wrote flash dump to flashdump.bin

Весь исходный код для k360_spi.py находится здесь: https://gist.github.com/jamchamb/b2892a22ac0760346d4d617fedf9b541. Следующим шагом будет анализ прошивки.


Подробнее..

Перевод SB181 логический вычислитель на базе АЛУ 74LS181

10.06.2021 16:07:13 | Автор: admin


Уже какое-то время в моей мастерской дожидаются своего проекта пара микросхем АЛУ 74LS181. Но так как мысль о создании на их базе целого процессора была несколько пугающей, я решил задействовать эти чипы в роли логического вычислителя: своеобразного 8-битного калькулятора, который получает шестнадцатеричные входные данные и отображает результаты различных логических операций в двоичном и hex-форматах. Будучи собранным исключительно на базе логики микросхем 74-й серии без какого-либо микроконтроллера или ЦПУ, такое устройство оказывается удобным помощником в 8-битном программировании.

Описание схемы


Микросхема шифратора 74C923 на 20 клавиш (хотя подключена она только к 16) получает пользовательский ввод. В зависимости от положения движкового переключателя вводимое число сохраняется в регистре А, регистре В или функциональном регистре. Переключатель просто перенаправляет стробирующий импульс Data Available из 74С923 на вход синхронизации соответствующего регистра. Резистор подтягивает входы синхронизации каждого регистра на высокий уровень, исключая их произвольное срабатывание в пассивном состоянии. Этот импульс срабатывает до появления на выходе фактических данных, поэтому я просто инвертировал данный сигнал через элемент И-НЕ, чтобы регистры защелкивались на заднем фронте. В противном случае защелки всегда сохраняли бы предыдущую введенную цифру.


Небрежно проложенные провода

Регистры А и В являются 8-битными защелками, использующими 74HC273. Каждое нажатие кнопки сдвигает 4 нижних бита регистра в 4 верхних, и в то же время значение кнопки загружается в нижние 4. Это позволяет вводить 2 шестнадцатеричные цифры одну за другой, как это делается на калькуляторе. Затем содержимое этих двух регистров передается на входы операндов А и В АЛУ 74LS181. Эти сохраненные значения регистров также отправляются на дисплейную плату, где hex-значение отображается двумя TIL311, а двоичное при помощи светодиодной гистограммы.

Функциональный регистр это 4-битный 74HC175. Несмотря на то, что АЛУ 74LS181 поддерживает множество функций, практическое применение имеет лишь их ограниченное число. Поэтому в данном случае я выбрал те же 16 функций, что и agp.cooper. Отображение вывода кнопочной панели 0-15 в разные значения, требуемые на входах S-функции микросхемы 74LS181, реализуется с помощью EEPROM. Это также означает, что можно выбирать различные функции или их порядок на кнопочной панели просто перепрограммируя ее. То есть я могу приблизительно сгруппировать функции каждой кнопки, расположив простые внизу, а более сложные сверху. В таком случае EEPROM просто обработает этот перенос.

4-битный вывод функционального регистра отправляется обратно на плату клавишного блока. Затем дешифратор 74HC4514, подключенный к светодиодам в каждом переключателе блока клавиш, подсвечивает текущую выбранную функцию.


Логическая плата с тремя регистрами-защелками, EEPROM и двойной 74LS181

Другая кнопка подключена к схеме защелки, основанной на логическом элементе И-НЕ. Это позволяет устанавливать входной сигнал переноса для нижнего 74LS181. Ширина каждого из этих двух чипов АЛУ составляет всего 4 бита, но подключение выхода переноса нижнего к входу верхнего дает уже 8-битное АЛУ. Здесь мне не нужно озадачиваться схемой ускоренного переноса, поскольку это полностью статичная настройка, где не требуется отслеживать синхронизацию или другие тайминги.

Затем сигналы выхода F АЛУ отправляются на плату дисплея, где снова отображаются в hex- и двоичном форматах. Дополнительный светодиод показывает, был ли сгенерирован верхним 74LS181 выход переноса.


Дисплейная плата

Сборка


SB181 состоит из 3 отдельных печатных плат. Это позволило добиться относительно компактного размера корпуса, а также вписаться в стоимость по $2 за JLCPCB.

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

Используя 3-позиционный движковый переключатель, те же 16 кнопок можно задействовать для ввода различных операндов или применяемой к этим операндам функции.

Основная логическая плата содержит всего 3 регистра-защелки, функциональную EEPROM и пару АЛУ 74LS181. Вывод этих компонентов передается на плату дисплея через шлейф.
Дисплей постоянно показывает значения двух операндов в регистрах А и В, а также вывод текущей функции АЛУ.

Микросхемы TIL311 представляют красивый шестнадцатеричный дисплей. Расстроила меня лишь их стоимость, а также характеристики энергопотребления, которые не позволили запитать устройство по USB. В результате я использовал стандартную схему импульсного источника питания, которая получает 12В при 750мА и понижает их до 5В. Гистограммные дисплеи тоже отлично справляются с отображением двоичных данных, для чего задействуют 8 из 10 доступных светодиодов. Один из оставшихся при этом используется для индикации выхода переноса.

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

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

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


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

Схемы


Плата клавишного блока
Логическая плата
Дисплейная плата


Подробнее..

Перевод CSS, JavaScript и блокировка парсинга веб-страниц

12.06.2021 14:07:17 | Автор: admin
Недавно мне попался материал, посвящённый проблеме загрузки CSS-файлов, которая замедляет обработку материалов страниц. Я читал ту статью, стремясь научиться чему-то новому, но мне показалось, что то, о чём там говорилось, не вполне соответствует истине. Поэтому я провёл собственное исследование этой темы и поэкспериментировал с загрузкой CSS и JavaScript.



Может ли загрузка CSS-ресурсов блокировать парсинг страницы?


Прежде всего скажу, что на вопрос из заголовка этого раздела можно, без всякого сомнения, дать положительный ответ. Загрузка CSS-файлов способна не только заблокировать парсинг HTML-кода, но и не дать выполняться JavaScript-коду.

Для начала предлагаю поэкспериментировать. Для этого нам понадобится соответствующим образом настроить браузер. CSS-файл мы будем загружать с CDN, поэтому ограничим скорость работы с сетью в браузере Google Chrome. Для этого, на вкладке инструментов разработчика Performance, поменяем значение параметра Network на Slow 3G. Исследовать будем следующую страницу:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta data-fr-http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link href="http://personeltest.ru/aways/cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet"><script>document.addEventListener('DOMContentLoaded', () => {console.log('DOMContentLoaded');})</script><script>console.log('script');Promise.resolve(1).then(res => {console.log('then');});</script></head><body><h1>hello</h1></body></html>

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


Вывод данных в JS-консоль

Может ли загрузка и выполнение JS-кода блокировать парсинг страницы?


Загрузка и обработка JS-файлов, безусловно, блокирует парсинг страницы. Но, чтобы исправить эту проблему, при подключении скриптов к странице можно пользоваться атрибутами defer и async тега <script>. Сейчас мы изучим их воздействие на загрузку страницы.

Обычные загрузка и выполнение скрипта


Если в теге <script> не используются атрибуты async или defer процесс загрузки и обработки материалов страницы происходит так, как показано на следующей схеме. Загрузка JS-файлов и выполнение содержащегося в них кода блокирует парсинг HTML-кода.


Использование тега <script> без атрибутов async и defer

Здесь и далее мы будем пользоваться следующими цветовыми обозначениями.


HTML parsing Парсинг HTML; HTML parsing paused Парсинг HTML приостановлен; Script download Загрузка скрипта; Script execution Выполнение скрипта

Использование тега <script> с атрибутом async


Когда браузер обрабатывает тег <script> с атрибутом async, загрузка JavaScript-кода осуществляется в асинхронном режиме. Код скрипта выполняется сразу после загрузки. При этом выполнение JS-кода блокирует парсинг HTML.


Использование тега <script> с атрибутом async

Использование тега <script> с атрибутом defer


Если в теге <script> имеется атрибут defer код скрипта загружается асинхронно. При этом код, после завершения его загрузки, выполняется только тогда, когда будет завершён парсинг HTML-кода.


Использование тега <script> с атрибутом defer

Эксперименты


Давайте поэкспериментируем с атрибутами async и defer. Начнём со следующей страницы:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta data-fr-http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>DomContentLoaded</title></head><body><script src="http://personeltest.ru/away/code.jquery.com/jquery-1.4.4.min.js"></script><script src="./index.js"/> // 0<script src="./index2.js"/> // 2<script >console.log('inline');Promise.resolve().then(res=>{console.log('then');})</script><div id="hello">hello world</div><script>document.addEventListener('DOMContentLoaded', () => {console.log('DOMContentLoaded');})</script></body></html>

Эта страница, помимо загрузки скрипта jquery-1.4.4.min.js с CDN, загружает пару собственных скриптов index.js и index2.js. Ниже приведён их код.

Файл index.js:

Promise.resolve().then((res) => {console.log('index1');return res;});

Файл index2.js:

Promise.resolve().then((res) => {console.log('index2');return res;});

В ходе загрузки этой страницы в JS-консоль попадает то, что показано ниже.


Вывод данных в JS-консоль

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

Теперь посмотрим на то, как ведут себя скрипты, в тегах <script> которых используется атрибут <async>:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta data-fr-http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>DomContentLoaded</title></head><body><script async src="http://personeltest.ru/away/code.jquery.com/jquery-1.4.4.min.js"></script><script src="./index.js"></script><script src="./index2.js"/></script><script>console.log('inline');Promise.resolve().then(res=>{console.log('then');})</script><div id="hello">hello world</div><script>document.addEventListener('DOMContentLoaded', () => {console.log('DOMContentLoaded');})</script></body></html>

Изучим то, что попадёт в консоль.


Вывод данных в JS-консоль

Скрипт библиотеки jQuery загружается асинхронно. То, что попадает в консоль, выводится там до его загрузки. Если скрипт библиотеки загружается слишком медленно это не помешает парсингу HTML-кода. Сообщение DOMContentLoaded может быть выведено и до, и после завершения загрузки и выполнения async-скрипта. А при применении атрибута defer скрипт будет загружен асинхронно, дождётся завершения обработки материалов документа, а потом, но до события DOMContentLoaded, будет выполнен.

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

Сталкивались ли вы с проблемами, связанными с блокировкой обработки материалов веб-страниц?


Подробнее..

Перевод Стриминг видео с помощью Akka Streams

12.06.2021 18:20:44 | Автор: admin
Автор статьи, перевод которой мы сегодня публикуем, говорит, что стриминг видео не должен быть такой задачей, с которой у кого-либо возникают сложности. Всё дело в правильном подборе инструментов, среди которых можно отметить пакет Akka Streams. Использование этого пакета позволяет эффективно разрабатывать приложения для потоковой передачи видео.



Правда, не следует думать, что то, о чём мы будем тут говорить, подобно простому примеру, вроде println(Hello world), в котором используется система акторов Akka. Сегодня вы узнаете о том, как создать свой первый сервис для потоковой передачи видео (прошу прощения, если моё предположение неверно, и у вас это уже не первый такой проект). В частности, тут будут использованы пакеты Akka HTTP и Akka Streams, с помощью которых мы создадим REST API, который обладает способностями стриминга видеофайлов в формате MP4. При этом устроен этот API будет так, чтобы то, что он выдаёт, соответствовало бы ожиданиям HTML5-тега <video>. Кроме того, тут я скажу несколько слов о наборе инструментов Akka в целом, и о некоторых его компонентах, вроде Akka Streams. Это даст вам определённый объём теории, которая пригодится вам в работе. Но, прежде чем мы приступим к делу, хочу задать один вопрос.

Почему я решил рассказать о стриминге видео?


У того, что я посвятил эту статью потоковой передаче видео, есть три основных причины.

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

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

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

А теперь мы можем переходить к нашей основной теме.

Что такое набор инструментов Akka? Удобно ли им пользоваться?


Akka это опенсорсный набор инструментов, который нацелен на упрощение разработки многопоточных и распределённых приложений, и, кроме того, даёт программисту среду выполнения для подобных приложений. Системы, которые базируются на Akka, обычно очень и очень хорошо масштабируются. Проект Akka основан на модели акторов, в нём используется система конкурентного выполнения кода, основанная на акторах. Создатели этого проекта многое почерпнули из Erlang. Инструменты Akka написаны на Scala, но в проекте имеются DSL и для Scala, и для Java.

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

Основной объём кода, который мы будем тут рассматривать, будет использовать пакеты HTTP и Streams. Мы практически не будем пользоваться стандартным пакетом Akka Actors.

Теперь, когда мы разобрались с самыми базовыми сведениями об Akka, пришло время вплотную заняться пакетом Akka Streams.

Что такое Akka Streams?


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

Для нас важен тот факт, что в Akka Streams есть встроенный механизм back-pressure (обратное давление). Благодаря этому решается одна из самых сложных проблем мира стриминговой передачи данных. Это настройка правильной реакции поставщика данных на работу в условиях, когда потребитель данных не может справиться с нагрузкой. И эту проблему решает инструмент, которым мы будем пользоваться, а нам остаётся лишь научиться работать с этим инструментом, не вдаваясь в какие-то чрезмерно сложные и запутанные темы.

Пакет Akka Streams, кроме того, даёт в наше распоряжение API, который совместим с интерфейсами, необходимыми для работы с Reactive Streams SPI. И, между прочим, стоит отметить, что сам проект Akka входит в число основателей инициативы Reactive Streams.

Про Akka Streams мы поговорили. Поэтому можем переходить к нашей следующей теоретической теме.

Что такое Akka HTTP?


Akka HTTP это, как и Akka Streams, пакет, входящий в набор инструментов Akka. Этот пакет основан на пакетах Akka Streams и Akka Actors. Он направлен на то, чтобы упростить работу приложений, в которых используются инструменты Akka, с внешним миром по протоколу HTTP. Этот пакет поддерживает и серверные, и клиентские возможности. Поэтому с его помощью можно создавать и REST API, и HTTP-клиентов, которые отправляют запросы к неким внешним сервисам.

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

HTML5-тег <video>


Тег <video> это новый элемент, который появился в HTML5. Он создавался как замена Adobe Video Player. Несложно понять, что главная задача этого HTML-элемента заключается в предоставлении разработчикам возможностей встраивания в HTML-документы медиаплееров, способных воспроизводить видеофайлы. Собственно, этот тег очень похож на <img>.

В теге <video> размещают тег <source>, имеющий два важных атрибута. Первый это src, который используется для хранения ссылки на видео, которое нужно воспроизвести. Второй это type, который содержит сведения о формате видео.

Между открывающим и закрывающим тегом <video> </video> можно ввести какой-то текст, который будет использован в роли текста, выводимого вместо элемента <video> в тех случаях, когда этот элемент не поддерживается браузером. Но в наши дни тег <video> поддерживает даже Internet Explorer, поэтому вероятность возникновения ситуации, в которой может понадобиться подобный текст, стремится к нулю.

Как будет работать стриминг с использованием тега и FileIO?</font></h2>Хотя на первый взгляд кажется, что стриминг с использованием тега <video> и FileIO это нечто архисложное, если в этот вопрос немного вникнуть, окажется, что ничего особенного в нём нет. Дело в том, что то, о чём идёт речь, представлено готовыми блоками, из которых нужно лишь собрать то, что нам нужно.На стороне сервера основная нагрузка ложится на объект <code>FileIO</code>. Он будет генерировать события, содержащие фрагменты файла, потоковую передачу которого мы осуществляем. Конечно, размеры этих фрагментов поддаются настройке. И, более того, настроить можно и позицию, начиная с которой будет осуществляться стриминг файла. То есть видео необязательно смотреть сначала его можно начинать смотреть с любого места, интересующего пользователя. Всё это отлично сочетается с возможностями тега <code><video></code>, выполняющего запрос HTTP GET с заголовком <code>Range</code> для того чтобы включить воспроизведение видео без его предварительной загрузки.Вот пара примеров запросов, выполняемых элементом <code><video></code>:<img src="http://personeltest.ru/aways/lh3.googleusercontent.com/Dfgd_RjgpV9W3jzBF9TVNLT_n8cAf5_VfvhxbfeK5wYH5slwUtXhjQ0FlZ0I79gCWsNgIF1Q3q7nPty5HqpCpgXIYrEtjQINuyMaMHe1nWu228-p8o9Hf3n-cHNZN_5iS0QGktk" align="center"><img src="http://personeltest.ru/aways/lh3.googleusercontent.com/SQPBs8xy8HVsT8cw2_WRoOnQW1dqKRp2WVSAzfFsbCBkSmETbm7NKS8zMLBdwcWdIo1AQDO_tSJJw1_2Tcd_oMoaBzdTwDz62Tq2jtzKX0ujmuCEqo99NKH_YLnlL6FQbZ5UOd0" align="center">Если кому интересно заголовок <code>Range:bytes=x-</code> отвечает за выбор позиции, с которой начинается воспроизведение видео. Первый запрос уходит на сервер в начале воспроизведения видео, а второй может быть отправлен тогда, когда пользователь решит куда-нибудь перемотать видео.Сейчас, после довольно-таки длинного вступления, пришло время заняться кодом.<h2><font color="#3AC1EF">Пишем стриминговый сервис</font></h2>В нескольких следующих разделах я расскажу о реализации серверной части нашего стримингового сервиса. А потом мы создадим простую HTML-страницу, с помощью которой проверим правильность работы этого сервиса.Я люблю делать предположения, поэтому сделаю ещё одно, которое заключается в том, что я ожидают, что тот, кто это читает, владеет основами Scala и SBT.<h3><font color="#3AC1EF">1</font></h3>Добавим в файл <code>build.sbt</code> необходимые зависимости. В этом проекте нам понадобится 3 пакета: <code>akka-http</code>, <code>akka-actor-typed</code> (пакета <code>akka-actor</code>, в теории, достаточно, но никогда нельзя забывать о типобезопасности) и <code>akka-stream</code>.<source lang="scala">libraryDependencies ++= Seq("com.typesafe.akka" %% "akka-actor-typed" % "2.6.14","com.typesafe.akka" %% "akka-stream" % "2.6.14","com.typesafe.akka" %% "akka-http" % "10.2.4")</source><h3><font color="#3AC1EF">2</font></h3>Теперь можно создать главный класс, ответственный за запуск приложения. Я решил расширить класс <code>App</code>. Мне кажется, что это удобнее, чем создавать метод <code>main</code>. На следующем шаге мы поместим сюда код, имеющий отношение к созданию системы акторов и HTTP-сервера.<source lang="scala">object Runner extends App {}</source><h3><font color="#3AC1EF">3</font></h3>После создания главного класса мы можем добавить в него код, о котором говорили выше.<source lang="scala">object Runner extends App {val (host, port) = ("localhost", 8090)implicit val system: ActorSystem[Nothing] = ActorSystem(Behaviors.empty, "akka-video-stream")Http().newServerAt(host, port)}</source>Сейчас нас вполне устроит такая конфигурация. На последнем шаге мы добавим в код вызов метода <code>bind</code>, что позволит открыть доступ к нашему REST API. Тут мы создаём объект <code>ActorSystem</code> с именем <code>akka-video-stream</code> и HTTP-сервер, прослушивающий порт <code>8090</code> на локальном компьютере. Не забудьте о ключевом слове <code>implicit</code> в определении системы акторов, так как подобный неявный параметр необходим в сигнатуре метода <code>Http</code>.<h3><font color="#3AC1EF">4</font></h3>А тут мы, наконец, реализуем конечную точку REST API, используемую для обработки запросов от тега <code><video></code>.<source lang="scala">object Streamer {val route: Route =path("api" / "files" / "default") {get {optionalHeaderValueByName("Range") {case None =>complete(StatusCodes.RangeNotSatisfiable)case Some(range) => complete(HttpResponse(StatusCodes.OK))}}}}</source>Как видите, я создал конечную точку с URL <code>api/files/default/</code>. В её коде проверяется, есть ли в запросе заголовок <code>Range</code>. Если он содержит корректные данные сервер возвращает ответ с кодом <code>200</code> (<code>OK</code>). В противном случае возвращается ответ с кодом <code>416</code> (<code>Range Not Satisfiable</code>).<h3><font color="#3AC1EF">5</font></h3>Пятый шаг нашей работы отлично подходит для реализации метода, ради которого, собственно, и была написана эта статья.<source lang="scala">private def stream(rangeHeader: String): HttpResponse = {val path = "path/to/file"val file = new File(path)val fileSize = file.length()val range = rangeHeader.split("=")(1).split("-")val start = range(0).toIntval end = fileSize - 1val headers = List(RawHeader("Content-Range", s"bytes ${start}-${end}/${fileSize}"),RawHeader("Accept-Ranges", s"bytes"))val fileSource: Source[ByteString, Future[IOResult]] = FileIO.fromPath(file.toPath, 1024, start)val responseEntity = HttpEntity(MediaTypes.`video/mp4`, fileSource)HttpResponse(StatusCodes.PartialContent, headers, responseEntity)}</source>Тут я сделал следующее:<ul><li>Загрузил файл, потоковую передачу которого я хочу организовать, а затем, учитывая заголовок из запроса, и данные о файле, нашёл позицию в файле, с которой начнётся стриминг, а так же сформировал заголовок <code>Content Range</code>.</li><li>С помощью <code>FileIO</code> создал поток из ранее загруженного файла. Затем я использовал этот поток в роли данных в <code>HttpEntity</code>.</li><li>Я создал ответ, <code>HttpResponse</code>, с кодом <code>206</code> (<code>Partial Content</code>), с соответствующими заголовками и с телом в виде <code>responseEntity</code>.</li></ul>Ещё мне хочется подробнее поговорить о <code>FileIO</code>, так как это самый удивительный механизм во всей статье. Что, на самом деле, происходит при выполнении строки <code>FileIO.fromPath(file.toPath, 1024, start)</code>?Тут, из содержимого файла, находящегося по заданному пути, создаётся объект <code>Source</code> (Akka-аналог Producer из Reactive Streams). Каждый элемент, выдаваемый этим объектом, имеет размер, в точности равный 1 Мб. Первый элемент будет взят из позиции, указанной в параметре <code>start</code>. Поэтому, если в <code>start </code>будет 0 первый элемент окажется первым мегабайтом файла.<h3><font color="#3AC1EF">6</font></h3>Мы уже реализовали основную логику серверной части приложения. А сейчас нам надо отрефакторить её код для того чтобы нашим сервером можно было бы пользоваться.Начнём с внесения изменений в определение REST API:<source>complete(HttpResponse(StatusCodes.Ok)) => complete(stream(range))</source>Получается, что вместо того, чтобы просто возвращать <code>OK</code>, мы вызываем метод <code>stream</code> с передачей ему параметра <code>range</code> и начинаем стриминг.Нельзя забывать о том, что наш API всё ещё недоступен для внешнего мира. Поэтому нам нужно модифицировать соответствующий фрагмент кода, ответственный за запуск HTTP-сервера:<source>Http().newServerAt(host, port) =>Http().newServerAt(host, port).bind(Streamer.route)</source>Готово! Теперь у нас есть рабочий бэкенд, а наш REST API ждёт подключений от любых программ, которым он нужен. Осталось лишь всё протестировать.<h2><font color="#3AC1EF">Тестирование стримингового сервиса</font></h2>Мы, чтобы протестировать приложение, создадим простую HTTP-страницу, единственным достойным внимания элементом которой будет тег <code><video></code>. Причём, обо всём, что надо знать для понимания работы этой страницы, мы уже говорили. Поэтому я просто приведу ниже полный код соответствующего HTML-документа.То, что вы в итоге увидите в окне браузера, открыв эту страницу, должно, более или менее, напоминать то, что я покажу ниже. Конечно, ваш стриминговый сервис вполне может передавать не тот видеофайл, который использовал я.<source lang="html"><!DOCTYPE html><html lang="en"><head><title>Akka streaming example</title></head><body style="background: black"><div style="display: flex; justify-content: center"><video width="1280" height="960" muted autoplay controls><source src="http://personeltest.ru/away/localhost:8090/api/files/default" type="video/mp4">Update your browser to one from XXI century




Тут мне хотелось бы обратить ваше внимание на 5 важных вещей:

  1. Я использовал возможности тега <source> вместо использования соответствующих атрибутов тега <video>.
  2. В атрибуте src тега <source> я указал путь, при обращении к которому бэкенд начнёт потоковую передачу видеофайла.
  3. В атрибуте type тега <source> я указал тип файла.
  4. Я добавил к тегу <video> атрибуты autoplay и muted для того чтобы видео начинало бы воспроизводиться автоматически.
  5. К тегу <video> я добавил и атрибут controls, благодаря чему будут выводиться элементы управления видеоплеера, встроенного в страницу.

На элемент <div> можете особого внимания не обращать. Он тут нужен лишь для стилизации плеера.

Для проверки правильности работы системы достаточно запустить бэкенд и открыть вышеописанный HTML-документ в любом современном браузере.


Правильная работа стримингового сервиса

Обратите внимание на то, что автоматическое воспроизведение видео не начнётся до тех пор, пока к тегу <video> не будут добавлены атрибуты muted и autoplay. Если не оснастить тег <video> этими атрибутами воспроизведение придётся включать вручную, нажимая на соответствующую кнопку.

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

Что можно улучшить?


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

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

Итоги


Сегодня я постарался доказать то, что реализация простого стримингового сервиса это, при условии использования правильных инструментов, проще, чем кажется. Использование инструментов Akka и подходящего HTML-тега способно значительно сократить объём работы. Правда, не забывайте о том, что тут показан очень простой пример. Для реализации реального стримингового сервиса этого может быть недостаточно.

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

Вот GitHub-репозиторий с кодом проекта.

Какие инструменты вы выбрали бы для создания собственного стримингового сервиса?


Подробнее..

Перевод Как я программировал шахматную партию против брата

13.06.2021 12:23:01 | Автор: admin


Это история о том, как я попытался выиграть у брата партию в шахматы. Всего лишь гребаную одну игру. Что в этом особенного? Хорош ли я в шахматах? Вовсе нет. Научился ли я чему-то в процессе игры? Тоже нет. Может, это история о путешествии ради путешествия, а не цели? Не совсем. Получил ли я хотя бы удовольствие от этого? Не уверен.

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

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

Почему я вообще связался с шахматами


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

И все же однажды на его вызов я поддался. Стоит ли говорить, что проигрыш был разгромным. Я знал правила и основы игры, так как немного играл еще в детстве, но с навыками брата это ни в коей мере сопоставить было нельзя. В последствии, просматривая анализ игры на chess.com, я увидел, что мое тактическое отставание ход за ходом только росло, пока не достигло оценки в +9 (что равно потере ладьи, слона и пешки против отсутствия потерь противника). В тот момент, утратив всяческую надежду, я сдался. Подобная ситуация повторялась на протяжении еще пары партий, когда я понял с этим нужно что-то делать.

Первым моим решением было углубиться в изучение игры.

Попытка первая: изучение


Моя первая попытка улучшить качество игры состояла в очевидном: обратиться к Reddit и YouTube за рекомендациями других обучающихся. В перерывах между уроками от GM Naroditsky, чтением и решением задачек на Lichess я также сыграл несколько игр со случайными соперниками по интернету. Несмотря на все это мой рейтинг оставался низким (1300 1400 Rapid на Lichess).



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

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

Попытка вторая: изучение противника


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

На стадии миттельшпиля преимущество обычно достигается за счет развертывания долгосрочной стратегии и применения тактики. Стратегию можно совершенствовать чтением и изучением принципов игры (мне это нравится), а тактика вырабатывается только через решение задач (что мне особенно не нравится). Поэтому я понимал, что в тактических навыках буду однозначно отставать, учитывая, что мой брат решал на chess.com около 20 таких задач ежедневно. Для меня это был недостижимый предел. Таким образом, оставалась всего одна возможность: получать преимущество на стадии дебюта.

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

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

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

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

Попытка третья: программирование


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

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

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

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

import jsonimport requestsdef get_month_games(player, yyyy_mm):    url = 'https://api.chess.com/pub/player/{}/games/{}'    r = requests.get(url.format(player, yyyy_mm))    if not r.ok:        raise Exception('get_month_games failed')    games = json.loads(r.content)    # Format: {games: [{url, pgn}, ...]}    return games['games']# ...

import chess.pgnimport ioimport jsonwith open('games.json') as f:    data = json.load(f)games = []for game in data:    pgn = io.StringIO(game)    games.append(chess.pgn.read_game(pgn))black_games = [g for g in games if g.headers["Black"] == "playerx"]

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

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

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

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

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

class GamesGraph():    def __init__(self):        self.graph = igraph.Graph(directed=True)    def add_move(self, start_fen, end_fen, uci):        vs = self._ensure_vertex(start_fen)        vt = self._ensure_vertex(end_fen)        try:            e = self.graph.es.find(_source=vs.index, _target=vt.index)            e["count"] += 1        except:            e = self.graph.add_edge(vs, vt)            e["uci"] = uci            e["count"] = 1    @property    def start_node(self):        return self.graph.vs.find(chess.STARTING_FEN)    def _ensure_vertex(self, fen):        try:            return self.graph.vs.find(fen)        except:            v = self.graph.add_vertex(name=fen)            v["fen"] = fen            v["turn"] = chess.Board(fen).turn            return v

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



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

Я также хотел получить оценку каждой позиции в плане преимущества белых, для чего использовал Stockfish. Учитывая, что процесс оценки тысяч позиций требует времени, я решил выполнить его отдельно и создал объект JSON, сопоставляющий каждую уникальную позицию FEN с ее оценкой Stockfish.

from stockfish import Stockfishstock = Stockfish(parameters={"Threads": 8})stock.set_depth(20)stock.set_skill_level(20)def eval_pos(fen):    stock.set_fen_position(fen)    return stock.get_evaluation()# fens - это сопоставление между строкой FEN и узлом графа.for fen, node in graph.fens.items():    node.eva = eval_pos(fen)

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

{"type":"cp", "value":12}    # Преимущество белых в 12 сантипешек.{"type":"mate", "value":-3}  # Черные получают мат в три хода.

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

Мне нужно было отобразить эту оценку во что-то более удобное для обработки, например в числа между 0 и 1. Для этого я навскидку решил, что преимущество в 300+ будет отображаться в 1.0, а отставание в 300+ в 0. Помимо этого, любой мат в X ходов (даже если X равен 20) будет 1 или 0.

# Возвращает [-1;1]def rating(ev, fen):    val = ev["value"]    if ev["type"] == "cp":        # Закрепить -300, +300. Достаточно захватить фигуру.        val = max(-300, min(300, val))        return val / 300.0    # Мат в X ходов: также max рейтинг.    if val > 0: return 1.0    if val < 0: return -1.0    # Это уже мат, но для белых или черных?    b = chess.Board(fen)    return 1.0 if b.turn == chess.WHITE else -1.0# Возвращает [0;1], где 0 - это min, а 1 - это max преимущество для черных.def rating_black(ev, fen):    return -rating(ev, fen) * 0.5 + 0.5

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

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

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

  • Они представляли расстояние, а не вероятность (т.е. чем больше расстояние, тем ниже вероятность выбора пути).
  • Расстояние между двумя узлами являлось суммой весов пройденных ребер (в противоположность произведению вероятностей).

На деле это гораздо легче сделать, чем объяснять. Формула очень проста:

distance(e) = -log(prob(e))

В Python это будет выглядеть так:

def compute_edges_weight(vertex):    all_count = sum(map(lambda x: x["count"], vertex.out_edges()))    for edge in vertex.out_edges():        prob = edge["count"] / all_count        edge["prob"] = prob        edge["weight"] = -math.log(prob)

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

  • Сумма логарифмов равна логарифму произведения их аргументов: log(a) + log(b) = log(a*b).
  • Чем больше результат, тем ниже определяющая его вероятность.



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

В этот момент я произвольно выбрал минимальное значение преимущества и упорядочил пути по вероятности. Первые несколько путей представляли для меня наибольший шанс выйти из дебюта с преимуществом перед PlayerX.

Доработки


Что я выяснил? Среди выданных этим алгоритмом позиций была следующая (ход белых):



Как видите, черные находятся в очень неловком положении (+8.9 согласно Stockfish), потому что g6, последний ход черных, был ошибкой. Белые продолжат, забирая пешку с e5 и слона. На этом партия для черных практически закончена, так как спасать им придется коня, пешку на h7 и слона. Еще один результат алгоритма был таким (ход белых):



Здесь мы видим мат в один ход (детский мат).

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

Еще одна проблема была связана с последовательностями ходов, которые происходили только раз, но из типичных позиций. Вероятность их заключительной позиции оказывалась такой же, что и вероятность последней типичной позиции, потому что каждое ребро имело вероятность 1.0 (учитывая, что другие возможности не разыгрывались). В примере ниже можно проследовать по ребрам 7 и 6 (наиболее распространенная позиция на втором ходу), а затем по одному из ребер с 1-ми. Далее все последующие ходы будут сыграны только раз (потому что данная позиция сложилась только в одном матче), в результате чего каждый ход будет иметь вероятность 1.0.



А вот и вероятности:



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

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

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

def compute_edges_weight(vertex, prob_ceiling=0.9):    all_count = sum(map(lambda x: x["count"], vertex.out_edges()))    for edge in vertex.out_edges():        # Уверенности нет... Установим потолок вероятности (default 90%).        prob = min(edge["count"] / all_count, prob_ceiling)        edge["prob"] = prob        edge["weight"] = -math.log(prob)

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

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



Подготовка


В процессе изучения я остановил свой выбор на следующей позиции:



Согласно Lichess, это защита Алехина (атака двух пешек). В этой позиции для черных есть всего один удачный ход (Nb6), после которого они все равно остаются в менее выгодном положении (+0.6 согласно Stockfish). Однако из этой позиции PlayerX зачастую играет на Nf4, что весьма для него неудачно (+2.3). Я создал на Lichess студию и начал просматривать несколько вариаций (хороших ходов и ходов, сыгранных PlayerX).

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

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

Решающая партия


Все случилось так, словно я глядел в будущее: мы с PlayerX попали в позицию защиты Алехина. Оказавшись в неудобной ситуации, он прозевал своего коня на пятом ходу. Оказывается, что даже игроки намного опытнее тебя начинают одну за другой совершать ошибки, когда попадают в проигрышные условия. Легко играть четко, когда ты побеждаешь, но удастся ли тебе сохранить хладнокровие в противоположной ситуации? На 10 ходу я уже вел с преимуществом +7.1, при котором сложно проиграть, но на этом также завершалась проработанная мной схема. Взгляните, насколько стеснены сейчас черные, и как мои фигуры нацелены напасть на короля:



С этого момента я начал тут и там совершать ошибки, но при этом мне удалось сохранять некоторое преимущество вплоть до 27 хода:



К сожалению, я был очень ограничен во времени (мы играли ускоренную 10-минутную партию), поэтому ходить приходилось быстро. В конечном итоге я совершил фатальные ошибки на 32 и 33 ходах, а еще через один получил от своего недобитого противника мат :/



Вот весь матч (с грубыми ошибками и прочим):


Интерактивный просмотр партии: lichess.org/2qKKl2MI

Выводы


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

  1. Подготовка под конкретного противника может дать значительное преимущество в дебюте.
  2. Начинающие игроки часто упускают возможность воспользоваться сомнительными ходами соперника. В связи с этим легко получить преимущество, доведя противника до позиции, из которой есть лишь один удачный ход.
  3. Дебют не является определяющим. Если вы не умеете действовать по времени и слабы в тактике, то вполне можете проиграть даже абсолютно выигрышные позиции. Шахматная партия порой решается одним неверным ходом.
  4. Очень важно изучать игру, и нет никакого универсального средства против оппонента, который намного опытнее вас. Однако за счет правильной подготовки разрыв в навыках можно сократить .
  5. Применение к шахматам принципов программной разработки оказалось занятной идеей, особенно учитывая, что самоцелью было победить брата.

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

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


Подробнее..

Перевод Портируем Quake 3 на Rust

14.06.2021 20:18:14 | Автор: admin


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

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

Подготовка: исходники Quake 3


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

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

$ make release

Сборка ioquake3 создает несколько библиотек и исполняемых файлов:

$ tree --prune -I missionpack -P "*.so|*x86_64". build     debug-linux-x86_64         baseq3            cgamex86_64.so          # клиент             qagamex86_64.so         # игровой сервер            uix86_64.so             # ui         ioq3ded.x86_64              # исполняемый файл выделенного сервера         ioquake3.x86_64             # основной исполняемый файл         renderer_opengl1_x86_64.so  # модуль рендеринга opengl1         renderer_opengl2_x86_64.so  # модуль рендеринга opengl2

Клиентскую, серверную и UI библиотеки можно собрать в виде Quake VM либо как нативные общие библиотеки x86. Мы предпочли второй вариант. Переносить VM на Rust и использовать версии QVM было бы существенно проще, но задачей было протестировать C2Rust максимально тщательно.

Сосредоточились мы на UI, игровом сервере, клиенте, модуле рендеринга OpenGL1 и основном исполняемом файле. Можно было также перевести модуль OpenGL2, но мы решили его не трогать, так как он активно использует файлы шейдеров .glsl, которые система сборки включает в исходники Си в виде строковых литералов. Конечно, можно было добавить поддержку скрипта кастомной сборки для встраивания кода GLSL в строки Rust после транспиляции, но нас остановило отсутствие надежного автоматического способа транспилировать эти автогенерируемые временные файлы.

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

Транспиляция


Чтобы сохранить используемую в Quake 3 структуру каталогов и не прибегать к изменению ее исходного кода, нужно было создать в точности такие же двоичные файлы, что и в нативной сборке, то есть четыре общие библиотеки и один двоичный файл. Поскольку C2Rust использует для сборки файлов Cargo, каждому исполняемому файлу требуется собственный контейнер Rust с соответствующим файлом Cargo.toml. Чтобы C2Rust на выходе создал для каждого исполняемого файла контейнер, ему нужно предоставить список двоичных файлов вместе с соответствующими им объектными или исходными файлами, а также вызов компоновщика, определяющего прочие детали, такие как зависимости.

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

В большинстве инструментов, создающих такую базу данных, подобное ограничение присутствует преднамеренно, например cmake с CMAKE_EXPORT_COMPILE_COMMANDS, bear и compiledb. Насколько нам известно, единственным инструментом, включающим команды компоновки, является build-logger из CodeChecker, который мы не задействовали только потому, что узнали о нем после написания собственных оберток (приводятся ниже). Это означало невозможность использовать файл compile_commands.json, создаваемый любым типовым инструментом для транспиляции мультибинарной программы Си.

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

$ make release

Мы добавили обертки для перехвата процесса сборки:

$ make release CC=/path/to/C2Rust/scripts/cc-wrappers/cc

Обертки создают каталог с файлами JSON, по одному файлу за вызов. Второй скрипт агрегирует все эти файлы в новый compile_commands.json, который содержит уже и команды компиляции, и команды сборки. Мы расширили C2Rust для считывания компонующих команд из базы данных и создания отдельного контейнера для каждого связанного двоичного файла. Кроме того, теперь C2Rust считывает зависимости каждого исполняемого файла и автоматически добавляет их в файл build.rs его контейнера.

В качестве облегчения процесса все исполняемые файлы можно собрать за раз, если они будут находиться в одном рабочем пространстве. C2Rust производит высокоуровневый файл рабочего пространства Cargo.toml, позволяя собирать проект одной командой cargo build в каталоге quake3-rs:

$ tree -L 1. Cargo.lock Cargo.toml cgamex86_64 ioquake3 qagamex86_64 renderer_opengl1_x86_64 rust-toolchain uix86_64$ cargo build --release

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


При первой попытке собрать переносимый код возникла пара проблем с исходниками Quake 3, которые C2Rust не мог корректно обработать или не обрабатывал совсем.

Указатели на массивы


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

int array[1024];int *p;// ...if (p >= &array[1024]) {   // error...}

Стандарт Си (загляните, например, в C11, Section 6.5.6) допускает указатели на элемент, выходящий за границу массива. Проблема же в том, что Rust это запрещает даже при получении только адреса элемента. Примеры этого шаблона мы нашли в функции AAS_TraceClientBBox.

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

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

Члены динамических массивов


На первый проверочный запуск игры Rust отреагировал паникой:

thread 'main' panicked at 'index out of bounds: the len is 4 but the index is 4', quake3-client/src/cm_polylib.rs:973:17

Заглянув в cm_polylib.c, мы заметили разыменовывание поля p в следующей структуре:

typedef struct{intnumpoints;vec3_tp[4];// переменный размер} winding_t;

Поле p здесь это более ранняя несовместимая с C99 версия члена массива переменной длины, которая до сих пор принимается gcc. C2Rust распознает членов динамических массивов с синтаксисом С99 (vec3_t p[]) и реализует простую эвристику для попутного обнаружения более ранних версий этого шаблона (массивов с размером 0 и 1 в конце структур; в исходниках ioquake3 мы нашли несколько таких).

Панику удалось устранить, изменив вышеприведенную структуру на синтаксис C99:

typedef struct{intnumpoints;vec3_tp[];// переменный размер} winding_t;

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

Связанные операнды во встроенном ассемблере


Еще одним источником сбоев был встроенный в Си код ассемблера из системного заголовка /usr/include/bits/select.h:

# define __FD_ZERO(fdsp)                                            \  do {                                                              \    int __d0, __d1;                                                 \    __asm__ __volatile__ ("cld; rep; " __FD_ZERO_STOS               \                          : "=c" (__d0), "=D" (__d1)                \                          : "a" (0), "0" (sizeof (fd_set)           \                                          / sizeof (__fd_mask)),    \                            "1" (&__FDS_BITS (fdsp)[0])             \                          : "memory");                              \  } while (0)

Он определяет внутреннюю версию макроса __FD_ZERO. Это определение вызывает редкий граничный случай встроенного ассемблерного кода gcc: связанные входные/выходные операнды разного размера.

Выходной операнд =D (_d1) привязывает регистр edi к переменной _d1 в качестве 32-битного значения, в то время как 1 (&__FDS_BITS (fdsp)[0]) привязывает тот же регистр к адресу fdsp->fds_bits в качестве 64-битного указателя. gcc и clang исправляют это несоответствие, взамен используя 64-битный регистр rdi и затем усекая его значение перед присваиванием к _d1. Rust же по умолчанию использует семантику LLVM, которая оставляет этот случай неопределенным. В отладочных сборках (не релизных, которые работали корректно) оба операнда присваивались регистру edi, обуславливая преждевременное усечение указателя до 32 бит еще до достижения встроенного кода ассемблера, что и вызывало сбои.

Поскольку rustc передает встроенный ассемблерный код Rust в LLVM с минимальными изменениями, мы решили исправить этот частный случай в C2Rust. Для этого мы реализовали новый контейнер c2rust-asm-casts, корректирующий проблему через систему типов Rust с помощью типажа (trait) и вспомогательных функций, которые автоматически расширяют и усекают значения связанных операндов до внутреннего размера, достаточного для хранения обоих операндов.

Вышеприведенный код корректно транспилируется в следующее:

let mut __d0: c_int = 0;let mut __d1: c_int = 0;// Ссылка на выходное значение первого операндаlet fresh5 = &mut __d0;// Внутреннее хранилище для первого связанного операндаlet fresh6;// Ссылка на выходное значение второго операндаlet fresh7 = &mut __d1;// Внутреннее хранилище для второго операндаlet fresh8;// Входное значение первого операндаlet fresh9 = (::std::mem::size_of::<fd_set>() as c_ulong).wrapping_div(::std::mem::size_of::<__fd_mask>() as c_ulong);// Входное значение второго операндаlet fresh10 = &mut *fdset.__fds_bits.as_mut_ptr().offset(0) as *mut __fd_mask;asm!("cld; rep; stosq"     : "={cx}" (fresh6), "={di}" (fresh8)     : "{ax}" (0),       // Приведение входных операндов к внутреннему типу хранилища       // с дополнительным нулевым или знаковым расширением       "0" (AsmCast::cast_in(fresh5, fresh9)),       "1" (AsmCast::cast_in(fresh7, fresh10))     : "memory"     : "volatile");// Приведение операндов к внешнему типу (с выведением типов) и усечениеAsmCast::cast_out(fresh5, fresh9, fresh6);AsmCast::cast_out(fresh7, fresh10, fresh8);

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

Выравнивание глобальных переменных


Последним источником сбоев была следующая глобальная переменная, где хранится константа SSE:
static unsigned char ssemask[16] __attribute__((aligned(16))) ={"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00"};

На данный момент Rust поддерживает атрибут выравнивания для типов структур, но не глобальных переменных, то есть элементов static. Мы продолжаем искать универсальный способ решения этой проблемы в Rust или C2Rust, но для ioquake3 пока что решили ее вручную с помощью небольшого патча. Этот патч заменяет Rust-эквивалент ssemask на:

#[repr(C, align(16))]struct SseMask([u8; 16]);static mut ssemask: SseMask = SseMask([    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,]);

Запуск quake3-rs


Выполнение cargo build --release генерирует двоичные файлы, но все они генерируются в target/release с использованием структуры каталогов, не распознаваемой бинарником ioquake3. Мы написали скрипт, который создает символические ссылки на текущую директорию, чтобы дублировать верную структуру каталогов (включая ссылки на файлы .pk3, содержащие ресурсы игры):

$ /path/to/make_quake3_rs_links.sh /path/to/quake3-rs/target/release /path/to/paks

Путь /path/to/paks должен указывать на каталог, содержащий файлы .pk3.

Ну а теперь пора запускать игру! При запуске нужно передать команду +set vm_game 0 и пр., чтобы загрузить эти модули как общие библиотеки Rust, а не ассемблерный код QVM, а также команду cl_renderer, чтобы использовать OpenGL1.

$ ./ioquake3 +set sv_pure 0 +set vm_game 0 +set vm_cgame 0 +set vm_ui 0 +set cl_renderer "opengl1"

Иии



Перед нами рабочая Rust-версия Quake3!



Вот видео, в котором мы транспилируем игру, загружаем ее и недолго тестируем игровой процесс:


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

Инструкции по транспиляции


Если вы захотите повторить аналогичный процесс переноса и запуска Quake 3, то имейте в виду, что вам понадобятся либо оригинальные ресурсы игры, либо скачанные из интернета демоверсии таких ресурсов. Помимо этого, потребуется установить C2Rust (минимальная необходимая ночная версия Rust на момент написания это nightly-2019-12-05, но мы рекомендуем заглянуть в репозиторий C2Rust или на сайт crates.io на предмет наличия последней):

$ cargo +nightly-2019-12-05 install c2rust

а также копии репозиториев C2Rust и ioquake3:

$ git clone git@github.com:immunant/c2rust.git$ git clone git@github.com:immunant/ioq3.git

В качестве альтернативы установке c2rust вышеприведенной командой вы можете собрать C2Rust вручную с помощью cargo build --release. В обоих случаях вам все равно потребуется репозиторий C2Rust, так как там находятся скрипты оберток компилятора, необходимые для транспиляции ioquake3.

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

$ ./transpile.sh </path/to/C2Rust repository> </path/to/c2rust binary>

Эта команда должна создать подкаталог quake3-rs, содержащий код Rust, где можно будет последовательно выполнять cargo build --release и остальные ранее описанные шаги.


Подробнее..

Перевод Юмористичный обзор Rust с перспективы JavaScript

16.06.2021 20:20:54 | Автор: admin

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

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

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

Хорошие новости


Современный Rust оказывается весьма схож с JavaScript. Переменные объявляются через let, функции выглядят очень похоже, типы уже не чужды, так как мы привыкли к TypeScript, присутствуют async/await, да и в общем формируется весьма знакомое ощущение.

Плохие новости


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

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

Управлению памятью быть!


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

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

Когда речь заходит об управлении памятью, то Rust как бы говорит: Ничего не знаю реальные шеф-повара сами за собой убирают. И на то есть хорошая причина, потому что сборщик мусора несет в себе собственный набор неочевидных проблем, которые могут навредить в самый неожиданный момент. Хотя в то же время, обогащенный опытом других языков, Rust признает, что заставлять программиста управлять памятью столь же разумно, сколь поручить Дугласу Адамсу написать Звездолет Титаник.

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



Как розу ты ни назови, а запах ее столь же сладок


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


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

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

На землях Вестероса (смею я сказать ВестеRust?) есть крохотные, небольшие, а также крупные феоды. Загвоздка в том, что все они оккупированы Ланнистерами. Внутренне феоды занимаются своими собственными делами, а когда им требуются товары извне, то они берут на себя долг, чтобы эти товары получить. В последствии долг необходимо возвращать Богам Вестероса. Rust подобен королеве драконов этого мира он узрит свысока все нюансы и проследит, чтобы все долги перед Богами были уплачены.

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

Может ли возникнуть кризис?


Посмотрим, как это работает.



Здесь у нас две области: внешняя main и внутренняя, будем звать ее inner scope, для демонстрации. В этом случае владение работает так:

  1. main владеет a и b
  2. a хочет поработать в inner scope, поэтому main передает a во владение inner scope
  3. inner scope делает свои дела с a и завершается
  4. Скрытый код Rust отбрасывает a
  5. main делает свои дела с b и тоже завершается
  6. Rust отбрасывает b

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

Что, если у нас будет такой код?



Здесь область main хочет снова использовать a, но мы сказали, что Rust уже ее отбросил по завершении inner scope.

Не даст ли программа сбой и не сгорит ли, когда достигнет этой точки выполнения?



Да, так и будет. Но, как спартанцы ответили отцу Александра, королю Филиппу II Македонскому: если она этой точки достигнет.

Абсолютный бюрократ


Компилятор Rust является гордым послушником традиции Легизма, настолько верным, что Хан Фей-цзы с восторгом бы объявил вне закона все остальные языки. Ничто не происходит в землях Rust, пока компилятор не скрепит это действие печатью Утверждаю. Он будет тщательно проверять каждую мелочь, оценивая, насколько безопасен запуск программы, и только при удовлетворении всех требований выдаст-таки исполняемый файл.



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

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

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

Rust RPG


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

Помните пример с dbg!()? Это макрос, представляющий грубый эквивалент console.log из JS. Давайте создадим собственную типизированную переменную и выведем ее в консоль.



Мы создали struct, которая, по сути, является типом. Затем мы создали объект этого типа. В завершении мы запросили вывод созданного объекта.



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

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

Нажмем F.



На этот раз работает. Единственное отличие в появившейся сверху строке. Здесь мы снаряжаем Noob типажом Debug. Теперь наш игрок готов к выходу в консоль какое достижение!

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

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

Типажи являются глубинным принципом работы фабрики Rust. Вернемся еще раз к примеру с владением. Если внимательно прочесть сообщение об ошибке, то мы заметим в нем компилятор объясняет, что владение переменной пришлось переместить, потому что String не реализует типаж Copy. В противном случае компилятор не стал бы ее перемещать, а сделал копию.

Типаж Copy означает, что вы берете раздел памяти и memcpy его в другое место, работая непосредственно с байтами. Хорошо, значит String не имеет типажа Copy, нужно ли нам просто сообщить компилятору, чтобы он его присвоил? К сожалению, нет. В целях безопасного использования Copy является слишком низкоуровневой для применения к String.

Конечно же, Rust бы не был полезен, если бы история на этом завершалась. Есть более явный типаж, который проделывает практически то же самое Clone. String обладает типажом Clone, значит нам просто нужно использовать его вместо Copy.

В качестве примера немного подправим код:



Здесь компилятор видит, что нам нужно использовать a внутри inner scope, но теперь он также видит, что мы научились все делать правильно, задействовав вместо фактической a ее клона. Итак, получается следующее:

  1. a принадлежит main
  2. Создается a.clone и одалживается в inner scope
  3. inner scope делает свои дела и завершается
  4. Rust отбрасывает a.clone
  5. main без проблем использует a, потому что a всегда оставалась в ее владении

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

Прежде чем завершать пост, считаю необходимым вкратце проговорить еще кое-что. Мы рассмотрели владение и то, как оно реализуется в области, но правда в том, что это было всего лишь упрощение. Для отслеживания владения Rust задействует принцип времени жизни (Lifetimes). Просто, получается, что чаще всего времена жизни и области совпадают. Хотя иногда компилятору все же требуется помощь. Следовательно, мы можем работать напрямую с временами жизни, а в некоторых случаях даже обязаны.

Конец?


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

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


Подробнее..

Перевод Разработка REST-серверов на Go. Часть 3 использование веб-фреймворка Gin

17.06.2021 16:13:58 | Автор: admin
Сегодня, в третьей части серии материалов, посвящённых разработке серверов на Go, мы займёмся реализацией нашего REST-сервера с использованием Gin одного из самых популярных веб-фреймворков для Go. Вот код, который мы будем тут обсуждать.



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


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

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

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

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

Маршрутизация и Gin


Наша функция main настраивает новый маршрутизатор Gin и регистрирует маршруты:

router := gin.Default()server := NewTaskServer()router.POST("/task/", server.createTaskHandler)router.GET("/task/", server.getAllTasksHandler)router.DELETE("/task/", server.deleteAllTasksHandler)router.GET("/task/:id", server.getTaskHandler)router.DELETE("/task/:id", server.deleteTaskHandler)router.GET("/tag/:tag", server.tagHandler)router.GET("/due/:year/:month/:day", server.dueHandler)

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

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

  1. Вместо указания HTTP-метода в виде дополнительного (Go) вызова метода в маршруте, метод закодирован в имени функции, используемой для регистрации маршрута. Например тут используется конструкция вида router.POST, а не что-то вроде router.HandleFunc(...).Methods(POST).
  2. Gorilla поддерживает обработку запросов с использованием регулярных выражений. А Gin нет. К этому ограничению мы ещё вернёмся.

Обработчики запросов


Посмотрим на код обработчиков запросов, используемых при применении Gin. Начнём с самых простых, в частности с getAllTasksHandler:

func (ts *taskServer) getAllTasksHandler(c *gin.Context) {allTasks := ts.store.GetAllTasks()c.JSON(http.StatusOK, allTasks)}

Тут стоит обратить внимание на несколько интересных моментов:

  1. У обработчиков, используемых в Gin, нет стандартных сигнатур HTTP-обработчиков Go. Они просто принимают объект gin.Context, который может быть использован для анализа запроса и для формирования ответа. Но в Gin есть механизмы для взаимодействия со стандартными обработчиками вспомогательные функции gin.WrapF и gin.WrapH.
  2. В отличие от ранней версии нашего сервера, тут нет нужды вручную писать в журнал сведения о запросах, так как стандартный механизм логирования Gin, представленный ПО промежуточного уровня, сам решает эту задачу (и делается это с использованием всяческих полезных мелочей, вроде оформления вывода разными цветами и включения в журнал сведений о времени обработки запросов).
  3. Нам, кроме того, больше не нужно самостоятельно реализовывать вспомогательную функцию renderJSON, так как в Gin есть собственный механизм Context.JSON, который позволяет формировать JSON-ответы.

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

func (ts *taskServer) getTaskHandler(c *gin.Context) {id, err := strconv.Atoi(c.Params.ByName("id"))if err != nil {c.String(http.StatusBadRequest, err.Error())return}task, err := ts.store.GetTask(id)if err != nil {c.String(http.StatusNotFound, err.Error())return}c.JSON(http.StatusOK, task)}

Тут особенно интересно выглядит обработка параметров. Gin позволяет обращаться к параметрам маршрута (к тому, что начинается с двоеточия, вроде :id) через Context.Params.

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

Привязка данных запросов


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

func (ts *taskServer) createTaskHandler(c *gin.Context) {type RequestTask struct {Text string  `json:"text"`Tags []string `json:"tags"`Due time.Time `json:"due"`}var rt RequestTaskif err := c.ShouldBindJSON(&rt); err != nil {c.String(http.StatusBadRequest, err.Error())}id := ts.store.CreateTask(rt.Text, rt.Tags, rt.Due)c.JSON(http.StatusOK, gin.H{"Id": id})}

В Gin имеется серьёзная инфраструктура для организации привязки запросов к структурам данных Go, содержащих данные из запросов. Тут под привязкой понимается обработка содержимого запросов (которое может быть представлено данными в различных форматах, например JSON и YAML), проверка полученных данных и запись соответствующих значений в структуры Go. Здесь мы пользуемся весьма примитивной формой привязки данных для RequestTask, где проверка данных не используется. Но, полагаю, нам стоит знать не только о базовых, но и о более продвинутых возможностях Gin.

Можно заметить, что Gin-версия createTaskHandler существенно короче более ранних версий аналогичного обработчика, так как за разбор JSON-данных запроса отвечает ShouldBindJSON.

Ещё внимание обратить стоит на то, что теперь нам не нужно пользоваться одноразовой структурой для ID ответа. Вместо этого мы используем gin.H псевдоним для map[string]interface{}; это очень просто, но, всё же, позволяет весьма эффективно конструировать ответы, используя совсем небольшие объёмы кода.

Дополнительные возможности Gin


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

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

Ограничения фреймворков


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

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

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

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

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

Каким фреймворком вы воспользовались бы при разработке сервера на Go?


Подробнее..

Перевод Как попасть в состояние потока?

18.06.2021 20:15:41 | Автор: admin
Для меня попадание в состояние потока является единственным способом продуктивной работы над сложными программными проектами. И я полагаю, что разработчик может так организовать свою жизнь, чтобы как можно сильнее удлинить время, которое он каждый день может проводить в этом состоянии. Тут я хочу рассказать о том, что лично я пытаюсь делать для того, чтобы чаще попадать в состояние потока.



Сон


image

Сон это самый важный фактор среди тех, что влияют на мою продуктивность. Если бы мне пришлось выбирать между хорошим ночным отдыхом и другими пунктами моего списка я выбрал бы сон. У меня есть жёсткое правило не пить кофе после 16:00. Если я делаю себе кофе в 15:45, а потом забываю о нём до 16:05, я убираю его в холодильник и оставляю на следующий день. Незначительный рост эффективности вечерней работы не стоит серьёзного ухудшения моей продуктивности на следующий день.

В те дни, когда я перевозбуждён, мне обычно помогает успокоиться приём 0,3-0,9 мг мелатонина. Я, кроме того, считаю, что спать лучше в прохладной комнате. Мне кажется, что оптимальным вариантом является температура в 16-18C.

Кофе


image

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

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

Качество воздуха


image

Можно очень сильно улучшить продуктивность, понизив уровень CO2 в воздухе рабочей комнаты. Под очень сильным улучшением я понимаю что-то в районе 20% или больше. Было одно исследование, где изучали воздействие уровня CO2 на когнитивную деятельность людей. Одна группа работала в помещении с уровнем CO2 в 1000 ppm, вторая с уровнем CO2 в 600 ppm. Для справки на открытом воздухе уровень CO2 составляет 400 ppm. Люди, работавшие в комнате с более высокой концентрацией CO2, набрали в когнитивных тестах примерно на 20% меньше баллов, чем люди, которые дышали воздухом с более низким содержанием CO2.

Я, пока не обзавёлся монитором качества воздуха, считал, что уровень CO2 в моей рабочей комнате достаточно низок, так как я часто проветривал эту комнату. А оказалось, что уровень CO2 в моей комнате составляет примерно 1200 ppm, что даже выше, чем в неблагополучной комнате вышеописанного исследования! Для того чтобы это исправить, я почти всегда, когда работаю, держу окно приоткрытым. Это позволяет поддерживать уровень CO2 в моей комнате примерно на отметке в 450 ppm.

Физические упражнения


image

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

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

Работа в одиночестве


image

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

Удаление игр


image

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

Блокировка сайтов


image

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

Составление плана работ на день


image

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

Музыка


image

Я полагаю, что идеальный музыкальный жанр, который позволяет мне длительное время не терять сосредоточения, это Deep House. Вот один из моих любимых миксов (живое выступление на воздушном шаре!). Ещё я пользуюсь наушниками с шумоподавлением. Без них я просто не могу сосредоточиться. Даже если я работаю в тихой комнате, мне очень нравится невероятная тишина, которую обеспечивают такие наушники.

Питание


image

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

Выбор правильной задачи


image

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

Как вы относитесь к состоянию потока?


Подробнее..

Перевод Практический взгляд на Raspberry Pi Pico с точки зрения STM32

19.06.2021 14:07:57 | Автор: admin
Сравнительно недавно Raspberry Pi Foundation выпустила плату Raspberry Pi Pico, основанную на микроконтроллере (Micro Controller Unit, MCU) RP2040. Эта плата привлекла большое внимание членов сообщества разработчиков различных электронных устройств. Появилось довольно много проектов, в которых используются программируемые модули ввода-вывода (Programmable I/O, PIO) Raspberry Pi Pico. Например, это проект PicoDVI, в котором конечные автоматы PIO используются для вывода DVI-сигнала.



Но с появлением Raspberry Pi Pico связано не только радостное возбуждение разработчиков электроники. Это событие заставило сообщество задаться важным вопросом о том, окажет ли появление платы какое-то ощутимое влияние на тех, кто пользуется STM32, SAM и другими микроконтроллерами, основанными на Cortex-M. Станет ли микроконтроллер RP2040 жизнеспособным выбором для некоторых из проектов, в которых используются похожие MCU? Учитывая то, что в состав RP2040 входит двухъядерный процессор ARM Cortex-M0+, кажется справедливой идея использования этого микроконтроллера там же, где применяются 32-битные MCU от ведущих производителей компонентов такого рода, в частности, от STMicroelectronics.

Сможет ли небольшой проект Raspberry Pi Foundation показать инженерам STM как надо делать микроконтроллеры, или создателям платы на RP2040 стоит пересмотреть некоторые из своих гипотез? Сложно ли будет портировать на RP2040 низкоуровневый код, рассчитанный на STM32?

Сложно ли перенести STM32-проект на RP2040?


Короче говоря, когда я обратила внимание на RP2040, мне подумалось, что будет интересно попытаться портировать на новый микроконтроллер мой C++-фреймворк для STM32. Правда, эта идея меня заинтересовала не из-за двухъядерного ARM Cortex-M0+. У меня есть двухъядерные микроконтроллеры STM32H7 (M4 и M7), которые, за счёт более совершенных характеристик, легко обойдут RP2040. Сильнее всего меня заинтриговали программируемые модули ввода-вывода RP2040, возникало такое ощущение, что они достойны того, чтобы познакомиться с ними поближе.


Плата Raspberry Pi Pico, основанная на RP2040 подключена к одноплатному компьютеру Raspberry Pi, играющему роль SWD-адаптера (оригинал)

Основываясь на опыте работы с STM32 я поняла, что смогу быстро портировать некоторые файлы, создав в репозитории проекта ветку RP, рассчитанную на другую архитектуру, и принявшись за дело. Ведь и в основном проекте, и в новой ветке код будет рассчитан на Cortex-M. Обычно работа с новым ARM-микроконтроллером заключается в том, чтобы найти даташит, справочное руководство и CMSIS-файлы для соответствующего устройства. А потом существующий низкоуровневый код можно легко адаптировать под новый подход к именованию периферийных устройств и под новую схему регистров, учитывая то, что фундаментальные компоненты нового и старого микроконтроллеров (SysTick, NVIC и так далее) ничем не отличаются.

Может, я поступила слишком опрометчиво, но я заказала плату Raspberry Pi Pico, даже не поинтересовавшись тем, есть ли для неё CMSIS-файлы, и даже не взглянув в справочное руководство по ней. Позже я, к своему удивлению, выяснила, что пока нет даже и речи о наличии CMSIS-файлов для Raspberry Pi Pico, или хотя бы о возможности взаимодействия RP2040 с другими устройствами из экосистемы Cortex-M. Но при этом SVD-файл для MCU RP2040 имеется в Pico SDK, а на основе этого файла можно создать заголовочный файл для устройства. Благодаря проекту cmsis-pi-pico в моём распоряжении, в итоге, оказалось рабочее решение.

Решение моей задачи можно было бы и упростить


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


Последовательность загрузки RP2040 (даташит RP2040, рисунок 15) (оригинал)

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

Первая сложность, которую нужно было преодолеть для того чтобы научиться работать с RP2040, заключалась в понимании особенностей цепочечного процесса загрузки микроконтроллера. Тут всё очень похоже на то, как в прошлом, на обычных компьютерах, была организована загрузка с дискет, или то, как устроена загрузка с HDD/SSD. А именно внешняя QSPI Flash ROM рассматривается MCU лишь как устройство, которое, возможно, содержит загрузочные данные. Загрузчик первой фазы загрузки интегрирован в MCU и располагается в ROM по адресу 0x0000 0000. Он обращается к интерфейсу QSPI и пытается загрузить из него 256 байт данных. Потом будет проверен CRC32-хеш этих данных. Если проверка пройдёт успешно, они будут признаны загрузчиком второй фазы загрузки.

Загрузчик второй фазы может решать множество задач, при этом некоторые задачи он должен решать в обязательном порядке. Этот процесс, реализованный в RP2040, если сравнить его с аналогичным процессом в некоторых знаменитых клонах STM32, тоже имеющих SPI ROM (вроде отличных клонов компании GigaDevice), не так понятен, не так хорошо документирован, не так прозрачен, как мог бы быть. В нём много такого, в чём можно запутаться.

Говорят, что хорошие художники копируют


У меня ушло достаточно много времени на то, чтобы понять, как подход к управлению тактированием периферийных устройств, принятый в STM32, соотносится с системной архитектурой RP2040. Я внимательно читала даташит RP2040 и всех вокруг спрашивала об этом. Как оказалось, RP2040-версия системы управления тактированием периферии называется RESETS. Эта система является полной противоположностью той, что применяется в STM32. А именно, нужно установить условие сброса периферийного блока в 0 для того чтобы включить его тактирование. Так, чтобы включить тактирование GPIO, нужно переключить бит 8 в RESETS_RESET (PADS_BANK0).


Функциональная схема GPIO-пина RP2040 (оригинал)

Когда я это поняла, я посмотрела раздел документации по GPIO-периферии (раздел 2.19). И кое-что тут же бросилось мне в глаза. А именно, то, что я там увидела, совершенно не похоже на то, как устроена практически вся GPIO-периферия, с которой я когда-либо сталкивалась. В частности, речь идёт о периферии STM32, AVR и SAM.

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

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

Причуды загрузки


Как уже было сказано, загрузчик второй фазы загрузки должен быть расположен в начале образа прошивки. Я считала, что это должен быть какой-то достаточно стандартный код, поэтому просто взяла готовый ASM-код, который выдал официальный Pico SDK, и использовала его при сборке примера Blinky. Добавив этот код к RP2040-порту моего проекта Nodate, я смогла без проблем собрать Blinky.

Запись результирующего ELF-бинарника в RP2040 стала ещё одним приключением. Дело в том, что на плате Raspberry Pi Pico нет встроенного SWD-адаптера, чего-то в духе ST-Link. А микроконтроллеру на двухъядерном Cortex-M нужен могоканальный SWD-адаптер. Единственным подобным устройством, которое было у меня под рукой, оказался адаптер, интегрированный в плату Nucleo-STM32H7. Поэтому я решила использовать кастомный форк OpenOCD, созданный Raspberry Pi Foundation. Его я запустила на Raspberry Pi.

После столь основательной подготовки мне удалось успешно прошить RP2040, но ничего не заработало. Беглая проверка вызвала у меня такое ощущение, что в ходе загрузки мне не удалось выйти за пределы исходного загрузчика и добраться до прошивки, находящейся в SPI ROM. Сейчас мне сложно дать ответ о причинах происходящего. Это могла быть проблема с ASM-кодом второй фазы загрузки, это могла быть ошибка в экспериментальных CMSIS-файлах RP2040, которые создавала не я. Это могло быть и что-то совершенно иное.

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



Raspberry Pi Pico (оригинал)

После того, как я потратила много часов на то, чтобы завести RP2040 с использованием CMSIS-файлов и файлов загрузчика второй фазы загрузки, мне кажется, что можно немного отстраниться от ситуации и переоценить происходящее. А именно, с того момента, когда начинала формироваться моя точка зрения на Raspberry Pi Pico, в запросе по поводу CMSIS-файлов появились сведения о том, что официальные CMSIS-файлы, возможно, появятся в Pico SDK 1.2.0. Это довольно-таки приятно.

Полагаю, любому, кто хочет поближе познакомиться с RP2040, пользуясь инструментами, ставшими индустриальным стандартом, имеет смысл дождаться этого релиза Pico SDK. А после того, как в моём распоряжении окажутся официальные CMSIS-файлы, я, вероятно, начну с переделывания примера Nodate Blinky, а потом попробую поработать с PIO. Перспектива создавать собственные интерфейсы кажется мне весьма привлекательной. И хотя возможности Raspberry Pi Pico не так мощны, как возможности CPLD или FPGA, они, всё равно, способны лечь в основу интереснейших проектов.

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

Полагаю, тех, кто захочет написать для Raspberry Pi Pico что-то своё, не особенно порадуют такие особенности платы, как запутанная работа с GPIO-периферией, сложный процесс загрузки, необходимость в загрузчике второй фазы загрузки, непрозрачность внешней ROM. В общем тому, кому интересна плата Raspberry Pi Pico, пока приходится ориентироваться на официальный SDK.

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

Пользовались ли вы Raspberry Pi Pico?


Подробнее..

Перевод Почему я всё ещё люблю C, но при этом терпеть не могу C?

19.06.2021 18:15:23 | Автор: admin
Мне на удивление часто приходится говорить о том, почему мне всё ещё нравится язык C, и о том, почему я плохо отношусь к C++. Поэтому я решил, что мне стоит об этом написать, а не снова и снова повторять одно и то же.



Как это обычно бывает у C-программистов, язык C не был ни моим первым языком, ни языком, после которого я уже не изучал ничего другого. Но мне всё ещё нравится этот язык, и когда мне нужно писать программы я выбираю именно его. Правда, в то же время, я стараюсь быть в курсе того, что происходит в мире современных (и не очень) языков программирования. Я слежу за тенденциями в этой сфере и пишу собственный хобби-проект, связанный с мультимедийными технологиями, на Rust. Почему же я до сих пор не поменял C на что-то более современное? И при чём тут C++?

Почему C это не самый лучший язык программирования?


Сразу скажу то, что, пожалуй, и так все знают: нет такого понятия, как самый лучший язык программирования. С каждым языком связан набор задач, для решения которых он подходит лучше всего. Например, хотя и можно заниматься трассировкой лучей в Excel, применяя VBA, лучше будет делать это с использованием более подходящего языка. Поэтому полезно знать об ограничениях языков программирования чтобы не жаловаться на то, что веб-серверы не пишут на Fortran, и на то, что почти нигде Perl или C++ не используется в роли внутренних скриптовых языков. C может считаться не очень хорошим языком по причинам, которые я перечислю ниже (это помимо того, что язык этот просто очень старый и того, что его нельзя назвать активно развивающимся языком, но это дело вкуса).

В синтаксисе C имеются неоднозначные конструкции (например, символ * может играть роль обычного оператора умножения, может применяться в виде унарного оператора разыменования, или может использоваться при объявлении указателей; а радости работы с typedef достойны отдельной статьи).

Этот язык не является безопасным. Например в C-программах довольно часто встречаются ошибки, связанные с обращением к элементам массивов, которые находятся за пределами границ массивов. В языке нет проверок на ошибки такого рода, производимых во время выполнения программы. А вот, например, в Borland Pascal, не говоря уже о более современных языках, такие проверки есть (это плюс, даже если подобные возможности можно, пользуясь параметрами компиляции, отключить ради повышения производительности). А использование в языке указателей ещё сильнее усложняет задачу программиста по поддержанию кода в хорошем состоянии. И, кроме того, C имеет и некоторые другие неприятные особенности, вроде возможности вызова функции без объявления прототипа, в результате чего функции легко можно передать аргумент неправильного типа.

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

Почему я, несмотря на все недостатки C, пользуясь именно этим языком?


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

Например, если нужно получить значение элемента массива, имея два смещения, одно из которых может быть отрицательным числом, то при программировании на C можно воспользоваться такой конструкцией: arr[off1 + off2]. А при использовании Rust это уже будет arr[((off1 as isize) + off2) as usize]. C-циклы часто короче, чем идиоматичные механизмы Rust, использование которых предусматривает комбинирование итераторов (конечно, обычными циклами можно пользоваться и в Rust, но это не приветствуется линтером, который всегда рекомендует заменять их итераторами). И, аналогично, мощными инструментами являются memset() и memmove().

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

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

При чём тут C++?


Если говорить о C++, то хочу сразу сказать, что я не отношусь к тем, кто ненавидит этот язык. Если вы этим языком пользуетесь и он вам нравится я ничего против этого не имею. Я не могу отрицать того, что C++, в сравнении с C, даёт нам два следующих преимущества. Во-первых это улучшение структуры программ (это поддержка пространств имён и классов; в Simula, в конце концов, есть хоть что-то хорошее). Во-вторых это концепция RAII (если описать это в двух словах, то речь идёт о наличии конструкторов для инициализации объектов при их создании и о наличии деструкторов для очистки ресурсов после уничтожения объектов; а если глубже разработать эту идею, то можно прийти к понятию времени жизни объекта из Rust). Но, в то же время, у C++ есть несколько особенностей, из-за которых мне этот язык очень и очень не нравится.

Это, прежде всего склонность языка постоянно вбирать в себя что-то новое. Если в каком-то другом языке появится какая-нибудь возможность, которая станет популярной, она окажется и в C++. В результате стандарт C++ пересматривают каждые пару лет, и при этом всякий раз в него добавляют новые возможности. Это привело к тому, что C++ представляет собой монструозный язык, которого никто не знает на 100%, язык, в котором одни возможности часто дублируют другие. И, кроме того, тут нет стандартного способа указания того, возможности из какой редакции C++ планируется использовать в коде. В Rust это поддерживается на уровне единицы компиляции. В IIRC C++ для той же цели предлагалась концепция эпох, но эта затея не удалась. И вот одно интересное наблюдение. Время от времени мне попадаются новости о том, что кто-то в одиночку (и за приемлемое время) написал рабочий компилятор C. Но я ни разу не встречал похожих новостей о компиляторе C++.

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

И, наконец, я мог бы вообще не обращать внимания на C++, если бы этот язык не был бы связан с C и не оказывал бы на C плохое влияние. Я не говорю о ситуации, когда, говоря о C и C++, их объединяют, упоминая как C/C++, и считая, что тот, кто знает C, знает и C++. Я имею в виду связь между языками, которая оказывает влияние и на стандарты, и на компиляторы. С одной стороны C++ основан на C, что придало C++, так сказать, хорошее начальное ускорение. А с другой стороны сейчас C++, вероятно, лучше смотрелся бы без большей части своего C-наследия. Конечно, от него пытаются избавиться, называя соответствующие конструкции устаревшими, но C-фундамент C++ никуда пока не делся. Да и будет ли популярным, скажем, некий С++24, вышедший в виде самостоятельного языка, основанного на C++21 и лишённого большей части устаревших механизмов? Не думаю.

Воздействие компиляторов C++ на C


Вышеописанная связь C и C++ приводит к тому, что к C относятся как к C++, лишённому некоторых возможностей. Печально известным примером такого восприятия C является C-компилятор Microsoft, разработчики которого не позаботились о поддержке возможностей C99 до выхода версии компилятора 2015 года (и даже тогда разработчики придерживались стратегии bug-for-bug compatibility, когда в новой реализации чего-либо воспроизводят старые известные ошибки; делалось это для того, чтобы пользователи компилятора не были бы шокированы, внезапно обнаружив, что вариадические макросы вдруг там заработали). Но тот же подход можно видеть и в стандартах, и в других компиляторах. Эти проблемы связаны друг с другом.

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

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

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

  • Поведение, определяемое архитектурой системы (то есть то, что зависит от архитектуры процессора). Сюда, в основном, входят особенности выполнения арифметических операций. Например, если я знаю, что целевая машина использует для представления чисел дополнение до двух (нет, это не CDC 6600), то почему компилятор (который, как представляется, тоже знает об особенностях целевой архитектуры) должен считать, что система будет вести себя иначе? Ведь тогда он сможет лучше выполнять некоторые теоретически возможные оптимизации. То же применимо к операциям побитового сдвига. Если я знаю о том, что в архитектуре x86 старшие биты, выходящие за пределы числа, отбрасываются, а на ARM отрицательный сдвиг влево это сдвиг вправо, почему я не могу воспользоваться этими знаниями, разрабатывая программу для конкретной архитектуры? В конце концов, то, что на разных платформах целые числа имеют разные размеры в байтах, считается вполне приемлемым. Компилятор, обнаружив нечто, рассчитанное на конкретную платформу, может просто выдать предупреждение о том, что код нельзя будет перенести на другую платформу, и позволить мне писать код так, как я его писал.
  • Неочевидные приёмы работы с указателями и каламбуры типизации. Такое ощущение, что плохое отношение к подобным вещам возникло лишь ради потенциальной возможности оптимизации кода компиляторами. Я согласен с тем, что использование memcpy() на перекрывающихся областях памяти может, в зависимости от реализации (в современных x86-реализациях копирование начинается с конца области) и от относительного расположения адресов, работать неправильно. Разумно будет воздержаться от подобного. А вот другие ограничения того же плана кажутся мне менее оправданными. Например запрещение одновременной работы с одной и той же областью памяти с использованием двух указателей разных типов. Я не могу представить себе проблему, возникновение которой может предотвратить наличие подобного ограничения (это не может быть проблема, связанная с выравниванием). Возможно, сделано это для того чтобы не мешать компилятору оптимизировать код. Кульминацией всего этого является невозможность преобразования, например, целых чисел в числа с плавающей запятой с использованием объединений. Об этом уже рассуждал Линус Торвальдс, поэтому я повторяться не буду. С моей точки зрения это делается либо для улучшения возможностей компиляторов по оптимизации кода, либо из-за того, что этого требует C++ для обеспечения работы системы отслеживания типов данных (чтобы нельзя было поместить экземпляр некоего класса в объединение, а потом извлечь его как экземпляр совсем другого класса; это тоже может как-то повлиять на оптимизации).
  • Поведение кода, определяемое реализацией (то есть поведение, которое не в точности отражает то, что предписано стандартом). Мой любимый пример подобной ситуации это вызов функции: в зависимости от соглашения о вызове функций и от реализации компилятора аргументы функций могут вычисляться в совершенно произвольном порядке. Поэтому результат вызова foo(*ptr++, *ptr++, *ptr++) неопределён и на подобную конструкцию не стоит полагаться даже в том случае, если программисту известна целевая архитектура, на которой будет выполняться код. Если аргументы передаются в регистрах (как в архитектуре AMD64) компилятор может вычислить значение для любого регистра, который покажется ему подходящим.
  • Полностью неопределённое поведение кода. Это тоже такой случай, когда сложно спорить со стандартом. Пожалуй, самый заметный пример такого поведения кода связан с нарушением правила, в соответствии с которым значение переменной в одном выражении можно менять лишь один раз. Вот как это выглядит в одном знаменитом примере: i++ + i++. А вот пример, который выглядит ещё страшнее: *ptr++ = *ptr++ + *ptr++е.

C++ это язык более высокого уровня, чем C. В то время, как в C++ имеется большинство возможностей C, использовать эти возможности не рекомендуется. Например, вместо прямого преобразования типов надо применять reinterpret_cast<>, а вместо указателей надо применять ссылки. От С++-программистов не ждут того, что они будут понимать низкоуровневый код так же хорошо, как C-программисты (это, конечно, лишь средняя температура по больнице, в реальности всё зависит от конкретного программиста). И всё же, из-за того, что существует очень много C++-программистов, и из-за того, что C и C++ часто воспринимают как C/C++, C-компиляторы часто расширяют в расчёте на поддержку ими C++ и переписывают на C++ для того чтобы упростить реализацию сложных конструкций. Это произошло с GCC, и того, кто начнёт отлаживать с помощью GDB С++-код, находящийся в .c-файлах, ждёт много интересного. Грустно то, что для того чтобы скомпилировать код C-компилятора нужен C++-компилятор, но, чему нельзя не радоваться, всё ещё существуют чистые C-компиляторы, вроде LCC, PCC и TCC.

Итоги


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

Но, по крайней мере, нельзя заменить C90 на какой-нибудь C90 Special Edition и сделать вид, что C90 никогда не существовало.

Как вы относитесь к языку C?


Подробнее..

Перевод Мы стоим на пороге кризиса Фальшивой науки

20.06.2021 14:08:21 | Автор: admin


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

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

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

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

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

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

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

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

Camille Nos


Часть этой проблемы смоделирована намеренно. К примеру, Camille Nos никак не связано с ИИ, но все равно заслуживает упоминания. Созданное в марте 2020 года, Nos уже выступило соавтором более, чем 180 работ в таких разносторонних областях, как астрофизика, компьютерная наука и биология.

Я использовал оно, потому что Nos не является реальным человеком. На деле это псевдо-личность, созданная французским движением в защиту науки RogueESR. В качестве первого имени было взято французское гендерно-нейтральное Camille, а в качестве фамилии слияние греческого слова , означающего разум/познание, и французского слова nous, означающего мы.

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

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

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


Усилия сообщества должны быть стандартизированы, но пока для этого нет системы

Указание авторов там, где они не участвовали


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

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

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

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


Когда речь идет о написании научных работ, то путь мысли старого-доброго человеческого ума пока еще превосходит налучший ИИ

Алгоритмы плохие писатели


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

Аналогичным образом в 2005 году трое студентов, изучавших компьютерные науки, решили приколоться над научным сообществом, разработав программу SCIgen. Она генерирует абсолютно бессмысленные работы с графами, иллюстрациями и цитатами, приправленные множеством заумных слов из компьютерной науки. Одна из таких статей даже была принята к участию в конференции. Более того, в 2013 году различными издателями было отозвано 120 работ, когда вскрылось, что написала их SCIgen. За 2015 год сайт программы все еще зарегистрировал около 600 000 посещений.

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

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

Конечно, это не все, и на горизонте маячит еще один вопрос: Как долго написанием научных работ будут заниматься только люди? Может ли случиться так, что через 10 или 20 лет ИИ-алгоритмы станут способны автоматически анализировать обширные объемы литературы и делать собственные заключения в новой работе, соответствующей высшим научным стандартам? Как тогда мы будем отдавать должное этим алгоритмам или их создателям?

Хотя сегодня мы пока имеем дело с намного более простыми вопросами: Как выявить работы, написанные относительно несложными алгоритмами, и не несущие никакой смысловой нагрузки? Что с ними в итоге делать? Помимо добровольных усилий и принуждения лже-авторов отзывать свои работы, научное сообщество имеет поистине мало ответов на эти вопросы.


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

Противодействие фальшивой науке


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

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

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

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

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

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

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

Помимо науки: все больше фейковых новостей


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

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

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

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

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

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

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


Подробнее..

Перевод Оптимизация веб-графики в 2021 году

20.06.2021 18:15:44 | Автор: admin
Изображения, используемые на веб-страницах, привлекают пользователей, пользователи довольно-таки охотно щёлкают по ним мышью. Изображения делают веб-страницы лучше во всём кроме скорости работы страниц. Изображения это огромные куски байтов, которые обычно являются теми частями сайтов, которые загружаются медленнее всего. В этом материале я собрал всё, что нужно знать в 2021 году об улучшении скорости работы веб-страниц через оптимизацию работы с изображениями.



Изображения обычно имеют большие размеры. Даже очень большие. В большинстве случаев CSS- и JavaScript-ресурсы, необходимые для обеспечения работоспособности страниц это мелочь в сравнении с тем объёмом данных, который нужно передать по сети для загрузки изображений, используемых на страницах. Медленные изображения могут повредить показателям Core Web Vitals сайта, могут оказать воздействие на SEO и потребовать дополнительных затрат на трафик. Изображения это обычно тот самый ресурс сайта, который оказывает решающее воздействие на показатель Largest Contentful Paint (LCP) и на задержки загрузки сайта. Они способны увеличить показатель Cumulative Layout Shift (CLS). Если вы не знакомы с этими показателями производительности сайтов почитайте о них в Definitive Guide to Measuring Web Performance.

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

1. Формат изображений


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

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


Изображения ленивца

Слева мы можем видеть фото нашего товарища-ленивца Сэма. Эта картинка в формате JPG занимает всего лишь 32,7 Кб. А если то же самое изображение преобразовать в формат PNG размер графического файла увеличится более чем вдвое до 90,6 Кб!

Справа находится рисунок со всё тем же Сэмом. Этот рисунок лучше всего хранить в формате PNG. Так он занимает всего 5,5 Кб. А если преобразовать его в JPG, то его размер подскочит до 11,3 Кб.

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

Существует, конечно, ещё много графических форматов! Если у вас имеется некое векторное изображение (состоящее из всяческих линий и геометрических фигур), то вам лучше всего подойдёт формат SVG. Более новые браузеры поддерживают и более современные графические форматы вроде AVIF и WebP. Их использование для хранения подходящих изображений позволяет добиться ещё более серьёзного уменьшения размеров графических файлов.

2. Отзывчивые изображения и их пиксельные размеры


Не все посетители сайта будут просматривать его в одних и тех же условиях. У кого-то имеется огромный монитор шириной в 1600 пикселей. А кто-то смотрит сайт на планшете с шириной экрана в 900 пикселей, или на телефоне с экраном шириной в 600 пикселей. Если на сайте применяется изображение шириной в 1200 пикселей это будет означать, что при просмотре такого сайта на устройствах с небольшими экранами сетевые и другие ресурсы будут тратиться впустую, так как размер таких изображений при выводе на экран, всё равно, будет уменьшен.


Просмотр сайта на устройствах с разными экранами

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

<img src="picture-1200.jpg"srcset="picture-600.jpg  600w,picture-900.jpg  900w,picture-1200.jpg 1200w"sizes="(max-width: 900px) 100vw, 1200px"alt="my awesome picture" height="900" width="1200" />

В данном случае ширина базового изображения составляет 1200 пикселей. Оно, кроме того, является изображением, записанным в атрибут src тега и используемым по умолчанию. В srcset описаны 3 варианта изображения шириной в 600, 900 и 1200 пикселей. В sizes используются медиа-запросы CSS, позволяющие дать браузеру подсказку, касающуюся видимой области, доступной для вывода изображения. Если ширина окна меньше 900 пикселей место, где будет выведено изображение, займёт всю его ширину 100vw. В противном случае место для вывода изображения никогда не окажется шире 1200 пикселей.

Большинство инструментов для работы с изображениями, вроде Photoshop, Gimp и Paint.NET, умеют экспортировать изображения в различных размерах. Стандартные системные графические программы тоже, в определённых пределах, способны решать подобные задачи. А если надо автоматизировать обработку очень большого количества изображений возможно, есть смысл взглянуть на соответствующие инструменты командной строки вроде ImageMagick.

Скрытие изображений при просмотре сайта на мобильных устройствах


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

<img src="picture-1200.jpg"srcset="picture-600.jpg  600w,picture-900.jpg  900w,picture-1200.jpg 1200w"sizes="(max-width: 600px) 0, 600px"alt="my awesome picture" height="900" width="1200" />

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

3. Качество изображений


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


Исходное PNG-изображение с прозрачными участками имеет размер 57 Кб. Такое же изображение, но сжатое, имеет размер 15 Кб.

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

4. Встраивание изображений в веб-страницы


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

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


Изображение, встроенное в страницу

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

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

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

А вот удобный веб-инструмент для преобразования изображений в формат base64.

5. Ленивая загрузка изображений


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

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

В спецификации HTML есть атрибут loading, которым можно воспользоваться для организации ленивой загрузки изображений, но он пока имеет статус экспериментального. А Safari ещё даже его не поддерживает. В результате о его широком использовании говорить пока нельзя. Но, к счастью, ленивую загрузку изображений можно организовать средствами JavaScript.

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

var lazyEls = [].slice.call(document.querySelectorAll("[data-src]"));var lazyObserver = new IntersectionObserver(function(entries) {entries.forEach(function(entry) {if (entry.isIntersecting) {var el = entry.target;var src = el.getAttribute("data-src");if (src) { el.setAttribute("src", src); }lazyObserver.unobserve(el);}});});lazyEls.forEach(function(el) {lazyObserver.observe(el);});

Тут, для определения того момента, когда надо загружать изображение, используется объект IntersectionObserver. Когда наступает нужный момент содержимое атрибута data-src копируется в атрибут src и изображение загружается. Тот же подход можно применить к атрибуту srcset и воспользоваться им при работе с любым количеством изображений.

Пользуются этим, переименовывая атрибут src в data-src.

<img data-src="picture-1200.jpg"loading="lazy" class="lazy" />

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

Настройка размеров области, которую займёт изображение


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

Избежать сдвига макета страницы можно, указав атрибуты height и width тега <img>.

<img data-src="picture-1200.jpg"loading="lazy" class="lazy"width="1200" height="900" />

Обратите на то, что значения атрибутов height и width это не 1200px и 900px. Это просто 1200 и 900. И работают они немного не так, как можно было бы ожидать. Размер соответствующего изображения не обязательно будет составлять 1200x900 пикселей. Этот размер будет зависеть от CSS и от размеров макета страницы. Но браузер, благодаря этим атрибутам, получит сведения о соотношении сторон изображения. В результате, узнав ширину изображения, браузер сможет правильно настроить его высоту.

То есть, например, если макет страницы имеет в ширину всего 800px, то браузер, не загружая изображение, будет знать о том, что ему надо зарезервировать 600px вертикального пространства для вывода изображения с правильным соотношением сторон.

Итоги


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

Как вы оптимизируете изображения, используемые в ваших веб-проектах?


Подробнее..

Категории

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

  • Имя: Билал
    04.12.2024 | 19:28
  • Имя: Murshin
    13.06.2024 | 14:01
    Нейросеть-это мозг вселенной.Если к ней подключиться,то можно получить все знания,накопленные Вселенной,но этому препятствуют аннуннаки.Аннуннаки нас от неё отгородили,установив в головах барьер. Подр Подробнее..
  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2025, personeltest.ru