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

Перевод

Перевод Как ловили хакера и инсайдера во Всемирном банке

31.05.2021 10:13:24 | Автор: admin

Продолжаем веб-серфинг в поисках крутых ИБ-историй. И вот сегодня поучительный рассказ о том, как Амели Коран (Amlie Koran aka webjedi) практически голыми руками поймала хакера, атаковавшего сервера Всемирного банка, а также инсайдера, который пытался сыграть на этой истории. Обезвредив их, она вышла на гораздо более крупную и опасную дичь. Историей этой с общественностью поделился англоязычный подкастDarknetdiaries. Приводим пересказ эпизода.

Скриншот из https://darknetdiaries.com/transcript/91/Скриншот из https://darknetdiaries.com/transcript/91/

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

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

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

Решение о создании Всемирного банка, а также Международного валютного фонда, было принято на Бреттон-Вудской конференции, состоявшейся в США в 1944 году. Банк начал активную деятельность в 1945-м и ставил своей целью кредитование стран, которым требовалась помощь в восстановлении экономики, пострадавшей от Второй мировой войны. Таким образом, практически весь восстановившийся послевоенный мир оказался со временем должен Всемирному банку. Позднее кредитная организация добавила в число своих клиентов-должников неохваченные изначально развивающиеся страны.

Как такие, как Амели, становятся мистером Вульфом, который решает все проблемы

Фото - https://twitter.com/webjediФото - https://twitter.com/webjedi

Прежде чем рассказать о сути инцидента, несколько слов о человеке, который с ним справился. Амели Коран закончила колледж в 1993-м, изучала программирование и социологию. Поработала в Xerox дизайнером пользовательских интерфейсов, системным администратором, отвечала за безопасность серверной инфраструктуры Американского химического сообщества. По-настоящему масштабные задачи ждали Амели в компании, отвечавшей за поставки газа и электричества в ряд штатов США. Не успела она устроиться туда, в службу DFIR (Digital Forensics and Incident Response), как налетел ураган и вывел из строя часть инфраструктуры. Пришлось на ходу и на ветру менять подходы к проектированию катастрофоустойчивых ЦОД, а заодно учиться работать в авральном режиме. Полученный опыт Амели развила в компании Mandiant, специализировавшейся на кибербезопасности и позднее в FireEye одном из мировых лидеров по борьбе с угрозами нулевого дня.

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

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

Инцидент

Система мониторинга целостности файлов Всемирного банка зафиксировала изменения на одном из серверов, HSM (Hardware Security Module), по сути, секретном шкафчике со всем банковским криптографическим материалом. Служба безопасности выяснила, что к событиям не причастны системные администраторы, подозревали кого-то из посторонних.

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

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

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

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

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

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

Скриншот из мультфильма "Головоломка", Walt Disney PicturesPixar Animation Studios. 2015 г.Скриншот из мультфильма "Головоломка", Walt Disney PicturesPixar Animation Studios. 2015 г.

Амели Коран вспоминает, как во время очередного безумного конф-колла, где принимали участие CIO, CISO и прочие шишки, ей, скромному наемному подрядчику, даже пришлось прикрикнуть на них: Успокойтесь все, черт вас возьми! В оригинале фраза звучала грубее. Сожалений за этот эмоциональный всплеск она не испытывает. Наоборот. Одна из основных задач специалиста по сложным инцидентам, считает она, внести хоть какое-то подобие порядка в ситуацию, когда у людей от стресса уже идет пар из ушей. Ни высокий IQ, ни глубокие познания в киберрасследованиях не заменят холодной головы.

Вчера в кабинете, а завтра в газете!

Рядовым сотрудникам банка не были известны подробности инцидента, зато о них как-то прознала пресса. Издание Wall Street Journal, а затем и Fox News сообщили, в частности, что Всемирный банк переживает беспрецедентный кризис, сославшись на письмо технического директора. То, что ситуация стала публичной, добавило нервозности. Но главное, злоумышленник, если еще не был в курсе, узнал, что его обнаружили.

Скриншот статьи на Fox News https://www.foxnews.com/story/world-bank-under-cyber-siege-in-unprecedented-crisis Скриншот статьи на Fox News https://www.foxnews.com/story/world-bank-under-cyber-siege-in-unprecedented-crisis

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

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

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

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

Крупная рыба

С жесткого диска ПК инсайдера был сделан отпечаток. Использовав для этого EnCase (инструмент для форензики) и некоторые другие инструменты, эксперт обнаружила, что письма в СМИ сотрудник отправлял через веб-почту Yahoo, а не через корпоративную Lotus Notes.

Параллельно выяснилось, что инсайдер был связан с прежним руководителем Всемирного банка Полом Вольфовицем (Paul Wolfowitz). Об этом человеке стоит рассказать чуть подробнее. Кандидатуру Пола в качестве руководителя Всемирного банка предложил не кто-нибудь, а лично президент США Джорж Буш-младший. Это вызвало недоумение у финансовых журналистов, поскольку прежней должностью Пола была заместитель Министра обороны. Правда, уволен Пол был не по причине финансового или военного скандала, а потому что устроил в банк свою знакомую.

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

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

Как и зачем преступники вошли в банк?

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

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

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

Так кому же потребовался взлом Всемирного банка?

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

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

Подробнее..

Перевод Почему я считаю Haskell хорошим выбором с точки зрения безопасности ПО?

31.05.2021 18:12:13 | Автор: admin


Команда Typeable понимает ценность безопасности. Мы любим Haskell, но стоит ли его выбирать, если ваша цель создание защищенного программного обеспечения? Хотелось бы сказать да, но как и для большинства эмпирических вопросов о разработке ПО, здесь просто нет объективного доказательства, подтверждающего, что Haskell или ещё какой-нибудьй язык программирования обеспечивает большую безопасность, чем любой другой. Нельзя сказать, что выбор языка в Typeable не имеет значения для безопасности, но какое именно значение он имеет, еще нужно подумать.


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


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


   Чисто техническая            Уязвимость, относящаяся        уязвимость                исключительно к предметной области                                                                                           Инструментарий Инструментарий  Нужно     должен исправить может помочь  подумать

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


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


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


В таких сервисах обычно есть соблазн записать файл пользователя непосредственно в файловую систему сервера. Однако под каким именем файла? Использовать непосредственно имя файла пользователя верный путь к катастрофе, так как оно может выглядеть как ../../../etc/nginx/nginx.conf, ../../../etc/passwd/ или любые другие файлы, к которым сервер имеет доступ, но не должен их изменять.


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


Использование шкалы


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


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


Haskell нижняя часть шкалы


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


// From imaginary CSRF token protection:if ($tokenHash == $hashFromInternet->{'tokenHash'}) {  echo "200 OK - Request accepted", PHP_EOL;}else { echo "403 DENIED - Bad CSRF token", PHP_EOL;};

Видите, в чем здесь проблема? В большинстве динамических языков, таких как PHP, тип записи JSON определяется в процессе выполнения и зачастую основывается на структуре входных данных. Кроме того, в объектно-ориентированном программировании тип используется для выбора поведения с помощью динамической диспетчеризации, что фактически позволяет злоумышленнику выбирать, какой код будет исполнен. Наконец, в языке PHP равенство, выраженное через ==, зависит от типа вводимых данных, и в приведенном выше примере атакующий может полностью обойти защиту.


