Не очень понятно зачем. Если девиртуализацию сделать получилось — то её имеет смысл сделать. А если не получилось, то она просто некорректно.
Можно, конечно, заменять виртуальный вызов на if по vtable. Но для этого нужна статистика, а какие здесь vtable-ы вообще случаются. Это либо jit, либо pgo.
На практике, такие шифры использовались до изобретения компьютеров и вроде как не особо поддавались взлому.
Но вообще говоря, тут есть переполненная статистика, а потому от частотного анализа должны быть результаты. Насколько реалистично использовать эти результаты для взлома шифра — не подскажу.
Ну, вообще, по-хорошему, любой ключ должен быть одноразовым. Если мы хотите зашифровать несколько сообщений один ключом, то мы мысленно дописываем одно сообщение после другого и всё равно шифруем только один раз.
В этом смысле, размер "датасета", как Вы выразились — это просто длина шифруемого сообщения. И важным параметрам для шифра является именно "какой длины сообщение можно им зашифровать без риска для взлома".
Переиспользование ключей для нескольких разных сообщений в лучшем случае является сомнительной практикой, а в худшем просто напросто влечёт к прямому взлому. Поэтому нет особого смысла об этом говорить(если мы о хороших практиках, разумеется).
Поэтому в классической постановке, без нарушений условий эксплуатации одноразовый блокнот не поддаётся частотный анализу. А если же мы допускаем нарушения условий эксплуатации, то гораздо более эффективно воспользоваться человеческим фактором...
Чтобы применить частотный анализ, Вам нужны закономерности, перекошенная статистика в шифротексте.
Для шифра Цезаря очевидно статистика совпадает со статистикой открытого текста — просто дешифруется. Для шифра Виженера с ключевой фразой длины K можно рассмотреть статистику на каждом K-ом символе шифротекста и открытого текста — они, очевидно, совпадают.
В одноразовом блокноте у нас шифротекст распределен равномерно. Ну можно рассматривать его как шифр Виженера с бесконечным периодом. Каждый символ шифруется независимо от других, по независимому ключу. А по одному символу шифротекста статистику тяжело построить.
Или с другой стороны: нет даже способа проверить, что задданный ключ можно использовать для расшифрования — при расшифровке мы можем получить абсолютно любой текст заданной длины.
Поэтому нет, одноразовый блокнот не поддаётся статистическому анализу (если он действительно одноразовый и если он истинно случайный (чего само по себе сложно добиться)).
А есть ли шифры, которые нельзя математически нельзя расшифровать без закрытого ключа или иной секретной информации?
Ассиметрический шифр всегда можно взломать, если потратить достаточно много времени.
Для этого достаточно перебрать все возможные сообщения, все возможные закрытые ключи и все возможные значения источников случайности. Таким образом, рано или поздно, мы найдём а) наш шифротекст б) закрытый ключ (или несколько) который расшифровывает все сообщения (которые мы перебрали). (Разумеется сообщений для перебора бесконечно много, но мы можем перебрать все сообщения шифротексты которых подходят по длине искомого шифротекста — это можно сделать за конечное время).
Сравнивать на json-е скучно. Возьмите хотя бы парсер выражений на 10-15 уровней приоритетов.
В код не вчитывается, но кажется Вы используете рекурсивный спуск. Он, вообще говоря, довольно неоптимален на сложных грамматиках. Плюс, у ANTLR чуть шире класс разбираемых грамматик, чем LL(k).
Не очень понятно, а какую проблему Ваша система пытается решить. Contextual keywords, language embedding, что-то ещё?
Сам синтаксис всё ещё довольно сложный и шумный (сравните с условными antlr и tree sitter). Встраивания фрагментов на C++ являются скорее недостатком, чем преимуществом: если уж мы полезли в генератор, то хочется в нём и оставаться.
Также, пример с выражениями Вам не просто так привели. Разумеется, есть классические примеры, как делать грамматику выражений с приоритетами. Но это довольно плохой подход. Хочется видеть хотя бы shunting yard/pratt parsing. Тут же вопрос: а какой parse tree Вы для этих выражений строите?... Помниться была статья, где паркинг питона ускоряли в 300 раз, просто устраняя вот эту лозу из дерева разбора.
Не, ну бенчмаркать нужно очень аккуратно. Как минимум, нужно делать бенчи в независимых окружениях: у Вас сначала оптимизатор специализирует getA на один архетип, а потом видит, что эвристика сломалась и переоптимизирует её - вот и дополнительные 6мс. Дальше вопрос в том, что сам код с циклом лучше тоже завернуть в функцию - чтобы сам цикл тоже оптимизировать. Плюс мб имеет смысл сначала заготовить массив объектов, а потом уже получать доступ к свойствам. А то инлайнер может чудеса творить.
В целом, внезапно неплохой сайт для бенчей у Карловского сделан.
Потому что break никогда не вернет значение. Например:
let v: Int32 = break;
//^ можно, мы из этого v никогда не прочитаем
let x: Int32 = for(...) {}
//^ нельзя: цикл может закончиться, а значения у нас нет
Разумеется, в таком применении, это не очень полезно. Но в комбинации с другими фичами, это может быть интересным:
let v: Int32 = if isPrime(n) {
n*n
} else {
continue
}
Здесь мы в одной ветке не считаем значение, а просто обрываем цикл.
В целом, тип Nothing также дает возможность объявить функцию, которая всегда кидает исключение (какой-нибудь throwExceptionWithExtendedInfo()).
Сами типы Unit и Nothing, судя по всему, пришли из котлина. Аналогичные типы есть в расте: пустой кортеж `()` и never type `!`. Поэтому стандартная история для современного мейнстрима.
Вообще, язык выглядит как помесь котлина и свифта, с элементами раста. Хотя из описания статьи многое осталось непонятно про собственно сам язык и среду исполнения. Видимо прийдется курить доку, чтобы составить представление о языке.
Первый вариант почти что UB, из-за TBAA. Спасает только, что uint8_t разрешено алиаситься с любыми типами.
Если бы каст был бы хотя бы между int16_t и int32_t (или например между флоатом и интом), то этот код был бы UB. И емнип, крайние версии кланга стали эксплуатировать это уб в подобном коде.
Знали ли Вы это или нет — хороший вопрос.
Чтобы правильно делать reinterpret, нужно делать memcpy в буфер и обратно в другой тип.
Такой подход ломается на банальном `await process(state)` — поскольку внутри process могут быть другие await-ы, или того хуже коллбеки. Препроцессить абсолютно весь код — сложно (например, потому что браузерные апи не выйдет препроцессить).
Вообще, правильным решением было бы профорсить AsyncLocalStorage (он же cls). Но кажется проползал завис и есть только реализация в ноде :(
Можно было бы проэмулировать cls через препроцессор, и я думаю уже даже есть такие решения. Но хрупко это все, в прод бы я такое не потянул.
Ну, как поддержать синхронный авторан конкурентно с ассинхронным — понятно.
По сути, нужно внутри библиотеки вставлять семафор на ассинхронные автораны (чтобы одновременно было запущенно не более одного авторана). Иначе, как подтверждает @nin-jin, это выстрел в ногу, как только у нас запустится два ассинхронных авторана (например, если у них поменяется общая зависимость). Я об этом и написал в самом первом своем комментарии.
Поэтому для меня совершенно непонятна затея await-ить автораны (мы же делаем await только для первого запуска, а со всеми остальными ничего не понятно).
Ограничение на последовательное исполнение ассинхронных авторанов, в целом, понятно. Вопрос только в том, а что можно сделать полезного с учетом этого ограничения. Не будет ли это источником водопадов?
Ну, так Ваши замеры и показывают, что 2.5 секунды. Не, понятно, что там ещё какие-то копейки будут от собственно синхронного исполнения — но это вообще не суть вопроса. Суть вопроса в том, чтобы понять как поведет Ваша методика, если там будут хоть какие-то нетривиальные промисы.
То, что Вы меряете только один промис, а не оба — просто придираетесь к формулировкам. Разумеется мерять нужно время от старта до момента появления обоих write-ов.
Ну и вопрос "сколько времени займет" здесь носит качественный, а не количественный характер. Вы делаете то, что в других библиотеках совершенно отсутствует — в этом случае вести разговор об абсолютном перфе нет особого смысла. Интересно понять качественное поведение Вашего решения.
Если хотите вести конструктивный диалог, приводите реальные примеры,
Я вроде задаю конкретные вопросы и привожу конкретные примеры. Основной вопрос сводится к тому, что не до конца понятен принцип работы Вашего подхода, а также его особенности (и в течении нашего разговора у меня вопросов становиться только больше).
Про расчеты написал выше, что в них нет смысла при разговоре о качественном поведении подхода.
P. S. Вы почему-то то и дело пытаетесь меня в чем-то обвинить. Не надо так. Вы кажется достаточно хорошо знакомы с деталями, чтобы можно было опустить какие-то формальности и терминологические споры.
Ну, хорошая реализация исполняет этот код за 2.5 секунды. Если же вводится запрет на параллельное исполнение ассинхронных эффектов, то потребуется целых 5 секунд. Из Вашего описания в статье, я так понимаю, Вы именно что вводите этот запрет, чтобы понимать их какого эффекта происходит чтение.
Ваши упоминания LIFO стека я увидел и при прочтении статьи. Но что конкретно это значит — разительно непонятно. Я могу себе представить 2-3+ сильно разных способов использовать LIFO стек. Вот поэтому и спрашиваю.
Тогда не очень понятно. Если у Вас могут параллельно могут исполняться два ассинхронных эффекта, то как Вы отслеживание их зависимости?
Тест из примера в статье не показывает параллельно ли они исполняются, или последовательно (может у Вас внутри там семафор).
await эффектов это не какой-то отдельный кейс, и это никак не противоречит тому, что эффекты мы создаем один раз, а выполняются они сколько угодно раз.
Тогда совершенно непонятно, зачем он нужен. Какой юзкейс, безопасно ли пользователю забыть сделать await эффекту?
Если я правильно понял, Вы сделали в системе не более одного активного ассинхронного эффекта. Если это так, то в этом нет смысла — ассинхронные эффекты хочется всё же исполнять параллельно.
Также, совершенно непонятный кейс с await-ом эффектов. Мы же эффекты один раз настраиваем и они потом много раз перезапускаются — между эффектами нет явного порядка.
Короче, непонятно зачем это всё нужно (не в смысле ассинхронные эффекты в принципе, а конкретно Ваше виденье).
Не очень понятно зачем. Если девиртуализацию сделать получилось — то её имеет смысл сделать. А если не получилось, то она просто некорректно.
Можно, конечно, заменять виртуальный вызов на if по vtable. Но для этого нужна статистика, а какие здесь vtable-ы вообще случаются. Это либо jit, либо pgo.
На практике, такие шифры использовались до изобретения компьютеров и вроде как не особо поддавались взлому.
Но вообще говоря, тут есть переполненная статистика, а потому от частотного анализа должны быть результаты. Насколько реалистично использовать эти результаты для взлома шифра — не подскажу.
Ну, вообще, по-хорошему, любой ключ должен быть одноразовым. Если мы хотите зашифровать несколько сообщений один ключом, то мы мысленно дописываем одно сообщение после другого и всё равно шифруем только один раз.
В этом смысле, размер "датасета", как Вы выразились — это просто длина шифруемого сообщения. И важным параметрам для шифра является именно "какой длины сообщение можно им зашифровать без риска для взлома".
Переиспользование ключей для нескольких разных сообщений в лучшем случае является сомнительной практикой, а в худшем просто напросто влечёт к прямому взлому. Поэтому нет особого смысла об этом говорить(если мы о хороших практиках, разумеется).
Поэтому в классической постановке, без нарушений условий эксплуатации одноразовый блокнот не поддаётся частотный анализу. А если же мы допускаем нарушения условий эксплуатации, то гораздо более эффективно воспользоваться человеческим фактором...
Чтобы применить частотный анализ, Вам нужны закономерности, перекошенная статистика в шифротексте.
Для шифра Цезаря очевидно статистика совпадает со статистикой открытого текста — просто дешифруется. Для шифра Виженера с ключевой фразой длины K можно рассмотреть статистику на каждом K-ом символе шифротекста и открытого текста — они, очевидно, совпадают.
В одноразовом блокноте у нас шифротекст распределен равномерно. Ну можно рассматривать его как шифр Виженера с бесконечным периодом. Каждый символ шифруется независимо от других, по независимому ключу. А по одному символу шифротекста статистику тяжело построить.
Или с другой стороны: нет даже способа проверить, что задданный ключ можно использовать для расшифрования — при расшифровке мы можем получить абсолютно любой текст заданной длины.
Поэтому нет, одноразовый блокнот не поддаётся статистическому анализу (если он действительно одноразовый и если он истинно случайный (чего само по себе сложно добиться)).
Ассиметрический шифр всегда можно взломать, если потратить достаточно много времени.
Для этого достаточно перебрать все возможные сообщения, все возможные закрытые ключи и все возможные значения источников случайности. Таким образом, рано или поздно, мы найдём а) наш шифротекст б) закрытый ключ (или несколько) который расшифровывает все сообщения (которые мы перебрали). (Разумеется сообщений для перебора бесконечно много, но мы можем перебрать все сообщения шифротексты которых подходят по длине искомого шифротекста — это можно сделать за конечное время).
Если действительно случайный символ, то частотный анализ не сработает.
Сравнивать на json-е скучно. Возьмите хотя бы парсер выражений на 10-15 уровней приоритетов.
В код не вчитывается, но кажется Вы используете рекурсивный спуск. Он, вообще говоря, довольно неоптимален на сложных грамматиках. Плюс, у ANTLR чуть шире класс разбираемых грамматик, чем LL(k).
Не очень понятно, а какую проблему Ваша система пытается решить. Contextual keywords, language embedding, что-то ещё?
Сам синтаксис всё ещё довольно сложный и шумный (сравните с условными antlr и tree sitter). Встраивания фрагментов на C++ являются скорее недостатком, чем преимуществом: если уж мы полезли в генератор, то хочется в нём и оставаться.
Также, пример с выражениями Вам не просто так привели. Разумеется, есть классические примеры, как делать грамматику выражений с приоритетами. Но это довольно плохой подход. Хочется видеть хотя бы shunting yard/pratt parsing. Тут же вопрос: а какой parse tree Вы для этих выражений строите?... Помниться была статья, где паркинг питона ускоряли в 300 раз, просто устраняя вот эту лозу из дерева разбора.
Не, ну бенчмаркать нужно очень аккуратно. Как минимум, нужно делать бенчи в независимых окружениях: у Вас сначала оптимизатор специализирует getA на один архетип, а потом видит, что эвристика сломалась и переоптимизирует её - вот и дополнительные 6мс. Дальше вопрос в том, что сам код с циклом лучше тоже завернуть в функцию - чтобы сам цикл тоже оптимизировать. Плюс мб имеет смысл сначала заготовить массив объектов, а потом уже получать доступ к свойствам. А то инлайнер может чудеса творить.
В целом, внезапно неплохой сайт для бенчей у Карловского сделан.
Ага. Обычно в таких начинаниях делают бектест на обучающих данных... Переобучение, короче говоря =/
Потому что break никогда не вернет значение. Например:
Разумеется, в таком применении, это не очень полезно. Но в комбинации с другими фичами, это может быть интересным:
Здесь мы в одной ветке не считаем значение, а просто обрываем цикл.
В целом, тип Nothing также дает возможность объявить функцию, которая всегда кидает исключение (какой-нибудь throwExceptionWithExtendedInfo()).
Сами типы Unit и Nothing, судя по всему, пришли из котлина. Аналогичные типы есть в расте: пустой кортеж `()` и never type `!`. Поэтому стандартная история для современного мейнстрима.
Вообще, язык выглядит как помесь котлина и свифта, с элементами раста. Хотя из описания статьи многое осталось непонятно про собственно сам язык и среду исполнения. Видимо прийдется курить доку, чтобы составить представление о языке.
Первый вариант почти что UB, из-за TBAA. Спасает только, что uint8_t разрешено алиаситься с любыми типами.
Если бы каст был бы хотя бы между int16_t и int32_t (или например между флоатом и интом), то этот код был бы UB. И емнип, крайние версии кланга стали эксплуатировать это уб в подобном коде.
Знали ли Вы это или нет — хороший вопрос.
Чтобы правильно делать reinterpret, нужно делать memcpy в буфер и обратно в другой тип.
Такой подход ломается на банальном `await process(state)` — поскольку внутри process могут быть другие await-ы, или того хуже коллбеки. Препроцессить абсолютно весь код — сложно (например, потому что браузерные апи не выйдет препроцессить).
Вообще, правильным решением было бы профорсить AsyncLocalStorage (он же cls). Но кажется проползал завис и есть только реализация в ноде :(
Можно было бы проэмулировать cls через препроцессор, и я думаю уже даже есть такие решения. Но хрупко это все, в прод бы я такое не потянул.
Ну, как поддержать синхронный авторан конкурентно с ассинхронным — понятно.
По сути, нужно внутри библиотеки вставлять семафор на ассинхронные автораны (чтобы одновременно было запущенно не более одного авторана). Иначе, как подтверждает @nin-jin, это выстрел в ногу, как только у нас запустится два ассинхронных авторана (например, если у них поменяется общая зависимость). Я об этом и написал в самом первом своем комментарии.
Поэтому для меня совершенно непонятна затея await-ить автораны (мы же делаем await только для первого запуска, а со всеми остальными ничего не понятно).
Ограничение на последовательное исполнение ассинхронных авторанов, в целом, понятно. Вопрос только в том, а что можно сделать полезного с учетом этого ограничения. Не будет ли это источником водопадов?
Ну, так Ваши замеры и показывают, что 2.5 секунды. Не, понятно, что там ещё какие-то копейки будут от собственно синхронного исполнения — но это вообще не суть вопроса. Суть вопроса в том, чтобы понять как поведет Ваша методика, если там будут хоть какие-то нетривиальные промисы.
То, что Вы меряете только один промис, а не оба — просто придираетесь к формулировкам. Разумеется мерять нужно время от старта до момента появления обоих write-ов.
Ну и вопрос "сколько времени займет" здесь носит качественный, а не количественный характер. Вы делаете то, что в других библиотеках совершенно отсутствует — в этом случае вести разговор об абсолютном перфе нет особого смысла. Интересно понять качественное поведение Вашего решения.
Я вроде задаю конкретные вопросы и привожу конкретные примеры. Основной вопрос сводится к тому, что не до конца понятен принцип работы Вашего подхода, а также его особенности (и в течении нашего разговора у меня вопросов становиться только больше).
Про расчеты написал выше, что в них нет смысла при разговоре о качественном поведении подхода.
P. S. Вы почему-то то и дело пытаетесь меня в чем-то обвинить. Не надо так. Вы кажется достаточно хорошо знакомы с деталями, чтобы можно было опустить какие-то формальности и терминологические споры.
Ну, хорошая реализация исполняет этот код за 2.5 секунды. Если же вводится запрет на параллельное исполнение ассинхронных эффектов, то потребуется целых 5 секунд. Из Вашего описания в статье, я так понимаю, Вы именно что вводите этот запрет, чтобы понимать их какого эффекта происходит чтение.
Ваши упоминания LIFO стека я увидел и при прочтении статьи. Но что конкретно это значит — разительно непонятно. Я могу себе представить 2-3+ сильно разных способов использовать LIFO стек. Вот поэтому и спрашиваю.
Не очень понятно. Давайте такой пример:
Сколько будет исполняться данный код:
При старте
После изменения shared
После изменения x2
После изменения y2
?
P.S. Я умышленно не сделал await autorun. Если это существенно влияет на ответы, либо корректность - скажите.
Тогда не очень понятно. Если у Вас могут параллельно могут исполняться два ассинхронных эффекта, то как Вы отслеживание их зависимости?
Тест из примера в статье не показывает параллельно ли они исполняются, или последовательно (может у Вас внутри там семафор).
Тогда совершенно непонятно, зачем он нужен. Какой юзкейс, безопасно ли пользователю забыть сделать await эффекту?
А какое значение будет показано пользователю в res1 и res2? Они будут одинаковыми, будут ли они зависеть от порядка исполнения?
Мне не кажется это поведение разумным. Всё же ошибка в деве для такого гораздо лучше.
Если я правильно понял, Вы сделали в системе не более одного активного ассинхронного эффекта. Если это так, то в этом нет смысла — ассинхронные эффекты хочется всё же исполнять параллельно.
Также, совершенно непонятный кейс с await-ом эффектов. Мы же эффекты один раз настраиваем и они потом много раз перезапускаются — между эффектами нет явного порядка.
Короче, непонятно зачем это всё нужно (не в смысле ассинхронные эффекты в принципе, а конкретно Ваше виденье).