20 октября после продолжительной борьбы с глиобластомой скончался первоначальный автор декомпилятора Java Fernflower Стивер.

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

В то время ландшафт декомпиляции Java был очень беден. Время первого поколения декомпиляторов, таких как JAD или JODE, подходило к концу. В то время как Java как язык развивался, существующие декомпиляторы в основном не поддерживались, что затрудняло работу с новыми языковыми конструкциями, такие как операторы утверждений, аннотации и универсальные типы. Более того, прекращение поддержки инструкций JSR/RET в байт-коде Java привело к существенной разнице в том, как компилируются блоки try-finally. Декомпиляторы не смогли обрабатывать новый байт-код.

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

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

Стивер сам занимался основной разработкой в ​​период с 2008 по 2010 год. Первая публичная версия стала доступна в мае 2009 года в качестве веб-сервиса. Пользователь мог загрузить отдельный файл CLASS или целый файл JAR и получить декомпилированный результат. Декомпилятор быстро набрал популярность: в течение первых четырех месяцев пользователи декомпилировали более полумиллиона классов Java. Публичной автономной версии не было, но Стивер отправил ее в частном порядке нескольким бета-тестерам. После выпуска Стивер внес многочисленные улучшения и исправил множество ошибок благодаря ранним последователям.

После 2010 года активная разработка декомпилятора прекратилась, так как Стивер стал больше занят своей основной работой. Несколько сторонних поставщиков программного обеспечения попросили Стивера предоставить декомпилятор для своих проектов. В частности, он был использован в Mod Coder Pack, который представляет собой набор инструментов для создания модов для игры Minecraft. Тем не менее, обычному пользователю Java стало сложно найти рабочую версию Fernflower.

В 2013 году JetBrains обратилась к Стиверу с предложением включить Fernflower в IntelliJ IDEA. Частью сделки было сделать Fernflower открытым исходным кодом. Все прошло хорошо, и контракт был подписан. 11 июля 2014 года была выпущена ранняя версия IntelliJ IDEA 14, впервые включающая декомпилятор. Вы все еще можете увидеть первоначальный импорт исходного кода Fernflower в истории IntelliJ IDEA Git 4 марта 2014 года.

Хотя Стивер никогда не был сотрудником JetBrains, он помогал улучшать декомпилятор в течение года после первоначального импорта. Поскольку Java 8 только что вышла, необходимо было поддерживать новые языковые конструкции, такие как лямбды. Еще одной большой проблемой была интеграция декомпилятора с отладчиком IntelliJ IDEA. Номера строк в декомпилированном коде отличаются от номеров строк в исходном коде и Стивер помог обеспечить понятное сопоставление между ними, чтобы вы могли легко отлаживать декомпилированный код.

Когда Fernflower стал открытым исходным кодом, многие другие инструменты в мире Java стали его использовать: вы можете найти несколько форков на GitHub. Команда Java в JetBrains постоянно совершенствует Fernflower, но в основном это все еще код Стивера.

Если бы Fernflower не существовал, нам, вероятно, пришлось бы вложить значительные ресурсы в разработку собственного декомпилятора. Это могло бы привести к тому, что у IntelliJ IDEA не было бы декомпилятора или был бы тот, который был бы значительно менее эффективен. Работа Стивера стала настоящим переломным моментом для разработчиков Java за последние 10 лет. Поскольку не было лучшей альтернативы для декомпиляции байт-кода Java, его вклад сэкономил разработчикам бесчисленное количество часов при отладке кода Java без исходников. 

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

Оригинал статьи