Аналогичная проблема возникла с Java (и другим языками, см. https://frohoff.github.io/appseccali-marshalling-pickles/). Java предложил исключительно удобный способ сериализации любого объекта на диск и восстановления этого объекта в исходной форме. Единственной досадной проблемой стало отсутствие способа сказать, какой объект вы ждете! Это позволяет злоумышленникам пересылать вам объекты, которые после десериализации в вашей программе превращаются во вредоносный код, сеющий разрушения и крадущий данные.


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


data Request = Request {csrfToken :: Token, ... other fields}doSomething :: Session -> Request -> Handler ()doSomething session request  | csrfToken session == csrfToken request = ... do something  | otherwise = throwM BadCsrfTokenError

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


Haskell середина шкалы


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


Прежде всего, в Haskell имеется возможность моделировать данные более точно по сравнению с такими языками, как как C, Javascript или даже Java. В основном это обусловлено удобством его синтаксиса и наличием типов-сумм. Точное моделирование данных имеет значение для безопасности, поскольку код домена в основном представляет собой модель некоторого реального явления. Чем меньше ее точность, тем больше возможностей имеют злоумышленники.


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


data SSN = Unknown | Redacted | SSN Text

А теперь сравним моделирование той же идеи с использованием строковых величин "", "<REDACTED>" и "191091C211A". Что произойдет, если пользователь введет "<REDACTED>" в поле ввода SSN? Может ли это в дальнейшем привести к проблеме? В Haskell об этом можно не беспокоиться.


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


storeFileUpload :: Path Abs File -> ByteString -> IO ()storeFileUpload path = ...

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


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


Haskell и ошибки домена


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


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


Однако это все догадки. Сообщество Haskell до сих пор достаточно мало, чтобы не быть объектом атак, а специалисты по Haskell в общем случае еще не так сильно озабочены проблемами безопасности, как разработчики на Javascript или Python.


Заключение


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

Подробнее..

Перевод Проект, который сжег меня до тла

14.04.2021 02:22:44 | Автор: admin

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

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

Пролог

Чтобы понять, что со мной происходит в данный момент, надо немного отмотать время.

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

Менеджер вызывает меня в комнату и объясняет, что мое первое задание это крупный проект. Да, я работаю в небольшом агентстве, но клиент очень важный. Объем проекта? Сумасшедший. Сроки? Еще безумнее. Хуже всего, что мне придется работать над этим самостоятельно. Я еще не понимаю почему, но осознаю, что мне это не понравится.

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

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

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

Запомни, ты не рок-звезда

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

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

Проблема в том, что вы не дотягиваете до рок-звезд...

И когда вы понимаете это, начинаете пахать на износ. Пытаетесь достичь целей, которые достижимы только при работе адовое количество часов. Спустя какое-то время СТОЛЬКО работать становится нормальным. Но вы больше не замечаете, сколько кодите. Хоть именно это ежедневно опускает вас все глубже на дно.

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

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

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

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

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

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

Добро пожаловать в ад

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

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

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

Я не мог остановиться, несмотря на то, сколько людей говорили мне это.

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

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

Ты не машина

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

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

1. Чем больше времени вы тратите на проект, тем менее вы продуктивны

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

Чем больше ты работаешь, тем больше устаешь, чем больше устаешь, тем хуже работаешь, чем хуже работаешь, тем больше стрессуешь, чем больше стрессуешь, тем хуже работаешь.

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

2. Коммуникация и прозрачность являются ключевыми факторами

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

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

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

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

3. Спокойствие и дисциплина это не вариант

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

Нет, он обязательно облажается. И вы вместе с ним.

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

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

Не так уж и важно

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

Пора было идти к клиенту.

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

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

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

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

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

Что бы вы ни кодили, это не так уж важно

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

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

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

У вас есть выбор

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

Вы склонны забывать об этом, но вы король на рынке незнакомцев.

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

Самый простой способ выйти из подобной ситуации поискать что-то другое.

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

Ситуация, в которой вы оказались, это ваша вина.

У вас есть выбор. Зайдите на сайт с вакансиями и найдите вариант получше. Вы заслуживаете лучшего. Эта работа не должна так сильно влиять на вашу жизнь. Ни одна работа не должна. Это просто работа.

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

Никогда так эмоционально не увлекайтесь работой.

Эпилог

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

Оригинал текста отсюда, картинки из фильмов "Адреналин" и "Адреналин 2"

Подробнее..

Что такое алгоритм?? Part three and a quarter. Язык

10.06.2021 08:18:29 | Автор: admin

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


.

Title


Задача


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


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


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


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


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


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


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


И давайте обсуждать вместе. Ведь это "жжж" рядом с заголовком "Что такое алгоритм ?!" неспроста.


А зачем тебе жужжать, если ты не пчела? По-моему, так

А зачем на свете пчелы? Для того, чтобы делать мёд.

Жжж


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


Слово


Пора уже начать. И в этот раз совсем нет причин изменять привычному для этой серии статей формату. Мы опять будем "терзать" общепринятые термины из Википедии. На нашем пути термин "Слово":


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

Как ни странно, но представленное определение весьма "крепкое", хоть пока и не формальное. А для выполнения его формализации необходима лишь малость. Нужно:


  • формализовать алгоритм именования предметов, взаимодействий и их качеств (характеристик);
  • понять и определить, что есть "мнимое и отвлеченное понятие", и затем формализовать алгоритм создания этой абстракции в человеческом воображении.

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


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


мультфильм головоломка


Коммуникация


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


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


Ибо именно уникальность и коллективная идентифицируемость это первооснова любого "Слова"? Если бы сигнал "Опасность" и сигнал "Вкусная еда" были бы одинаковы, то алгоритмы избегания стадом хищника работали бы совсем неудовлетворительно, и в результате исчезло бы и стадо, а вместе со стадом и выполняемые им алгоритмы.


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


Итак, есть мостик от алгоритмов коллективного выживания к алгоритмам "именования предметов". Почему здесь использовано слово "алгоритмам" во множественном числе? Это связано с разной природой "именуемых" явлений. И совсем немного этому посвящена пара статей "Математика" и "Физика". Там намеренно отдельно в каждой статье рассмотрены особенности "переноса" в пространство модели для "стада коров" и для "падения яблока".


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


Именование трансляцией


Абстракция


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


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


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


Итак, на основе рассмотренного ранее мы с Вами уже можем еще неформально, но в формальных терминах (отмеченных курсивом) теоретической части этой работы, сформулировать своё определение "Слова":


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

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


Копирование поведения


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


Смысл


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


Question of life


$\begin{gathered}42 \\ = \\ (-80538738812075974)^3 + \\ 80435758145817515^3 + \\ 12602123297335631^3\end{gathered}$



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


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


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


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


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


Главный смысл "Слова" содержится в полезных алгоритмах, которые используют это слово! Да, смысл совсем не внутри фонем, букв, слогов "слова" и не в способах именовать объекты окружающего пространства. Этот смысл "снаружи"! Он в использовании. Он в алгоритмах!!!


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


Для "гуманитарных" слов порой отсутствует "использующий смысл". И слово становится просто интересным алгоритмом, необходимым только для развлечения того кто его слышит. Вспомним для примера "Антиподов" упомянутых Алисой в предыдущей статье. До появления использования этого слова в статье "Физика" оно было просто развлечением и игрой Льюиса Кэрролла (или даже скорее переводчика его сказки на русский язык). Это было ироничное издевательство над алгоритмом "именования" с копированием того, как в своих играх этот алгоритм изучают дети. Но ситуация поменялась с появлением текущей статьи. В ней появилось новое использование слова "Антипод" в качестве лингвистического примера. И у этого "Слова" появился новый смысл! Ведь это совсем неожиданный для математика процесс?


Алиса и Аня


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


Перевод


Вы можете сказать, что перевод на другой язык для отдельного "Слова" не очень интересен. И он с успехом происходит во всех двуязычных словарях. И будете, конечно, правы. Но для перехода к несомненно более сложному переводу на другой язык сложной совокупности "Слов", которой является "Предложение" и "Текст", ведь необходимо формализовать что есть "Предложение" и "Текст"? И, конечно, это тоже нам будет необходимо сделать, но чуть позже. А сейчас полезным является даже рассмотрение более "простого" варианта. И это ограничение только одним "Словом" своим упрощением даже является нам помощью. Потому что причины сложности перевода с одного языка на другой во многом обусловлены смыслом, который несёт каждое "Слово".


Думаю сейчас опять стоит обратиться к Википедии. И разобрать следующий существующий на её страницах термин слово "Перевод":


Перевод деятельность по интерпретации смысла текста на одном языке (исходном языке) и созданию нового эквивалентного ему текста на другом языке (переводящем языке).

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


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


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


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


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


Кузинатра


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


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


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


Выводы


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


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


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


Намечен эволюционный переход от естественных языков к языкам программирования.


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


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


Спасибо Вам за внимание.


Отзывы


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


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


Ссылки


Подробнее..

Перевод Почему Kotlin хуже, чем Java?

20.05.2021 18:13:57 | Автор: admin

Такой провокационный вопрос задал реддитор nenemen в сабреддите Java:

Я думаю о том, чтобы свой следующий проект сделать на Kotlin + Spring Boot, но мощь всенародной любви к Kotlin и одновременно ненависти к Java заставляют всё это походить на какой-то культ. Поэтому хотел бы услышать аргументы против.

Мы в FunCorp в своё время сделали именно такой выбор в пользу Kotlin. И сегодня соотношение Java/Kotlin у нас составляет примерно 20 на 80, продолжая уменьшаться при каждом удобном случае. Поэтому ответы на этот вопрос меня заинтересовали, и я стал листать секцию комментариев. Там наткнулся на реплику реддитора rzwitserloot, которая мне показалась настолько взвешенной, многосторонней и рациональной, что я захотел поделиться ей с нашей командой, а заодно и читателями Хабра.

Далее перевод его аргументов.

Причины использовать Java вместо Kotlin

1. Kotlin более проприетарный. Например, изрядное количество подробностей внутренней работы kotlinc скрыто внутри сгенерированных файлов классов, представляющих из себя аннотации @Metadata с бинарными данными (байтовыми массивами, разрешёнными в аннотациях) внутри. Насколько мне известно, эти данные не описаны ни в каких публичных спецификациях. Также многие типы в Kotlin жёстко закодированы. Это вполне утилитарный подход, но он означает, что без IDEA (автор, видимо, имел в виду JetBrains компанию-разработчик языка Kotlin и серии IDE для работы с разными языками программирования прим. переводчика) Kotlin немедленно умрет. Конечно, это мелкая придирка, но, возможно кому-то этот недостаток открытости будет важен.

2. Есть ощущение, что и сообщество, и IDEA продвигают Kotlin так, будто это Java, но без уродливых нашлёпок. Но что из этого следует? Останется ли Kotlin языком, который чрезвычайно легко освоить, если вы уже знаете Java и так похож на Java, что вы можете взаимозаменять Java и Kotlin, а также вызывать одно из другого почти без усилий в обозримом будущем (в этом случае я вижу проблемы, о которых расскажу ниже)? Или это был способ первоначальной популяризации и роста пользовательской базы, чтобы быстро заполучить сразу кучу Java-разработчиков и дать им возможность постепенно переводить свой код с одного языка на другой шаг за шагом, используя совместимость вызовов и двойную компиляцию? В этом случае к будущему языка тоже есть много вопросов. Мне кажется, что ребята, делающие Kotlin, думали, что оба варианта верные, но на самом деле они взаимоисключающие. Если попросить людей, которые понимают в Kotlin больше моего, объяснить, почему два следующих варианта неверны или не приведут в будущем к проблемам, то сначала решите между собой, что такое Kotlin и для чего он предназначен.

Если Kotlin всегда будет как Java, только лучше

Это реальная проблема. Лучше всего объяснить её на примере новой фичи языка.

Все фичи Kotlin делятся на три категории:

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

  • Что-то, чего в Java не было и нет до сих пор. Они решили, что это нечто важное и сделали полностью по-своему.

  • Что-то, чего в Java раньше не было, но теперь появилось. Если Kotlin очень повезло, то в Java это сделали достаточно похоже, и разработчику в процессе переучивания с Java на Kotlin это не создаёт проблем. Но что, если это не так?

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

Приведу пример: присвоение типа конструкцией instanceof. Когда она появилась в Kotlin, в Java такого функционала не было даже в проекте: не было ни JEP, ни постов в блогах или рассылках (например amber-dev@openjdk.net).Такое поведение instanceof решает типичную задачу: проверить, принадлежит ли переменная к подходящему классу, и если да то работать с данными в переменной с учётом этого.

В Java вплоть до 14-й версии это выглядело так:

if (x instanceof String) {String y = (String) x;System.out.println(y.toLowerCase());}

В Kotlin сделали примерно так:

if (x instanceof String) {// теперь x имеет тип String!System.out.println(x.toLowerCase());}

Но в Java версии 16+ стало так:

if (x instanceof String y) {System.out.println(y.toLowerCase());}

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

if (!(x instanceof String y)) return;System.out.println(y.toLowerCase());

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

Теперь Kotlin должен сделать выбор:

  1. Всё сломать, убить старый способ и переделать как в Java. Это будет катастрофой, и потому крайне маловероятно. Такой вариант сломает огромную кучу чужого кода и навсегда подорвёт доверие к языку. Кому нужен инструмент, который будет ломаться каждый год или два?

  2. Оставить всё как есть. Это значит, что аргумент перейти с Java на Kotlin легко будет всё слабее и слабее с каждым новым релизом и новой фичей Java.

  3. Оставить всё как есть и добавить новый синтаксис. Звучит хорошо, но это значит, что с каждым своим релизом Kotlin будет накапливать вдвое больше плохих решений, чем Java. Со временем Kotlin превратится в ужасную разбухшую помойку формата куча-способов-сделать-одно-и-то-же. Это приведёт к тому, что изучать Kotlin будет сложнее, а пользы это не принесёт.

  4. Более хитроумное, но сложное решение. Например, сделать оба варианта, но исходный объявить устаревшим и через какое-то время удалить. Это означает, что Kotlin навсегда останется в тени Java без единого шанса оттуда выбраться.

Ни один из этих пунктов не кажется хорошим. Что приводит к более позитивному варианту номер два: начать отходить в сторону от синтаксиса Java. Отсюда следует:

Как Java, но лучше было способом получить первоначальное ускорение и пользовательскую базу

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

Это не худший вариант, но количество историй успеха новых языков программирования (видимо, автор имел в виду языки, работающие в JVM прим. переводчика) исчезающе мало. Scala, например, практическ мёртв. Конечно, он собрал изрядно хайпа, на него перешёл Twitter, организовал кучу классных митапов для разрабов, и на сегодня у Scala меньше пользователей, чем было тогда. Это подтверждается инструментами типа TIOBE, которые оставляют желать лучшего в части точности, но давайте будем честны с собой: набирает ли Scala обороты в последнее время? Fan/Fantom зашёл в абсолютный тупик, Groovy не подаёт признаков жизни настолько, что Gradle пытается диверсифицироваться от него подальше. JRuby и Jython появились и исчезли, в том смысле, опять же, что никого они не вдохновили.

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

Ему придётся опираться только на себя и перестать рекламировать доступность всех преимуществ Java и её экосистемы. Пример с instanceof уже демонстрирует, почему я думаю, что Kotlin не будет лучше Java: почти каждая новая фича, которая появилась в Java недавно или вот-вот появится (в смысле, имеет активный JPE и обсуждается в рассылках) выглядит более продуманной, чем любая фича Kotlin. Java развивается в сторону доступа к нативным 80-битным регистрам CPU сложных типов, сохраняющих производительность и требования к памяти как у примитивов, так и в целой новой парадигме программирования, основанной на присвоении типов и деконструировании значений.

[конец перевода]

От себя: очень хочется не согласиться с этим суждением, но в комментариях на Hacker News большинство контраргументов опровергаются. Например, поддержка со стороны Google штука во многом политическая, которая в любой момент может прекратиться, когда тяжба с Oracle закончится. Да и дефицит внимания характерная вещь для Google, пример Dart отбивает охоту делать на него ставку. Нативная поддержка null, которая греет душу любого котлиниста, легко заменяется в Java на обёртку из Optional.ofNullable. Data-объекты могут быть заменены более богатым функционалом record. Скорость компиляции, да и в целом производительность в Kotlin по сравнению с Java тоже оставляют желать лучшего.

Как думаете, сохранит Kotlin свою популярность через пять лет и почему?

Подробнее..

Перевод Rust 1.51.0 const generics MVP, новый распознаватель функциональности Cargo

26.03.2021 20:17:43 | Автор: admin

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


Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.51.0 вам достаточно выполнить следующую команду:


rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.


Что было стабилизировано в 1.51.0


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


Константные обобщения (Const Generics MVP)


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


struct FixedArray<T> {              // ^^^ Определение обобщённого типа.    list: [T; 32]        // ^ Где мы использовали его.}

Если затем мы используем FixedArray<u8>, компилятор создаст мономорфизированную версию FixedArray, которая выглядит так:


struct FixedArray<u8> {    list: [u8; 32]}

Этот полезный функционал позволяет писать повторно используемый код без дополнительных затрат во время выполнения. Однако до этого выпуска у нас не было возможности легко объединять значения таких типов. Это наиболее заметно в массивах, где длина указывается в определении типа ([T; N]). Теперь в версии 1.51.0 вы можете писать код, который будет обобщённым для значений любого числа, типа bool или char! (Использование значений struct и enum по-прежнему не стабилизировано.)


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


struct Array<T, const LENGTH: usize> {    //          ^^^^^^^^^^^^^^^^^^^ Определение константного обобщения.    list: [T; LENGTH]    //        ^^^^^^ Мы использовали его здесь.}

Теперь если мы используем Array<u8, 32>, компилятор создаст мономорфизированную версию Array, которая выглядит так:


struct Array<u8, 32> {    list: [u8; 32]}

Константные обобщения добавляют важный новый инструмент для разработчиков библиотек, чтобы создавать новые, мощные и безопасных API во время компиляции. Если вы хотите узнать больше о константных обобщениях, можете почитать статью в блоге Const Generics MVP Hits Beta для получения дополнительной информации об этой функции и её текущих ограничениях. Нам не терпится увидеть, какие новые библиотеки и API вы создадите!


Стабилизация array::IntoIter


Как часть стабилизации константных обобщений, мы также стабилизировали использующее их новое API std::array::IntoIter. IntoIter позволяет вам создать поверх массива итератор по значению. Ранее не было удобного способа итерироваться по самим значениям, только по ссылкам.


fn main() {  let array = [1, 2, 3, 4, 5];  // Раньше  for item in array.iter().copied() {      println!("{}", item);  }  // Теперь  for item in std::array::IntoIter::new(array) {      println!("{}", item);  }}

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


Новый распознаватель функциональности Cargo


Управление зависимостями сложная задача, и одна из самых сложных её частей выбор версии зависимости, когда от неё зависят два разных пакета. Здесь учитывается не только номер версии, но и то, какая функциональность была включена или выключена для пакета. По умолчанию Cargo объединяет функциональные флаги (features) для одного пакета, если он встречается в графе зависимостей несколько раз.


Например, у вас есть зависимость foo с функциональными флагами A и B, которые используются пакетами bar and baz, но bar зависит от foo+A, а baz от foo+B. Cargo объединит оба флага и соберёт foo как foo+AB. Выгода здесь в том, что foo будет собран только один раз и далее будет использован и для bar, и для baz.


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


Общим примером этого из экосистемы может служить опциональная функциональность std во многих #![no_std] пакетах, которая позволяет этим пакетам предоставить дополнительную функциональность, если она включена. Теперь представим, что вы хотите использовать #![no_std] версию foo в вашей #![no_std] программе и использовать foo во время сборки в build.rs. Так как во время сборки вы зависите от foo+std, то и ваша программа тоже зависит от foo+std, а значит более не может быть скомпилирована, так как std не доступна для вашей целевой платформы.


Это была давняя проблема в Cargo, и с этим выпуском появилась новая опция resolver в вашем Cargo.toml, где вы можете установить resolver="2", чтобы попробовать новый подход к разрешению функциональных флагов. Вы можете ознакомиться с RFC 2957 для получения подробного описания поведения, которое можно резюмировать следующим образом.


  • Dev dependencies когда пакет используется совместно как обычная зависимость и dev, возможности dev-зависимости включаются только в том случае, если текущая сборка включает dev-зависимости.
  • Host Dependencies когда пакет совместно используется как обычная зависимость и зависимость сборки или процедурный макрос, features для нормальной зависимости сохраняются независимо от зависимости сборки или процедурного макроса.
  • Target dependencies когда у пакета включены зависимые от платформы features, и он присутствует в графе сборки несколько раз, будут включены только features, подходящие текущей платформе сборки.

Хотя это может привести к компиляции некоторых пакетов более одного раза, это должно обеспечить гораздо более интуитивный опыт разработки при использовании функций с Cargo. Если вы хотите узнать больше, вы также можете прочитать раздел Feature Resolver в Cargo Book для получения дополнительной информации. Мы хотели бы поблагодарить команду Cargo и всех участников за их тяжёлую работу по разработке и внедрению нового механизма!


[package]resolver = "2"# Или если вы используете workspace[workspace]resolver = "2"

Разделение отладочной информации


Хоть это и нечасто освещается в релизах, команда Rust постоянно работает над сокращением времени компиляции. В этом выпуске вносится самое крупное улучшение за долгое время для Rust на macOS. Отладочная информация исходного кода содержится в собранном бинарнике, и за счет этого программа может дать больше информации о том, что происходит во время исполнения. Раньше в macOS отладочная информация собиралась в единую директорию .dSYM при помощи утилиты dsymutil, что могло занимать много времени и дискового пространства.


Сбор всей отладочной информации в эту директорию помогал найти её во время выполнения, особенно если бинарник перемещался. Однако у такого решения есть и обратная сторона: если вы сделали небольшое изменение в вашей программе, то dsymutil необходимо запустить над всем собранным бинарником, чтобы собрать директорию .dSYM. Иногда это могло сильно увеличить время сборки, особенно для крупных проектов, поскольку надо перебирать все зависимости, но это важный шаг, без которого стандартная библиотека Rust не знает, как загружать отладочную информацию на macOS.


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


Вы можете включить новое поведение, установив флаг -Csplit-debuginfo=unpacked при запуске rustc или задав опцию split-debuginfo в unpacked раздела [profile] в Cargo. С опцией "unpacked" rustc будет оставлять объектные файлы (.o) в директории сборки вместо их удаления и пропустит запуск dsymutil. Поддержка бэктрейсов Rust достаточно умна, чтобы понять, как найти эти .o файлы. Такие инструменты, как lldb, также знают, как это делается. Это должно работать до тех пор, пока вам не понадобится переместить бинарники в другое место и сохранить отладочную информацию.


[profile.dev]split-debuginfo = "unpacked"

Стабилизированные API


Итого: в этом выпуске было стабилизировано 18 новых методов для разных типов, например slice и Peekable. Одним из примечательных дополнений является стабилизация ptr::addr_of! и ptr::addr_of_mut!, которая позволяет вам создавать сырые указатели для полей без выравнивания. Ранее это было невозможно, так как Rust требовал, чтобы &/&mut были выровнены и указывали на инициализированные данные. Из-за этого преобразование &addr as *const _ приводило к неопределённому поведению, так как &addr должно быть выровнено. Теперь эти два макроса позволяют вам безопасно создать невыровненные указатели.


use std::ptr;#[repr(packed)]struct Packed {    f1: u8,    f2: u16,}let packed = Packed { f1: 1, f2: 2 };// `&packed.f2` будет создана ссылка на невыровненную память, таким образом это неопределённое поведение!let raw_f2 = ptr::addr_of!(packed.f2);assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);

Следующие методы были стабилизированы:



Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.51.0


Множество людей собрались вместе, чтобы создать Rust 1.51.0. Мы не смогли бы сделать это без всех вас. Спасибо!


От переводчиков


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


Данную статью совместными усилиями перевели andreevlex, TelegaOvoshey, blandger, nlinker и funkill.

Подробнее..

Перевод Vulkan. Руководство разработчика. Непрограммируемые стадии конвейера

26.04.2021 18:04:48 | Автор: admin
Я работаю переводчиком в компании CG Tribe в Ижевске и здесь публикую переводы Vulkan Tutorial (оригинал vulkan-tutorial.com) на русский язык.

Сегодня я хочу представить перевод новой главы раздела, посвященного графическому конвейеру (Graphics pipeline basics), которая называется Fixed functions.

Содержание
1. Вступление

2. Краткий обзор

3. Настройка окружения

4. Рисуем треугольник

  1. Подготовка к работе
  2. Отображение на экране
  3. Графический конвейер (pipeline)
  4. Отрисовка
  5. Повторное создание цепочки показа

5. Буферы вершин

  1. Описание
  2. Создание буфера вершин
  3. Staging буфер
  4. Буфер индексов

6. Uniform-буферы

  1. Дескриптор layout и буфера
  2. Дескриптор пула и sets

7. Текстурирование

  1. Изображения
  2. Image view и image sampler
  3. Комбинированный image sampler

8. Буфер глубины

9. Загрузка моделей

10. Создание мип-карт

11. Multisampling

FAQ

Политика конфиденциальности


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



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

Входные данные вершин


Структура VkPipelineVertexInputStateCreateInfo описывает формат данных вершин, которые передаются в вершинный шейдер. Есть два типа описаний:

  • Описание атрибутов: тип данных, передаваемый в вершинный шейдер, привязка к буферу данных и смещение в нем
  • Привязка (binding): расстояние между элементами данных и то, каким образом связаны данные и выводимая геометрия (повершинная привязка или per-instance) (см. Geometry instancing)

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

VkPipelineVertexInputStateCreateInfo vertexInputInfo{};vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;vertexInputInfo.vertexBindingDescriptionCount = 0;vertexInputInfo.pVertexBindingDescriptions = nullptr; // OptionalvertexInputInfo.vertexAttributeDescriptionCount = 0;vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional

Члены pVertexBindingDescriptions и pVertexAttributeDescriptions указывают на массив структур, которые описывают вышеупомянутые данные для загрузки атрибутов вершин. Добавьте эту структуру в функцию createGraphicsPipeline сразу после shaderStages.

Input assembler


Структура VkPipelineInputAssemblyStateCreateInfo описывает 2 вещи: какая геометрия образуется из вершин и разрешен ли рестарт геометрии для таких геометрий, как line strip и triangle strip. Геометрия указывается в поле topology и может иметь следующие значения:

  • VK_PRIMITIVE_TOPOLOGY_POINT_LIST: геометрия отрисовывается в виде отдельных точек, каждая вершина отдельная точка
  • VK_PRIMITIVE_TOPOLOGY_LINE_LIST: геометрия отрисовывается в виде набора отрезков, каждая пара вершин образует отдельный отрезок
  • VK_PRIMITIVE_TOPOLOGY_LINE_STRIP: геометрия отрисовывается в виде непрерывной ломаной, каждая последующая вершина добавляет к ломаной один отрезок
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: геометрия отрисовывается как набор треугольников, причем каждые 3 вершины образуют независимый треугольник
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: геометрия отрисовывается как набор связанных треугольников, причем две последние вершины предыдущего треугольника используются в качестве двух первых вершин для следующего треугольника

Обычно вершины загружаются последовательно в том порядке, в котором вы их расположите в вершинном буфере. Однако с помощью индексного буфера вы можете изменить порядок загрузки. Это позволяет выполнить оптимизацию, например, повторно использовать вершины. Если в поле primitiveRestartEnable задать значение VK_TRUE, можно прервать отрезки и треугольники с топологией VK_PRIMITIVE_TOPOLOGY_LINE_STRIP и VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP и начать рисовать новые примитивы, используя специальный индекс 0xFFFF или 0xFFFFFFFF.

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

VkPipelineInputAssemblyStateCreateInfo inputAssembly{};inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;inputAssembly.primitiveRestartEnable = VK_FALSE;

Вьюпорт и scissors


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

VkViewport viewport{};viewport.x = 0.0f;viewport.y = 0.0f;viewport.width = (float) swapChainExtent.width;viewport.height = (float) swapChainExtent.height;viewport.minDepth = 0.0f;viewport.maxDepth = 1.0f;

Помните, что размер swap chain и images может отличаться от значений WIDTH и HEIGHT окна. Позже images из swap chain будут использоваться в качестве фреймбуферов, поэтому мы должны использовать именно их размер.

minDepth и maxDepth определяют диапазон значений глубины для фреймбуфера. Эти значения должны находиться в диапазоне [0,0f, 1,0f], при этом minDepth может быть больше maxDepth. Используйте стандартные значения 0.0f и 1.0f, если не собираетесь делать ничего необычного.

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



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

VkRect2D scissor{};scissor.offset = {0, 0};scissor.extent = swapChainExtent;

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

VkPipelineViewportStateCreateInfo viewportState{};viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;viewportState.viewportCount = 1;viewportState.pViewports = &viewport;viewportState.scissorCount = 1;viewportState.pScissors = &scissor;

Растеризатор


Растеризатор преобразует геометрию, полученную из вершинного шейдера, во множество фрагментов. Здесь также выполняется тест глубины, face culling, scissor тест и настраивается способ заполнения полигонов фрагментами: заполнение всего полигона, либо только ребра полигонов (каркасный рендеринг). Все это настраивается в структуре VkPipelineRasterizationStateCreateInfo.

VkPipelineRasterizationStateCreateInfo rasterizer{};rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;rasterizer.depthClampEnable = VK_FALSE;

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

rasterizer.rasterizerDiscardEnable = VK_FALSE;

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

rasterizer.polygonMode = VK_POLYGON_MODE_FILL;

polygonMode определяет, каким образом генерируются фрагменты. Доступны следующие режимы:

  • VK_POLYGON_MODE_FILL: полигоны полностью заполняются фрагментами
  • VK_POLYGON_MODE_LINE: ребра полигонов преобразуются в отрезки
  • VK_POLYGON_MODE_POINT: вершины полигонов рисуются в виде точек

Для использования этих режимов, за исключением VK_POLYGON_MODE_FILL, нужно включить соответствующую опцию GPU.

rasterizer.lineWidth = 1.0f;

В поле lineWidth задается толщина отрезков. Максимальная поддерживаемая ширина отрезка зависит от вашего оборудования, а для отрезков толще 1,0f требуется включить опцию GPU wideLines.

rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;

Параметр cullMode определяет тип отсечения (face culling). Вы можете совсем отключить отсечение, либо включить отсечение лицевых и/или нелицевых граней. Переменная frontFace определяет порядок обхода вершин (по часовой стрелке или против) для определения лицевых граней.

rasterizer.depthBiasEnable = VK_FALSE;rasterizer.depthBiasConstantFactor = 0.0f; // Optionalrasterizer.depthBiasClamp = 0.0f; // Optionalrasterizer.depthBiasSlopeFactor = 0.0f; // Optional

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

Мультисэмплинг


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

VkPipelineMultisampleStateCreateInfo multisampling{};multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;multisampling.sampleShadingEnable = VK_FALSE;multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;multisampling.minSampleShading = 1.0f; // Optionalmultisampling.pSampleMask = nullptr; // Optionalmultisampling.alphaToCoverageEnable = VK_FALSE; // Optionalmultisampling.alphaToOneEnable = VK_FALSE; // Optional

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

Тест глубины и тест трафарета


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

Смешивание цветов


Цвет, возвращаемый фрагментным шейдером, нужно объединить с цветом, уже находящимся во фреймбуфере. Этот процесс называется смешиванием цветов, и есть два способа его сделать:

  • Смешать старое и новое значение, чтобы получить выходной цвет
  • Объединить старое и новое значение с помощью побитовой операции

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

VkPipelineColorBlendAttachmentState colorBlendAttachment{};colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;colorBlendAttachment.blendEnable = VK_FALSE;colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // OptionalcolorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // OptionalcolorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // OptionalcolorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // OptionalcolorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // OptionalcolorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional

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

if (blendEnable) {    finalColor.rgb = (srcColorBlendFactor * newColor.rgb) <colorBlendOp> (dstColorBlendFactor * oldColor.rgb);    finalColor.a = (srcAlphaBlendFactor * newColor.a) <alphaBlendOp> (dstAlphaBlendFactor * oldColor.a);} else {    finalColor = newColor;}finalColor = finalColor & colorWriteMask;

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

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

finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor;finalColor.a = newAlpha.a;

Это может быть настроено с помощью следующих параметров:

colorBlendAttachment.blendEnable = VK_TRUE;colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;

Все возможные операции вы можете найти в перечислениях VkBlendFactor и VkBlendOp в спецификации.

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

VkPipelineColorBlendStateCreateInfo colorBlending{};colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;colorBlending.logicOpEnable = VK_FALSE;colorBlending.logicOp = VK_LOGIC_OP_COPY; // OptionalcolorBlending.attachmentCount = 1;colorBlending.pAttachments = &colorBlendAttachment;colorBlending.blendConstants[0] = 0.0f; // OptionalcolorBlending.blendConstants[1] = 0.0f; // OptionalcolorBlending.blendConstants[2] = 0.0f; // OptionalcolorBlending.blendConstants[3] = 0.0f; // Optional

Если вы хотите использовать второй способ смешивания (побитовая операция), установите VK_TRUE для logicOpEnable. После этого вы сможете указать побитовую операцию в поле logicOp. Обратите внимание, что первый способ автоматически становится недоступным, как если бы в каждом подключенном фреймбуфере для blendEnable было установлено VK_FALSE! Обратите внимание, colorWriteMask используется и для побитовых операций, чтобы определить, содержимое каких каналов будут изменено. Вы можете отключить оба режима, как это сделали мы, в этом случае цвета фрагментов будут записаны во фреймбуфер без изменений.

Динамическое состояние


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

VkDynamicState dynamicStates[] = {    VK_DYNAMIC_STATE_VIEWPORT,    VK_DYNAMIC_STATE_LINE_WIDTH};VkPipelineDynamicStateCreateInfo dynamicState{};dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;dynamicState.dynamicStateCount = 2;dynamicState.pDynamicStates = dynamicStates;

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

Layout конвейера


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

Эти uniform-переменные необходимо указать во время создания конвейера с помощью объекта VkPipelineLayout. Несмотря на то, что мы пока не будем использовать эти переменные, нам все равно нужно создать пустой layout конвейера.

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

VkPipelineLayout pipelineLayout;

Затем создадим объект в функции createGraphicsPipeline:

VkPipelineLayoutCreateInfo pipelineLayoutInfo{};pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;pipelineLayoutInfo.setLayoutCount = 0; // OptionalpipelineLayoutInfo.pSetLayouts = nullptr; // OptionalpipelineLayoutInfo.pushConstantRangeCount = 0; // OptionalpipelineLayoutInfo.pPushConstantRanges = nullptr; // Optionalif (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {    throw std::runtime_error("failed to create pipeline layout!");}

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

void cleanup() {    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);    ...}

Заключение


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

Для создания графического конвейера осталось создать последний объект проход рендера.
Подробнее..

Перевод Rust 1.52.0 улучшения Clippy и стабилизация API

07.05.2021 14:19:42 | Автор: admin

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


Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.52.0 вам достаточно выполнить следующую команду:


rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.


Что было стабилизировано в 1.52.0


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


Ранее запуск cargo clippy после cargo check не запускал Clippy: кэширование в Cargo не видело разницы между ними. В версии 1.52 это поведение было исправлено, а значит, теперь пользователи будут получать то поведение, которое ожидают, независимо от порядка запуска этих команд.


Стабилизированные API


Следующие методы были стабилизированы:



Следующие ранее стабилизированные API стали const:



Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.52.0


Множество людей собрались вместе, чтобы создать Rust 1.52.0. Мы не смогли бы сделать это без всех вас. Спасибо!


От переводчиков


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


Данную статью совместными усилиями перевели Belanchuk, TelegaOvoshey, blandger, nlinker и funkill.

Подробнее..

Перевод Vulkan. Руководство разработчика. Проходы рендера (Render passes)

04.06.2021 18:05:38 | Автор: admin
Меня зовут Александра, я работаю в IT-компании CG Tribe в Ижевске и занимаюсь переводом Vulkan Tutorial на русский язык (ссылка на источник vulkan-tutorial.com).

Сегодня хочу поделиться переводом заключительных глав раздела, посвященного графическому конвейеру (Graphics pipeline basics), Render passes и Conclusion.

Содержание
1. Вступление

2. Краткий обзор

3. Настройка окружения

4. Рисуем треугольник

  1. Подготовка к работе
  2. Отображение на экране
  3. Графический конвейер (pipeline)
  4. Отрисовка
  5. Повторное создание цепочки показа

5. Буферы вершин

  1. Описание
  2. Создание буфера вершин
  3. Staging буфер
  4. Буфер индексов

6. Uniform-буферы

  1. Дескриптор layout и буфера
  2. Дескриптор пула и sets

7. Текстурирование

  1. Изображения
  2. Image view и image sampler
  3. Комбинированный image sampler

8. Буфер глубины

9. Загрузка моделей

10. Создание мип-карт

11. Multisampling

FAQ

Политика конфиденциальности


Проходы рендера




Подготовка


Прежде чем завершить создание графического конвейера нужно сообщить Vulkan, какие буферы (attachments) будут использоваться во время рендеринга. Необходимо указать, сколько будет буферов цвета, буферов глубины и сэмплов для каждого буфера. Также нужно указать, как должно обрабатываться содержимое буферов во время рендеринга. Вся эта информация обернута в объект прохода рендера (render pass), для которого мы создадим новую функцию createRenderPass. Вызовем эту функцию из initVulkan перед createGraphicsPipeline.

void initVulkan() {    createInstance();    setupDebugMessenger();    createSurface();    pickPhysicalDevice();    createLogicalDevice();    createSwapChain();    createImageViews();    createRenderPass();    createGraphicsPipeline();}...void createRenderPass() {}


Настройка буферов (attachments)


Мы используем только один цветовой буфер, представленный одним из images в swap chain.

void createRenderPass() {    VkAttachmentDescription colorAttachment{};    colorAttachment.format = swapChainImageFormat;    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;}

Формат цветового буфера (поле format) должен соответствовать формату image из swap chain, и поскольку мы пока не задействуем мультисэмплинг, нам понадобится только 1 сэмпл.

colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;

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

  • VK_ATTACHMENT_LOAD_OP_LOAD: буфер будет содержать те данные, которые были помещены в него до этого прохода (например, во время предыдущего прохода)
  • VK_ATTACHMENT_LOAD_OP_CLEAR: буфер очищается в начале прохода рендера
  • VK_ATTACHMENT_LOAD_OP_DONT_CARE: содержимое буфера не определено; для нас оно не имеет значения

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

Для storeOp возможны только два значения:

  • VK_ATTACHMENT_STORE_OP_STORE: содержимое буфера сохраняется в память для дальнейшего использования
  • VK_ATTACHMENT_STORE_OP_DONT_CARE: после рендеринга буфер больше не используется, и его содержимое не имеет значения

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

colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;

loadOp и storeOp применяются к буферам цвета и глубины. Для буфера трафарета используются поля stencilLoadOp/stencilStoreOp. Мы не используем буфер трафарета, поэтому результаты загрузки и сохранения нас не интересуют.

colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

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

Вот некоторые из наиболее распространенных layout-ов:

  • VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: images используются в качестве цветового буфера
  • VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: images используются для показа на экране
  • VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: image принимает данные во время операций копирования

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

В initialLayout указывается layout, в котором будет image перед началом прохода рендера. В finalLayout указывается layout, в который image будет автоматически переведен после завершения прохода рендера. Значение VK_IMAGE_LAYOUT_UNDEFINED в поле initialLayout обозначает, что нас не интересует предыдущий layout, в котором был image. Использование этого значения не гарантирует сохранение содержимого image, но это и не важно, поскольку мы все равно очистим его. После рендеринга нам нужно вывести наш image на экран, поэтому в поле finalLayout укажем VK_IMAGE_LAYOUT_PRESENT_SRC_KHR.

Подпроходы (subpasses)


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

Каждый подпроход ссылается на один или несколько attachment-ов. Эти отсылки представляют собой структуры VkAttachmentReference:

VkAttachmentReference colorAttachmentRef{};colorAttachmentRef.attachment = 0;colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

В поле attachment указывается порядковый номер буфера в массиве, на который ссылается подпроход. Наш массив состоит только из одного буфера VkAttachmentDescription, его индекс равен 0. В поле layout мы указываем layout буфера во время подпрохода, ссылающегося на этот буфер. Vulkan автоматически переведет буфер в этот layout, когда начнется подпроход. Мы используем attachment в качестве буфера цвета, и layout VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL обеспечит нам самую высокую производительность.

Подпроход описывается с помощью структуры VkSubpassDescription:

VkSubpassDescription subpass{};subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;

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

subpass.colorAttachmentCount = 1;subpass.pColorAttachments = &colorAttachmentRef;

Директива layout(location = 0) out vec4 outColor ссылается именно на порядковый номер буфера в массиве subpass.pColorAttachments.

Подпроход может ссылаться на следующие типы буферов:

  • pInputAttachments: буферы, содержимое которых читается из шейдера
  • pResolveAttachments: буферы, которые используются для цветовых буферов с мультисэмплингом
  • pDepthStencilAttachment: буферы глубины и трафарета
  • pPreserveAttachments: буферы, которые не используются в текущем подпроходе, но данные которых должны быть сохранены


Проход рендера (render pass)


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

VkRenderPass renderPass;VkPipelineLayout pipelineLayout;

Теперь создадим объект прохода рендера. Для этого заполним структуру VkRenderPassCreateInfo массивом буферов и подпроходами рендера. Обратите внимание, объекты VkAttachmentReference используют индексы из этого массива (прим. переводчика: видимо, имеется в виду массив renderPassInfo.pAttachments).

VkRenderPassCreateInfo renderPassInfo{};renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;renderPassInfo.attachmentCount = 1;renderPassInfo.pAttachments = &colorAttachment;renderPassInfo.subpassCount = 1;renderPassInfo.pSubpasses = &subpass;if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {    throw std::runtime_error("failed to create render pass!");}

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

void cleanup() {    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);    vkDestroyRenderPass(device, renderPass, nullptr);    ...}

Мы проделали большую работу, и осталось лишь собрать все воедино, чтобы наконец-то создать графический конвейер!

C++ code / Vertex shader / Fragment shader


Заключение


Теперь мы можем объединить все структуры и объекты, чтобы создать графический конвейер!
Давайте вспомним, какие объекты у нас уже есть:

  • Шейдеры: шейдерные модули, определяющие функционал программируемых стадий конвейера
  • Непрограммируемые стадии: структуры, описывающие работу конвейера на непрограммируемых стадиях, таких как input assembler, растеризатор, вьюпорт и функция смешивания цветов
  • Layout конвейера: описание uniform-переменных и push-констант, которые используются конвейером и которые могут обновляться динамически
  • Проход рендера (render pass): описания буферов (attachments), в которые будет производиться рендер

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

VkGraphicsPipelineCreateInfo pipelineInfo{};pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;pipelineInfo.stageCount = 2;pipelineInfo.pStages = shaderStages;

Начнем с указателя на массив структур VkPipelineShaderStageCreateInfo.

pipelineInfo.pVertexInputState = &vertexInputInfo;pipelineInfo.pInputAssemblyState = &inputAssembly;pipelineInfo.pViewportState = &viewportState;pipelineInfo.pRasterizationState = &rasterizer;pipelineInfo.pMultisampleState = &multisampling;pipelineInfo.pDepthStencilState = nullptr; // OptionalpipelineInfo.pColorBlendState = &colorBlending;pipelineInfo.pDynamicState = nullptr; // Optional

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

pipelineInfo.layout = pipelineLayout;

После этого укажем layout конвейера, который является дескриптором Vulkan, а не указателем на структуру.

pipelineInfo.renderPass = renderPass;pipelineInfo.subpass = 0;

В конце сделаем ссылку на проход (render pass) и номер подпрохода (subpass), который используется в создаваемом пайплайне. Во время рендера можно использовать и другие объекты прохода, но они должны быть совместимы с нашим renderPass. Требования к совместимости вы можете найти здесь, однако в руководстве мы будем использовать только один проход.

pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // OptionalpipelineInfo.basePipelineIndex = -1; // Optional

Остались два параметра basePipelineHandle и basePipelineIndex. Vulkan позволяет создать производный графический конвейер из существующего конвейера. Суть в том, что создание производного конвейера не требует больших затрат, поскольку большинство функций берется из родительского конвейера. Также переключение между дочерними конвейерами одного родителя осуществляется намного быстрее. В поле basePipelineHandle вы можете указать дескриптор существующего конвейера, либо сделать отсылку к другому конвейеру, который будет создан по индексу, в поле basePipelineIndex. У нас только один конвейер, поэтому укажем VK_NULL_HANDLE и невалидный порядковый номер. Эти значения используются только в том случае, если в VkGraphicsPipelineCreateInfo в поле flags указано VK_PIPELINE_CREATE_DERIVATIVE_BIT.

Прежде чем завершить создание конвейера создадим член класса для хранения объекта VkPipeline:

VkPipeline graphicsPipeline;

И наконец создадим графический конвейер:

if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {    throw std::runtime_error("failed to create graphics pipeline!");}

Функция vkCreateGraphicsPipelines содержит больше параметров, чем обычная функция создания объектов в Vulkan. За один вызов она позволяет создать несколько объектов VkPipeline из массива структур VkGraphicsPipelineCreateInfo.

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

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

void cleanup() {    vkDestroyPipeline(device, graphicsPipeline, nullptr);    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);    ...}

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

C++ code / Vertex shader / Fragment shader
Подробнее..

Перевод Flutter 2.2 что нового

09.06.2021 12:21:23 | Автор: admin

Представляем свежий релиз Flutter 2.2, анонсированный на Google I/O. Да, оригинальная статья вышла ещё в мае, но мы считаем, что лучше поздно, чем никогда. Публикуем перевод статьи с комментариями Евгения Сатурова ex-Flutter TeamLead Surf, а ныне DevRel Surf.

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

Основа Flutter 2

За основу Flutter 2.2 взят Flutter 2, который работает не только с мобильными устройствами, но и с вебом, ПК и встраиваемые системами. Он создан специально для мира, где нас окружают компьютеры: множество разных устройств и форм-факторов создают потребность в единообразии интерфейсов.

С помощью Flutter 2.2 все корпорации, стартапы, предприниматели смогут создавать решения высокого качества, способные по-максимуму раскрыть потенциал целевого рынка. Единственным ограничением станет креативность а не целевая платформа.

Flutter стал самым популярным фреймворком для кроссплатформенной разработки.

Рост Flutter отметили в недавнем исследовании мобильной разработки. Компания SlashData провела анализ и выпустила статью Mobile Developer Population Forecast 2021, согласно которой Flutter стал самым популярным фреймворком в кроссплатформенной разработке: его выбирают 45% разработчиков. Рост между Q1 2020 и Q1 2021 составил 47%. Интерес к Flutter продолжает расти: за последние30 дней одно из восьми приложений, загруженных в Play Store, написано на Flutter.

Комментарий Жени Сатурова

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

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

Сегодня клиенты приходят за Flutter целенаправленно и часто даже не рассматривают нативный подход. В наших кейсах можно найти несколько хороших примеров.

На I/O мы рассказали вам, что только в Play Store загружено более 200 тысяч приложений, написанных на Flutter. Эти приложения пишут очень серьёзные компании. Например:

  • Tencent их мессенджером WeChat пользуется более 1,2 миллиарда пользователей iOS и Android.

  • ByteDance создатели TikTok, которые уже написали 70 разных приложений на Flutter.

  • Другие компании, в том числе BMW, SHEIN, Grab и DiDi.

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

  • Wombo вирусное приложение с поющими селфи.

  • Fastly приложение для интервального голодания.

  • Kite красивое приложение для инвестиций и трейдинга.

Flutter 2.2

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

Sound null safety теперь по умолчанию работает для новых проектов. Null safety защищает от ошибок типа null reference exception, так как даёт разработчикам возможность указать non-nullable типы в своём коде. А раз Dart абсолютно непротиворечив (sound), компилятор может не проверять на null во время выполнения. В результате производительность приложения повышается. Наша экосистема отреагировала быстро: около 5 000 пакетов уже обновлены и поддерживают null safety.

Комментарий Жени Сатурова

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

Хорошая новость заключается в том, что столь масштабных изменений во фреймворке и языке Dart в ближайшие годы не предвидится. Шутка ли к внедрению null safety в прод шли больше двух лет. Чтобы облегчить переезд коллегам из других компаний, мы оперативно обновили все наши пакеты, входящие в состав репозитория SurfGear.

Кроме того, в этом релизе есть множество возможностей повысить производительность:

  • Для веб-приложений фоновое кэширование с помощью сервисных работников.

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

  • Для iOS мы разработали инструменты предварительной компиляции шейдеров.

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

Комментарий Жени Сатурова

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

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

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

Мы не только совершенствуем сам Flutter. Вместе с другими командами Google мы работаем над интеграцией Flutter в более масштабный стек технологий компании. В частности мы продолжаем разработку надёжных сервисов, с помощью которых разработчики смогут с умом монетизировать приложения. В этом релизе мы дополнили новый ads SDK: добавили в него null safety и поддержку адаптивных баннеров. Ещё мы добавили новый плагин для оплаты, который написали в сотрудничестве с командой Google Pay. С его помощью можно провести оплату за материальный товар как на iOS, так и на Android. Также мы обновили свой плагин для оплаты из приложения и соответствующую статью на codelab.

Комментарий Жени Сатурова

Радует, что экосистема вокруг Flutter развивается в правильном направлении. Не так много осталось незакрытых официальными плагинами вопросов. Возможность процессинга платежей через Google Pay существовала уже давно, но официальная реализация это гарантия качества решения. В нашем последнем релизе The Hole мы внедрили in_app_purchase ещё до выхода пакета в стабильный релиз. Надеемся, что теперь работать с ним станет ещё приятнее.

Так как Dart секретный ингредиент в составе Flutter, в этом релизе мы обновили и его. В Dart 2.13 мы расширили возможность интеграции нативного кода, добавив в FFI поддержку массивов и упакованных структур. В том числе появилась поддержка псевдонимов типов, с которыми код станет читабельнее, а некоторые сценарии рефакторинга получится осуществить менее болезненно. Мы продолжаем добавлять новые возможности интеграции с более широкой экосистемой через GitHub Action для Dart и Docker Official Image. Последний проходит тщательную проверку и оптимально подходит для внедрения бизнес-логики в облачную среду.

Не просто проект Google

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

За последние месяцы больше всего роста было связано с тем, что всё больше платформ и операционных систем стали доступны для Flutter. На Flutter Engage мы объявили, что Toyota собирается использовать Flutter в следующем поколении информационно-развлекательных систем своих автомобилей. А месяц назад Canonical отправили в свободное плаванье первый релиз Ubuntu с поддержкой Flutter, в том числе интеграцией со Snap и поддержкой Wayland.

Рост экосистемы отлично демонстрируют два наших новых партнёра. Samsung портирует Flutter на Tizen и оставляет репозиторий опенсорсным, чтобы остальные тоже могли в него контрибьютить. А Sony руководит разработкой решения для Linux на встраиваемых системах.

Дизайнеры тоже выигрывают от того, что проект опенсорсный: так, Adobe анонсировали, что обновили плагин XD to Flutter. Adobe XD даёт дизайнерам отличную возможность экспериментировать и копировать свои предыдущие работы. С улучшенной поддержкой Flutter дизайнеры и разработчики могут вместе работать над материалами а значит, ещё быстрее выводить классные идеи в продакшн.

И наконец, с нами продолжает сотрудничать Microsoft. Команда Surface разрабатывает с помощью Flutter решения для складных устройств. А на этой неделе в альфа канале появится поддержка Flutter для приложений на UWP, разработанных для Windows 10. Мы очень рады, что всё больше мобильных, десктопных, веб- и других приложений используют способность Flutter подстраиваться под разные платформы.

Комментарий Жени Сатурова

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

Создаём отличный пользовательский опыт

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

Нам нравится смотреть, какое применение вы находите для Flutter. Один из примеров проект от US Veterans Administration. В видео о том, как приложение на Flutter помогаетсолдатам с реабилитацией от посттравматического стрессового расстройства.

Мы проведём множество разных воркшопов, презентаций, посвящённых Flutter и ответим на возникшие у вас вопросы на Google I/O. А пока не забудьте попробовать нашу веб-фотобудку, написанную во Flutter: в ней можно сделать селфи с нашим талисманом Dash и её друзьями!

Подробнее..

Перевод Rust 1.53.0 IntoIterator для массивов, quotquot в шаблонах, Unicode-идентификаторы, поддержка имени HEAD-ветки в Cargo

18.06.2021 18:20:53 | Автор: admin

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


Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.53.0 вам достаточно выполнить следующую команду:


rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.


Что было стабилизировано в 1.53.0


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


IntoIterator для массивов


Это первый выпуск Rust, в котором массивы реализуют типаж IntoIterator. Теперь вы можете итерироваться в массиве по значению:


for i in [1, 2, 3] {    ..}

Раньше это было возможно только по ссылке, с помощью &[1, 2, 3] или [1, 2, 3].iter().


Аналогично вы теперь можете передать массив в методы, ожидающие T: IntoIterator:


let set = BTreeSet::from_iter([1, 2, 3]);

for (a, b) in some_iterator.chain([1]).zip([1, 2, 3]) {    ..}

Это не было реализовано ранее из-за проблем с совместимостью. IntoIterator всегда реализуется для ссылок на массивы и в предыдущих выпусках array.into_iter() компилировался, преобразовываясь в (&array).into_iter().


Начиная с этого выпуска, массивы реализуют IntoIterator с небольшими оговорками для устранения несовместимости кода. Компилятор, как и прежде, преобразовывает array.into_iter() в (&array).into_iter(), как если бы реализации типажа ещё не было. Это касается только синтаксиса вызова метода .into_iter() и не затрагивает, например, for e in [1, 2, 3], iter.zip([1, 2, 3]) или IntoIterator::into_iter([1, 2, 3]), которые прекрасно компилируются.


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


"Или" в шаблонах


Синтаксис шаблонов был расширен поддержкой |, вложенного в шаблон где угодно. Это позволяет писать Some(1 | 2) вместо Some(1) | Some(2).


match result {     Ok(Some(1 | 2)) => { .. }     Err(MyError { kind: FileNotFound | PermissionDenied, .. }) => { .. }     _ => { .. }}

Unicode-идентификаторы


Теперь идентификаторы могут содержать не-ASCII символы. Можно использовать все действительные идентификаторы символов Unicode, определённые в UAX #31. Туда включены символы из многих разных языков и письменностей но не эмодзи.


Например:


const BLHAJ: &str = "";struct  {    : String,}let  = 1;

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


warning: identifier pair considered confusable between `` and `s`

Поддержка имени HEAD-ветки в Cargo


Cargo больше не предполагает, что HEAD-ветка в git-репозитории называется master. А следовательно, вам не надо указывать branch = "main" для зависимостей из git-репозиториев, в которых ветка по умолчанию main.


Инкрементальная компиляция до сих пор отключена по умолчанию


Как ранее говорилось в анонсе 1.52.1, инкрементальная компиляция была отключена для стабильных выпусков Rust. Функциональность остаётся доступной в каналах beta и nightly. Метод включения инкрементальной компиляции в 1.53.0 не изменился с 1.52.1.


Стабилизированные API


Следующие методы и реализации типажей были стабилизированы:



Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.53.0


Множество людей собрались вместе, чтобы создать Rust 1.53.0. Мы не смогли бы сделать это без всех вас. Спасибо!




От переводчиков


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


Данную статью совместными усилиями перевели TelegaOvoshey, blandger, Belanchuk и funkill.

Подробнее..

Искусство перевода, или почему английская Алиса в стране чудес вдруг стала Аней

19.03.2021 20:17:05 | Автор: admin

Алиса в стране чудес Льюиса Кэрролла одна из самых любимых сказок, которую с удовольствием перечитывают и дети, и взрослые по всему миру.

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

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

Такие разные переводы Кэрролла

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

Оригинальная Алиса вышла в 1865 году. И с того времени известно как минимум 20 опубликованных переводов сказки на русский. А после 2000 года в интернете появилось свыше 15 законченных фанатских адаптаций.

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

В нем есть свой шарм, но публика была просто не готова к нему. Вот сравнение отрывка оригинала и перевода:

Curiouser and curiouser! cried Alice (she was so much surprised, that for the moment she quite forgot how to speak good English); now Im opening out like the largest telescope that ever was! Good-bye, feet! (for when she looked down at her feet, they seemed to be almost out of sight, they were getting so far off). Oh, my poor little feet, I wonder who will put on your shoes and stockings for you now, dears? Im sure I shant be able! I shall be a great deal too far off to trouble myself about you: you must manage the best way you can;but I must be kind to them, thought Alice, or perhaps they wont walk the way I want to go! Let me see: Ill give them a new pair of boots every Christmas.

***

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

Тем не менее огромная популярность истории на Западе заставляла переводчиков пытаться еще и еще. Следующий варианты Приключеня Ани въ мр чудесъ Матильды Гранстрем и Приключеня Алисы въ волшебной стран Александры Рождественской известны только лингвистам и практически полностью забыты.

Даже брат известного Антона Чехова, Михаил Чехов, делал перевод сказки. Она вышла под именем Алиса в волшебной стране в сборнике Английские сказки, но тоже не снискала большого внимания.

Набоков и его Аня в стране чудес

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

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

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

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

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

You Are Old, Father William это пародия на известное в конце XIX века детское стихотворение The Old Man's Comforts and How He Gained Them.

Под спойлером сначала отрывок из пародии Кэрролла.

"You are old, Father William," the young man said,

"And your hair has become very white;

And yet you incessantly stand on your head

Do you think, at your age, it is right?"

"In my youth," Father William replied to his son,

"I feared it might injure the brain;

But now that I'm perfectly sure I have none,

Why, I do it again and again."

"You are old," said the youth, "as I mentioned before,

And have grown most uncommonly fat;

Yet you turned a back-somersault in at the door

Pray, what is the reason of that?"

А затем и из оригинала.

You are old, Father William, the young man cried,

The few locks which are left you are grey;

You are hale, Father William, a hearty old man,

Now tell me the reason I pray.

In the days of my youth, Father William replied,

I remember'd that youth would fly fast,

And abused not my health and my vigour at first

That I never might need them at last.

You are old, Father William, the young man cried,

And pleasures with youth pass away,

And yet you lament not the days that are gone,

Now tell me the reason I pray.

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

Скажи-ка, дядя, ведь недаром

Тебя считают очень старым:

Ведь, право же, ты сед,

И располнел ты несказанно.

Зачем же ходишь постоянно

На голове? Ведь, право ж, странно

Шалить на склоне лет!

И молвил он: В былое время

Держал, как дорогое бремя,

Я голову свою

Теперь же, скажем откровенно,

Мозгов лишен я совершенно

И с легким сердцем, вдохновенно

На голове стою.

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

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

Это Масляничный Кот, отвечала Герцогиня, вот почему. Хрюшка!

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

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

Не всегда коту масленица, ответила Герцогиня. Моему же коту всегда. Вот он и ухмыляется.

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

Работа над переводом оказала настолько сильное впечатление на Набокова, что аллюзии на Алису есть в его романе Приглашение на казнь.

Истинный и каноничный перевод Демуровой

Новая эра в русскоязычных переводах Кэрролла началась, когда в 1966 году за Алису взялась литературовед и переводчик Нина Демурова. Именно ее перевод считается наиболее близким по тексту и смыслу к оригинальной истории.

В своей статье Голос и скрипка Демурова пишет:

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

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

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

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

В третьей главе истории мы встречаем персонажей Duck, Lory, Eaglet и Dodo. Мы намеренно представили их в оригинале.

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

Имя Duck в произведении недвусмысленно отсылается на Дакворта, Lory на старшую сестру Лорину, а Eaglet на Эдит. Dodo это сам Кэрролл.

Когда книга вышла, Кэрролл подарил своему другу экземпляр с подписью: The Duck from the Dodo (дословно: Утке от Додо).

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

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

Lory и Eaglet стали попугайчиком Лори и орленком Эдом отсылки на дочерей друга писателя также были сохранены.

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

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

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

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

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

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

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

Онлайн-школа EnglishDom.com вдохновляем выучить английский через технологии и человеческую заботу

Только для читателей Хабра первый урок с преподавателем в интерактивном цифровом учебнике бесплатно! А при покупке занятий получите до 3 уроков в подарок!

Получи целый месяц премиум-подписки на приложение ED Words в подарок. Введи промокод march2021 на этой странице или прямо в приложении ED Words. Промокод действителен до 01.05.2021.

Наши продукты:

Подробнее..

Перевод Каким должен быть ИТ-лидер в 2021 году. Версия Gartner

23.03.2021 10:07:12 | Автор: admin
Основываясь на опыте 2020 года, эксперты Gartner выделили 10 стратегий, которым должны следовать ИТ-директора в 2021 году. Так, например, уже сейчас ИТ-лидерам необходимо оценить, чем новый год будет отличаться от предыдущих, и что позволит в ближайшие месяцы усилить их лидерские качества. При этом, как полагают эксперты, для того, чтобы быть результативными, нет необходимости в том, чтобы реализовывать все десять стратегий, ИТ-лидерам достаточно сконцентрироваться на двух или трех направлениях, которые соотносятся с бОльшими темами: быть более эффективными, прогрессивными и осознанными.

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

Каждая из 10 рекомендаций на 2021 год направлена на развитие гибких навыков, являющихся ответом на вызовы прошлого года, а также специальных навыков, обусловленных задачами наступившего года. Кроме этого, в дополнение к описанным стратегиям, в текущем обзоре эксперты НОРБИТ делятся своим видением, над чем стоит работать ИТ-лидерам, чтобы повысить личную эффективность, и каким рекомендациям Gartner стоит уделить отдельное внимание.

Источник

1. Действуйте без сожалений


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

2: Будьте виртуозом виртуального пространства


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

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

3: Играйте на опережение


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

4. Думайте нестандартно


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

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

5. Поддерживайте идеи нейроразнообразия


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

6. Внедряйте принципы устойчивого развития


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

7. Будьте осознанным лидером


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

8. Смотрите глубоко


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

9. Будьте добры к себе


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

10: Найдите время, чтобы тестировать новые технологии


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

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

* * *


Нужно ли на 100% придерживаться всех этих трендов в 2021 году? Мы в НОРБИТ считаем, что, конечно, нет. Вот что порекомендовали бы мы.

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

А что бы порекомендовали ИТ-лидерам в этом году вы? Давайте обсудим это в комментариях.

Подробнее..

Перевод От int main() до BeginPlay как происходит инициализация Unreal Engine под капотом

24.03.2021 14:22:50 | Автор: admin

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

Но когда вы пишете игровой код на Unreal Engine, вы не имеете дело с игровым циклом напрямую. Вы не начинаете работать сразу с основной функцией сначала вы определяете подкласс GameMode и переопределяете функцию под названием InitGame. Или пишете одноразовые классы Actor и Component и переопределяете их функции BeginPlay или Tick для добавления собственной логики. Это самый минимум того, что вам нужно сделать: обо всем остальном движок позаботится за вас.

Unreal Engine также предлагает вам как программисту большую мощность и гибкость: конечно, он имеет открытый исходный код, но также возможно и расширение несколькими другими способами. Даже если вы только начинаете работать с этим движком, было бы не лишним получить представление о его GameFramework: о таких классах, как GameMode, GameState, PlayerController, Pawn и PlayerState.

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

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

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

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

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

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

Все начинается в модуле Launch, где вы найдете различные основные функции, определенные для разных платформ. В конце концов, все они находят свой путь к функции GuardedMain в Launch.cpp. Где-то здесь как раз можно увидеть базовый игровой цикл.

Основной цикл движка реализован в классе FEngineLoop:

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

PreInit это место, где загружается большинство модулей. Когда вы создаете игровой проект или плагин с исходным кодом на C++, вы определяете один или несколько исходных модулей в файле .uproject или .uplugin и можете указать в LoadingPhase, когда нужно загрузить этот модуль.

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

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

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

Так что же происходит после загрузки вашего модуля?

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

Итак, если вы определили пользовательский тип Актер (Actor), или пользовательский игровой режим, или что-то еще, объявленное перед UCLASS, цикл движка выделяет экземпляр этого класса по умолчанию, затем запускает его конструктор, передавая CDO родительского класса в качестве шаблона. Это одна из причин, по которой конструктор не должен содержать никакого кода, связанного с геймплеем: на самом деле он предназначен только для установления универсальных деталей класса, а не для изменения какого-либо конкретного экземпляра этого класса.

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

Итак, цикл Engine загрузил все необходимые модули движка, проекта и подключаемых модулей, зарегистрировал классы из этих модулей и инициализировал все необходимые низкоуровневые системы. На этом этап PreInit завершается, и мы можем перейти к функции Init. Если мы немного упростим ее, то увидим, что она передает данные в класс под названием UEngine:

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

Движок это программный продукт, содержащий исходный модуль под названием Engine. В этом модуле находится заголовок под названием Engine.h, а в этом заголовке определен класс UEngine, реализованный как в UEditorEngine, так и в UGameEngine.

На этапе инициализации игры FEngineLoop проверяет файл конфигурации движка, чтобы определить, какой класс GameEngine нужно использовать. Затем он создает экземпляр этого класса и закрепляет его как глобальный экземпляр UEngine, доступный через глобальную переменную GEngine, объявленную в Engine/Engine.h.

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

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

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

Итак, давайте посмотрим на инициализацию класса Engine. Она происходит перед загрузкой карты и делает это посредством создания нескольких важных объектов: GameInstance, GameViewportClient и LocalPlayer. Можно утрированно представить, что LocalPlayer это представление пользователя, сидящего перед экраном, а GameViewportClient это и есть сам экран: по сути, это высокоуровневый интерфейс для систем рендеринга, звука и ввода.

Класс UGameInstance был добавлен в Unreal 4.4 и выделен из класса UGameEngine для обработки некоторых функций, более специфичных для проекта, которые ранее обрабатывались в Engine.

Итак, после инициализации класса Engine у нас появляются GameInstance, GameViewportClient и LocalPlayer. Теперь игра готова к запуску: именно здесь происходит первоначальный вызов LoadMap. К концу вызова LoadMap у нас будет UWorld, содержащий всех актеров, сохраненных на нашей карте, а также несколько новых актеров, формирующих ядро GameFramework, включающее игровой режим, игровую сессию, состояние игры, диспетчер игровой сети, контроллер игрока, состояние игрока и пешку.

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

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

Итак, давайте посмотрим на то, что происходит в LoadMap. Это сложная функция, но если мы вернемся ее к основам, то выясним, ее не так уж и сложно понять.

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

Короче говоря, к этому моменту World уже не будет. Однако у нас есть Контекст Мира (World Context). Этот объект создается экземпляром игры во время инициализации Engine, и по сути, это постоянный объект, который отслеживает, какой мир загружен в данный момент. Перед загрузкой чего-либо еще GameInstance может предварительно загрузить любые ассеты, которые ему могут понадобиться, но по умолчанию ничего не делает.

Далее нам нужно получить UWorld.

Если вы работаете с картой в редакторе, редактор загружает в память UWorld вместе с одним или несколькими ULevels, которые содержат размещенных вами Актеров. Когда вы сохраняете свой постоянный уровень, этот Мир, его Уровень и все его Актеры сериализуются в пакет карты, который записывается на диск в виде файла .umap. Во время LoadMap движок находит этот пакет и загружает его. На этом этапе мир, его постоянный уровень и актеры на этом уровне, а также WorldSettings загружаются обратно в память.

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

Движок дает миру ссылку на GameInstance, а затем инициализирует глобальную переменную GWorld. Затем мир устанавливается в WorldContext, ему присваивается тип в данном случае Game, и он добавляется в корневой каталог. InitWorld позволяет миру настраивать такие системы, как физика, навигация, искусственный интеллект и аудио.

Когда мы вызываем SetGameMode, мир просит GameInstance создать актера GameMode. Как только это происходит, движок полностью загружает карту то есть, загружаются все подуровни вместе с ассетами.

Далее мы переходим к InitializeActorsForPlay. Это то, что Engine называет подведением мира к игре. Здесь World перебирает всех актеров в нескольких разных циклах. Первый цикл регистрирует все компоненты актеров в мире.

Происходит регистрация каждого компонента ActorComponent в каждом Actor, что делает для компонента три важных вещи:

  • Во-первых, мы получаем ссылку на мир, в который он был загружен;

  • Затем рпроисходит вызов функции компонента OnRegister, дающий ему возможность выполнить любую раннюю инициализацию;

  • В случае PrimitiveComponent после регистрации компонент будет иметь FPrimitiveSceneProxy, созданный и добавленный в FScene, являющийся версией потока рендеринга UWorld.

После регистрации компонентов World вызывает функцию InitGame GameMode. Это заставляет GameMode породить актера GameSession. После этого у нас есть еще один цикл, в котором мир проходит от уровня к уровню, и каждый уровень инициализирует всех своих актеров. Это происходит за два прохода. В первом проходе Уровень вызывает функцию PreInitializeComponents для каждого Актера. Это дает участникам возможность инициализироваться довольно рано после регистрации компонентов, но до их инициализации.

GameMode это такой же актер, как и любой другой, поэтому здесь также вызывается функция PreInitializeComponents. После этого GameMode порождает объект GameState и связывает его с миром, а также порождает GameNetworkManager, прежде чем, наконец, вызвать функцию InitGameState.

Наконец, мы повторяем цикл по всем актерам, на этот раз вызывая InitializeComponents, а затем PostInitializeComponents. InitializeComponents перебирает все компоненты актеров и проверяет две вещи:

  • Если в компоненте включена функция bAutoActivate, необходимо активировать компонент;

  • Если в компоненте включен bWantsInitializeComponent, произойдет вызов функции InitializeComponent.

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

На этом этапе наш вызов LoadMap почти завершен: все актеры загружены и инициализированы, мир готов для запуска в игре, и теперь у нас есть набор актеров, используемых для управления общим состоянием игры: GameMode определяет правила игры, он же порождает большинство актеров кор-геймплея. Это высший авторитет того, что происходит во время игры, и он существует только на сервере. GameSession и GameNetworkManager также работают только на сервере. Сетевой менеджер используется для настройки таких вещей, как обнаружение читов и предсказание движения. А для онлайн-игр GameSession одобряет запросы на вход и служит интерфейсом для онлайн-сервисов (например, Steam или PSN).

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

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

В этот момент LoadMap перебирает все LocalPlayers, присутствующие в нашем GameInstance: обычно таковой существует только один. Для конкретного LocalPlayer он вызывает функцию SpawnPlayActor. Обратите внимание, что PlayActor здесь взаимозаменяем с PlayerController: эта функция порождает PlayerController. LocalPlayer, как мы уже убедились, является представлением игрока в движке, а PlayerController представлением игрока в игровом мире.

LocalPlayer на самом деле является специализацией базового класса Player. Есть еще один класс Player под названием NetConnection, который представляет игрока, подключенного из удаленно.

Чтобы любой игрок мог присоединиться к игре независимо от того, локальная она или удаленная, он должен пройти процесс входа в систему. Этот процесс обрабатывается GameMode. Функция PreLogin в GameMode вызывается только для попыток удаленного подключения: она отвечает за утверждение или отклонение запроса на вход. Как только мы получаем добро на добавление игрока в игру, происходит вызов Login. Функция Login порождает актера PlayerController и возвращает его в мир.

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

PlayerController и PlayerState похожи на GameMode и GameState в том, что один из них является официальным представлением игры (или игрока) на сервере, а второй содержит данные, которые каждый должен знать об игре (или игроке).

После создания PlayerController World полностью инициализирует его для работы в сети и связывает с объектом Player. После этого вызывается функция PostLogin игрового режима, которая дает игре возможность выполнить любую настройку, которая должна произойти в результате присоединения этого игрока. По умолчанию игровой режим будет пытаться создать Pawn для нового PlayerController в PostLogin. Pawn это особый тип актера, которым может владеть Controller. PlayerController это специализация базового класса Controller. Есть еще один подкласс под названием AIController, использующийся для неигровых персонажей.

Это давнее соглашение в Unreal: если у вас есть актер, который перемещается по миру, руководствуясь собственным автономным процессом принятия решений будь то игрок-человек, принимающий решения и переводящий их в данные ввода, или ИИ, принимающий решения более высокого уровня о том, куда идти и что делать, обычно у вас есть два актера. Controller это тот, кто управляет актером, а Pawn представление актера в мире. Поэтому, когда к игре присоединяется новый игрок, GameMode по умолчанию порождает Pawn для нового PlayerController.

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

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

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

В любом случае, после создания Pawn будет связана с PlayerController, которому она принадлежит. Теперь, когда мы вернемся в LoadMap, у нас будет все готово для фактического запуска игры. Все, что осталось сделать это маршрутизировать событие BeginPlay. От Engine к World, от World к GameMode, от GameMode к WorldSettings, а WorldSettings, в свою очередь, перебирает всех актеров.

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

Давайте в качестве повторения быстро пробежимся по всему еще раз.

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

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

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

На этапе инициализации мы начинаем настройку самого движка. Короче говоря, мы создаем объект Engine, инициализируем его, а затем запускаем игру. Чтобы инициализировать движок, мы создаем GameInstance и GameViewportClient, а затем создаем LocalPlayer и связываем его с GameInstance. После этого мы можем начать загрузку игры.

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

Остальная часть нашего процесса запуска происходит в вызове LoadMap. Сначала мы находим наш пакет карты, затем загружаем его: он переносит в память всех актеров, помещенных на постоянный уровень, а также дает нам объекты World и Level. Мы находим наш мир, даем ему ссылку на GameInstance, инициализируем некоторые системы, а затем создаем актера GameMode. После этого мы полностью загружаем карту, добавляя все необходимые подуровни и ассеты. Когда все оказывается полностью загружено, мы начинаем подходить мир к игре. Сначала мы регистрируем все компоненты для каждого актера на каждом уровне, а затем инициализируем GameMode, который, в свою очередь, порождает актера GameSession. После этого мы инициализируем всех актеров в мире.

Сначала мы вызываем PreInitializeComponents для каждого актера на каждом уровне: когда это происходит для GameMode, он порождает GameState и GameNetworkManager, а затем инициализирует GameState. Затем в другом цикле мы инициализируем каждого актера на каждом уровне: происходит вызов InitializeComponent (и, возможно, Activate) для всех компонентов, которым это необходимо, после чего актеры уже формируются окончательно.

Как только мир будет готов к запуску в игре, мы можем зарегистрировать LocalPlayer внутри игры. Здесь мы создаем PlayerController, который, в свою очередь, порождает для себя PlayerState и добавляет это PlayerState в GameState.

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

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

Мы рассмотрели здесь много вещей, поэтому выделим самое важное:

  • Мы рассматривали классы GameModeBase и GameStateBase, а не GameMode и GameState. Эти базовые классы были добавлены в Unreal 4.14, чтобы исключить некоторые функциональные возможности Unreal-Tournament из игрового режима. В то время как GameModeBase содержит все основные функции игрового режима, класс GameMode добавляет концепцию матча с изменениями состояния матча, которые происходят после BeginPlay. Это позволяет следить за потоком игры например, за готовностью всех игроков, за временем начала и окончания игры и переходом к новой карте для следующего матча.

  • Мы также рассмотрели класс Pawn, но помимо него GameFramework определяет класс Character, который является специализированным типом Pawn, включающим сразу несколько полезных функций. У класса Character есть капсула столкновения, которая используется в основном для движений, а также скелетная сетка, в связи с чем предполагается, что он является анимированным персонажем. Еще у него есть компонент CharacterMovementComponent, тесно связанный с классом Character и выполняющий несколько полезных вещей. Самым важным является то, что движение персонажа воспроизводится из коробки с предсказанием движения на стороне клиента. CharacterMovement реализует полный набор опций передвижения для ходьбы, прыжков, падений, плавания и полета.

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

Итак, вот все те классы, которые мы рассмотрели (за исключением UWorld и ULevel):

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

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

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

Подробнее..

Перевод Как и зачем Morrowind перезапускала оригинальный Xbox во время экрана загрузки

22.04.2021 12:11:11 | Автор: admin

Оригинальный Xbox известен тем, что имел всего 64 мегабайта оперативной памяти, чего даже в то время не всегда хватало играм. В недавнем подкасте о слиянии Bethesda и Xbox директор Bethesda Game Studios Тодд Говард рассказал о том, что именно из-за нехватки памяти и для ее освобождения Morrowind иногда перезагружала Xbox незаметно для пользователя. Долгие внутриигровые загрузки это как раз то, о чем идет речь.

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

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

Теперь настало время доказать это.

Для этого нам понадобятся три вещи:

  1. Копия игры;

  2. Комплект разработчика Xbox (XDK):

  3. Декомпилятор.

В качестве последнего будем использовать IDA Pro.

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

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

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

Отсюда вопрос: как все это работает?

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

Эта часть API Xbox XLaunchNewImage. Согласно документации, она перезагружает консоль для запуска другого файла XBE с DVD-диска.

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

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

Запустим игру на девките. В этом случае XLaunchNewImage должна перезапустить консоль. Теперь загрузим сохраненную игру с жесткого диска, но перед этим задействуем инструмент под названием Xbox File Event Viewer. Он будет отслеживать и выводить на экран создание каждого нового файла для чтения или записи.

Установим фильтр на названии morrowind.xbe. Если будет происходить запуск файла с таким именем, скорее всего, в этом вызове будет задействована XLaunchNewImage, которая и производит перезапуск консоли для исполнения другого файла XBE с диска.

Теперь запустим Xbox File Event Viewer и наш файл сохранения. Как мы видим, происходят множественные чтения файла morrowind.xbe. Это не обязательно говорит о перезапуске консоли, но наверняка указывает на то, что происходит активность XLaunchNewImage.

Теперь используем инструмент для реверс-инжиниринга и декомпилируем исполняемый файл morrowind.xbe. Для этого загрузим его в IDA Pro. И для начала поищем в нем непосредственно упоминание morrowind.xbe.

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

По-видимому, выделенный путь и исполняет XLaunchNewImage ведь, как мы видим в документации, один из требуемых для этого параметров строка для адреса XPE, а второй данные о запуске. Это вполне себе коррелирует с функцией sub_23AB90. Переименуем ее в XLaunchNewImage.

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

Если байт byte_3A3209 имеет значение true, выполняется один кусок кода, в противном случае другой, заканчивающийся исполнением команды XLaunchNewImage.

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

Перейдем к ней.

Один из двух байтов здесь говорит о том, чтобы если флаг No reboot on new game = 1 в файле Morrowind.ini, тогда этот байт равен true. Что касается второго, искомого байта, он становится true, если выполняется условие No reboot on load game = 1 в Morrowind.ini.

Если открыть Morrowind.ini, мы увидим, что оба значения No reboot on new game и No reboot on load game установлены в нуле, а значит игра будет перезагружаться на Xbox. Но на девкитах для отладки, когда игра разрабатывалась Bethesda, скорее всего, они имели значения единицы, поскольку те использовали 126 МБ вместо 64.

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

В этом месте происходит загрузка состояния, в котором игра была перед сохранением и перезапуском:

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

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

Подробнее..

Перевод Внутри материнской платы анализ технологий, лежащих в основе компонентов ПК

21.05.2021 12:08:14 | Автор: admin

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

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

Итак, начнем

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

  • Обеспечения компонентов электропитанием;

  • Обеспечения маршрутов для связи компонентов между собой.

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

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

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

  • Standard ATX 12 9,6 дюйма (305 244 мм);

  • Micro ATX 9,6 9,6 дюйма (244 244 мм);

  • Mini ATX 5,9 5,9 дюйма (150 150 мм).

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

Но что же такое материнская плата?

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

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

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

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

Подключаем мозг к ПК

На схеме выше мы видим структуру, имеющую обозначение LGA1150. Это обозначение используется в Intel для разъема, предназначенного для подключения различных процессоров. LGA здесь означает Land Grid Array распространенный тип технологии упаковки центральных процессоров и других чипов.

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

Металлический фиксатор помогает закрепить ЦП на месте, но мешает рассмотреть контакты, так что уберем его:

Помните, что это? LGA1150. 1150 здесь указывает на количество выводов в разъеме, и у других материнских плат оно может отличаться. Чем выше производительность ЦП (с точки зрения количества ядер, объема кэш-памяти и т. д.), тем больше контактов будет в разъеме. Большая их часть используется для обмена данными со следующей важной частью материнской планы.

Большому мозгу большая память

Ближе всего к ЦП находятся те слоты, что содержат модули DRAM, или системную память. Они подключены напрямую к процессору и больше ни к чему. Количество слотов DRAM зависит в основном от ЦП, ведь контроллер памяти встроен именно в него.

В нашем примере ЦП в материнской плате имеет 2 контроллера памяти, каждый из которых оперирует 2 картами памяти, всего 4 слота DRAM. На материнской плате они окрашены так, чтобы вы знали, какие из них каким контроллером управляются. Обычно их называют каналами памяти.

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

Допустим, у вас есть два модуля ОЗУ по 8 ГБ каждый. Независимо от того, в какие слоты вы их вставите, у вас всегда будет в общей сложности 16 ГБ доступной памяти. Однако, если оба модуля поместить только в черные или только в серые слоты, ЦП будет иметь два пути доступа к этой памяти. Но если поступить иначе и использовать слоты разных цветов, система будет вынуждена обращаться к памяти только с помощью одного контроллера памяти. Учитывая, что он может управлять только одним маршрутом за раз, нетрудно понять, как при этом изменится производительность.

При такой комбинации ЦП и материнской платы используются микросхемы DDR3 SDRAM (синхронная динамическая память с произвольным доступом с версией 3 двойной скорости передачи данных), и каждый слот может содержать один SIMM или DIMM. IMM обозначает X-рядный модуль памяти (In-line Memory Module); S и D указывают на то, заполнена ли чипами одна или две стороны модуля (Single или Dual, соответственно).

Вдоль нижнего края модуля памяти располагаются позолоченные разъемы для питания и обмена данными. Всего их у этого типа памяти 240 по 120 с каждой стороны.

Одинарный модуль DIMM DDR3 SDRAMОдинарный модуль DIMM DDR3 SDRAM

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

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

Каждый чип в DIMM (всего их 16 по 8 на каждую сторону) может передавать 8 бит за такт. Это означает, что каждому чипу требуется 8 контактов только для передачи данных; однако пара чипов использует одни и те же выводы, поэтому только 64 из 240 отвечают за обмен данными. Остальные 176 контактов необходимы для синхронизации и передачи адресов данных (места расположения данных в модуле), управления микросхемами и обеспечения их электроэнергией. Так вы можете убедиться, что наличие более 240 контактов не обязательно улучшит ситуацию.

ОЗУ не единственное, что подключено к процессору

Для повышения производительности системная память подключена напрямую к центральному процессору, но на материнской плате есть и другие разъемы, подключенные примерно так же и в тех же целях. Они используют технологию подключения PCI Express (сокращенно PCIe), и каждый современный ЦП имеет встроенный контроллер PCIe.

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

На изображении ниже показаны 3 разъема: два верхних это PCI Express, нижний гораздо более старая система под названием PCI (родственная PCIe, но намного медленнее). Маленький слот сверху, обозначенный как PCIEX1_1, имеет одну линию; тот, что ниже, 16 линий.

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

  • 2 разъема PCI Express на 1 линию;

  • 3 разъема PCI Express на 16 линий;

  • 2 разъема PCI.

Но если у контроллера ЦП всего 16 линий, то что тогда происходит? Прежде всего, к процессору подключены только PCIEX16_1 и PCIEX16_2, а третий и два однолинейных подключены к другому процессору на материнской плате (подробнее об этом чуть позже). Кроме того, если задействованы оба разъема на 16 линий PCIe, ЦП выделит каждому только по 8 линий. Это типично для всех современных процессоров: количество линий у них ограничено, и по мере того, как к ЦП подключается все больше устройств, на каждое выделяется все меньшее число линий.

Различные конфигурации процессора и материнской платы имеют собственные способы решения этой проблемы. Например, материнская плата Gigabyte B450M Gaming имеет один разъем PCIe на 16 линий, один PCIe на 4 линии и один M.2, использующий 4 линии PCIe. Поскольку ЦП имеет всего 16 линий, использование любых двух разъемов одновременно приведет к тому, что самый большой слот, 16-тилинейный, будет ограничен всего до 8 линий.

Так что же использует такие разъемы? Наиболее распространенные варианты:

  • 16 линии видеокарта;

  • 4 линии твердотельные накопители (SSD);

  • 1 линия звуковые карты и сетевые адаптеры.

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

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

Теперь двинемся через мост на юг

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

На фото выше показана материнская плата ASRock 939SLI32, где четко видны микросхемы NB и SB. Они обе скрыты под алюминиевыми радиаторами, но ближайшая к разъему ЦП в середине изображения это и есть северный мост. Спустя несколько лет после выхода этой платы и Intel, и AMD откажутся от использования NB и выпустят продукты, в которых NB интегрирован в ЦП. Южный мост, однако, остается отдельным, и, скорее всего, это не изменится в обозримом будущем. Интересно, что оба производителя процессоров перестали называть его SB и теперь часто зовут его просто чипсетом (собственное название Intel PCH, блок контроллеров платформы) несмотря на то, что это один чип.

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

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

  • 8 линий PCI Express (версия 2.0 PCIe);

  • 14 портов USB (6 для версии 3.0, 8 для версии 2.0);

  • 6 портов Serial ATA (версия 3.0).

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

В данном случае это процессор, обрабатывающий слоты PCIe с одной линией, третий слот с 16 линиями и слот M.2. Как и многие новые чипсеты, он обрабатывает все эти различные соединения с помощью набора высокоскоростных портов, которые можно переключить на PCI Express, USB, SATA или сетевое соединение, в зависимости от того, что подключено в данный момент. Это, к сожалению, накладывает ограничение на количество устройств, подключенных к материнской плате, несмотря на наличие всех этих разъемов.

В случае нашей материнской платы Asus порты SATA (используемые для подключения жестких дисков, DVD-приводов и т. д.) из-за этого ограничения сгруппированы так, как показано выше. Блок из 4 портов посередине использует стандартные USB-соединения чипсета, тогда как два слева используются некоторые из этих высокоскоростных соединений. Так что, если вы используете те, что слева, на чипсете будет меньше соединений для других разъемов. То же самое и с портами USB 3.0. В них есть поддержка до 6 устройств, но 2 порта обязательно будут подключены к высокоскоростным соединениям.

Разъем M.2, используемый для подключения SSD-накопителя, также использует быструю систему (вместе с третьим слотом PCI Express на 16 линий на этой материнской плате); однако в некоторых комбинациях ЦП и материнской платы разъемы M.2 подключаются непосредственно к ЦП, поскольку многие новые продукты имеют более 16 линий PCIe.

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

  • Разъем PS/2 для клавиатур и мышей (сверху слева);

  • Разъем VGA для недорогих и устаревших мониторов (сверху посередине);

  • Порты USB 2.0 черные (снизу слева);

  • Порты USB 3.0 синие (снизу посередине).

Встроенный графический процессор ЦП обрабатывает разъемы HDMI и DVI-D (внизу в центре), а остальные управляются дополнительными микросхемами. На большинстве материнских плат есть множество дополнительных маленьких процессоров для управления всевозможными разъемами, поэтому давайте взглянем на некоторые из них повнимательнее.

Дополнительные фишки для дополнительной помощи

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

Рассматриваемая нами материнская плата Asus не исключение. Например, микросхема Nuvoton NCT6791D обрабатывает все маленькие разъемы для кулеров и датчики температуры на плате. Расположенный рядом процессор Asmedia ASM1083 управляет двумя устаревшими разъемами PCI, поскольку у чипа Intel Z97 такой возможности нет.

Хоть у чипсета Intel и есть встроенный сетевой адаптер, но он нагружает столь драгоценные высокоскоростные соединения, поэтому Asus добавила еще один чип Intel (I218V) для управления красным разъемом Ethernet, который мы видели в блоке ввода/вывода. На изображении выше не видно, насколько мал этот чип: его площадь составляет всего 0,24 дюйма (6 мм).

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

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

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

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

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

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

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

Этот чип Winbond имеет объем памяти всего 8 МБ, но этого более чем достаточно для хранения всего необходимого ПО. Этот вид флэш-памяти имеет очень малое энергопотребление при использовании и надежно хранит данные в течение десятилетий.

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

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

Кстати о питании для него тоже есть отдельные разъемы.

Игорь, принеси мне питание!

Чтобы обеспечить материнскую плату и подключенные к ней устройства необходимым напряжением, блок питания (PSU) имеет ряд стандартных разъемов. Основной из них 24-контактный разъем ATX12V версии 2.4.

Величина тока зависит от блока питания, но промышленные стандарты напряжения составляют +3,3, +5 и +12 В.

Основная часть питания ЦП поступает от 12-вольтных контактов, но для современных высокопроизводительных систем этого недостаточно. Чтобы обойти эту проблему, существует дополнительный 8-контактный разъем питания, обеспечивающий еще четыре 12-вольтных линии.

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

Линии +3,3, +5 и +12 В подают питание на различные компоненты на самой материнской плате, а также ЦП, DRAM и любые устройства, подключенные к разъемам, таким как USB-порты или PCI Express. Однако все, что использует порты SATA, требует питания непосредственно от блока питания, а разъемы PCI Express могут обеспечить напряжение только до 75 Вт. Если устройству требуется больше энергии, например, как многим видеокартам, то их также необходимо подключить напрямую к блоку питания.

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

Например, процессоры Intel, совместимые в нашей материнской платой Asus Z97, работают с напряжением от 0,7 до 1,4 В. Это не фиксированное напряжение, поскольку современные процессоры меняют его для экономии энергии и уменьшения нагрева, и в спящем режиме процессор может потреблять менее 0,8 В. Затем, когда все ядра полностью загрузятся и начнут свою работу, оно возрастает до 1,4 В и более.

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

Каждый VRM обычно состоит из 4 компонентов:

  • 2 MOSFET сильноточные управляющие транзисторы (синие);

  • 1 дроссель (фиолетовый);

  • 1 конденсатор (желтый).

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

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

Даже стандартный настольный процессор, такой как Intel i7-9700K, может потреблять ток более 100 А при полной загрузке. VRM очень эффективны, но они не могут изменять напряжение без потерь. Принимая во внимание большой ток потребления, вы получаете отличное устройство для гриля.

Если снова взглянуть на фотографию нашей платы, можно увидеть пару VRM для модулей DRAM, но, поскольку напряжения там гораздо меньше, чем на ЦП, они не так сильно нагреваются (и поэтому радиатор не нужен).

И прочие назойливые мелочи

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

Здесь у нас есть:

  • 1 мягкий выключатель питания;

  • 1 кнопка сброса;

  • 2 светодиодных разъема;

  • 1 разъем для динамика.

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

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

Большинство материнских плат имеет аналогичный набор дополнительных разъемов, как показано выше слева направо:

  • Разъем аудиопанели если в корпус ПК встроены разъемы для наушников/микрофона, то их можно подключить к встроенному звуковому чипу;

  • Цифровой аудиоразъем такой же, как и обычный аудиоразъем, но для S/PDIF;

  • Перемычка сброса BIOS позволяет сбросить BIOS до заводских настроек. Также за ним спрятан разъем термозонда;

  • Разъем Trusted Platform Module используется для повышения безопасности материнской платы и системы.

  • Разъем последовательного порта (COM) древний интерфейс. Кто-нибудь вообще им пользуется? Кто-нибудь?

Также наклеены, но не показаны, разъемы для кулеров и дополнительных USB-портов. Не каждая материнская плата поддерживает все это, но многие.

Соединяя все вместе

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

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

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

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

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

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

В заключение

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

Подробнее..

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

28.05.2021 12:18:53 | Автор: admin

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

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

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

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

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

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

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

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

Так что же вызывает все эти проблемы?

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

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

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

Джиттер это колебания задержки, означающие, что пакеты отправляются и принимаются с разной скоростью. Это похоже на плохой frame pacing: то ваш пинг меняется с 20 миллисекунд до секунды, то с секунды до 90 миллисекунд, а затем возвращается к 30 миллисекундам, которые были когда-то уже давно.

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

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

Так почему же возникают подобные сбои?

Существуют три основных типа проблем с соединением:

  • проблемы первой мили, вызванные домашней сетью вашего ПК и подключением к Интернету;

  • проблемы средней мили, обусловленные перемещением данных по маршруту между вашим интернет-провайдером и игровым сервером;

  • проблемы последней мили, связанные непосредственно с игровым сервером.

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

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

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

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

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

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

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

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

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

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

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

Наконец, перейдем к последней миле в цепочке игровым серверам.

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

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

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

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

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

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

Некоторые игры работают с переменным весом тиков например, королевские битвы, в которых скорость тиков повышается по мере выбывания игроков, или Counter-Strike, где сторонние и киберспортивные матчи проводятся со скоростью 128 тиков в секунду по сравнению со встроенным в игру матчмейкингом, работающим на 64 тиках.

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

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

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

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

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

Dead reckoning это, по сути, алгоритм оценки положения объекта в виртуальном мире на основе его предыдущего положения, направления движения, скорости, ускорения и других параметров. Получив первый блок данных протокола состояния (protocol data unit, PDU) для объекта (например, персонажа другого игрока), каждый клиент начинает перемещение этого объекта, применяя согласованный алгоритм dead reckoning. Его движение обновляется при получении последующих PDU. Если для пакетов, несущих PDU, возникнет увеличенная задержка или вовсе их потеря, каждая копия виртуального мира продолжит показывать движение объектов в соответствии с алгоритмом до тех пор, пока не получит следующее обновление. Кроме того, при несоответствиях между статусом сервера и предсказанным клиентом некоторые игры могут сделать переход к новому статусу менее резким, используя алгоритмы сглаживания.

В дополнение к предсказанию клиента сервер может прибегать к методу компенсации сетевой задержки, чтобы правильно объединить виртуальные реальности, которые из-за проблем с неткодом испытывают рассинхронизацию. В таком случае сервер хранит историю последних позиций игроков (так, серверы, на которых работает движок Valve Source, сохраняют позиции игроков в течение 1 секунды), и когда ему нужно вычислить новое состояние, он сначала оценивает момент, когда действие было выполнено в клиентской версии состояния игрового мира. Другими словами, сервер перематывает время в соответствии с задержкой конкретного клиента, вычисляя выполнение введенной им команды (например, удалось ли выстрелу игрока поразить цель). Для этого используется следующая формула:

Время выполнения команды = Текущее время сервера Задержка пакета Интерполяция представления клиента

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

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


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

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

Подробнее..

Hi-перевод Обзор беспроводных наушников Bowers amp Wilkins PI7

07.06.2021 14:15:24 | Автор: admin

Портал о Hi-Fi, Hi-End технике(и не только) - hifiNews.ru подготовил перевод теста новых наушников британской компании Bowers & Wilkins (B&W).

Bowers & Wilkins PI7Bowers & Wilkins PI7

Bowers & Wilkins PI7представляют собой полностью беспроводные наушники, которые являются более дорогой из двух новых моделей данного типа, представленных в каталоге компании.

Надо заметить, что при разработке своих наушников B&W применят подход, отличный от компании Focal. Стоимость ее моделей никогда не превышает психологический барьер в $1000, в то время как модели Focal часто стоят дороже. Кроме того, если Focal придерживается последовательной концепции в дизайне своих наушников, подход Bowers & Wilkins более гибкий. И поскольку в настоящее время беспроводные модели находятся на пике популярности, эта британская компания решила выпустить и такую модель.

Однако стоит заметить, что Bowers & Wilkins никогда не принимает поспешных решений. Ее наушники создаются в результате серьезных исследований, и имеют интересные технические решения, а также впечатляющий дизайн. Продолжат ли PI7 эту хорошую традицию компании? Пришло время нам это узнать.

Технические характеристики и дизайн

PI7 - это первая полностью беспроводная модель от Bowers and Wilkins, но компания уже несколько лет выпускает наушники, поэтому неудивительно, что в PI7 нашли свое отражение несколько уже имеющихся у нее решений. Разумеется, здесь есть и несколько изменений в конструкции, связанных с тем, что данные наушники полностью беспроводные. Среди уже знакомого нам в PI7 9,2-миллиметровые динамические излучатели, аналогичные тем, которые мы видели в предыдущих разработках компании.

В PI7 к ним добавлен балансный излучатель, который работает на высоких частотах. Подобное мы тоже видели, в модели PI3, и это позволяет теоретически сочетать мощные басы от динамического излучателя с аккуратными верхами балансного. Ключевой особенностью PI7 является то, что каждый излучатель в них подключен к собственному усилителю. Заявленная частотная характеристика наушников составляет 10 Гц - 20 кГц.

Bowers & Wilkins утверждает, что до этого момента она воздерживалась от выпуска такой модели потому, что ее инженеров не удовлетворяло качество звука при использовании Bluetooth (интересный аргумент для компании с довольно широким ассортиментом беспроводных наушников, ну да ладно), а PI7 имеет полный из доступных на сегодня набор улучшений данной технологии. Наушники поддерживают все разновидности кодеков aptX, известных на данный момент: обычный, HD, с низкой задержкой LLC и адаптивный. Они также имеют поддержку AAC и, разумеется, стандартного кодека SBC. Дополнительно, имеется технология BLE, информирующая вас об уровне заряда аккумулятора, и функция управления через приложение. Разумеется, использование всего этого богатства будет возможным только в случае соответствующей поддержки и со стороны передающего устройства.

Для кодека aptX это означает, что, по крайней мере, теоретически, передачу аудио с параметрами 24 бит/48 кГц можно будет осуществлять. Наушники связываются между собой с использованием сигнала с такими же параметрами. Лично я считаю, что все это не слишком целесообразно. Если вы хотите слушать записи High resolution в мобильных условиях, то, скорее всего, предпочтете традиционное проводное соединение. Но, по крайней мере, вы можете быть уверенным, что сигнал на наушники приходит без дополнительного сжатия.

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

Учитывая, сколько в каждом наушнике находится излучателей, усилителей, микрофонов и тому подобного, места для размещения аккумуляторов там невелико. Bowers & Wilkins указывает продолжительность автономной работы PI7 в четыре часа, и я думаю, что это реалистичный показатель, хотя он может зависеть от выбранного кодека Bluetooth, а также от громкости прослушивания. Как и во многих подобных моделях, на помощь здесь приходит чехол-зарядник. Он обеспечивает четыре цикла заряда вкладышей, и может дать два часа прослушивания музыки после 15-минутной зарядки. Возможно, это не то устройство, которое я взял бы с собой в рейс в Новую Зеландию, но его хватит на неделю обычных поездок.

Это все хорошо, но есть в данных наушниках и некоторые особенности, которые я пока не встречал у конкурентов. Одна из них то, на что еще способен чехол PI7. В его основании находится разъем USB-C, который не только заряжает внутреннюю батарею, но и позволяет передавать аудио. Вы можете подключить его через специальный кабель к аналоговому выходу устройства (разъем 3,5 мм), сигнал с которого будет оцифрован, и передан на вкладыши по Bluetooth с кодеком aptX HD. Это значительно расширяет возможности использования PI7 и выгодно отличает их от конкурирующих моделей, у которых нет подобной функции.

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

Как проходило тестирование PI7?

Большая часть тестирования PI7 производилась с помощью Oppo Find X2 Neo, который имеет поддержку aptX Adaptive и HD, для качественного воспроизведения музыки. Часть тестирования также проводилось с помощьюAstell & Kern Kann. Затем iPad Pro был использован для тестирования качества звука с AAC, а ноутбук Lenovo T15P обеспечил возможность тестирования через USB и 3,5-мм подключения. Музыкальный материал почти полностью был с сервисов Qobuz и Deezer, также использовалось некоторое количество видеороликов.

Качество звучания

PI7 продемонстрировали звучание, которое я уже отмечал для модели Signature, но в миниатюре. Сопряжение с Oppo сработало сразу, а подсказки и сообщения, выводимые приложением, значительно упрощают процесс подключения. После установки соединения PI7 работали стабильно и безупречно переподключались 19 раз из 20.

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

За исключением этого, PI7 сразу произвели на меня сильное впечатление. Во-первых, в эту модель были перенесены все особенности, которыми Bowers & Wilkins наделила свои полноразмерные наушники. Чувствительность сенсоров управления PI7 на обоих вкладышах и работа системы шумоподавления были выше всяких похвал. Качество звука при разговоре по телефону приемлемое, если шум ветра не слишком высок, но это относится ко всем моделям данного класса. На практике PI7 ничем не уступает ни одному из конкурентов, которые я тестировал.

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

Typhoons от Royal Blood, возможно, не самая Hi-Fi-запись, однако ее насыщенный, взрывной звук способен прижать вас к креслу. PI7 удается поддерживать порядок среди этого музыкального хаоса, не теряя при этом его напора. Даже работая на высоких уровнях громкости, PI7 невозмутимы. Замечательно то, что контролируемость звука не уменьшает удовольствия от прослушивания музыки. Мощная начальная композиция Trouble's Coming по-прежнему остается в полной мере бунтарской, хотя и чуть-чуть управляемой.

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

Переключение на AAC через iPad действительно частично снижает качество звука, потому что у этого кодека более низкий битрейт. Если на Oppo музыка с сервисов Qobuz и (с потерями) Deezer звучит по-разному, то на iPad эта разница теряется. Это все еще хороший звук, но, очевидно, Apple оставила в нем какой-то запас, что бы полностью реализовать в собственном AirPod Pro, и, тем самым, дать им определенные преимущества. Однако в случае мобильного использования звук все равно вполне хорош.

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

Заключение

В основе любых дебатов о ценности PI7 для многих пользователей - оправдывают ли они стоимость на $150 фунтов выше, чем у AirPod Pro.

стоимость в России на 05.2021 ~ 29 990

На этот вопрос есть два ответа: один совершенно очевидный, а другой - более тонкий. Если вы не являетесь пользователем iPhone и у вас есть достойная реализация aptX, то качество звука PI7 за счет более совершенного кодека будет выше. Если вы пользователь iOS, решение больше зависит от личных предпочтений и от того, есть ли у вас дополнительные $150, но я думаю, что PI7 все же будут лучше. Качество реализации шумоподавления, качество сборки и уровень комфорта, предлагаемые здесь, в значительной степени являются лучшими в данном классе. Bowers & Wilkins не спешила выпускать полностью беспроводные наушники, но результат впечатляет и на сегодня PI7 являются бесспорной самой выгодной покупкой.

Вердикт

Понравилось
Превосходное качество звука, особенно с высококачественными кодеками Bluetooth
Отличное шумоподавление
Удобные и качественно сделанные

Не понравилось
Небольшое время автономной работы вкладышей
Звучание с использованием кодека AAC не впечатляет
Возможны конфликты с внешним оборудованием
Высокая стоимость


Оценки:

Качество сборки: 9
Легкость использования: 9
Качество звука: 9
Дизайн: 9
Чувствительность: 9
Вердикт: 9

Основные технические характеристики Bowers & Wilkins PI7

Тип:

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

Излучатели:

9,2 мм динамический + арматурный

Частотный диапазон:

10 Гц - 20 кГц

Чехол-зарядник в комплекте:

да

Заменяемые внутриушные вставки:

да

Соединение:

Bluetooth SBC, AAC, aptX, aptX HD, LLC, Adaptive

При подготовке обзора использовались материалы с www.avforums.com (перевод с английского - hifiNews.RU)


Подробнее..

Перевод ТОП-10 трендов в сфере данных и аналитики 2021. Версия Gartner

15.06.2021 10:13:02 | Автор: admin
Оракул технологического мира Gartner регулярно и охотно делится с обществом своими наблюдениями относительно текущих трендов. Эксперты компании составили подборку из 10 трендов в сфере данных и аналитики, которые стоит учитывать ИТ-лидерам в 2021 году от искусственного интеллекта до малых данных и применения графовых технологий.

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

Источник

Коротко о трендах


В предложенном материале Gartner выделяет ряд трендов в индустрии, связанной с машинным обучением и искусственным интеллектом. Не стоит ожидать, что статья откроет новые горизонты: в ней собраны те особенности и тренды, которые уже прошли этап новаторства, а также этап привлечения ранних последователей, однако если не обратить должного внимания на отмеченные тенденции, то можно опоздать даже попасть в категорию отстающих последователей. Кроме того, в статье явно прослеживаются рекламные и побудительные элементы, нацеленные на аудиторию, влияющую на инновации в своей области бизнеса, т.е. на основную аудиторию Gartner. В процессе перевода не удалось уйти от упомянутых элементов, однако рекомендуется к ним относиться снисходительно, т.к. эти рекламные вставки перемежаются ценной информацией. Некоторые из трендов напрямую связаны с изменениями в индустрии, к которым привела эпидемиологическая обстановка. Другие с растущей популярностью систем автоматического принятия решений и использованию ИИ в бизнес-аналитике. Отдельно хочется отметить тренд, связанный с графовыми методами, которые быстро развиваются и набирают все большую популярность. Тем не менее, некоторые из них носят скорее номинальный характер. Одним из таких номинальных трендов на первый взгляд кажется термин XOps, в котором Gartner объединяет направления DataOps, ModelOps и DevOps, комментируя свое видение следующим образом: Умножение дисциплин Ops, вытекающих из лучших практик DevOps, вызвало значительную путаницу на рынке. Тем не менее, их согласование может принести значительные преимущества организациям, которые способны гармонизировать эти дисциплины Практики XOps объединяют разработку, развертывание и обслуживание, чтобы создать общее понимание требований, передачу навыков и процессов для мониторинга и поддержки аналитики и артефактов ИИ. В этом, казалось бы, номинальном тренде, прослеживается мысль, отсылающая к теме Франкенштейна: мало состыковать отдельные рабочие части компании, т.к. они будут функционировать хаотично и не согласовано, жизнь и полезная активность начнется после того, как эти разрозненные части будут синхронизированы и гармонизированы. Но не буду раскрывать все карты сразу, предлагаю читателю самостоятельно ознакомиться с находками Gartner далее.

Как изменилась работа data-специалистов


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

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

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

Каждая из тенденций соответствует одной из трех основных тем:

  1. Ускорение изменений в данных и аналитике: использование инноваций в области искусственного интеллекта, улучшенная возможность по компоновке, а также более гибкая и эффективная интеграция разнообразных источников данных.
  2. Операционализация ценности бизнеса посредством более эффективного использования XOps: позволяет лучше принимать решения и превращать данные и аналитику в неотъемлемую часть бизнеса.
  3. Принцип все распределено: предполагает гибкое соотнесение данных и идей для расширения возможностей более широкой аудитории людей и объектов.

Тренд 1. Продвинутый, ответственный, масштабируемый ИИ


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

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

Тренд 2. Составные данные и аналитика


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

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

Тренд 3. Фабрика данных как основа


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

Фабрика данных сокращает время на проектирование интеграции на 30%, развертывание на 30% и поддержку на 70%, поскольку технологические разработки основаны на возможности использования / повторного использования и комбинирования различных стилей интеграции данных. Кроме того, фабрики данных могут использовать существующие навыки и технологии из data-хабов (data hubs), озер данных (data lakes) и хранилищ данных (data warehouses), а также внедрять новые подходы и инструменты для будущего.

Тренд 4. От больших данных к малым и широким данным


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

Источник

Тренд 5. XOps


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

Тренд 5. XOps. Источник

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

Тренд 6. Проектирование интеллекта принятия решений


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

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

Тренд 7. Данные и аналитика как ключевая бизнес-функция


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

Тренд 8. Графы в основе всего


Графовые подходы формируют основу современных данных и аналитики, предоставляя возможности для усиления и улучшения взаимодействия c пользователями, моделей машинного обучения и интерпретируемого ИИ. Хотя графические технологии не новы для данных и аналитики, произошел сдвиг в мышлении вокруг них, поскольку организации выявляют все больше вариантов их использования. Фактически, до 50% запросов клиентов Gartner о ИИ связаны с обсуждением использования graph-технологий.

Источник

Тренд 9. Расширение пользовательского опыта


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

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

Тренд 10. Данные и аналитика впереди планеты всей


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

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

В заключение


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

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

PHP-Compiler, или ныряем в кроличью нору FFI

17.06.2021 14:15:17 | Автор: admin

Однажды Энтони Феррара (Anthony Ferrara) решил скомпилировать PHP в низкоуровневый код, но результат получился слабым. Главной проблемой, с которой он столкнулся, было отсутствие подходящего бэкенда. К лучшему все изменилось после того, как в дело вступил FFI.

Я советую прочитать статью A PHP Compiler, aka The FFI Rabbit Hole, перевод который вы найдёте под катом.

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

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

Виды компиляторов

Существуют три основных способа выполнения программ.

  • Интерпретация: подавляющее большинство динамических языков (например, PHP, Python (CPython), Ruby и т. д.) можно интерпретировать с помощью какой-либо виртуальной машины.

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

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

  • Компиляция: значительная часть языков, которые мы считаем статическими, компилируется заранее (ahead of time, AOT) прямо в нативный машинный код. Многие языки (C, Go, Rust и т. д.) используют AOT-компилятор.

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

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

  • Just In Time (JIT): JIT относительно недавно стал популярным методом, благодаря которому можно взять лучшее от виртуальной машины и AOT. Многие языки программированияLua, Java, JavaScript, Python через интерпретатор PyPy, HHVM, PHP 8 и прочиеиспользуют JIT.

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

1) выяснить, какие части кода горячие, а значит, наиболее полезны для компиляции в машинный код;

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

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

Главный плюс JIT-компиляции в том, что в некоторых сценариях использования она объединяет скорость развертывания виртуальной машины с производительностью, характерной для AOT. Это невероятно сложный процесс, так как нужно собрать два полнофункциональных компилятора и интерфейс между ними.

Подытожим:

  • интерпретатор выполняет код;

  • AOT-компилятор генерирует машинный код, который потом выполняет компьютер;

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

Немного объяснений

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

  • Компилятор: значение термина компилятор зависит от контекста.

Если мы говорим о сборке рантаймов языков программирования (эти рантаймы тоже называют компиляторами), то компиляторпрограмма, которая преобразовывает код из одного языка в другой с отличной от него семантикой. Это не просто иное представление кодакод именно преобразовывают. Примеры такого преобразования: из PHP в опкоды, из C в промежуточное представление, из ассемблера или регулярного выражения в машинный код. Да, в версии PHP 7.0 есть компилятор, который компилирует исходный код языка PHP в опкоды.

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

Да уж, запутано...

  • Виртуальная машина (VM): я упомянул выше, что виртуальная машинагигантский switch в цикле. Чтобы понять, почему ее называют виртуальной, давайте немного поговорим о том, как работает настоящий физический CPU.

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

Этот код просто добавляет 1 к регистру rsi, затем добавляет к нему 2.

Посмотрите, как та же операция представлена в опкодах PHP:

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

Разница в том, что команды ассемблера очень низкоуровневые, и их сравнительно немного, в то время как в команды опкода виртуальной машины PHP встроено больше логики. В примере команда ассемблера incq ожидает аргумент в виде целого числа. С другой стороны, опкод POST_INC содержит всю логику, необходимую для того, чтобы сначала преобразовать аргумент в целое число. В виртуальной машине PHP гораздо больше логики, что в свою очередь:

а) делает существование PHP и любого интерпретируемого языка возможным,

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

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

  • Дерево абстрактного синтаксиса (Abstract Syntax Tree, AST): ASTвнутренняя структура данных, которая представляет исходный код программы в виде дерева. Таким образом, вместо $a = $b + $c; получаем что-то вроде Assign($a, Add($b, $c)). Главная характеристика деревау каждого узла только один родитель. PHP выполняет внутреннее преобразование исходного файла в AST перед компиляцией в опкоды.

