Найти в Дзене

Спекуляции на процессорах

Кстати, говоря про процессоры: помните про Spectre и Meltdown?

Побочным эффектом от них стало то, что массы узнали про "спекулятивное исполнение" - когда процессор предсказывает разные варианты развития событий. И вот что интересно: для меня это стало объяснением, почему в некоторых задачах высокоуровневые языки, вроде Java или JavaScript, иногда выигрывают у C++, который по идее напрямую управляет памятью и не имеет потерь "на передаче", или в процессе транспиляции - причем сильно, а иногда так же сильно проигрывают.

Теоретически - да, можно написать код на C++, который работает так же быстро, как и код на Java в решении определенных задач, но он будет заточен только под одно семейство процессоров. А писать под каждое семейство - так вообще кажется безумием.

Дело в многопоточных операциях. Нейронные сети выстрелили не из-за того, что они так хороши в решении задач, тот же градиентный бустинг с моей точки зрения более применим во множестве ситуаций, а из-за того, что они построены на легко распараллеливаемых операциях. И Java/JS/Python/Ruby/Go/etc. сильны именно в этом: они понимают собственные высокоуровневые инструкции вида "соверши действие над каждым элементом массива из 1000 элементов, и каждый из них не повлияет на результат остальных", и умеют подключать инструкции процессора, позволяющие это совершить параллельно. Но у нас много архитектур: x32, x86, arm, spark, отдельные наборы инструкций типа mmx2, mmx3, SIMD, и писать код с учетом каждой из них - это безумие, потому что это закончится комбинаторным взрывом: только те варианты, что я привел, приводят к 12 вариантам написания кода.

В итоге все заканчивается тем, что для отдельных задач С++ чертовски, невыносимо хорош, потому что он позволяет написать код, который вручную перекладывает байтики, и для конкретных процессоров можно написать код, который будет выполняться на невероятной скорости и с невероятной эффективностью (безумно люблю рассказ "история одного байта" - http://we.easyelectronics.ru/Asticon/istoriya-odnogo-bayta.html), но только под буквально одну-две архитектуры, иначе нужно безумное количество разрабочиков (или безумные разработчики), а если ты пытаешься спроектировать систему под некую generic платформу - ты обречен на провал, просто потому что писать код дольше, а поддерживать сложнее.

И тут возникает интересный вопрос: а как вообще происходят атаки типа spectre и meltdown?

Вот в чем дело: код загружается в процессор большими кусками, не пошагово. И чтобы ускорить этот код, Intel сделала очень интересные вещи. Если говорить вкратце - процессор умеет предсказывать следующие шаги. Если в 99 из 100 раз процессор в результате деления на 2 в определенной функции возвращал 0, то "на будущее" процессор загрузит не использующиеся на момент расчета конвейеры (блоки операций) для операций "а что будет, если там будет 0"?

Атака происходила именно так: ты не имеешь доступа к определенному пространству в памяти, но ошибка доступа может произойти через 0.1 наносекунды или 1 наносекунду. Если 0.1 - значит, в том значении был 0, иначе - 1. И именно поэтому начали выпускать патчи, которые замедляли процессоры в полтора раза.

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

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