Решил навести немного порядка здесь и развесить тегов, чтобы было проще ориентироваться:
#дух_машины - новый технический пост, чаще про низкий уровень, но посмотрим;
#откопали_стюардессу - что-то старое, но, вероятно, переработанное под местный формат. Не удивляйтесь, если испытываете deja vu при прочтении;
#the_real_science - про преподавание и чудесных студентов;
#ваня_читает - это про книги, которые я так нашел время прочитать (и мне есть, что про них сказать);
#министерство_культуры - про фильмы, аниме и все такое. Может солью с прошлым когда-нибудь;
#деградировать_пришел - это мемасы, конечно;
#гчкм - это про бег и спорт вообще
не знаю, правда, какой тег для всякой там рефлексии и выплескивания вам своей души завести, пусть пока будет без тега.
#дух_машины - новый технический пост, чаще про низкий уровень, но посмотрим;
#откопали_стюардессу - что-то старое, но, вероятно, переработанное под местный формат. Не удивляйтесь, если испытываете deja vu при прочтении;
#the_real_science - про преподавание и чудесных студентов;
#ваня_читает - это про книги, которые я так нашел время прочитать (и мне есть, что про них сказать);
#министерство_культуры - про фильмы, аниме и все такое. Может солью с прошлым когда-нибудь;
#деградировать_пришел - это мемасы, конечно;
#гчкм - это про бег и спорт вообще
не знаю, правда, какой тег для всякой там рефлексии и выплескивания вам своей души завести, пусть пока будет без тега.
🔥16❤2
Алло, это отладочная? pinned «Решил навести немного порядка здесь и развесить тегов, чтобы было проще ориентироваться: #дух_машины - новый технический пост, чаще про низкий уровень, но посмотрим; #откопали_стюардессу - что-то старое, но, вероятно, переработанное под местный формат. Не…»
"Similarly, I observe that external provision of an architecture enhances, not cramps, the creative stile of implementing group. They focus at once on the part of the problem no one has addressed, and inventions begin to flow. In an unconstrained implementing group, most thought and debate goes into architectural decisions, and implementation proper gets short shift.
This effect, which I have seen many times, is confirmed by R.W. Conway, whose group at Cornell built the PL/C compiler for the PL/1 language. He says, "we finally decided to implement the language unchanged and unimproved, for the debates about languages would have taken all our effort".
The Mythical Man-Month, Frederick P. Brooks, Jr.
Вот прошло уже почти 60 лет с момента описанных событий, но как же точно подмечено (как и очень многое у Брукса). Я с каждым годом все чаще думаю, что может нам компиляторщикам и рантаймщикам (тем самым имплементаторам) лучше совершенно и не вмешиваться в процесс дизайна языков. Точнее как: стоит просто давать оценки, чего будет та или иная фича стоить по производительности/времени реализации и тем самым отметать совсем уж оторванные от реальности предложения дизайнеров языков, но на этом и все. Есть редкие исключения: фичи сильно связанные с реализацией типа той же финализации, но глобально - не наше это дело.
Брукс правда в той же главе сравнивает архитекторов с аристократией, что, конечно, вызывает у меня сильнейшее отторжение, но не всем же аллегориям быть удачными. Думаю, здесь правильнее говорить именно про разделение труда.
👍8
У нас на сиспро есть традиция - дарить самым успешным (в этом семестре, по профильным предметам) студентам, бумажные книги по этим самым предметам. Если я ничего не путаю, ее начал @cypok, когда подарил студенту книгу после своего the missing semester курса
У меня в этот раз интересно получилось с плюсами:
Во-первых, курс читался на английском, а значит книги тоже нужны именно английские. Я каким-то чудом урвал Effective Modern C++ Майерса на английском просто в Яндекс маркете, а вот остальные книги уже пришли мне из Гонконга, спасибо большое @FOODzee за это!
Во-вторых, курс ведь в том числе и про проекты. А значит кроме технических книг нужны и менеджерские :) Конечно же для этого я выбрал Мифический человеко-месяц Брукса. Тут был соблазн: подарить их тем, кто наоборот: продолбал все дедлайны. Чтобы научились правильно планировать, называть сроки и т.д. Было бы красиво, но все-таки нечестно, так что их тоже получили самые молодцы
Горжусь нашими ребятами, надеюсь, книги им понравятся!
#the_real_science
У меня в этот раз интересно получилось с плюсами:
Во-первых, курс читался на английском, а значит книги тоже нужны именно английские. Я каким-то чудом урвал Effective Modern C++ Майерса на английском просто в Яндекс маркете, а вот остальные книги уже пришли мне из Гонконга, спасибо большое @FOODzee за это!
Во-вторых, курс ведь в том числе и про проекты. А значит кроме технических книг нужны и менеджерские :) Конечно же для этого я выбрал Мифический человеко-месяц Брукса. Тут был соблазн: подарить их тем, кто наоборот: продолбал все дедлайны. Чтобы научились правильно планировать, называть сроки и т.д. Было бы красиво, но все-таки нечестно, так что их тоже получили самые молодцы
Горжусь нашими ребятами, надеюсь, книги им понравятся!
#the_real_science
🔥21
Тут недавно была трешовая новость, что какие-то там депутаты внесли предложение об отмене ЕГЭ в думу. Я редкостно от этого сгорел и написал в тви, что они безумцы, и ЕГЭ отменять (закрывая таким образом один из немногих социальных лифтов в РФ) ни в коем случае нельзя.
И что удивительно, это безобидный казалось бы твит открыл ПОРТАЛ В АД и внешний интернет. Пришло какое-то огромное количество людей, которые стали всячески поддерживать эту идею и топить за отмену ЕГЭ!
Это, конечно, чуть подкосило мою веру в человечество, но зато я придумал аналогию для этого спора и соответствующих аргументов. Достаточно заменить "поступать по ЕГЭ, а не ездить в университет сдавать вступительные" на "иметь теплый туалет в доме, а не на сортир улице".
Смотрите сами:
– Теплый туалет в доме - это удобно, не стоит его отменять.
– И как же мы раньше то жили с сортирами на улице? И ничего, как то выжили ведь.
– Но разве это значит, что всем нужно продолжать так жить? Технологии ведь идут вперед.
– Да от этого только хуже! Вот раньше люди серьезнее относились к процессу, сосредотачивались и срали быстро, чтобы не замерзунть. А теперь сидят там по полчаса!
– Так и хорошо же? Так теплее, удобнее и безопаснее.
– Это слишком тепличные условия, туалет - это не про исполнение желаний и не про удобство, а про достижения результата.
– Да раньше даже дойти то сортира на улице не все люди могли, а теперь у гораздо большего количества людей с гигиеной стало лучше.
– Но действительно целеустремленные люди и раньше могли посрать!!! А если нет возможности, то пусть срут под себя! И вообще, знаете, я вот нанимаю людей и прекрасно вижу, насколько просел уровень из-за того, что люди теперь срут в тепле.
Может чуть утрирую, но в целом где-то так эти споры и выглядят.
P.S. сам поступал по вступительным (по олимпиаде), хотя и ЕГЭ уже сдавал. Как можно отрицать пользу ЕГЭ как унифицированной системы и социального лифта, решительно не понимаю.
И что удивительно, это безобидный казалось бы твит открыл ПОРТАЛ В АД и внешний интернет. Пришло какое-то огромное количество людей, которые стали всячески поддерживать эту идею и топить за отмену ЕГЭ!
Это, конечно, чуть подкосило мою веру в человечество, но зато я придумал аналогию для этого спора и соответствующих аргументов. Достаточно заменить "поступать по ЕГЭ, а не ездить в университет сдавать вступительные" на "иметь теплый туалет в доме, а не на сортир улице".
Смотрите сами:
– Теплый туалет в доме - это удобно, не стоит его отменять.
– И как же мы раньше то жили с сортирами на улице? И ничего, как то выжили ведь.
– Но разве это значит, что всем нужно продолжать так жить? Технологии ведь идут вперед.
– Да от этого только хуже! Вот раньше люди серьезнее относились к процессу, сосредотачивались и срали быстро, чтобы не замерзунть. А теперь сидят там по полчаса!
– Так и хорошо же? Так теплее, удобнее и безопаснее.
– Это слишком тепличные условия, туалет - это не про исполнение желаний и не про удобство, а про достижения результата.
– Да раньше даже дойти то сортира на улице не все люди могли, а теперь у гораздо большего количества людей с гигиеной стало лучше.
– Но действительно целеустремленные люди и раньше могли посрать!!! А если нет возможности, то пусть срут под себя! И вообще, знаете, я вот нанимаю людей и прекрасно вижу, насколько просел уровень из-за того, что люди теперь срут в тепле.
Может чуть утрирую, но в целом где-то так эти споры и выглядят.
P.S. сам поступал по вступительным (по олимпиаде), хотя и ЕГЭ уже сдавал. Как можно отрицать пользу ЕГЭ как унифицированной системы и социального лифта, решительно не понимаю.
❤15👍6😁4💯4
А был ли крэшлог? (часть 1)
В разработке рантайма есть такая интересная особенность: часто, когда коммитаешь какие-то серьезные правки, случается тестопад. Ночную сборку начинает лихорадить: совершенно разные тесты падают с непохожими друг на друга развалами (у тебя локально, конечно, все работало), тикеты в багтрекере множаться с пугающей скоростью, а QA отдел спускает на тебя всех собак. Это происходит не потому, что разработчики рантайма такие безалаберные, совсем наоборот: мы обычно параноим и гоняем огромное количество тестов перед коммитом. Просто рантайм... это рантайм. Огромная большая связность между различными модулями, постоянный эффект бабочки (см. папку бога), недетерминированность из-за GC и зависимость от порядка исполнения. В общем, у рантайма есть свое настроение, так что надеяться на коммит больших правок без последствий - это довольно наивно.
И тут ничего не остается, кроме как спокойненько разбирать развалы, классифицировать их и оперативно изготавливать фиксы. Вот и пару месяцев назад я своим очередным коммитом устроил маленький армагеддон. Неприятно, но дело привычное: разгребал проблемы, изготовил за день основные фиксы (все по мелочи), на следующий день все тесты и сборки позеленели, тикеты закрыты, Q&A отдел доволен, начальство тоже. Но какая-то неприятная, вертящаяся на задворках сознания мысль не давала мне покоя. Во время классификации проблем один крэшлог не попал ни в одну из категорий проблем. Он был... странным. Судя по нему, происходила какая-то совсем уж дичь, поэтому я решил его отложить, починив сначала более очевидные проблемы. В теории, конечно, это могла быть какая-то странная наведёнка из-за остальных проблем (такое часто случается), но не должны были мои фиксы это чинить! Да и вообще такой проблемы не должно было быть в природе!
Сам странный развал, конечно, больше не воспроизводится. Соответственно тикета на него нет. Его починка не запланирована, новые таски ждут. И в этот самый момент в голове появляется такой сладкий шепотСаурона, который говорит: "Да забей, ну ерунда... мало ли почему был развал? Наведёнка, 100%. Ты уже все починил, пошли дальше. А был ли вообще крэшлог? Ты ведь был очень уставшим, показалось тебе, иди отдохни". Так мозг пытается защититься, восстановить понятную и комфортную ему картину мира. Очень важно в этот момент не вестись, найти этот старый крэшлог и начинать копать.
Что я и сделал, раскопав абсолютно несвязанную с моими правками, но прекрасную проблему, про которую сейчас расскажу ↓
#дух_машины
В разработке рантайма есть такая интересная особенность: часто, когда коммитаешь какие-то серьезные правки, случается тестопад. Ночную сборку начинает лихорадить: совершенно разные тесты падают с непохожими друг на друга развалами (у тебя локально, конечно, все работало), тикеты в багтрекере множаться с пугающей скоростью, а QA отдел спускает на тебя всех собак. Это происходит не потому, что разработчики рантайма такие безалаберные, совсем наоборот: мы обычно параноим и гоняем огромное количество тестов перед коммитом. Просто рантайм... это рантайм. Огромная большая связность между различными модулями, постоянный эффект бабочки (см. папку бога), недетерминированность из-за GC и зависимость от порядка исполнения. В общем, у рантайма есть свое настроение, так что надеяться на коммит больших правок без последствий - это довольно наивно.
И тут ничего не остается, кроме как спокойненько разбирать развалы, классифицировать их и оперативно изготавливать фиксы. Вот и пару месяцев назад я своим очередным коммитом устроил маленький армагеддон. Неприятно, но дело привычное: разгребал проблемы, изготовил за день основные фиксы (все по мелочи), на следующий день все тесты и сборки позеленели, тикеты закрыты, Q&A отдел доволен, начальство тоже. Но какая-то неприятная, вертящаяся на задворках сознания мысль не давала мне покоя. Во время классификации проблем один крэшлог не попал ни в одну из категорий проблем. Он был... странным. Судя по нему, происходила какая-то совсем уж дичь, поэтому я решил его отложить, починив сначала более очевидные проблемы. В теории, конечно, это могла быть какая-то странная наведёнка из-за остальных проблем (такое часто случается), но не должны были мои фиксы это чинить! Да и вообще такой проблемы не должно было быть в природе!
Сам странный развал, конечно, больше не воспроизводится. Соответственно тикета на него нет. Его починка не запланирована, новые таски ждут. И в этот самый момент в голове появляется такой сладкий шепот
Что я и сделал, раскопав абсолютно несвязанную с моими правками, но прекрасную проблему, про которую сейчас расскажу ↓
#дух_машины
А был ли крэшлог? (часть 2)
Итак, о чем же говорил этот аномальный крэшлог? Если все максимально упростить и немного обфусцировать, то взрывающийся кусок кода выглядит так:
При этом вызов
В сгенерированном коде это выглядит так:
1) Структура Info разложилась наплесень и липовый мед регистры, конкретно поле
2) Действительно, больше никто не трогает x29 на протяжении всего исполнения метода;
3) Между загрузкой
При этом в крэшлоге написано, что на момент взрыва в x29 была единичка, а никакой не адрес. Если бы она была там изначально, то
—
Был бы у нас воспроизводящий пример, все было бы сильно проще: ставим watch на соответствующую память и аккуратно смотрим, кто же злодей (тоже задачка не самая простая, т.к. пришлось бы записать трассу изменений, ведь любой вызываемый метод мог бы сохранять x29 у себя в прологе и потом восстанавливать), но ведь у нас нет примера, только крэшлог!
Впрочем, зная конкретное место в коде, где все взрывается, можно и пример постараться написать самому, стрессуя именно его. Чем я и занялся, через какое-то время получив кролика (так мы в команде называем маленькие примеры для воспроизведения проблемы). Развал и на нем воспроизводился не всегда, но мне достаточно всего двух раз, спасибо соответствующим отладочным тулам. Например, rr time-travel debugger: потрясающая штука, которая позволяет записать исполнение и потом получить стабильно воспроизводящийся баг (а еще ходить назад во времени при отладке, чудо-чудесное).
Так что через пару-тройку часов получилось таки поймать вредителя с поличным ↓
Итак, о чем же говорил этот аномальный крэшлог? Если все максимально упростить и немного обфусцировать, то взрывающийся кусок кода выглядит так:
struct Info info;
assert(info.isValid());
// код (включая вызовы других функций), работающий со структурой info, но никак не меняющий ее поля
info.address.foo(); <--- SEGFAULT
При этом вызов
isValid()
как раз и призван гарантировать, что info
сконструирована корректно, к полю address
доступаться можно, вызывать foo()
- тоже. Но именно при попытке вызывать foo()
все и бахает.В сгенерированном коде это выглядит так:
1) Структура Info разложилась на
address
лежит на регистре x29 (все происходит на arm64, так что регистров много);2) Действительно, больше никто не трогает x29 на протяжении всего исполнения метода;
3) Между загрузкой
address
на x29 и его использованием (и развалом) происходит много вызовов, но это не проблема: x29 - неволатильный, а значит это ответственность вызываемого метода сохранять его при необходимости. Т.е. никакой вызов x29 попортить тоже не мог.При этом в крэшлоге написано, что на момент взрыва в x29 была единичка, а никакой не адрес. Если бы она была там изначально, то
isValid()
вернул бы false
, упал бы ассерт. Так кто же испортил x29?—
Был бы у нас воспроизводящий пример, все было бы сильно проще: ставим watch на соответствующую память и аккуратно смотрим, кто же злодей (тоже задачка не самая простая, т.к. пришлось бы записать трассу изменений, ведь любой вызываемый метод мог бы сохранять x29 у себя в прологе и потом восстанавливать), но ведь у нас нет примера, только крэшлог!
Впрочем, зная конкретное место в коде, где все взрывается, можно и пример постараться написать самому, стрессуя именно его. Чем я и занялся, через какое-то время получив кролика (так мы в команде называем маленькие примеры для воспроизведения проблемы). Развал и на нем воспроизводился не всегда, но мне достаточно всего двух раз, спасибо соответствующим отладочным тулам. Например, rr time-travel debugger: потрясающая штука, которая позволяет записать исполнение и потом получить стабильно воспроизводящийся баг (а еще ходить назад во времени при отладке, чудо-чудесное).
Так что через пару-тройку часов получилось таки поймать вредителя с поличным ↓
А был ли крэшлог? (часть 3, развязка)
Главным моим подозреваемым к моменту получения кролика был, конечно, компилятор. Да, код самого сегфолтающегося метода был сгенерирован корректно, но я грешил на то, что кто-то из callee не восстановил x29 после своих делишек. По факту все оказалось интереснее! И виновником оказался мой любимый рантайм.
Рантайм - он ведь на то и рантайм, что делает вещи незаметные глазу, смотрящему на сгенерированный код. Например, некоторые рантаймы реализуют stackful корутины (я довольно много говорю про них вот здесь). А это значит, что на уровне рантайма нужно уметь взять одну корутинку, сохранить ее текущее состояние где-то, приостановить ее и возобновить уже другую, активную. А что такое состояние корутины: это по сути значения регистров в данный момент исполнения (включая sp, который указывает на соответствующей ей стек).
Т.е. рантайм может тормозить исполнение, сохранять куда-то регистры, а через какое-то время восстанавливать их значения и продолжать исполнение. И, как вы можете догадаться, при этом самом восстановлении с регистром x29 все пошло совсем не так: вместо старого значения в него записали мусор. Код context-switch-а часто написан на ассемблере, так что ошибиться, конечно, не мудрено.
Почему раньше не проявлялось?
Логичный вопрос, казалось бы: баг в такой основополагающей механике, как context-switch корутины должен был проявляться постоянно. Но на самом деле ситуация то крайне редкая:
1) x29 - один из последних регистров, до него обычно не доходит дело во время regalloc-а. Т.е. сгенерированный метод должен быть прям ОГРОМНЫМ, чтобы в нем заиспользовался x29;
2) внутри такого огромного метода должен случиться context-switch между загрузкой важного значения на x29 и его использованием (при этом именно в самом методе, не в вызываемом из него!). Но context-switch не происходят когда попало, есть вполне ограниченный список инструкций или вызовов, когда это может произойти.
3) более того, context-switch, когда он случается из-за насильного preemption-а, недетерменирован, т.е. будет случаться только иногда, от запуска к запуску.
В сумме получается, что вероятность такого развала просто мизерная, ничтожная.
—
Проблему в context-switch я, конечно, починил. Мой пример падать перестал, а ночные тесты уже и так были зелеными, т.е. никто ничего и не почувствовал. Но даже представлять не хочу, как бы мы отлаживали это потом на проде, послушай я сладкий голос в голове, предлагающий не тратить время на какой-то шальной крэшлог при всех зеленых тестах.
Так что крэшлог был, да. Но больше нет. Всем удачной отладки и отличной рабочей недели!
Главным моим подозреваемым к моменту получения кролика был, конечно, компилятор. Да, код самого сегфолтающегося метода был сгенерирован корректно, но я грешил на то, что кто-то из callee не восстановил x29 после своих делишек. По факту все оказалось интереснее! И виновником оказался мой любимый рантайм.
Рантайм - он ведь на то и рантайм, что делает вещи незаметные глазу, смотрящему на сгенерированный код. Например, некоторые рантаймы реализуют stackful корутины (я довольно много говорю про них вот здесь). А это значит, что на уровне рантайма нужно уметь взять одну корутинку, сохранить ее текущее состояние где-то, приостановить ее и возобновить уже другую, активную. А что такое состояние корутины: это по сути значения регистров в данный момент исполнения (включая sp, который указывает на соответствующей ей стек).
Т.е. рантайм может тормозить исполнение, сохранять куда-то регистры, а через какое-то время восстанавливать их значения и продолжать исполнение. И, как вы можете догадаться, при этом самом восстановлении с регистром x29 все пошло совсем не так: вместо старого значения в него записали мусор. Код context-switch-а часто написан на ассемблере, так что ошибиться, конечно, не мудрено.
Почему раньше не проявлялось?
Логичный вопрос, казалось бы: баг в такой основополагающей механике, как context-switch корутины должен был проявляться постоянно. Но на самом деле ситуация то крайне редкая:
1) x29 - один из последних регистров, до него обычно не доходит дело во время regalloc-а. Т.е. сгенерированный метод должен быть прям ОГРОМНЫМ, чтобы в нем заиспользовался x29;
2) внутри такого огромного метода должен случиться context-switch между загрузкой важного значения на x29 и его использованием (при этом именно в самом методе, не в вызываемом из него!). Но context-switch не происходят когда попало, есть вполне ограниченный список инструкций или вызовов, когда это может произойти.
3) более того, context-switch, когда он случается из-за насильного preemption-а, недетерменирован, т.е. будет случаться только иногда, от запуска к запуску.
В сумме получается, что вероятность такого развала просто мизерная, ничтожная.
—
Проблему в context-switch я, конечно, починил. Мой пример падать перестал, а ночные тесты уже и так были зелеными, т.е. никто ничего и не почувствовал. Но даже представлять не хочу, как бы мы отлаживали это потом на проде, послушай я сладкий голос в голове, предлагающий не тратить время на какой-то шальной крэшлог при всех зеленых тестах.
Так что крэшлог был, да. Но больше нет. Всем удачной отладки и отличной рабочей недели!
YouTube
Иван Углянский — Thread Wars: проект Loom наносит ответный удар
Подробнее о Java-конференциях:
— весной — JPoint: https://jrg.su/gTrwHx
— осенью — Joker: https://jrg.su/h7yvG4
— —
На фоне приближающегося к релизу проекта Loom в Java-мире только и разговоров, что о корутинах да о легковесной многопоточности!
Но ведь Java…
— весной — JPoint: https://jrg.su/gTrwHx
— осенью — Joker: https://jrg.su/h7yvG4
— —
На фоне приближающегося к релизу проекта Loom в Java-мире только и разговоров, что о корутинах да о легковесной многопоточности!
Но ведь Java…
👍13❤🔥9
Где писать лонгриды (которые не влезают в 1 пост)?
Anonymous Poll
43%
Продолжать писать в telegram несколько постов
25%
Писать в telegraph и постить ссылку здесь
32%
Мне все равно
This media is not supported in your browser
VIEW IN TELEGRAM
Кстати, нас тут недавно стало уже больше двух сотен! Это неожиданно и приятно, я думал, что канал будет человек на 20-30) И в целом, атмосфера и правда получается очень ламповой и уютной, мне нравится.
Спасибо, что читаете всю эту графоманию!
Спасибо, что читаете всю эту графоманию!
❤28
Ну наконец-то!
Получилось пройти Hades 2 (ну, как пройти, первый раз пройти до конца), при том каким-то максимально неочевидным билдом.
Вообще, Hades 2 чуть разочаровал. Я в целом не очень то люблю рогалики, к тому же такие активные. Тем страннее, что первая часть забралась в топ-5 моих любимых игр.
Вторая часть в целом то хороша... если бы не главная героиня (господи, Мелиноя, хватит ныть) и не тот факт, что это все-таки сиквел, а сиквел неизбежно сравниваешь с первой частью.
Но снять стресс после тяжелого дня,заткнув наглую сирену в конце второй локации, вполне можно, рекомендую.
Получилось пройти Hades 2 (ну, как пройти, первый раз пройти до конца), при том каким-то максимально неочевидным билдом.
Вообще, Hades 2 чуть разочаровал. Я в целом не очень то люблю рогалики, к тому же такие активные. Тем страннее, что первая часть забралась в топ-5 моих любимых игр.
Вторая часть в целом то хороша... если бы не главная героиня (господи, Мелиноя, хватит ныть) и не тот факт, что это все-таки сиквел, а сиквел неизбежно сравниваешь с первой частью.
Но снять стресс после тяжелого дня,
💯6
Люблю запах финализаторов по утрам! (часть 1)
По моему опыту финализаторы - это одна из самых проблемных тем в managed языках. Из-за них рождается какое-то невозможное количество багов, неоправдавшихся надежд и разбитых мечтаний разработчиков (как разработчиков приложений, так и виртуальных машин). И у меня даже есть ответ на вопрос "почему" Но обо всем по порядку.
О чем речь: в большинстве языков программирования со сборкой мусора есть возможность зарегистрировать callback, который будет вызываться при смерти объекта, точнее - чуть раньше этой самой смерти. А что такое смерть в случае языка с GC? Правильно - это момент, когда GC решает его собрать.
Идея кажется буквально очевидной. Особенно, если вы пришли из мира C++. Вот там у классов есть деструкторы, они вызываются, когда объекты помирают (известно когда), давайте повторим этот фокус и для Java. Или C#, там даже синтаксис как у деструкторов:
Вот что тут может пойти не так? На самом же деле фича буквально открывает врата в ад. Приведу несколько (но далеко не все!) примеров различных проблем. Начнем с не самой очевидной но очень неприятной.
Производительность и memory drag.
Когда разработчики пишут финализаторы, они обычно не задумываются, чего это будет им стоить. Что в целом то правильно, как я уже говорил раньше, языковая фича в теории не должна оглядываться на реализацию, а значит производительность - это головная боль рантайм инженеров. Но давайте подумаем: как вообще можно реализовать финализаторы?
Раз смерть объекта - это момент, когда за ним приходит GC, то мы должны вмешаться в работу GC и буквально вытащить объект с финализатором из пасти льва, т.е. GC. Вместо того, чтобы объект собирать, мы вынуждены дать ему еще пожить, пока будет выполняться этот самый callback. Это необходимо, т.к. объект может использоваться в коде финализатора, а значит он точно переживет как минимум эту сборку GC. Далее осознаем, что финализаторов ведь может быть много, и все нужно исполнить, т.е. может накопиться целая очередь объектов, которые давно бы померли, но пока ждут своей очереди в чистилище. А теперь добавьте к этому тот факт, что не только сам объект застрял в Лимбе, но и все достижимые только из него объекты. Получаем тонкое место, потенциально очень долгую очередь из умирающих объектов и memory drag (удержание памяти, которую уже могли бы освободить).
Я уж промолчу о том, что есть возможность воскрешать объекты в финализаторах, т.е. создающие новые ссылки на них. Это особая головная боль и неочевидное поведение, но бог авторам такого кода судья.
Как managed языки пытаются такую проблему исправить?
- Java: метод
- Python: начиная с 3.4 вдобавок к старым финализаторам (
- Golang: тут красиво. Во-первых, как и с
Ну вот бандиты же? Но могут себе позволить, горутины считай бесплатные, поэтому почему бы и нет, круто ↓
#дух_машины
По моему опыту финализаторы - это одна из самых проблемных тем в managed языках. Из-за них рождается какое-то невозможное количество багов, неоправдавшихся надежд и разбитых мечтаний разработчиков (как разработчиков приложений, так и виртуальных машин).
О чем речь: в большинстве языков программирования со сборкой мусора есть возможность зарегистрировать callback, который будет вызываться при смерти объекта, точнее - чуть раньше этой самой смерти. А что такое смерть в случае языка с GC? Правильно - это момент, когда GC решает его собрать.
Идея кажется буквально очевидной. Особенно, если вы пришли из мира C++. Вот там у классов есть деструкторы, они вызываются, когда объекты помирают (известно когда), давайте повторим этот фокус и для Java. Или C#, там даже синтаксис как у деструкторов:
class Zombie
{
~Zombie()
{
System.Diagnostics.Trace.WriteLine("dying...");
}
}
Вот что тут может пойти не так? На самом же деле фича буквально открывает врата в ад. Приведу несколько (но далеко не все!) примеров различных проблем. Начнем с не самой очевидной но очень неприятной.
Производительность и memory drag.
Когда разработчики пишут финализаторы, они обычно не задумываются, чего это будет им стоить. Что в целом то правильно, как я уже говорил раньше, языковая фича в теории не должна оглядываться на реализацию, а значит производительность - это головная боль рантайм инженеров. Но давайте подумаем: как вообще можно реализовать финализаторы?
Раз смерть объекта - это момент, когда за ним приходит GC, то мы должны вмешаться в работу GC и буквально вытащить объект с финализатором из пасти льва, т.е. GC. Вместо того, чтобы объект собирать, мы вынуждены дать ему еще пожить, пока будет выполняться этот самый callback. Это необходимо, т.к. объект может использоваться в коде финализатора, а значит он точно переживет как минимум эту сборку GC. Далее осознаем, что финализаторов ведь может быть много, и все нужно исполнить, т.е. может накопиться целая очередь объектов, которые давно бы померли, но пока ждут своей очереди в чистилище. А теперь добавьте к этому тот факт, что не только сам объект застрял в Лимбе, но и все достижимые только из него объекты. Получаем тонкое место, потенциально очень долгую очередь из умирающих объектов и memory drag (удержание памяти, которую уже могли бы освободить).
Я уж промолчу о том, что есть возможность воскрешать объекты в финализаторах, т.е. создающие новые ссылки на них. Это особая головная боль и неочевидное поведение, но бог авторам такого кода судья.
Как managed языки пытаются такую проблему исправить?
- Java: метод
finalize()
в java.lang.Object
уже давно @Deprecated
. Вместо него предлагается использовать java.lang.ref.Cleaner
, работающий на слабых ссылках с очередями. Я довольно подробно рассказывал про это вот здесь. Если коротко, то идея в том, чтобы отделить callback от объекта, не хранить в нем ссылки на сам объект и таким образом срезать memory drag;- Python: начиная с 3.4 вдобавок к старым финализаторам (
_del_
) ввели weakref.finalize(...)
, который делает тоже самое, что и Java: регистрирует именно callback связанный с объектом только слабой ссылкой. Поэтому (в случае, когда объект будет собран в рамках поиска циклического мусора, а только там это проблема) callback, будет вызван, но объект при этом задержан в памяти не будет (конечно, если не делать глупостей);- Golang: тут красиво. Во-первых, как и с
Cleaner
-ами в Java и weakref.finalize
в Python, финализатор - это не свойство класса, но свойство объекта, которое стоит указывать при его создании (ну или когда вам удобно). А во-вторых, в документации явно указывается, что исполнение финализаторов - это узкое место, и говорится: A single goroutine runs all finalizers for a program, sequentially. If a finalizer must run for a long time, it should do so by starting a new goroutine.
Ну вот бандиты же? Но могут себе позволить, горутины считай бесплатные, поэтому почему бы и нет, круто ↓
#дух_машины
YouTube
Иван Углянский - Ходячие объекты-мертвецы, или GC всегда прав
Автоматическое управление памятью — одна из основных особенностей Java и других managed языков. При этом в спецификации про GC написано очень мало: как именно собирать мусор каждой конкретной реализации JVM, предлагается решать самостоятельно. В результате…
👍6💯3
Люблю запах финализаторов по утрам! (часть 2)
Но бог с ней с производительностью, что по поводу корректности? И здесь мне есть, что сказать!
Несовпадение времени жизни.
Совсем недавно отлаживал очень забавный, но, как в конце выяснилось, довольно простой развал. Все как мы любим: развал спорадичный, взрывается где-то в нативных сишных библиотеках. Пользователь пишет код, который очень хочет работать с регулярными выражениями, но почему-то делает это через сишную библиотеку. И в результате пишет популярный паттерн:
Здесь
Здесь
Вот и получается, что натив вовсю работает с нативной памятью, а в этот момент наш бравый финализатор эту самую память в другом треде очищает. Результат немного предсказуем.
Починить легко - достаточно добавить барьер, который будет удеживать
Как managed языки пытаются такую проблему исправить?
Тут на сцену эффектно выходит C#. Описанная выше проблема там вполне решена с помощью специальных оберток вокруг нативных ресурсов, которые называются SafeHandle. Они по духу похожи на
Красиво? Красиво.
Замечу только, что в статье выше кроме всего прочего говорится о неких гарантиях на время финализатора, которые достигаются благодаря свойствам CER. Если бы это было правдой, было бы уж совсем волшебно, но увы, как мне подсказал Андрей, это неактуально в современном CLR.
—
Но время жизни объектов - это еще не самое страшное в финализаторах. Что же может быть хуже? ↓
Но бог с ней с производительностью, что по поводу корректности? И здесь мне есть, что сказать!
Несовпадение времени жизни.
Совсем недавно отлаживал очень забавный, но, как в конце выяснилось, довольно простой развал. Все как мы любим: развал спорадичный, взрывается где-то в нативных сишных библиотеках. Пользователь пишет код, который очень хочет работать с регулярными выражениями, но почему-то делает это через сишную библиотеку. И в результате пишет популярный паттерн:
class Wrapper {
private long handle;
Wrapper() {
handle = allocateNativeResource();
}
long getHandle() {
return handle;
}
void finalize() {
deallocateNativeResource(handle);
}
}
Здесь
handle
- это просто адрес unmanaged сущности, которую саллоцировали в нативе. Вполне логичной идеей кажется освободить его (используя опять таки нативный вызов) в финализаторе managed wrapper-а. И все бы ничего, но используют этот Wrapper
вот так:Wrapper wrapper = new Wrapper();
...
useNativeResource(wrapper.getHandle());
Здесь
useNativeResource(...)
- это просто сишный метод, принимающий void*
, который что-то с ним на своей сишной стороне делает. Но вот беда, вызов getHandle()
- это было последнее использование объекта wrapper
, больше юзов нет. А значит GC имеет право его собрать, но до этого позовет финализатор. Тут стоит добавить маленькую деталь: сборщик мусора имеет право работать параллельно с вызовами нативов, поэтому ждать окончания useNativeResource
никто не будет. Вот и получается, что натив вовсю работает с нативной памятью, а в этот момент наш бравый финализатор эту самую память в другом треде очищает. Результат немного предсказуем.
Починить легко - достаточно добавить барьер, который будет удеживать
wrapper
в мире живых до конца работы натива, но ситуация очень неприятная и наводящая на мысли.Как managed языки пытаются такую проблему исправить?
Тут на сцену эффектно выходит C#. Описанная выше проблема там вполне решена с помощью специальных оберток вокруг нативных ресурсов, которые называются SafeHandle. Они по духу похожи на
Wrapper
из примера выше (тоже есть финализатор, который очищает ресурс), но работают с поддержкой рантайма. В каждом таком объекте есть счетчик ссылок, который автоматически инкрементируется, когда он передается в натив. Пока счетчик не равен 0, никто соответствующий ресурс очищать не будет. Таким образом передача в натив расшаривает владение ресурсом с этим самым нативом, и managed код уже не в праве решать судьбу объекта в одиночку. Красиво? Красиво.
Замечу только, что в статье выше кроме всего прочего говорится о неких гарантиях на время финализатора, которые достигаются благодаря свойствам CER. Если бы это было правдой, было бы уж совсем волшебно, но увы, как мне подсказал Андрей, это неактуально в современном CLR.
—
Но время жизни объектов - это еще не самое страшное в финализаторах. Что же может быть хуже? ↓
Docs
System.Runtime.InteropServices.SafeHandle class - .NET
Learn about the System.Runtime.InteropServices.SafeHandle class.
👍6💯3✍1
Люблю запах финализаторов по утрам! (часть 3)
Опускаемся еще ниже в ад, поближе к самому сатане.
Недетерминированное поведение.
Как и все связанное с GC, сами финализаторы довольно капризны, а их поведение трудно предсказать. Чаще всего про это говорят в контексте вопроса: "а позовется ли мой финализатор вообще?". Разные языки и рантаймы дают здесь разные ответы. Как я уже писал выше, C# старался дать гарантии введением CER, но теперь все в прошлом. Сейчас самые смелые гарантии из тех, что я видел, дает Python с
—
Но я расскажу вам куда более страшную историю про недетерминированное поведение финализаторов, которая по традиции пришла к нам из саппорта.
Развал в ntdll.dll => происходит только на нашей VM и то не всегда. Чаще всего при закрытии приложения => раскопки приводят к тому, что проблема в вызове сишной функции GlobalFree от какой-то невалидной памяти. Наш рантайм таким не занимается.
Как выяснилось после дальнейших раскопок, в приложении клиенты хотят позвать функцию
Как все это проделать из Java? Легко(ха-ха), с помощью JNI или, например, JNA. Но вам совершенно точно понадобится Java класс инкапсулирующий указатель на указатель. Ну и раз от внутреннего адреса нужно когда-нибудь (когда он будет больше не нужен) позвать GlobalFree, то где же это сделать? Конечно же в финализаторе этого класса!
Звучит то неплохо, но как это было реализовано: Java класс
И тут возникает интересный вопрос: допустим, что оба объекта: и
Так вот проблема в том, что порядок исполнения финализаторов не определен. Он может быть абсолютно произвольным для одновременно ставших недоступными объектов. Нам не везло, поэтому чаще вызывался вперед вызывался финализатор
Как managed языки пытаются такую проблему исправить?
Тот же Golang вводит четкий порядок финализации:
правда как и всегда с топологической сортировкой, все портят циклы:
Про Python сказано, что
—
Какие же мы из всего этого можем сделать выводы? ↓
Опускаемся еще ниже в ад, поближе к самому сатане.
Недетерминированное поведение.
Как и все связанное с GC, сами финализаторы довольно капризны, а их поведение трудно предсказать. Чаще всего про это говорят в контексте вопроса: "а позовется ли мой финализатор вообще?". Разные языки и рантаймы дают здесь разные ответы. Как я уже писал выше, C# старался дать гарантии введением CER, но теперь все в прошлом. Сейчас самые смелые гарантии из тех, что я видел, дает Python с
weakref.finalize
, но и там есть ограничения, связанные с daemonic threads (см. вот здесь приписочку внизу). В целом, на обязательность вызова финализатора лучше не полагаться вообще.—
Но я расскажу вам куда более страшную историю про недетерминированное поведение финализаторов, которая по традиции пришла к нам из саппорта.
Развал в ntdll.dll => происходит только на нашей VM и то не всегда. Чаще всего при закрытии приложения => раскопки приводят к тому, что проблема в вызове сишной функции GlobalFree от какой-то невалидной памяти. Наш рантайм таким не занимается.
Как выяснилось после дальнейших раскопок, в приложении клиенты хотят позвать функцию
WinHttpDetectAutoProxyConfigUrl
из WinApi, а та в качестве аргумента принимает LPWSTR*
, куда будет записан адрес на выделенную под строчку память (т.е. по факту имеем указатель на указатель). И что интересно: по контракту это уже ответственность пользователя почистить выделенную системой память, после того, как она перестанет быть нужна.Как все это проделать из Java? Легко
Звучит то неплохо, но как это было реализовано: Java класс
LPWSTRByReference
. Он в свою очередь содержит ссылку на класс Memory из JNA (все верно, указатель на указатель). В финализаторе LPWSTRByReference
аккуратно достаем из Memory
указатель, смотрим, не null
ли он, а потом вызываем от него GlobalFree
. И все бы ничего, но ведь и объектам Memory
тоже нужно как-то чистить свою память! Ведь это по сути контейнер для указателя, а память под этот контейнер выделяли маллоком. Поэтому что? Правильно, у Memory
тоже был финализатор, в котором вызывался free
уже от контейнера. Сам адрес при этом не зануляется, там остается мусор.И тут возникает интересный вопрос: допустим, что оба объекта: и
LPWSTRByReference
и Memory
стали недостижимы (а так и случается, единственная ссылка на Memory
из LPWSTRByReference
), чей финализатор позовется первее? Это важно, ведь если LPWSTRByReference
, то все сработает отлично, а если Memory
, то в финализаторе LPWSTRByReference
позовется GlobalFree
от битой уже памяти.Так вот проблема в том, что порядок исполнения финализаторов не определен. Он может быть абсолютно произвольным для одновременно ставших недоступными объектов. Нам не везло, поэтому чаще вызывался вперед вызывался финализатор
Memory
, отсюда спорадичные развалы. Finita la commedia.Как managed языки пытаются такую проблему исправить?
Тот же Golang вводит четкий порядок финализации:
Finalizers are run in dependency order: if A points at B, both have finalizers, and they are otherwise unreachable, only the finalizer for A runs; once A is freed, the finalizer for B can run.
правда как и всегда с топологической сортировкой, все портят циклы:
If a cyclic structure includes a block with a finalizer, that cycle is not guaranteed to be garbage collected and the finalizer is not guaranteed to run, because there is no ordering that respects the dependencies.
Про Python сказано, что
weakref.finalize(...)
вызываются в порядке обратном регистрации callbacks, что, конечно, тоже оставляет много вопросов, но хоть так порядок фиксирует.—
Какие же мы из всего этого можем сделать выводы? ↓
Docs
Constrained Execution Regions - .NET Framework
Get started with constrained execution regions (CER), which are part of a mechanism for authoring reliable managed code.
👍6💯3🌚1
Люблю запах финализаторов по утрам! (часть 4, заключительная)
Почему же все так плохо с финализацией? За простейшей, казалось бы идеей, кроется какое-то бесконечное минное поле, продолжать описывать проблемы можно было бы еще очень долго. Мне кажется, что ответа здесь два:
1) Финализация зачастую неотрывно связана с интеропом с unmanaged языками. По моему опыту главный клиент финализации - человек, который хочет почистить нативные ресурсы, это описывает прям большинство юзкейсов. А managed и unmanaged языки - это миры принципиально разного устройства: понятие времени жизни объектов, детерминированность исполнения и ее отсутствие, концепция владения ресурсами, все это может очень сильно отличаться. Когда вы пишите финализатор, вы зачастую начинаете эти миры смешивать,случается сопряжение сфер, отсюда и лезут всякие черти.
2) Финализация - это попытка вмешаться в работу GC, т.е. фича языка, заведомо тесно связанная с реализацией. И как бы не хотелось от реализации абстрагироваться, в этом случае реальность будет больно бить пользователя этой фичи по голове.
Как видите, разные рантаймы стараются решить проблему финализации, но все они имеют свои недостатки. Я видел еще несколько вариантов механизмов финализации в других языках, включая запретительные (когда в финализаторах почти ничего нельзя делать), но все они были не лишены изъянов, а точнее все так же создавали кошмарные ситуации и интересные баги. Мои фавориты на данный момент - это C# (
Если вы знаете managed язык, где проблема финализации полностью решена, напишите, пожалуйста, в комментарии, как именно там это было достигнуто, это действительно очень интересно!
Почему же все так плохо с финализацией? За простейшей, казалось бы идеей, кроется какое-то бесконечное минное поле, продолжать описывать проблемы можно было бы еще очень долго. Мне кажется, что ответа здесь два:
1) Финализация зачастую неотрывно связана с интеропом с unmanaged языками. По моему опыту главный клиент финализации - человек, который хочет почистить нативные ресурсы, это описывает прям большинство юзкейсов. А managed и unmanaged языки - это миры принципиально разного устройства: понятие времени жизни объектов, детерминированность исполнения и ее отсутствие, концепция владения ресурсами, все это может очень сильно отличаться. Когда вы пишите финализатор, вы зачастую начинаете эти миры смешивать,
2) Финализация - это попытка вмешаться в работу GC, т.е. фича языка, заведомо тесно связанная с реализацией. И как бы не хотелось от реализации абстрагироваться, в этом случае реальность будет больно бить пользователя этой фичи по голове.
Как видите, разные рантаймы стараются решить проблему финализации, но все они имеют свои недостатки. Я видел еще несколько вариантов механизмов финализации в других языках, включая запретительные (когда в финализаторах почти ничего нельзя делать), но все они были не лишены изъянов, а точнее все так же создавали кошмарные ситуации и интересные баги. Мои фавориты на данный момент - это C# (
SafeHandle
- круто!) и, на удивление, Python с weakrefs.finilze(...)
.Если вы знаете managed язык, где проблема финализации полностью решена, напишите, пожалуйста, в комментарии, как именно там это было достигнуто, это действительно очень интересно!
👍8💯3🥴1
А еще меня тут на днях позвали выступить со стендапом, прикиньте. Нет, не мой менеджер; нет, речь не про дейлики.
P.S. к сожалению пересекается с командировкой, а так бы я 100% согласился
- В цирк ходите?
- Я в НЕМ ЖИВУ
P.S. к сожалению пересекается с командировкой, а так бы я 100% согласился
😭16
Так, еще одно сообщение «увидели ваш LinkedIn, очень заинтересовались вашим опытом, пойдете к нам на позицию Java-developer?» от рекрутера, и я стреляю в шашлык.
😁26🥴1