Если дан следующий код:

то можно ожидать, что AST будет выглядеть так:

  • Граф потока управления (control flow graph, CFG): CFG во многом похож на AST, но если у первого может быть несколько корневых элементов, то у второго только один. Это можно объяснить так: CFG включает в себя связи между циклами и т. п., так что можно увидеть все возможные пути управления, проходящие через весь код. Расширение Opcache Optimizer для PHP использует внутри CFG.

Если дан следующий код:

то можно ожидать, что CFG будет выглядеть так:

В этом случае longцелое число PHP, numericцелое число или число с плавающей запятой, jumpzпереход к другой команде в зависимости от того, равна ли bool_21 0 или нет.

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

  • Промежуточное представление (Intermediary Representation, IR): IRязык программирования, который полностью живет в компиляторе. Вы никогда не будете писать на этом языке, его для вас генерируют. Тем не менее, стоит отметить, что IR нужен для некоторых манипуляций компилятора (например, для реализации оптимизаций), а также для того, чтобы компоненты компилятора были разделены (в итоге их легче настраивать). Вышеупомянутые структуры AST и CFGформы IR.

Немного предыстории

Я впервые попытался выполнить PHP поверх PHP в рамках проекта PHPPHP еще в 2013 году. Суть проекта состояла в том, чтобы перевести исходный код из репозитория php-src с языка C на PHP. Не было и речи о том, что виртуальная машина будет работать быстро (слово быстро в кавычках, так как эта машина примерно в 200 раз медленнее, чем PHP, и нет никакого способа ее разогнать). Я просто развлекался, мне нравилось экспериментировать и учиться чему-то новому и интересному.