Комментарии (36)


  1. Keeper11
    05.11.2024 09:21

    Это тот же Стивер -- основатель Флибусты, или какой-то другой?


    1. lovermann
      05.11.2024 09:21

      Да, это тот же человек.


      1. NickDoom
        05.11.2024 09:21

        А если прочитать как «Стивьер» — в воздухе отчётливо зазвенят пиастры ^______^


  1. NickDoom
    05.11.2024 09:21

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


    1. xenon
      05.11.2024 09:21

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


      1. Zangasta Автор
        05.11.2024 09:21

        Кстати, это было бы очень интересно почитать...

        Это пока что секретно. Флибуста работает.

        Если вам интересно, могу написать статью о работе библиотечного сообщества Флибусты, которую имел (не) счастье наблюдать со стороны.


        1. axweye
          05.11.2024 09:21

          Я бы почитал


        1. xenon
          05.11.2024 09:21

          Конечно, да.


      1. nick758
        05.11.2024 09:21

        АЗАПИ утверждала, что его вычислили: https://www.cnews.ru/news/top/2016-07-08_pravoobladateli_vychislili_vladeltsa_znamenitoj


  1. Metotron0
    05.11.2024 09:21

    Зачем ссылки через translate.google? Особенно если учесть, что https://wasm.in/blogs/unsafe-java-i-nebezopanaja-zhaba.416/ и так уже на русском.


    1. Zangasta Автор
      05.11.2024 09:21

      Сейчас поправлю.


  1. NickDoom
    05.11.2024 09:21

    Раз заговорили о кишках жавы, повторю свой старый вопрос…

    Допустим, у нас есть жаба. Которая «нажралась памяти и спит» © Башорг.

    Мы подключили вторую машину по гигабитке. Запустили там вторую жабу.

    Пока ничего сложного.

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

    Первая жаба более-менее помониторила статистику обращений к памяти, помониторила лаги и поняла, какие данные ей отправить в своп, а какие — держать под рукой. Все так делают, с большей или меньшей эффективностью и с более или менее «умными» профилировщиками. Даже Wolf3D так делал. Прямо на 80286-й. Там всё было предельно просто — страницы фиксированного размера, ридонли с дискетки, история последнего обращения к странице… тут, конечно, всё сложнее. При желании можно даже по коду предсказывать заранее, когда потребуется страница, и запрашивать из свопа чуть загодя. Виртуалка же, всё перед глазами.

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

    И тут мы принимаем эпохальное решение: отправить этот тред в ссылку на вторую машину вместе с его страницами памяти. Получается, что для нас эти страницы — своп, а тред поимел дикие лаги в плане общения с остальными тредами. Но для него, наоборот, свопом выглядит вся остальная память, а лаги, таки да, обоюдоострые. Синхронизация? Так у нас же виртуалка! Мы можем напихать в ходе JiT любые дополнительные операции, которые нужны для прозрачной синхры тредов, крутящихся на разных машинах, чтобы мерзавцы не заподозрили подвоха. А гигабитка — штука очень быстрая и лаги зачастую у неё на уровне самой PCIe, в которую она воткнута.

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

    Кто что по этому поводу может сказать?


    1. qw1
      05.11.2024 09:21

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


      1. NickDoom
        05.11.2024 09:21

        Согласен, поэтому про нативное ПО не заикался… но разве мы не вольны в JRE перехватить что угодно и отфорвардить куда угодно, в рамках такой реализации?

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


        1. qw1
          05.11.2024 09:21

          Не могу придумать пример ПО, где это было бы востребовано и уместно.


          1. NickDoom
            05.11.2024 09:21

            Ну, это самое лёгкое — игры с ожиревшей физикой типа Мимокрафта, где уже более-менее изолированные клиент и сервер под одним капотом (там даже вручную можно распараллелить вроде бы), синтезаторы-монстры типа XILINX (часть тредов проверяет одни варианты трассировки, часть — другие, обмен между ними минимален), а уж обработка видео-то…

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


            1. qw1
              05.11.2024 09:21

              игры с ожиревшей физикой типа Мимокрафта, где уже более-менее изолированные клиент и сервер под одним капотом

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

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

              синтезаторы-монстры типа XILINX (часть тредов проверяет одни варианты трассировки, часть — другие

              Тут будет конфликт на общих данных. Нужно, чтобы при обращении к данным они погдружались в память одной машины, но тогда должны быть "вытеснены в своп" для другой машины, т.е. при обращении к данным происходил Page Fault и перенос на ноду, которая обращается. Тогда какие-то общие данные, сам проект, который трассируем, какие-то правила трассировки, настройки, к которым идёт постоянное обращение, будут безостановочно кочевать с одной ноды на другую, если заранее не озаботиться копией для каждого потока. Но мы же хотим, чтобы всё работало автоматом. Ручное распараллеливание и так понятно, что можно сделать.


              1. NickDoom
                05.11.2024 09:21

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

                Немного сложнее, но в целом плюс-минус понятно.


                1. qw1
                  05.11.2024 09:21

                  Как потом изменения мержить, если оба конца захотят выполнить запись.


                  1. NickDoom
                    05.11.2024 09:21

                    Видимо, «потом» тут не прокатит, а придётся при записи в дублированную область приостановить тред, записать в обе копии (локальную и удалённую), а затем возобновить.

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

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

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

                    UPD: кармаплюс за хорошие, конструктивные, предметные вопросы.


                    1. qw1
                      05.11.2024 09:21

                      Видимо, «потом» тут не прокатит, а придётся при записи в дублированную область приостановить тред, записать в обе копии (локальную и удалённую), а затем возобновить

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


                      1. NickDoom
                        05.11.2024 09:21

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

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

                        Будь это данные из треда А или из треда Б — не суть, раз разработчику не важно, то и нам — тем более.

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

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


                      1. NickDoom
                        05.11.2024 09:21

                        Вот ещё сам придумал ситуацию.

                        Тред А пишет в ячейку в дуп-области. Ждёт подтверждения от второй машины (ну, или опровержения, потому что там тред Бэ записал туда же другое значение, тогда вместо подтверждающего пакета прилетит пакет на свою запись, не суть; в обоих случаях мы знаем, что «там» ячейка уже записалась, нашим значением или конкурентским, и знаем это значение).

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

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

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


                      1. qw1
                        05.11.2024 09:21

                        Расшаренные области будут проблемой, потому что грануляция - 1 страница. А на странице могут быть все глобальные переменные в одной куче (все поля StartUp-класса, что будет аналогом глобалов в Java). А это значит, каждая запись - Page Fault и сетевая синхронизация. Учитывайте ещё работу сборщика мусора, который ходит от глобальных рутов )))

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

                        Там, где алгоритм расчитан на частые наносекундные задержки (например, управление жизненным циклом через подсчёт ссылок), время будет на 4-6 порядков больше.

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

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

                              add [rsi+rbx*8+1000056], rax
                        

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

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


                      1. NickDoom
                        05.11.2024 09:21

                        синхрить по сети все, абсолютно все мьютексы/семафоры/спинлоки, потому что кто знает, какую область память защищает конкретный мьютекс

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

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

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

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

                        Стоп. Получается, что я придумал не расширение JRE, а расширение JiT, по сути.

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

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

                        Труд явно не на мою серую голову, а как минимум на молодого и энергичного Стивера…


                      1. qw1
                        05.11.2024 09:21

                        Но по сути при оптимизации JiT мы разве не делаем уже что-то подобное?

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

                        А мы же вроде можем посмотреть по коду, где ещё к ним идут обращения, проверки состояний и так далее, а где — не идут?

                        Не можем. Например, код

                        mutex.lock();
                        DoSomething();
                        mutex.unlock();
                        

                        Где функция DoSomething() через 10 уровней вызова трогает 100500 страниц памяти, но автоматически невозможно понять намерение программиста, какие из этих операций чтения/записи он защищает мьютексом, а какие

                        На том конце — не можем и не нужно, там обычная гонка состояний типа «тред ещё туда не успел записать», если это нормально приложению — нормально и нам)


                      1. NickDoom
                        05.11.2024 09:21

                        Похоже на то… ну тогда тем более ещё раз спасибо за анализ!


    1. lgorSL
      05.11.2024 09:21

      Посмотрите на модель акторов в Scala и библиотеке Akka. Там в основе всего лежит идея, что есть отдельные акторы, которые обмениваются сообщениями и в идеале больше никак не связаны с остальным кодом.

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

      Подход интересный, но очень сильно влияет на архитектуру.

      P.S. В чистом виде эта идея реализована в Эрланге: https://ru.wikipedia.org/wiki/Erlang


    1. Tsimur_S
      05.11.2024 09:21

      Ну чисто теоретически можно наверное нагнать несколько сотен суперкрутых инженеров и заделать за пару лет распределенную JDK работающую поверх RDMA но вот только зачем? Кто за это заплатит?

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


    1. Naves
      05.11.2024 09:21

      А как же Erlang?


      1. NickDoom
        05.11.2024 09:21

        Речь не о языках, а о умеренной модификации JRE, не трогая само прикладное ПО (допустим, его некому и не на какие шиши переписывать на других языках/в других парадигмах/с другой структурой).

        Если не повезло — остаётся при своих.

        Если повезло и нашли что вытащить на соседнюю машину — получили свои 20-30-40% производительности.

        Если сказочно повезло — получили почти +100%, то есть почти в два раза. Но «таких слоников не бывает», конечно. Даже хиний-хиний ХИЛИНХ вряд ли прямо вот так уж и состоит из однородных тредов.


    1. 0x131315
      05.11.2024 09:21

      Выглядит как изобретение велосипеда.

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

      Если нужна внешняя память или кеш, есть миллион специализированных сервисов, типа redis.

      Если это попытка сэкономить ресурсы, то лучше просто обновить железо: на современном железе можно не думать о памяти и потоках. Бытовое железо позволяет крутить 32 потока, серверное - больше 256 потоков. Память измеряется десятками или тысячами гигов соответственно. И все это локально, без всяких игр с сетью, с минимальными задержками. Основная сложность на таком железе это максимально его нагрузить, что сделать совсем не просто.

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

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

      А вообще на практике успешно работают следующие способы оптимизации:

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

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

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

      • размен циклов на время. Например сжатие больших кешей или отправляемых данных, если они хорошо жмутся: это дает дополнительную нагрузку на cpu, но ускоряет ввод/вывод в 5-10 раз. Жмут современные cpu быстро, хватает на большинство каналов ввода/вывода. Если cpu систематически недогружен и много текстового ввода/вывода, появляется возможность использовать такой размен

      Просто комбинируя эти принципы можно ускорить код на 2-3 порядка практически на ровном месте


      1. NickDoom
        05.11.2024 09:21

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

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

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

        И да, априорный скепсис — это не ответ «нет», это сомнения в ответе «да» :) пока чётко гробящей всю концепцию ситуации не нашли (вроде), так что я пришёл с сомнениями и остаюсь с сомнениями :-D


    1. AntonyMcGreen
      05.11.2024 09:21

      del
      я буду читать все ответы на комментарий перед отправкой


  1. foal
    05.11.2024 09:21

    O! @tagir_valeevуже в переводе на Хабре :)


    1. tagir_valeev
      05.11.2024 09:21

      Времена меняются...