Мы точно о разных кальвинах. Какая разница, сколько шардов затрагивает транзакция? Когда идёт двухфазная фиксация, это важно, когда кальвин – никакой разницы вообще.
Не, я о том, что там не простые входные команды, они скорее всего мельче, чем данные над которыми проводятся превращения. Потому реплицируются входные команды, а промежуточный слой уже размазывает эти команды по большому объему данных. Иначе бы смысла не было всю эту систему городить.
Для этого действительно проще всего взять готовый Zookeeper. Но из-за этого считать всю систему «надстройкой над Zookeeper’ом» – явное преувеличение.
Без ZooKeeper система теряет отказоустойчивость -- checked.
ZooKeeper составляет основную сложность системы и его ничем нельзя заменить -- checked.
ZooKeeper определяет производительность выполнения транзакций -- checked.
Может быть это и большая надстройка, может быть очень большая и сложная надстройка. но по итогу все ключевые характеристики системы определяются ZooKeeper-ом.
А что же ещё может быть в мире key-value СУБД? Особенно если учесть, что даже в мире реляционных СУБД логическая репликация основана на подходе "установить по ключу"?
Ну так при логической репликации "установить по ключу" не реплицируется всё содержимое записи. В статье в бенчах упоминается 10% транзакций, затрагивающие сразу несколько шардов -- очевидно, что это не простая установка по ключу, а какая-то сложная операция сразу над большим числом ключей. И такие операции прекрасно реплицируются на уровне входной команды.
Подождал, спросил ещё раз. Совершенно не вижу проблемы собрать кластер высокой доступности для секвенсера.
Один узел сдох. Совсем. Или даже целая реплика пропала, потому что роут к датацентру потерялся. Разве не в этом смысл высокой доступности?
в наборе узлов один секвенсер. И этот секвенсер заранен знает не весь набор транзакций, а последовательность сообщений от других секвенсеров. Этого достаточно.
От каких "других секвенсоров" он будет знать последовательность в условиях отказов? Я и пишу -- от каких-то случайных, случайное подмножество последовательности операций. Без отказов мне ZooKeeper не нужен -- я просто кину SQLite базу на один комп и на этом проблемы мои закончатся.
Мы точно про один и тот же кальвин говорим? Что такое «входной запрос»?
Кальвин – это чистый key/value, входной запрос там равен данным.
Ну в мире key-value СУБД запросы не ограничиваются простым "запросить по ключу" и "установить по ключу". Потому что в таком случае, как я уже согласился, можно взять ZooKeeper и не морочить голову.
Он знает, что у него должна быть последовательность A1 B1 C1 A2 B2 C2 и т. д. Если B2 почему-то нет, можно поинтересоваться у секвенсера B, что у него там было под номером 2?
Поинтересовался -- получил таймаут, дальше что? А я скажу, что дальше: на части узлов транзакции B2 есть, а на другой части -- нет. Всё, приехали.
Raft и прочие ZAB'ы нужны только лишь потому, что никто заранее не знает, что будет в логах. Секвенсеры в кальвине знают это заранее, поэтому им проще
Заранее? Схема на Figure 1 говорит, что заранее про транзакции знает лишь один набор узлов -- реплики не знают ничего.
Но во-первых, журнал транзакций у Кальвина не сказать чтобы компактный – там передаётся ключ, значение и номер предыдущей версии значения, так что терабайты набираются легко.
Calvin реплицирует входной запрос, он не реплицирует значение самих данных. Если запросы больше, чем сами данные -- да, Calvin не нужен, это можно реализовать на голом ZooKeeper.
А во-вторых я не понимаю, зачем там paxos-based (ну или zab-based) репликация, если можно заранее договориться, в какой последовательности идут транзакции от разных секвенсоров.
Да, можно. Но кто потом будет публиковать эту последовательность? Допустим, публикация происходит "все ко всем". Представь, что во время публикации один из узлов отказывает. К части узлов транзакции с него дошли, к другой части -- нет. Что делать? Нужен консенсус, то есть, одинаковый набор команд на всех узлах кластера. Как делать консенсус? Либо назначить один из узлов Calvin эталонным, и таким образом потерять отказоустойчивость, либо применять внешнее решение для распределенного консенсуса -- а это и есть ZooKeeper.
В принципе, отказ малого числа узлов можно пережить через gossip protocol, то есть, процесс публикации "все ко всем" непрерывно распространяет не только самую последнюю эпоху, но и предыдущие, которые потенциально не дошли всем от умершего узла. Но если целью стоит устойчивость к отказам вплоть до кворума и даже потенциально ниже него со временной остановкой обработки данных, то тут уже никакой gossip не спасет и узлы пойдут в разнобой. Это уже не говоря о том, что gossip protocol сожрет все сетевые каналы.
В референсной реализации кальвин-протокола секвенсеров много, они независимы друг от друга, а упорядоченность достигается синхронизацией часов и пакетированием транзакций по 10 мс.
Советую читать оригинальную статью, а не буклетик по первой ссылке из гугла:
At the end of each epoch, all requests that have arrived at a sequencer node are compiled into a batch. This is the point at which replication of transactional inputs (discussed below) occurs.
Calvin currently supports two modes for replicating transactional input: asynchronous replication and Paxos-based synchronous replication
In asynchronous replication mode, one replica is designated as a master replica, and all transaction requests are forwarded immediately to sequencers located at nodes of this replica
Calvin also supports Paxos-based synchronous replication of transactional inputs. In this mode, all sequencers within a replication group use Paxos to agree on a combined batch of transaction requests for each epoch. Calvin’s current implementation uses ZooKeeper
Забавно, как авторы Calvin, ровно как и люди из Яндекса, которые на презентациях рассказывали про YandexDB, упорно маскировали тот факт, что вся их разработка по сути является небольшой надстройкой над Zookeeper.
Если есть zookeeper, то никакой кальвин уже не нужен
Нужен для того, чтобы не гонять терабайты данных через сам zookeeper. Таким образом уменьшается объем строго согласованных данных и можно обрабатывать больше транзакций в секунду. Плюс к тому же эти терабайты можно шардировать, реплицируя по всем шардам только метаданные транзакций, а не сами данные.
Остаётся открытым вопрос, зачем Кальвину Zookeeper.
Я уже не стал вскрывать эту тему. Вы молодые, шутливые, вам все легко. Статейка-то была про многозадачность, я и так сильно растянул про распределенные системы.
Кальвину Zookeeper нужен для того, чтобы определять, какая транзакция после какой будет выполняться, причем, этот порядок должен быть идеально одинаковым для всего кластера, чтобы везде получились одинаковые данные по одинаковому номеру транзакции. Дальше уже локально узел может как угодно медленно применять утвержденный список транзакций.
Задачу же работы со строго согласованным состоянием с гарантиями отказоустойчивости и приемлимой производительностью во всей индустрии умеют решать ровно три софтины -- Zookeeper, Etcd, и Google Chubby. Конечно, может быть есть еще какое-то проприетарное решение, которое публике не показывают.
Как я понимаю, программист окажется неприятно стеснен в выразительных средствах, что в том числе выльется в низкую производительность кода. И это один из способов достигнуть верифицируемости алгоритма — связать кодеру руки и ноги, чтобы он сильно не рыпался, и тогда уже для простейших конструкций доказательство будет тривиально. Правда, при таком подходе уже недалеко до чего-то вроде STM/PSO, которое из коробки гарантирует корректность многопоточного доступа безо всяких внешних доказательств.
Про попытки сделать автоматический доказыватель корректности многопоточной программы я слышал, но я слабо верю в их применимость к задачам сложнее обедающих философов, поскольку достаточно сложный и/или несериализуемый (https://en.wikipedia.org/wiki/Serializability) алгоритм может легко улететь в бесконечную сложность при наивном анализе выполнения. В начале статьи я упоминал пример с простыми четырьмя операциями и циклом из четырех инструкций, но в реальной задаче это может быть два бесконечных цикла с условиями внутри, для которых уже теоретически невозможно реализовать алгоритм, который хотя бы оценил конечность выполнения этих циклов (https://ru.wikipedia.org/wiki/Проблема_остановки), не говоря уже про корректность операций, которые будут в этих циклах исполняться. По этой причине эдак половина ноши "доказательства корректности" прежде всего заключается в том, чтобы не писать некорректные алгоритмы, которые никто не сможет проанализировать. То есть, не наращивать сложность алгоритма без причины.
Кстати, думаю перевести статью на русский, но не уверен, что кому-то это будет сильно интересно -- все-таки материал такой, что целевая аудитория обычно не испытввает проблем с английским языком.
Библиотека нигде не используется, и, как я написал, это скорее proof-of-concept, там потенциально куча багов, довольно много неподдерживаемых фич и просто недоработок.
NumPy-массив, как я писал в статье, можно поместить в разделяемую память ( и модель PyTorch тоже), но дальше возникает проблема - что с ними делать? Передавать между процессами ID сегмента? Это можно сделать любым инструментом, будь то multiprocessing или ZeroMQ, там накладные расходы от передачи строки ничтожны по сравнению с обработкой с самих данных. Смысл многозадачности, как правило, заключается в возможности параллельного координированного изменения данных, или хотя бы чтения параллельно с изменением. В PyTorch есть какие-то подвиги в этом направлении (torch.multiprocessing), а в NumPy подразумевается, что параллельное выполнение возможно только вне питона со специальными внутренними структурами данных, то есть, там уже всё подогнанно под специальные костыли в обход питона. Я могу лишь точно сказать, что реализация полноценной поддержки NumPy займет не день и не два.
Ваш угар это библиотека core.async никто её использовать не заставляет
Зачем вы мне что-то пытаетесь доказать если даже и минимально язык не знаете?
Это действительно core.async, браво. Я так понимаю, способность узнавать основные библиотеки была той самой демонстрацией глубоких познаний в языке. Правда, я так и не увидел ответа на вторую часть моего сообщения — про несовместимость структур данных JS и ClojureScript.
Пока вижу что плаваете вы, утверждаете что оказывается может быть прирост производительности при работе с неизменяемыми коллекциями и что такое персистентность так и изучили. Прироста производительности там по определению быть не может, есть лишь приемы минимизирующие потери, один из них персистентность(изучайте, https://en.wikipedia.org/wiki/Persistent_data_structure)
Не вижу определения, указывающего на невозможность прироста производительности. Приемы, вроде возврата указателя на позицию внутри строки, стары, как сами компьютеры — они показывают прекрасную производительность и опираются на неизменность строки по указателю.
Наверно ваш коллега с вами тоже спорил, пытался что-то про DNS доказать. Сейчас вы на его месте
Наверное, кому-то стоит прекращать с завидной регулярностью писать бредовые догадки. Это я к нему обратился с проблемой доступа к его серверу и дал ответ DNS, чтобы он разобрался с проблемой. В ответ на что он похлопал глазками и попросил дать IP, выдаваемый ping-ом.
Вообще про мой опыт не стоит что-то заявлять, вы ничего не знаете я про вас тоже, могу судить только в рамках конкретного вашего высказывания
Ну вот же, трезвая оценка ситуации, наконец. Действительно, по двум сообщениям тяжело понять уровень профессиональных навыков. Зато по двум сообщениям я могу примерно узнать нечто, похожее на мою восторженность, когда я первый раз познакомился со структурами данных Clojure. Разница наша заключается в том, что я в итоге отнеся скептически и к языку, и к многоверсионным хранилищам, осознав, что за пределами Clojure потребность в хранении нескольких версий данных сильно ниже, а многоверсионные СУБД были с нами уже очень давно.
В ClojureScript можно напрямую вызывать и использоватью любой другой JS код без всяких прослоек
Вызвать функцию, действительно, можно без прослоек. Прослойки начинают требоваться уже на уровне передачи функции массива или объекта в качестве аргумента. Еще более веселыми прослойки становятся, когда нужно передать callback. И совсем угар начинается, когда поверх этого нужно еще и ошибки ловить:
В первую очередь в clojure имутабельные структуры, персистентность это оптимизация скоросит операций для имутабельных структур, очевидно вы этого не знаете
То, что вы вчера прочитали про неизменяемые структуры в clojure, не ставит вас выше посетителей хабра, и тем более не является поводом делать выводы, что никто кроме вас не смог про них прочитать.
Неизменяемые структуры могут давать прирост производительности, а могут не давать. Обычно — не дают, поскольку простое изменение ячейки массива выполняется проще с точки зрения алгоритма и не оставляет после себя горы мусора в виде копий узлов дерева разных версий.
Наверняка не знаете что такое REPL ориентировання разработка и даже понятие не имеете насколько это упрощает процесс
Вот прямо сейчас работаю с Vue, и там есть REPL. Где ваш бог теперь?
У меня опыт на проде 2 года с Clojure/ClojureScript. Я вижу что вы просто пишите о чем не имеете понятия
У меня есть знакомый человек, который 5 лет пишет веб, но до сих пор не знает, как работает DNS. И что дальше? Деньги ему платят — больше его ничего не волнует.
Про ClojureScript. Что за бред? Из написанно понятно кто кроме названия о ClojureScript вы ничего не знаете
А вы знаете, но не напишите, правильно? Замысел Clojure был в реализации фундаментально многопоточного языка на базе «persistent data structures». Это довольно сильно отличает его как от других лиспов, так и от большинства других языков. Разве что некоторые языки серверной логики СУБД дают подобный функционал.
ClojureScript? В однопоточном JS? Он здесь вообще зачем? Я понимаю, что хотелось на лиспе писать веб, но разработка на вебе нынче сводится к тому, сколько готовых библиотек я могу использовать в своем проекте. А в итоге библиотек нет, многопоточности нет — и пишут на нем только фанаты.
В точности как класс, который не является функцией, возвращающей объект. У конструктора и return нет. Хотя оба являются callable и возвращают объекты
Мы так дойдем до того, что функция тоже не совсем является функцией. Обычные классы питона можно вызвать, потому что для них определен метод __call__ в общем базовом классе «type», и реализован он в Objects/typeobject.c: type_call(...). С таким же успехом можно было бы сделать создание экземпляра класса, например, через оператор индексации массива. Однако, в случае функций питона мы вступаем на скользкую дорожку...
У конструктора и return нет
Есть. Мало кто знает, что в питоне конструктор класса называется __new__.
Не имеет никакого значения, как это реализовано на уровне C API. Интерпретатор PyPy делает по-другому, и что?
Но я все-таки хотел сказать о том, что такая реализации генераторов уничтожает читаемость кода, поскольку вы не можете знать поведение функции до тех пор, пока не прочитали каждую ее строчку и не убедились, что там есть или нет yield. Потому что в том числе поведение return изменится на совершенно иное, как только будет хотя бы один раз упомянуто yield в теле функции.
Выражение generator() подразумевает вызов функции, которая возвращает объект генератора
Да, только generator не является функцией, возвращающей генератор. У нее и return нет вовсе. Возвращающей генератор функцией является PyGen_NewWithQualName, вызываемая _PyEval_EvalCodeWithName вместо целевой функции при наличии флага CO_GENERATOR у целевой функции. Гвидо фактически признал свой провал, когда ввел модификатор async для определений функций. Естественно, старый кривой функционал менять никто не будет, потому что на основе него уже написано куча программ.
Вызов generator() не выполняет тело генератора подобно тому, как вызов iter(iterable) не приводит к выполнению итератора — оба лишь создают итераторы, которые затем исполняются при вызовах i.__next__(). Сначала создание контекста, потом выполнение
Моя претензия заключалась в том, что функции-генераторы по записи не отличаются от функций, при этом с простыми функциями имеют мало общего. Запись generator() по здравому смыслу (с которым не дружат создатели языка) не подразумевает ничего, кроме вызова generator(). iter(iterable) прежде всего, вызывает функцию iter(), и делает это явно. Если generator — это класс, то мы вызываем этот класс, то есть, конструктор класса. Если generator — это функция-генератор, то мы вызываем чёрт пойми что.
Нет, вы создали замыкание. Генератор не хранит никакой стек (он у потока только один), он хранит исключительно свой стековый кадр, который удерживает внутреннее состояние генератора (в том числе замкнутые переменные). Этот кадр является "осыротевшим" в моменты между выполнением генератора
Да, выйти из своего фрейма в вышестоящий фрейм создателя генератор не может — можно условно говорить о том, что это уже не стэк, а разрозненные фреймы с данными, часть из которых уже могла быть уничтожена. Так что формально дополнительного стэка нет, действительно.
Люди пишут именно конечные автоматы (я вот тоже писал — для хорошо структурированной обработки UI-событий), это вы там потом ищете скрытые машины Тьюринга
При исполнении на машине Тьюринга конечный автомат можно определить как часть системы по отсутствию признаков, а не по их наличию. То есть, негативное определение, а именно — ограниченность состояния рассматриваемой части. Если некий код обработки событий UI имеет ограниченное число входных параметров, ограниченный набор внутренних состояний, не являющихся частью автомата, и код ограничен в своих действиях входными параметрами и внутренним состоянием, то можно говорить о конечности автомата.
Я так понимаю, под структурированным кодом обработки событий имеется в виду большой switch или другой вариант перехода к коду обработки события по коду события. Однако, switch сам по себе не только теряет свойство бесконечным — он вообще не является автоматом, поскольку у него отсутствует состояние, а есть только входные данные: https://ru.wikipedia.org/wiki/Абстрактный_автомат
Но если мы расширим область нашего зрения и возьмем компоненты как состояние, то быстро может выясниться, что у нас исчезло ограничение конечности этого состояния, поскольку мы начали взаимодействовать с большим числом компонентов или производить рекурсивную обработку.
Как хорошо разрезать код обработки так, чтобы вычленить оттуда конечный автомат? Я не знаю, как это сделать для GLib и VCL, поскольку одни события просты и могут уместиться в конечный автомат, и в то же время рядом есть сложные события, с рекурсией в рамках одного компонента и разных компонентов, и даже рекурсивные циклы самой диспетчеризации, что уже не умещается в конечный автомат. Элементарные пример сложного поведения — это контекстное меню или горячие клавиши.
Вызов get_gen() вернул объект генератора, но его тело еще не выполняется. И никакого get_gen в кадре стека генератора не существует, он вообще ничего не знает что у него "сверху" в моменты, когда он не выполняется
Да, это правда, вызов функции-генератора не имеет ничего общего с вызовом функции. Это что-то там про "явное лучше неявного".
Однако же, стэк создания генератора существует и остается до уничтожения генератора:
>>> def gen():
... x = 1
... def nested():
... for i in range(5):
... yield i + x
... return nested()
...
>>> g = gen()
>>> list(g)
[1, 2, 3, 4, 5]
>>> list(range(5))
[0, 1, 2, 3, 4]
>>> def gen():
... x = 1
... def nested():
... for i in range(5):
... yield i + x
... raise Exception('test')
... return nested()
...
>>> g = gen()
>>> list(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in nested
Exception: test
Да, это не проблема, это языковое ограничение. Точно так же как проблемой не является невозможность сложить число со строкой или объявить функцию (через def) в контексте выражения
Аналогия не прокатывает, потому что в питоне таки были контекстные менеджеры в генераторах, а контекстные менеджеры подразумевают точно такее же try..finally. Поэтому, это было ограничение реализации, а не ограничение языка. Если спрашивать мое мнение (а кого оно волнует?), то я бы сказал, что не чувствую предсказуемости поведения ни в варианте контекстных менеджеров, ни в варианте try..finally, и я бы предпочел выделить функцию финализации. Что в итоге и сделали, на самом деле, введя __del__ для сопрограмм. Правда, теперь питон вынужден поддерживать все эти варианты реализаций.
Не вижу связи с PEP 342
Весь их код базируется на PEP 342, которая по сути ввела понятие сопрограмм в питон. Если посмотреть, например, в cpython/Python/ceval.c, то можно увидеть, что сопрограммы и генераторы имеют лишь часть общего кода, но реализация сопрограмм отличается от реализации генераторов. И реализация питона нынче должна реализовывать эти два варианта генераторов.
Не, я о том, что там не простые входные команды, они скорее всего мельче, чем данные над которыми проводятся превращения. Потому реплицируются входные команды, а промежуточный слой уже размазывает эти команды по большому объему данных. Иначе бы смысла не было всю эту систему городить.
Без ZooKeeper система теряет отказоустойчивость -- checked.
ZooKeeper составляет основную сложность системы и его ничем нельзя заменить -- checked.
ZooKeeper определяет производительность выполнения транзакций -- checked.
Может быть это и большая надстройка, может быть очень большая и сложная надстройка. но по итогу все ключевые характеристики системы определяются ZooKeeper-ом.
Ну так при логической репликации "установить по ключу" не реплицируется всё содержимое записи. В статье в бенчах упоминается 10% транзакций, затрагивающие сразу несколько шардов -- очевидно, что это не простая установка по ключу, а какая-то сложная операция сразу над большим числом ключей. И такие операции прекрасно реплицируются на уровне входной команды.
Один узел сдох. Совсем. Или даже целая реплика пропала, потому что роут к датацентру потерялся. Разве не в этом смысл высокой доступности?
От каких "других секвенсоров" он будет знать последовательность в условиях отказов? Я и пишу -- от каких-то случайных, случайное подмножество последовательности операций. Без отказов мне ZooKeeper не нужен -- я просто кину SQLite базу на один комп и на этом проблемы мои закончатся.
Ну в мире key-value СУБД запросы не ограничиваются простым "запросить по ключу" и "установить по ключу". Потому что в таком случае, как я уже согласился, можно взять ZooKeeper и не морочить голову.
Поинтересовался -- получил таймаут, дальше что? А я скажу, что дальше: на части узлов транзакции B2 есть, а на другой части -- нет. Всё, приехали.
Заранее? Схема на Figure 1 говорит, что заранее про транзакции знает лишь один набор узлов -- реплики не знают ничего.
Calvin реплицирует входной запрос, он не реплицирует значение самих данных. Если запросы больше, чем сами данные -- да, Calvin не нужен, это можно реализовать на голом ZooKeeper.
Да, можно. Но кто потом будет публиковать эту последовательность? Допустим, публикация происходит "все ко всем". Представь, что во время публикации один из узлов отказывает. К части узлов транзакции с него дошли, к другой части -- нет. Что делать? Нужен консенсус, то есть, одинаковый набор команд на всех узлах кластера. Как делать консенсус? Либо назначить один из узлов Calvin эталонным, и таким образом потерять отказоустойчивость, либо применять внешнее решение для распределенного консенсуса -- а это и есть ZooKeeper.
В принципе, отказ малого числа узлов можно пережить через gossip protocol, то есть, процесс публикации "все ко всем" непрерывно распространяет не только самую последнюю эпоху, но и предыдущие, которые потенциально не дошли всем от умершего узла. Но если целью стоит устойчивость к отказам вплоть до кворума и даже потенциально ниже него со временной остановкой обработки данных, то тут уже никакой gossip не спасет и узлы пойдут в разнобой. Это уже не говоря о том, что gossip protocol сожрет все сетевые каналы.
Советую читать оригинальную статью, а не буклетик по первой ссылке из гугла:
http://cs.yale.edu/homes/thomson/publications/calvin-sigmod12.pdf
Забавно, как авторы Calvin, ровно как и люди из Яндекса, которые на презентациях рассказывали про YandexDB, упорно маскировали тот факт, что вся их разработка по сути является небольшой надстройкой над Zookeeper.
Нужен для того, чтобы не гонять терабайты данных через сам zookeeper. Таким образом уменьшается объем строго согласованных данных и можно обрабатывать больше транзакций в секунду. Плюс к тому же эти терабайты можно шардировать, реплицируя по всем шардам только метаданные транзакций, а не сами данные.
Я уже не стал вскрывать эту тему. Вы молодые, шутливые, вам все легко. Статейка-то была про многозадачность, я и так сильно растянул про распределенные системы.
Кальвину Zookeeper нужен для того, чтобы определять, какая транзакция после какой будет выполняться, причем, этот порядок должен быть идеально одинаковым для всего кластера, чтобы везде получились одинаковые данные по одинаковому номеру транзакции. Дальше уже локально узел может как угодно медленно применять утвержденный список транзакций.
Задачу же работы со строго согласованным состоянием с гарантиями отказоустойчивости и приемлимой производительностью во всей индустрии умеют решать ровно три софтины -- Zookeeper, Etcd, и Google Chubby. Конечно, может быть есть еще какое-то проприетарное решение, которое публике не показывают.
Неужели Coq поможет доказать корректность кода, в котором мы хотим реализовать, например, атомарные ячейки памяти Data.Atomics?
https://hackage.haskell.org/package/atomic-primops-0.8.4/docs/Data-Atomics.html
Как я понимаю, программист окажется неприятно стеснен в выразительных средствах, что в том числе выльется в низкую производительность кода. И это один из способов достигнуть верифицируемости алгоритма — связать кодеру руки и ноги, чтобы он сильно не рыпался, и тогда уже для простейших конструкций доказательство будет тривиально. Правда, при таком подходе уже недалеко до чего-то вроде STM/PSO, которое из коробки гарантирует корректность многопоточного доступа безо всяких внешних доказательств.
Сам себя не похвалишь - никто не похвалит.
Про попытки сделать автоматический доказыватель корректности многопоточной программы я слышал, но я слабо верю в их применимость к задачам сложнее обедающих философов, поскольку достаточно сложный и/или несериализуемый (https://en.wikipedia.org/wiki/Serializability) алгоритм может легко улететь в бесконечную сложность при наивном анализе выполнения. В начале статьи я упоминал пример с простыми четырьмя операциями и циклом из четырех инструкций, но в реальной задаче это может быть два бесконечных цикла с условиями внутри, для которых уже теоретически невозможно реализовать алгоритм, который хотя бы оценил конечность выполнения этих циклов (https://ru.wikipedia.org/wiki/Проблема_остановки), не говоря уже про корректность операций, которые будут в этих циклах исполняться. По этой причине эдак половина ноши "доказательства корректности" прежде всего заключается в том, чтобы не писать некорректные алгоритмы, которые никто не сможет проанализировать. То есть, не наращивать сложность алгоритма без причины.
Кстати, думаю перевести статью на русский, но не уверен, что кому-то это будет сильно интересно -- все-таки материал такой, что целевая аудитория обычно не испытввает проблем с английским языком.
Библиотека нигде не используется, и, как я написал, это скорее proof-of-concept, там потенциально куча багов, довольно много неподдерживаемых фич и просто недоработок.
NumPy-массив, как я писал в статье, можно поместить в разделяемую память ( и модель PyTorch тоже), но дальше возникает проблема - что с ними делать? Передавать между процессами ID сегмента? Это можно сделать любым инструментом, будь то multiprocessing или ZeroMQ, там накладные расходы от передачи строки ничтожны по сравнению с обработкой с самих данных. Смысл многозадачности, как правило, заключается в возможности параллельного координированного изменения данных, или хотя бы чтения параллельно с изменением. В PyTorch есть какие-то подвиги в этом направлении (torch.multiprocessing), а в NumPy подразумевается, что параллельное выполнение возможно только вне питона со специальными внутренними структурами данных, то есть, там уже всё подогнанно под специальные костыли в обход питона. Я могу лишь точно сказать, что реализация полноценной поддержки NumPy займет не день и не два.
Это действительно core.async, браво. Я так понимаю, способность узнавать основные библиотеки была той самой демонстрацией глубоких познаний в языке. Правда, я так и не увидел ответа на вторую часть моего сообщения — про несовместимость структур данных JS и ClojureScript.
Не вижу определения, указывающего на невозможность прироста производительности. Приемы, вроде возврата указателя на позицию внутри строки, стары, как сами компьютеры — они показывают прекрасную производительность и опираются на неизменность строки по указателю.
Наверное, кому-то стоит прекращать с завидной регулярностью писать бредовые догадки. Это я к нему обратился с проблемой доступа к его серверу и дал ответ DNS, чтобы он разобрался с проблемой. В ответ на что он похлопал глазками и попросил дать IP, выдаваемый ping-ом.
Ну вот же, трезвая оценка ситуации, наконец. Действительно, по двум сообщениям тяжело понять уровень профессиональных навыков. Зато по двум сообщениям я могу примерно узнать нечто, похожее на мою восторженность, когда я первый раз познакомился со структурами данных Clojure. Разница наша заключается в том, что я в итоге отнеся скептически и к языку, и к многоверсионным хранилищам, осознав, что за пределами Clojure потребность в хранении нескольких версий данных сильно ниже, а многоверсионные СУБД были с нами уже очень давно.
Вызвать функцию, действительно, можно без прослоек. Прослойки начинают требоваться уже на уровне передачи функции массива или объекта в качестве аргумента. Еще более веселыми прослойки становятся, когда нужно передать callback. И совсем угар начинается, когда поверх этого нужно еще и ошибки ловить:
Этот же код на JS пишется как:
То, что вы вчера прочитали про неизменяемые структуры в clojure, не ставит вас выше посетителей хабра, и тем более не является поводом делать выводы, что никто кроме вас не смог про них прочитать.
Неизменяемые структуры могут давать прирост производительности, а могут не давать. Обычно — не дают, поскольку простое изменение ячейки массива выполняется проще с точки зрения алгоритма и не оставляет после себя горы мусора в виде копий узлов дерева разных версий.
Вот прямо сейчас работаю с Vue, и там есть REPL. Где ваш бог теперь?
У меня есть знакомый человек, который 5 лет пишет веб, но до сих пор не знает, как работает DNS. И что дальше? Деньги ему платят — больше его ничего не волнует.
А вы знаете, но не напишите, правильно? Замысел Clojure был в реализации фундаментально многопоточного языка на базе «persistent data structures». Это довольно сильно отличает его как от других лиспов, так и от большинства других языков. Разве что некоторые языки серверной логики СУБД дают подобный функционал.
ClojureScript? В однопоточном JS? Он здесь вообще зачем? Я понимаю, что хотелось на лиспе писать веб, но разработка на вебе нынче сводится к тому, сколько готовых библиотек я могу использовать в своем проекте. А в итоге библиотек нет, многопоточности нет — и пишут на нем только фанаты.
Мы так дойдем до того, что функция тоже не совсем является функцией. Обычные классы питона можно вызвать, потому что для них определен метод __call__ в общем базовом классе «type», и реализован он в Objects/typeobject.c: type_call(...). С таким же успехом можно было бы сделать создание экземпляра класса, например, через оператор индексации массива. Однако, в случае функций питона мы вступаем на скользкую дорожку...
Есть. Мало кто знает, что в питоне конструктор класса называется __new__.
В интерпретаторе PyPy даже название флагов то же, потому что он пытается максимально мимикрировать под CPython — просто вместо Си те же функции реализованы в RPython:
https://github.com/mozillazg/pypy/blob/master/pypy/interpreter/pyframe.py#L245
Но я все-таки хотел сказать о том, что такая реализации генераторов уничтожает читаемость кода, поскольку вы не можете знать поведение функции до тех пор, пока не прочитали каждую ее строчку и не убедились, что там есть или нет yield. Потому что в том числе поведение return изменится на совершенно иное, как только будет хотя бы один раз упомянуто yield в теле функции.
Да, только generator не является функцией, возвращающей генератор. У нее и return нет вовсе. Возвращающей генератор функцией является PyGen_NewWithQualName, вызываемая _PyEval_EvalCodeWithName вместо целевой функции при наличии флага CO_GENERATOR у целевой функции. Гвидо фактически признал свой провал, когда ввел модификатор async для определений функций. Естественно, старый кривой функционал менять никто не будет, потому что на основе него уже написано куча программ.
Моя претензия заключалась в том, что функции-генераторы по записи не отличаются от функций, при этом с простыми функциями имеют мало общего. Запись generator() по здравому смыслу (с которым не дружат создатели языка) не подразумевает ничего, кроме вызова generator(). iter(iterable) прежде всего, вызывает функцию iter(), и делает это явно. Если generator — это класс, то мы вызываем этот класс, то есть, конструктор класса. Если generator — это функция-генератор, то мы вызываем чёрт пойми что.
Да, выйти из своего фрейма в вышестоящий фрейм создателя генератор не может — можно условно говорить о том, что это уже не стэк, а разрозненные фреймы с данными, часть из которых уже могла быть уничтожена. Так что формально дополнительного стэка нет, действительно.
При исполнении на машине Тьюринга конечный автомат можно определить как часть системы по отсутствию признаков, а не по их наличию. То есть, негативное определение, а именно — ограниченность состояния рассматриваемой части. Если некий код обработки событий UI имеет ограниченное число входных параметров, ограниченный набор внутренних состояний, не являющихся частью автомата, и код ограничен в своих действиях входными параметрами и внутренним состоянием, то можно говорить о конечности автомата.
Я так понимаю, под структурированным кодом обработки событий имеется в виду большой switch или другой вариант перехода к коду обработки события по коду события. Однако, switch сам по себе не только теряет свойство бесконечным — он вообще не является автоматом, поскольку у него отсутствует состояние, а есть только входные данные:
https://ru.wikipedia.org/wiki/Абстрактный_автомат
Но если мы расширим область нашего зрения и возьмем компоненты как состояние, то быстро может выясниться, что у нас исчезло ограничение конечности этого состояния, поскольку мы начали взаимодействовать с большим числом компонентов или производить рекурсивную обработку.
Как хорошо разрезать код обработки так, чтобы вычленить оттуда конечный автомат? Я не знаю, как это сделать для GLib и VCL, поскольку одни события просты и могут уместиться в конечный автомат, и в то же время рядом есть сложные события, с рекурсией в рамках одного компонента и разных компонентов, и даже рекурсивные циклы самой диспетчеризации, что уже не умещается в конечный автомат. Элементарные пример сложного поведения — это контекстное меню или горячие клавиши.
Да, это правда, вызов функции-генератора не имеет ничего общего с вызовом функции. Это что-то там про "явное лучше неявного".
Однако же, стэк создания генератора существует и остается до уничтожения генератора:
Аналогия не прокатывает, потому что в питоне таки были контекстные менеджеры в генераторах, а контекстные менеджеры подразумевают точно такее же try..finally. Поэтому, это было ограничение реализации, а не ограничение языка. Если спрашивать мое мнение (а кого оно волнует?), то я бы сказал, что не чувствую предсказуемости поведения ни в варианте контекстных менеджеров, ни в варианте try..finally, и я бы предпочел выделить функцию финализации. Что в итоге и сделали, на самом деле, введя __del__ для сопрограмм. Правда, теперь питон вынужден поддерживать все эти варианты реализаций.
Весь их код базируется на PEP 342, которая по сути ввела понятие сопрограмм в питон. Если посмотреть, например, в cpython/Python/ceval.c, то можно увидеть, что сопрограммы и генераторы имеют лишь часть общего кода, но реализация сопрограмм отличается от реализации генераторов. И реализация питона нынче должна реализовывать эти два варианта генераторов.
Он есть, он никуда не девается до высвобождения генератора, ровно как и у любого замыкания.
Хм-м-м… невозможность вызова yield внутри try..finally — это не проблема? Это изменение довольно сильно меняет требования к поведению интерпретатора, и в конечном итоге приводит к довольно сложным багам:
https://github.com/python-trio/async_generator/issues/13#issuecomment-365728710
Ну да, это в духе разработки питона: мы тут что-то придумаем, а вы потом разбирайтесь, как с этим бороться.