Полтора года спустя я создал набор инструментов Recki-CT, который работал по иной схеме. Вместо того, чтоб реанимировать прошлую попыткуPHP в PHP, я создал многоступенчатый компилятор. Он парсил PHP в AST, преобразовывал AST в CFG, проводил оптимизацию, затем выдавал код через бэкенд. Для этой задачи я собрал два начальных бэкенда: один компилировал код в расширение PECL, а второй использовал расширение JitFu для непосредственного выполнения кода, оперативно компилировал его и запускал в виде нативного машинного кода. Эта реализация работала довольно неплохо, но была мало применима на практике по ряду причин.

Несколько лет спустя я снова вернулся к этой идее, но решил не создавать единый монолитный проект, а заняться серией взаимосвязанных проектов по парсингу и анализу PHP. В рамках этих проектов были реализованы следующие инструменты: PHP-CFGпарсинг CFG, PHP-Typesсистема вывода типов, PHP-Optimizerбазовый набор оптимизаций поверх CFG. Я разработал эти инструменты для того, чтобы встроить их в другие проекты для различных целей (например, Tuliранняя версия статического анализатора кода PHP). В проекте PHP-Compiler я пытался компилировать PHP в низкоуровневый код, но результат получился слабым.

Главной проблемой, с которой я столкнулся при создании полезного низкоуровневого компилятора, было наличие (точнее отсутствие) подходящего бэкенда. Библиотека libjit (используемая расширением JitFu) работала хорошо и быстро, но не могла генерировать бинарники. Я мог бы написать расширение на C, привязанное к LLVM (HHVM использовала инфраструктуру LLVM и многие другие), но это ОЧЕНЬ трудозатратный процесс. Я не захотел идти этим путем и отложил эти проекты до лучших времен.

