Эта интересная библиотека реализует множество концепций из статически типизированных функциональных языков семейства ML, таких так Haskell, Ocaml и Scala.
Не знал, что Haskell является языком семейства ML. Очень познавательно. Ссылочкой не поделитесь откуда это следует?
Я как понял, что меня клавиатура не устраивает (была какая-то старая logitech с чёрными кнопками) - на работе попросил MX keys, а домой купил механику Thermaltake (speed silver keys).
Так вот я с пол года приходил на работу - кайфовал от тактильных ощущений при печати. Потом приходил домой и опять кайфовал от других тактильных ощущений при печати. Хорошая клавиатура - это просто приятно.
Проблема в том, что чем дальше пользователь от бэкэнда оптимизирующего компилятора (middle-end в терминологии LLVM) тем больше он готов учить всех что надо делать.
Почему компилятор сгенерировал функцию, у которой нет инструкции возврата?
Компилятор - он про эквивалентные преобразования(*). Если в ф-ии нет возврата - значит и не задумывалось программистом и позволяется языком (LLVM - он бэкэнд ко многим языкам). *) За исключением концепции UB - когда преобразования могут быть не эквивалентными. Поэтому и вопросы не к компилятору, а к UB.
Каждый раз будем спрашивать "if is_only_main_path() {...}" - попытка заткнуть решето.
Проблема именно в концепции UB и довольно злокачественном её использовании в погоне за +0.(0)х% производительности. Собственно от неё и надо бы избавляться.
DCE - важная и нужная фаза и для скорости компиляции и для качества результирующего кода.
Формально (с т.з. изучаемого в школе прескрептивистского подхода к грамотности) вы не правы и следовало бы писать в родительном падеже "1/е количества кандидатов".
С точки же зрения речи, как средства общения - большинство языков избавляются от сложной падежной системы, заменяя её предложной потому, что она проще.
Я вот честно вообще не понял что статья делает на Хабре. Там вместо информации сомнительные истории уровня ЖЖ. Из информации - там: - Платёжная система - это посредник между банками, чтобы им самим не договариваться. - 1.5-2% стоимость перевода (и она как-то делится) - как именно разбивается 16-значный № карты (криво и косо - про вычисление контрольной цифры ничего) - Существуют, и могут отличасться от системы к системе MCC-коды.
[на второй картинке] Основной минус в том, что на самокате неудобно ехать в дальние поездки, он медленный, он небезопасен, он не закрыт от дождя, т.е. он слишком далек от того, что действительно хочет клиент в итоге. Поэтому у нас есть шанс на этом этапе клиента потерять.
А по-моему на второй картинке основной минус в том, что вы последовательно создаёте 4 разных продукта просто в рамках "создания VMP". Так ещё и вряд ли приобретаете компетенции по ходу дела (вряд ли умение делать велосипеды поможет вам при изготовлении автомобиля).
Чисто технически могло быть так (это пишут выше): 1. В компиляторе есть микро-фаза DCE (dead code elimination) - она удаляет весь код, до которого доказанно не дойдёт управление (и чисто транзитивно - весь доминитуемый "мёртвым кодом" код - тоже мёртвый).
2. Доказанно встретили UB - пометили цикл как DeadCode.
3. После какой-то фазы вызвали DCE - удалили сам цикл и всё что им доминировалось.
Какое именно поведение тут пытался сохранить компилятор?
Спасибо за вопрос. Он отличный. Всё, что сделал компилятор - он сделал чисто технически боле-менее корректно: 1. Подкорректировал main (удаление return - несомненно malevolent действие) из-за UB, право имел. 2. Сохранил never_called (она не статик => её чисто теоретически мог вызывать кто-то другой извне). 3. Сложил 2 функции в машинных кодах в исполняемый файл, легли подряд.
Вы спросите: так бю... а виноват кто. Виновата концепция UB, которая делает FAIL-SLOW (т.е. она явно говорит - что-то может пойти не так, как угодно плохо, но до конца пытается сделать вид, что UB это не ошибка, и может ещё удастся вырулить и всё наладится) - и в итоге в плохом случае портит вообще всё.
Сейчас эту проблему осознали. В новых языках, даже низкоуровневых UB нет. Например Rust - даже по производительности он не медленнии С\С++.
Проблема со старыми языками. Там от UB избавитсья крайне сложно. Малой кровью - надо было бы запретить return в этм месте. Но тут надо копать кишки LLVM.
Цикл while (true) {} — это бесконечный цикл без сайд-эффектов, то есть UB.… Можно просто удалить весь последующий код вместе с return'ом.
Вообще конкретно это UB довольно логично и служит для помощи программисту. А вот компилятор, так зловредно его эксплуатирующий... ну выпускнику MIT надо в резюме воткнуть строчку "мой коммит в LLVM ускорил specperf_*** на 0.01%.
Логика тут примерно такая: 1. Любой код без side-effects можно (и нужно в целях производительности) удалять. 2. Кроме бесконечного цикла - который сам по себе side effect. 3. Но если мы не можем доказать про цикл, что он конечный/бесконечный (и цикл без side-effects) - давайте его удалим. Пользы намного больше чем вреда. 3.1 при этом явно скажем программисту, что бесконечные циклы без других side-effects запрещены
============ а вот дальше идёт довольно-таки плохая логика 4. а давайте удалять те циклы, которые без side-effects даже явно бесконечные, формально ссылаясь на то, что это UB и его быть в коде не должно. 4.1 В оправдание п.4 - c развинием компиляторов многие циклы про которые раньше было непонятно finite\infinite стало можно сделать выводы. Поэтому если не принимать "4" - то для некоторых других существующих программ с обновлнием компилятора получим изменение поведения. В общем как не крути всё плохо.
ПС > для бесконечного цикла надо давать команду ожидания окончания потока. В Rust (примерно) так и сделано. Цикл loop {} именно бесконечный с гарантией что не удалится бэкэндом. В С \ С++ часто сложно сделать что-то разумное не поламав обратную совместимость.
При этом совершенно игнорируя суть вопроса (если мы хотим донести до людей "почему"): - для обучения С вам надо ссылать на то, чего студенты ещё не знают (потому, что это середина курса "архитектура ЭВМ" - для обучения Python это ссылка на только что пройденное начало "курса ООП".
Python не является ОО-языком (а Волга впадает в Каспийское море).
Ну и что? На объяснение разницы уйдёт пара минут. И оно будет относится к уже понятным вещам (в то время как при объяснении С вам постоянно придётся ссылаться на непонятные вещи).
ПС > это я просто косплею ваши...
Косплеите неконструктивно. В Си например из одного #define можно квиз устроить - но разумный человек найдёт компромис, что для обучения достаточно самых простых объяснений, с чем я и согласился. Вы же пытаетесь быть максимально неудобным в каждом вопросе - в итоге вместо "сильной позиции" вы показываете себя "неудобным собеседником".
Если у вас действительно есть хорошие контраргументы - вопросы, задайте пожалуйста их. А "брутфорс банальзыми вопросами" ещё у Хаджи Насреддина высмеивался как форма демагогии.
Вот вы действительно не понимаете того, что вы пишите? Почему в С надо объяснять дольше и неудобнее одни и те же концепции?
Всё просто: в ООП-языке вы попали на 2-3 первых лекции по ООП. Дальше ваши студенты знают что такое ООП и вы говорите (ну это так устроен объект в нашем ООП языке). И есть куча способов изучать ЯП и ООП параллельно без проблем.
А в случае С вы попадаете на середину курса "архитектуры ЭВМ". И какого-то хорошего способа изучения С, без отвлечения на посторонние темы (или оставления студентов без объяснения) я не вижу.
P.S. Как все эти пункты связаны с утиной типизацией?
Э.. а я точно с квалифицированным программистом разговариваю? Для более традиционных систем типизации объяснения будут "(почти всегда) тип удовлетворяет ограничениям на аргумент функции", в то время как для утиной типизации "у объекта есть функция с именем". Несколько разные объясниня, не находите?
Вы предлагаете писать учебник, как плохую документацию. Из личного опыта - в 10 классе я не понял С, как второй ЯП (первым был Pascal) ИМХО ровно потому, что нам объясняли "что" без "зачем" (в целом я думаю что вся наша компьютерная подгруппа не поняла - во всяком случае никто им не стал пользоваться как ходовым инструментом). Потом пришло время - и понял.
При этом есть места, где это будет "умеренно плохо" (define \ main) а есть места где очень плохо (printf); include - где-то на границе.
Т.е. с define \ main можно поступить на первых порах как с "отрезанием сосисочных жёпок" (запомните и делайте так, потом объясним). Хотя делать как обезьянака потому, что так принято - это прям супер не по программистски.
А вот с printf - вы просто создаёте у ученика диссонанс и противоречие. Вот вы объяснили типы, аргументы и параметры ф-ии, приведение типов. И тут делаете printf, которая просто противоречит всему сказанному. Человек хочет разобраться - просто потому, что у него хорошее программистское мышление а ему "опа не твоего ума дело или это займёт пару лекций".
Хорошее правило написания документации - не пишите "что" пишите "почему". Преподавать "что без почему" для #include и main() - не смертельно но плохо (нас не научили C, а это был второй язык - отчасти поэтому).
А вот для printf("") - это же epic fail. Это же реально магия. Везде надо передавать либо точноый тип, либо приводимый, либо (void *). А тут вы прямо передаёте произвольный тип в функцию, функция "понимает". Как это вообще сделано? Где границы применимости? Как это использовать?....
Мне кажется вообще не стоит бросаться такими фразами. Начиная с того, что Go - конкурент С++ и в каком месте он конкурирует за нишу С вообще не понятно. Продолжая... (хотя зачем продолжать если Go не конкурент за нишу С совсем).
Не знал, что Haskell является языком семейства ML. Очень познавательно. Ссылочкой не поделитесь откуда это следует?
Я как понял, что меня клавиатура не устраивает (была какая-то старая logitech с чёрными кнопками) - на работе попросил MX keys, а домой купил механику Thermaltake (speed silver keys).
Так вот я с пол года приходил на работу - кайфовал от тактильных ощущений при печати. Потом приходил домой и опять кайфовал от других тактильных ощущений при печати.
Хорошая клавиатура - это просто приятно.
Придумали же американсие фирмы и взаимовыгодно с Китайскими фирмами сотрудничают.
А запрещает US Federal Government.
Придумайте пожалуйста аналогию, не содержащую искажения фактов.
А запрет на экспорт полупроводниковых технологий в Китай - тоже происки пропаганды?
Распространяемые теми, на кого США санкции накладывает?
https://www.bis.doc.gov/index.php/documents/about-bis/newsroom/press-releases/3158-2022-10-07-bis-press-release-advanced-computing-and-semiconductor-manufacturing-controls-final/file
Проблема в том, что чем дальше пользователь от бэкэнда оптимизирующего компилятора (middle-end в терминологии LLVM) тем больше он готов учить всех что надо делать.
Компилятор - он про эквивалентные преобразования(*). Если в ф-ии нет возврата - значит и не задумывалось программистом и позволяется языком (LLVM - он бэкэнд ко многим языкам).
*) За исключением концепции UB - когда преобразования могут быть не эквивалентными. Поэтому и вопросы не к компилятору, а к UB.
Каждый раз будем спрашивать "if is_only_main_path() {...}" - попытка заткнуть решето.
Проблема именно в концепции UB и довольно злокачественном её использовании в погоне за +0.(0)х% производительности. Собственно от неё и надо бы избавляться.
DCE - важная и нужная фаза и для скорости компиляции и для качества результирующего кода.
Формально (с т.з. изучаемого в школе прескрептивистского подхода к грамотности) вы не правы и следовало бы писать в родительном падеже "1/е количества кандидатов".
С точки же зрения речи, как средства общения - большинство языков избавляются от сложной падежной системы, заменяя её предложной потому, что она проще.
Я вот честно вообще не понял что статья делает на Хабре. Там вместо информации сомнительные истории уровня ЖЖ.
Из информации - там:
- Платёжная система - это посредник между банками, чтобы им самим не договариваться.
- 1.5-2% стоимость перевода (и она как-то делится)
- как именно разбивается 16-значный № карты (криво и косо - про вычисление контрольной цифры ничего)
- Существуют, и могут отличасться от системы к системе MCC-коды.
А по-моему на второй картинке основной минус в том, что вы последовательно создаёте 4 разных продукта просто в рамках "создания VMP". Так ещё и вряд ли приобретаете компетенции по ходу дела (вряд ли умение делать велосипеды поможет вам при изготовлении автомобиля).
Чисто технически могло быть так (это пишут выше):
1. В компиляторе есть микро-фаза DCE (dead code elimination) - она удаляет весь код, до которого доказанно не дойдёт управление (и чисто транзитивно - весь доминитуемый "мёртвым кодом" код - тоже мёртвый).
2. Доказанно встретили UB - пометили цикл как DeadCode.
3. После какой-то фазы вызвали DCE - удалили сам цикл и всё что им доминировалось.
Спасибо за вопрос. Он отличный.
Всё, что сделал компилятор - он сделал чисто технически боле-менее корректно:
1. Подкорректировал main (удаление return - несомненно malevolent действие) из-за UB, право имел.
2. Сохранил never_called (она не статик => её чисто теоретически мог вызывать кто-то другой извне).
3. Сложил 2 функции в машинных кодах в исполняемый файл, легли подряд.
Вы спросите: так бю... а виноват кто.
Виновата концепция UB, которая делает FAIL-SLOW (т.е. она явно говорит - что-то может пойти не так, как угодно плохо, но до конца пытается сделать вид, что UB это не ошибка, и может ещё удастся вырулить и всё наладится) - и в итоге в плохом случае портит вообще всё.
Сейчас эту проблему осознали. В новых языках, даже низкоуровневых UB нет. Например Rust - даже по производительности он не медленнии С\С++.
Проблема со старыми языками. Там от UB избавитсья крайне сложно.
Малой кровью - надо было бы запретить return в этм месте. Но тут надо копать кишки LLVM.
Вообще конкретно это UB довольно логично и служит для помощи программисту.
А вот компилятор, так зловредно его эксплуатирующий... ну выпускнику MIT надо в резюме воткнуть строчку "мой коммит в LLVM ускорил specperf_*** на 0.01%.
Логика тут примерно такая:
1. Любой код без side-effects можно (и нужно в целях производительности) удалять.
2. Кроме бесконечного цикла - который сам по себе side effect.
3. Но если мы не можем доказать про цикл, что он конечный/бесконечный (и цикл без side-effects) - давайте его удалим. Пользы намного больше чем вреда.
3.1 при этом явно скажем программисту, что бесконечные циклы без других side-effects запрещены
============ а вот дальше идёт довольно-таки плохая логика
4. а давайте удалять те циклы, которые без side-effects даже явно бесконечные, формально ссылаясь на то, что это UB и его быть в коде не должно.
4.1 В оправдание п.4 - c развинием компиляторов многие циклы про которые раньше было непонятно finite\infinite стало можно сделать выводы. Поэтому если не принимать "4" - то для некоторых других существующих программ с обновлнием компилятора получим изменение поведения. В общем как не крути всё плохо.
ПС
> для бесконечного цикла надо давать команду ожидания окончания потока.
В Rust (примерно) так и сделано.
Цикл loop {} именно бесконечный с гарантией что не удалится бэкэндом.
В С \ С++ часто сложно сделать что-то разумное не поламав обратную совместимость.
never_called не была помечена как static.
А значит обязана остаться в единице компиляции.
ПС
Но вообще требовать от компилятора удалить неиспользуемый код довольно странно.
Спасибо.
Один интересный вопрос остался не покрытым.
В С и С++ разные наборы UB - как бэкэнд LLVM это разруливает?
При этом совершенно игнорируя суть вопроса (если мы хотим донести до людей "почему"):
- для обучения С вам надо ссылать на то, чего студенты ещё не знают (потому, что это середина курса "архитектура ЭВМ"
- для обучения Python это ссылка на только что пройденное начало "курса ООП".
Ну и что? На объяснение разницы уйдёт пара минут. И оно будет относится к уже понятным вещам (в то время как при объяснении С вам постоянно придётся ссылаться на непонятные вещи).
ПС
> это я просто косплею ваши...
Косплеите неконструктивно.
В Си например из одного #define можно квиз устроить - но разумный человек найдёт компромис, что для обучения достаточно самых простых объяснений, с чем я и согласился. Вы же пытаетесь быть максимально неудобным в каждом вопросе - в итоге вместо "сильной позиции" вы показываете себя "неудобным собеседником".
Если у вас действительно есть хорошие контраргументы - вопросы, задайте пожалуйста их. А "брутфорс банальзыми вопросами" ещё у Хаджи Насреддина высмеивался как форма демагогии.
Вот вы действительно не понимаете того, что вы пишите? Почему в С надо объяснять дольше и неудобнее одни и те же концепции?
Всё просто: в ООП-языке вы попали на 2-3 первых лекции по ООП. Дальше ваши студенты знают что такое ООП и вы говорите (ну это так устроен объект в нашем ООП языке). И есть куча способов изучать ЯП и ООП параллельно без проблем.
А в случае С вы попадаете на середину курса "архитектуры ЭВМ". И какого-то хорошего способа изучения С, без отвлечения на посторонние темы (или оставления студентов без объяснения) я не вижу.
Э.. а я точно с квалифицированным программистом разговариваю?
Для более традиционных систем типизации объяснения будут "(почти всегда) тип удовлетворяет ограничениям на аргумент функции", в то время как для утиной типизации "у объекта есть функция с именем".
Несколько разные объясниня, не находите?
Плохая документация объясняет "что", хорошая "почему".
Вы предлагаете писать учебник, как плохую документацию.
Из личного опыта - в 10 классе я не понял С, как второй ЯП (первым был Pascal) ИМХО ровно потому, что нам объясняли "что" без "зачем" (в целом я думаю что вся наша компьютерная подгруппа не поняла - во всяком случае никто им не стал пользоваться как ходовым инструментом). Потом пришло время - и понял.
При этом есть места, где это будет "умеренно плохо" (define \ main) а есть места где очень плохо (printf); include - где-то на границе.
Т.е. с define \ main можно поступить на первых порах как с "отрезанием сосисочных жёпок" (запомните и делайте так, потом объясним). Хотя делать как обезьянака потому, что так принято - это прям супер не по программистски.
А вот с printf - вы просто создаёте у ученика диссонанс и противоречие.
Вот вы объяснили типы, аргументы и параметры ф-ии, приведение типов.
И тут делаете printf, которая просто противоречит всему сказанному. Человек хочет разобраться - просто потому, что у него хорошее программистское мышление а ему "опа не твоего ума дело или это займёт пару лекций".
Хорошее правило написания документации - не пишите "что" пишите "почему".
Преподавать "что без почему" для #include и main() - не смертельно но плохо (нас не научили C, а это был второй язык - отчасти поэтому).
А вот для printf("") - это же epic fail. Это же реально магия.
Везде надо передавать либо точноый тип, либо приводимый, либо (void *).
А тут вы прямо передаёте произвольный тип в функцию, функция "понимает". Как это вообще сделано? Где границы применимости? Как это использовать?....
Согласен.
Мне кажется вообще не стоит бросаться такими фразами.
Начиная с того, что Go - конкурент С++ и в каком месте он конкурирует за нишу С вообще не понятно. Продолжая... (хотя зачем продолжать если Go не конкурент за нишу С совсем).