В игру вступают PHP 7.4 и FFI

Нет, версия PHP 7.4 еще не вышла (Пост был опубликован в 22 апреля 2019 года). Возможно, она будет выпущена через полгода. Несколько месяцев назад небольшое предложение по включению расширения FFI в PHP успешно прошло голосование. Я решил поиграть с этим расширением, чтобы узнать, как оно работает.

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

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

FFIMe

Мой первый шагсоздание обертки над libgccjit. FFI требует заголовочный файл, похожий на C, но сам не может обрабатывать макросы препроцессора C. Непонятно? Ок, просто знайте, что у каждой библиотеки есть по крайней мере один заголовочный файл, который описывает функции, и FFI нужна его сокращенная версия.

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

Встречайте FFIMe.

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

По сути, FFIMe получает путь к Shared Object File и к директивам #include. Он парсит получившийся С, убирает несовместимый с FFI код и затем генерирует класс. Хорошо, МНОГО классов. Теперь сгенерированный файл можно рассмотреть (файл из примера выше на GitHub).

Если вы посмотрите на этот файл, то увидите ОГРОМНОЕ количество кодапочти 5000 строк. Он включает в себя все числовые #define заголовков C как константы класса, все ENUM как константы класса, а также все функции и классы-обертки всех базовых типов C. Файл также рекурсивно содержит все другие заголовки (поэтому у заголовка выше есть некоторые на первый взгляд лишние файловые функции).

Код использовать весьма просто. Примечание: не обращайте внимание на то, что делает библиотека, просто смотрите на типы и на вызовы, затем сравните с эквивалентным кодом на C:

Теперь можно работать с библиотеками C в PHP как будто бы они в C! Ура!

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

PHP-CParser

Сделав рефакторинг FFIMe, я решил собрать полнофункциональный C parser. Он работает так же, как PHPParser разработчика Никиты Попова, но не в PHP, а в C.

Пока поддерживается не весь синтаксис C, но PHP-CParser использует стандартную грамматику C, так что теоретически он способен парсить все без исключения.

В начале препроцессор C обрабатывает заголовочные файлыон резолвит все стандартные директивы, такие как #include, #define, #ifdef и т. д. Потом PHP-CParser парсит код в AST (по мотивам CLANG).

Таким образом, например, следующий код C:

и includes_and_typedefs.h:

даст такой AST:

Синие именаимена классов объектов, а красные имена в нижнем регистреимена свойств указанных объектов. Так, внешний объект здесьPHPCParser\Node\TranslationUnitDecl с массивом свойств declarations. И так далее...

Очень редко кому-то надо парсить код C в PHP, поэтому я полагаю, что эту библиотеку будут использовать только с FFIMe. Но если вы захотите ее использоватьвперед!

PHP-Compiler

Я вернулся к проекту PHP-Compiler и начал работу над ним. В этот раз я добавил несколько этапов к компилятору. Я решил не компилировать непосредственно из CFG в нативный код, а применить Virtual Machine interpreter (именно так и работает PHP). Это ГОРАЗДО более зрелый подход, чем тот, который я использовал в PHPPHP. Я не остановился на этом и создал компилятор, который может брать опкоды виртуальной машины и генерировать нативный машинный код. Это позволило применить как JIT-компиляцию (Just In Time), так и AOT-компиляцию (Ahead of Time). Таким образом, я могу не только запускать код или компилировать его во время запуска, но и предоставить компилятору кодовую базу для того, чтобы он сгенерировал файл машинного кода.

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

Я начал собирать PHP-Compiler поверх libgccjit и получил весьма интересные результаты. Простой набор бенчмарков, взятых из пакета PHP, показывает, что хотя сейчас и есть МНОГО накладных расходов, скомпилированный код может быть действительно блестящим.

Следующие тесты сравнивают производительность PHP-Compiler, PHP 7.4 с OPcache (Zend Optimizer) и без него, а также экспериментального JIT (в включенном и выключенном состоянии) для PHP 8.

Заметен ощутимый простой при старте (помните, это PHP!). Однако компиляция кода (как в режиме JIT, так и в режиме AOT) происходит значительно быстрее, чем в PHP 8 с JIT-компиляцией в особо сложных вариантах использования.

Стоит отметить, что мы сравниваем совершенно разные вещи. Не стоит ожидать такие же показатели в продакшен-проектах, но по ним можно сделать вывод о перспективности такого подхода...

Сейчас можно использовать эти 4 команды:

  • php bin/vm.phpзапустить код в виртуальной машине;

  • php bin/jit.phpкомпилировать весь код, затем запустить его;

  • php bin/compile.phpкомпилировать весь код и вывести файл a.o;

  • php bin/print.phpкомпилировать и вывести CFG и сгенерированные опкоды (полезно для отладки).

В командной строке все работает как PHP:

Да, здесь echo "Hello World\n"; работает как нативный машинный код. Перебор? Определенно. Прикольно? Однозначно!

Подробности в описании проекта.

Я приостановил сборку, потому что не знал, стоит ли и дальше использовать libgccjit или лучше перейти на LLVM?

Есть только один способ выяснить это...

PHP-Compiler-Toolkit

Как вы уже поняли, я не умею давать названия вещам...

PHP-Compiler-Toolkitуровень абстракции поверх libjit, libgccjit и LLVM.

Вы просто встраиваете код, похожий на код языка C, в кастомное промежуточное представление через нативный интерфейс PHP. Например, это выражение (обратите внимание, что long long 64-битное целое число, как и тип PHP int):

можно использовать так:

Это описывает код. Отсюда можно передать контекст бэкенду для компиляции:

и потом просто получить в ответ:

Вот мы и получили чистый нативный код.

Теперь я могу собрать фронтенд (PHP-Compiler) поверх этой абстракции и менять бэкенды для тестирования.

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

Хотя все бэкенды и имеют сравнимые показатели в рантайме, продолжительность компиляции для libgccjit зашкаливает. Хм, может я был прав, когда думал перейти на LLVM?..

Да, и для такой простой функции накладные расходы FFI весьма значительные. На запуск этого же кода в PHP уходит примерно 0,02524 секунды.

Чтобы продемонстрировать, что PHP-Compiler может в перспективе работать гораздо быстрее, чем PHP, представьте себе такой бенчмарк:

В нативном PHP запуск этого кода миллион раз займет примерно 2,5 секунды. Не то чтобы медленно, но и не супер быстро. Однако с PHP-Compiler мы видим следующее:

В этом искусственном примере можно увидеть десятикратный прирост производительности по сравнению с нативным PHP 7.4.

Вы можете посмотреть этот пример и скомпилированный код в examples folder of php-compiler-toolkit.

PHP-LLVM

После проекта PHP-Compiler-Toolkit я начал работу над PHP-LLVM. Эксперименты с Toolkit показали, что у libgccjit нет реальных преимуществ перед LLVM, у которой есть преимущества в производительности и функциональности, поэтому я решил перевести PHP-Compiler исключительно на нее.

Я не стал обращаться непосредственно к LLVM C-API, а написал обертку над ним. Я преследовал две цели:

1) я получаю более объектно-ориентированный API, так как, чтобы получить тип значения, пишу $value->typeOf(), а не LLVMGetType($value);

2) с оберткой я могу не обращать внимание на различия в версиях LLVM.

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

PHP-ELF-SymbolResolver

Стоит отметить, что в LLVM были ошибки, поэтому мне нужно было видеть, какие символы на самом деле компилируются в LLVM.Таким образом, я хотел проверить общий файл объекта (.so), который содержит скомпилированную библиотеку LLVM. Для этого я написал PHP-ELF-SymbolResolver, который парсит файлы формата ELF и показывает объявленные символы.

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

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

Портируя PHP-Compiler на PHP-LLVM, я понял, что генерация кода с использованием API как билдера быстро становится многословной. Код невозможно прочитать. Например, возьмем сравнительно простую встроенную функцию __string__alloc, которая определяет новую внутреннюю структуру строки. Если использовать API как билдер, то она будет выглядеть примерно так:

Просто куча мусора. Трудно понять что-либо, а если какую-то часть кода и можно прочитать, то с ней очень сложно работать).

Чтобы избежать такого результата, я написал систему макросов с помощью PreProcess.ioиYay. Теперь тот же код выглядит так:

Читать код стало гораздо легче. Это смесь синтаксиса C и PHP, заточенная под нужды PHP-Compiler.

Язык макросов частично задокументирован на GitHub-е.

Подробности о применении макросов смотрите на src/macros.yay (GitHub).

Беспокоитесь о производительности? Правильно делаете. На обработку файлов нужно время (примерно одна секунда на файл). Есть два способа борьбы с этим:

1) предварительная обработка возможна только при установке PHP-Compiler с dev-зависимостями с помощью композера, иначе будут загружены только скомпилированные файлы PHP;

2) предварительная обработка произойдет на лету только при изменении файла .pre, даже с dev-зависимостями.

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

Запуск

Сначала установите PHP 7.4 с включенным расширением FFI. Насколько мне известно, эта версия PHP еще не вышла (и до того, как выйдет, пройдет еще немало времени).

Запуск FFIMe:

Объявите FFIMe dev-зависимостью композера ("ircmaxell/ffime": "dev-master") и запустите генератор кода через файл стиля rebuild.php. Например, файл rebuild.php, используемый PHP-Compiler-Toolkit, выглядит так:

Потом сравните сгенерированные файлы. Я предлагаю включать сгенерированные файлы через композер с ключевым словом files, а не загружать их автоматически, потому что композер сгенерирует ОГРОМНОЕ количество классов в один файл.

Замените строку "...so.0" путем к общей библиотеке, которую вы хотите загрузить, и файл .h заголовками, которые нужно парсить (можно много раз вписать ->include()).

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

Запуск PHP-Compiler нативным образом

Сейчас PHP-Compiler работает нестабильно, что-то может ломаться, поэтому сначала установите зависимости (можно использовать LLVM 4.0, 7, 8 и 9).

После окончания установки просто запустите их.

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

Еще можно задать файл:

При компиляции bin/compile.php также можно задать выходной файл с -o (по умолчанию будет перезаписан исходный файл без расширения .php). Для системы будет сгенерирован готовый к выполнению бинарник:

Или по умолчанию:

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

Запуск PHP-Compiler с Docker

Удобства ради я опубликовал два docker-образа для PHP-Compiler. Оба основаны на старой версии Ubuntu (16.04) из-за некоторых проблем с PHP-C-Parser, до которых у меня не дошли руки. Но вы можете скачать их и поиграть с ними:

  • Ircmaxell/php-compiler:16.04полнофункциональный компилятор, полностью установленный и сконфигурированный со всем необходимым для его запуска;

  • Ircmaxell/php-compiler:16.04-devтолько dev-зависимости. Контейнер предназначен для работы с вашей собственной сборкой PHP-Compiler, чтобы вы могли разрабатывать его в стабильной среде.

Для запуска кода:

Код будет по умолчанию запущен с bin/jit.php. Если вы хотите запустить код с другой точки входа, то ее можно изменить:

Да, и если вы хотите передать скомпилированный код, то можно расширить docker-файл. Например:

Во время запуска сборки docker код будет скомпилирован в index.php, а файл машинного кода будет сгенерирован в /app/index. Затем этот бинарник будет выполнен при запуске docker run ..... Обратите внимание: контейнер не предназначен для продакшена, так как в нем много лишнего, это просто демонстрация работы.

Что дальше

Теперь, когда PHP-Compiler поддерживает LLVM, можно продолжать работу по расширению поддержки языка. Еще многое предстоит сделать:например, массивы, объекты, нетипизированные переменные, обработку ошибок, стандартную библиотеку и т. д.). Хе-хе. В PHP-CFG и PHP-Types также есть, чем заняться: поддержкой исключений и ссылок, исправлением пары ошибок и многим другим.

Да, нужны тесты. Много тестов. И тестировщики. Пробуйте, ломайтеэто легко, обещаю. И создавайте ишью.

Протестируете?

PHP Russia 2021пройдет28 июнявМосква, Radisson Slavyanskaya. Но уже сейчас можно ознакомиться срасписаниеми присмотреть доклады, которые вы точно не захотите пропустить.

А еще вы можете выбрать формат участия: онлайн или офлайн. Не забудьтекупить билетыуже сегодня!

На всех наших офлайн площадках мы соблюдаем антиковидные меры:

1. Все сотрудники конференции сдают тест ПЦР и ходят в масках.

2. Участникам выдаём комплект медицинских масок и санитайзеры.

3. Во всех помещениях конференции и в фойе работают мощные рециркуляторы воздуха.

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

5. В зоне кофебрейков и обедов соблюдаются нормы социальной дистанции.

Подробнее..

Категории

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

  • Имя: Билал
    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