Приколы современных оптимизаторов С++ (шланг)

Последнее время тут были топики от «вайтивайти» в С++.

Вот очередная хорошая статья с хабра про современный оптимизаторы С++ и как они помогут вам отстрелить не только себе, но и другим всё.

«Почему LLVM может вызвать никогда не вызываемую функцию?»
habrahabr.ru/...​031&utm_content=link2post

Лично меня уже пугают все -Ox.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Странно, что про most vexing parse не упомянули. Наверное, не придумали достаточно остроумной хохмы, чтобы приплести этот подводный камень C++ к хеллоуинской тематике.

дайте вгадаю, Clang + LLVM під віндовс ?

Windows-то при чём?

Так а сколько еще всяких не открытых багов в С++ компиляторах/оптимизаторах.
Помнится когда только вышел свеженький gcc 4.7 (который С+11 поддреживал)
я там нашел какокй то баг связанный с nullptr + std::function (ньюансов уже не помню) и тоже он возникал при включенной оптимизации.

В старые версии — не начнут :)

Это само собой. Я вот недавно попытался фрю 2.2.5 запустить под virtualbox. Она не согласилась :) 3.5.1 уже работает. GCC 2.7.2 не собирается под нынешние системы.
Но я-то имел в виду «застывший» набор тулзов для конкретной специфичной платформы.

А ещё — огромный вклад в проблему вносят преподаватели языков.
Типичная книга и типичный вузовский курс по C или C++ ни слова не говорят про то, что тут водятся страшные звери, которые тебя готовы съесть, если ты случайно отклонился с пути истинного (или тебя отклонил кто-то совсем другой, а ты не успел заметить этого).
А потом начинается шок и «вывсёврёти!» на вроде бы давно выученном и освоенном материале, когда люди вдруг понимают, что твёрдая почва оказалась бездонным болотом.

А где же ты таких найдешь, что и теорию знают и практику идеально?
20 лет на подготовку преподавателя потратишь?

Я не хочу тут ввязываться в тему реформы образования, но во всяких США давно склоняются в сторону, что преподавание (как минимум в вузе) это работа для практика, который занимается структурированием, оформлением и передачей своего опыта (и получает за это не 1/20 от коммерческой деятельности, а заметно больше). Всё, умолкаю.

Но даже тема качества преподавания не связана с тем, что _книги_ от вполне себе практиков тоже по большей части умалчивают эти аспекты.
Взял с полки: Шилдт, «C++ базовый курс», 3ed. — ни слова. Там же Страуструп, 3ed. — очень вскользь на одной странице (из тысячи).
Вот и получается, что хабр/rsdn/dou/etc. учат тому, что должно было быть вбито на уровне азов.
Доколе? ©

и не займутся рефакторингом стандарта они укокошат и С и С++. Время рефакторинга уже почти прошло и у С и С++ есть всё шансы просто не выдержать всех поддерживающих его костылей.

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

Потому что сложность С и С++ возросла до безумия.

Это не оправдание. Описанные проблемы — самые азы, в отличие от всяких variadic templates, которые действительно можно осваивать годами.

как ты такое в систему обучения введешь? Никак.

А такие нюансы и не нужны.

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

Это как раз то, что IMO надо рассказывать. Но это по сути база, а не супертонкости.

А еще выравнивание совсем нетривиальное. Прагмы, ключики компилятора, ось — всё влияет на выравнивание, что получишь в конкретном месте.

Выравнивание вообще за пределами стандарта, так что не удивительно. Заметная часть прочего названного — тоже.

На десктопных платформах выключено по умолчанию.

Что именно выключено?

С утра было включено... когда успели?

Блин, да не связываться с переполнением, и всё.
Знать допустимый диапазон переменной (65к или 4М) — вроде ж не сложная задача.

разрядность char<=short<=int<=long<=long long

 — плох тот кросплатформенный проект, на котором нет своих Int8, Int16, Int32, ...

Знать допустимый диапазон переменной (65к или 4М) — вроде ж не сложная задача.

Иногда — очень сложная. Когда переплетаются факторы, часть которых от тебя не зависит.

— плох тот кросплатформенный проект, на котором нет своих Int8, Int16, Int32, ...

Сейчас, наоборот, хорош. Потому что stdint.h даже у MS.

std::int8_t, std::int16_t ...

Нет уже варианта изучить С++ меньше чем за 10-15 лет

Очень сильное преувеличение, как по мне. Хотя, конечно, «изучить C++» — понятие растяжимое. Весь его учить от корки до корки никому не надо.
Но научиться писать качественный плюсовый код без UB вполне можно за пару лет, учитывая, что про UB и прочие весёлости есть куча статей (даже на том же хабре) и множество выступлений на всяких CppCon’ах.

Вопрос к знатокам.
Можно ли запилить язык, который не имеет undefined behavior, и транслятор к этому языку в С. В итоге получаем лучшее от двух миров:
1. Никакого UB -> программы, которые делают то, что написано, а не то, что хочется оптимизатору
2. Вся инфраструктра, существующая вокруг С, вклюая библиотки, компиляторы, оптимизаторы и т.п.
3. .....
4. PROFIT!!!???

язык, который не имеет undefined behavior

Потеряем скорость

Во что это выражается в абсолютных числах?

5-10% это не пшик, так что tradeoff между ебанутостью языка и производительностью вполне может быть оправдан.

Тем более, если я правильно понимаю, все эти плюшки можно поотключать в компиляторе?

Это можно сравнить по результатам, например, -O1 и -O2 в GCC. Только начиная с -O2 он включает -fstrict-aliasing, -fstrict-overflow, -fdelete-null-pointer-checks и прочие основные источники реакции на UdB.

Вот пример статистики, но надо учитывать, что в -O2 входят сильно больше оптимизаций, чем эта группа. В основном разницы нет, есть один случай деградации, есть один +40% и есть один +5%. Я бы сказал, софт слишком разный, чтобы давать общую цифру, но в целом я большой пользы не вижу.
-O3 даёт больше, но он имеет (кроме векторизации) мало смысла, если не опирается на результаты -O2.

Если кто-то подскажет правильные бенчмарки — можно будет сравнить с ручным заданием -O1 плюс комплекта диверсий.

В разы, так как надо проверять любой переход по указателю, что убьет локальность работы с кешем. Получим недоджаву.

так как надо проверять любой переход по указателю

С чего вывод, что сразу надо проверять? Речь-то не о ситуации «в указателе ХЗ что => в рантайме ХЗ что произойдёт», а о выводах компилятора типа «в указателе ХЗ что => у меня полная свобода перекорёжить по-своему».

что убьет локальность работы с кешем

А даже если так — ничего не убьёт. Если железная платформа имеет явные подсказки для переходов типа likely/unlikely — все нештатные варианты рассматривать как unlikely, а если сама предсказывает (современные x86 или AA64) — после 1-2 первых проходов будет основной путь.

Получим недоджаву.

Java JIT, к сведению, умеет исключать такие проверки, если считает, что null маловероятен.

С чего вывод, что сразу надо проверять? Речь-то не о ситуации «в указателе ХЗ что => в рантайме ХЗ что произойдёт», а о выводах компилятора типа «в указателе ХЗ что => у меня полная свобода перекорёжить по-своему».

Нет, если говорить про UB, то переход по указателю или запись в массив вызывают UB при невалидных входных данных. Давайте уже определимся с терминологией.

Нет, если говорить про UB,

1. На всякий случай — я пишу UdB, потому что есть ещё UsB (unspecified behavior)
2. Надо определяться не с терминологией, а с темой обсуждения. А она тут — именно сверх-ожидаемый интеллект оптимизаторов, а не просто факт UdB от доступа по мусору.

А даже если так — ничего не убьёт. Если железная платформа имеет явные подсказки для переходов типа likely/unlikely — все нештатные варианты рассматривать как unlikely, а если сама предсказывает (современные x86 или AA64) — после 1-2 первых проходов будет основной путь.

Убьет потому, что вместо локальной работы со структурой данных надо будет лезть неизвестно куда для валидации указателей и массивов, то есть, структуры рантайма языка будут частично вытеснять из кеша рабочие данные.

Это не в разы, а на единицы процентов.

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

Java JIT, к сведению, умеет исключать такие проверки, если считает, что null маловероятен.

UB в Java?

Нет, собственный обработчик всяких SIGSEGV, который конвертит ситуацию в генерацию NullPointerException (а если была явная проверка на null — в переход на соответствующую ветку).

Шо то, шо это. Лучше прога, работающая и быстро, и правильно.

Обычно надо новые фичи быстрее чем у конкурентов, а скорость и правильность не так важны (в разумных пределах)

це де так? невже в автомотів? чи хвінанасах?
чи петпроектах?

усюди, де є конкуренція. В пет проектах, певне, її нема.

Це так на певній ступені проекту. Коли кажуть щось накшталт: або ми виходимо в prod на %дата% або усі розходимося.

Ну дык в том и суть UB, что при нём оптимизатор может натворить любую фигню и будет прав. Это приколы C++ в целом.

Я ещё в разговорах об UB люблю приводить вот этот пример:
ideone.com/pcXaQk
Оптимизатор увидел, что мы выводим в stdout значение mul*i, имеющее тип int. И поэтому решил выбросить проверку i<10, поскольку i, по его логике, ну никак не может быть больше двух. Ибо при i == 3 уже произошло бы переполнение типа int, а переполнение знаковых целых — UB.

Угу, я его уже тут упоминал с подробным обсуждением на RSDN. Там ещё есть противоположный результат — сокращения цикла до 3-х итераций — всё на основе того же «знания».

В 1990-х годах разработчики языка Си и ОС Unix (Томпсон, Керниган, Ритчи) сделали длинное заявление со множеством акцентов, в котором они раскрыли, что эти разработки были первоапрельской шуткой, пародией на язык Pascal и ОС Multics в форме доведения сложности до абсурда, что сами они пишут на Лиспе под Apple Macintosh, и что теперь они крайне поражены популярностью Си и Unix, и даже планировали продать их Советскому Союзу для отбрасывания его уровня компьютерных технологий на 20 лет назад; ведущие фирмы-разработчики компиляторов Си и С++ и дистрибутивов Unix отказались комментировать это заявление[256]. Вскоре после этого авторы Си и Unix предложили язык Limbo под слоганом «что бы мы сделали, если бы начали с начала» и написанную на нём ОС Inferno.
--- Это со ссылочки

Ну так, как видим, всё это оказалось правдой. Go — наследник Limbo. У которого куча паскалевских черт.

Виктор, остановись, мы не хотим еще и тебя потерять

и даже планировали продать их Советскому Союзу для отбрасывания его уровня компьютерных технологий на 20 лет назад

По моему, это новый слоган для ватника — совок развалился не из за рукожопости руководителей и дибилизации системы а из за злых буржуев и ЯП С !!!

Здається з С++ в усі часи і на багатьох компіляторах були різноманітні трабли при використанні неініціалізованих змінних. В чому проблема запаритись інціалізувати якимся дефолтними значеннями або нульом самому для більшої надійності?

Пусть сын Порошенка ининициализирует! %)

Просто люди часто не задумываются о соглашениях между ЯП и ОС. Например С++ может не инициализирвоать данные, но динамический линкер ОС проинициализирует данные хотя бы из-за соображений секьюрности, чтобы память с чужим контентом не была дана новому процессу и счастливый программер получает там NULL. А компилятор максимум знает назначение ОС для осуществления calling convention, но ни версию ОС ни тому подобных ньюансов, их знает линкер. Вот и получается что компилятор может делать с неинициализорованными данными что ему угодно.

динамический линкер ОС проинициализирует данные хотя бы из-за соображений секьюрности, чтобы память с чужим контентом не была дана новому процессу и счастливый программер получает там NULL

Для статических и глобальных данных инициализация нулевыми значениями (и конструктором, где есть) прописана в стандарте (C11 — 6.7.9). Если компилятор не отклоняется от этого, то остаётся проблема инициализации только тех, что с automatic storage duration. Но, как правило, в результате работы всяких ld.so в стеке уже была какая-то возня и там никак не нули. Нули будут в новоаллоцированных страницах (если их специально не заливают всяким мусором).

юзай −00 и оптимизируй вручную.

оптимизировать надо боттлнеки

На компиляторах, основанных на SSA (как нынешние GCC или Clang), -O0 приводит к чудовищному количеству лишних операций, типа пять копирований одного и того же между регистрами, часто по кругу. Оптимизировать вручную это невозможно.
GCC стал давать -Og, которое даёт некоторый весьма малый уровень вмешательства, но уже устраняет эти безобразия. Поэтому начинать надо минимум с него.

Появилось в GCC 4.8. Так что до некоторых видов embedded могло не доползти.

Clang не ругается на неё с 3.9 или 4.0, но приравнивает к -O1, обещая в туманном будущем реализовать меньше странных оптимизаций.

Ещё мне понравился такой кусок в Linux Makefile:

ifdef CONFIG_READABLE_ASM
# Disable optimizations that make assembler listings hard to read.
# reorder blocks reorders the control in the function
# ipa clone creates specialized cloned functions
# partial inlining inlines only parts of functions
KBUILD_CFLAGS += $(call cc-option,-fno-reorder-blocks,) \
                 $(call cc-option,-fno-ipa-cp-clone,) \
                 $(call cc-option,-fno-partial-inlining)
endif

но это уже когда отладчиком лезешь аж в машинный код.

Все примеры в статье — отходы от стандартов написания правильного кода. Зачем тогда удивлятся что код написанный чёрть и как стреляет в ногу?!

Ну то лише приклади для наглядності, ідея в тому, що навіть в найбільш чисто написаному коді можуть вилізти приколи при оптимізації компілятором.

Да не совсем. Мне лень открывать С++ стандарт, но кто-то может посмотреть правила инициализации глобальных данных? На сколько я помню там указаны скалярные типы, массивы, юнионы, структуры, но типа указатель на функцию там нет.

typedef int (*Function)();
static Function Do;

Если это так, то это настоящий UB. Почему бы не сложиться звёздам так, что Do будет указывать на EraseAll() и так? Оптимизатор ИМХО сделал всё правильно, если память будет заполнена рандомом, то почему бы рандому не указывать на EraseAll()?

imgs.xkcd.com/comics/random_number.png

Вероятности. У EraseAll один адрес, а того, что может оказаться в том указателе 2^n (n размер указателя).

Ты себе даже не представляешь как тесно в 32-битовом виртуальном пространстве процесса. Там куда не плюнь — попадёшь в чью-то память, расшаренные динамические библиотеки с их функциями и т.п. EraseAll() может показаться ещё ангельской функцией %)

А такое трактование стандарта как в этом примере — это называется в народе «заставь дурака богу молиться — он лоб себе расшибет».

Какой указатель лучше 0xDEADBEEF или 0xEBADEBAD ? %)

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

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

Причём тут ошибки? Я пишу код в детерминированном стиле и не надеюсь, что с большой вероятностью он будет работать как надо или с большой вероятностью он упадёт, если что-то пойдёт не так. Если ты хочешь, чтобы он упал, присвой ему NULL явно.

Расставляешь 100500 ассертов и не мучаешься

Я пишу код в детерминированном стиле и не надеюсь, что с большой вероятностью он будет работать как надо или с большой вероятностью он упадёт, если что-то пойдёт не так.

Ассерты как раз для этого

Это одна из причин, почему математики не должны лезть в программирование %) В детерминированной системе нет места вероятностям.

Ты программы пишешь не для вселенского процессора, а для полностью детерминированной системы. И вообще:

God does not play dice with the universe. © Einstein

Вообще-то система никогда полностью недерминирована, всегда есть сбои (и даже штатные в оборудовании, космические лучи для ram, бед сектора для диска и всё-такое, не говоря уже о багах в самой оси и даже процессорах).

Вообщем-то из определения самого понятия алгоритма следует, что он должен выполняться единственным детерминированным путём. То есть по сути дела, в записанном алгоритме undefined behavior не должно возникать в принципе, никогда. Поскольку стандарт C++ такие ситуации допускает — это косяк дизайна языка как такогого. Обсуждаемая в топике проблема возникла вследствие ошибки в дизайне/идеологии языка.

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

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

Вот это я бы назвал вредительством. Выйграть 0.001% быстродействия за счет внесения ошибки в логику программы.

Никакой ошибки. Например, компилятор знает, что индексы массива от 0 до 99, а тут индексация будет значением от 3 до 20.

Вот у меня итерационный алгоритм, а оптимизатор решил его пройти в обратном порядке или выкинуть проверки выхода — в результате, что будет?

Это ты о другом случае говоришь. Там я согласен — он не имеет права делать изменения, которые ломают логику входного кода.

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

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

К этому относится даже простейшая ситуация, когда ты пишешь b = f(a, a+1); — решение прочитать `a` только один раз это уже оптимизация (которая не сработает, например, если `a` атрибута volatile, или в чём-то чуть более сложном типа b = f(g(a), a+1);, а `a` может быть модифицировано в g() (она глобальная, её адрес куда-то передавали, и т.п.)

«Во всём нужны мера, норма и предел» ©. Некоторый уровень таких оптимизаций разумен и полезен. Все споры в этой теме и ещё 100500 аналогичных в других местах — в том, где этот уровень превзойдён, и почему программист не может контролировать происходящее.

Ее задача выполнять то, что написано и оптимизировать только там, где это явно возможно и прозрачно без всяких UB.

Так в том и заковыка, что это условие чётко определить невозможно: граница между теми оптимизациями, что приводят к переэксплуатации понятия UdB, и теми, что не приводят — слишком размыта. Но это я быстро пояснить не могу — нужен именно опыт разработки оптимизатора.

Пока же мы имеем или тотальные запреты на оптимизацию сверх минимума (Java), или возможность компилятору творить вообще что угодно в рамках своего понимания (C, C++)...

ты пишешь b = f(a, a+1); — решение прочитать `a` только один раз это уже оптимизация (которая не сработает, например, если `a` атрибута volatile, или в чём-то чуть более сложном типа b = f(g(a), a+1);, а `a` может быть модифицировано в g()

Небольшое уточнение: вы перепутали «не сработает» и «не хотелось бы, чтобы сработало».

Может сработать даже с f( g(&a), a+1 ) , т.к. изменение ’a’ и использование его в пределах одной точки следования — UB, результат которого вполне может совпасть с чтением переменной до изменения. Кстати, порядок вычисления ’a+1′ и ’g(a)’ не определен. В целях оптимизации, естественно.

Теперь новый термин

«sequenced-before»

Согласен по сути с уточнениями. Но не «перепутал», а переупростил. Надо было дать вариант с промежуточными присвоениями.

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

Как раз запись за пределы массива — типичнейший UB, отличающий С от остального. Можно о нем поговорить.

Можно, но не в связи с темой целочисленного переполнения или пустого указателя.

В детерминированной системе нет места вероятностям.

Непонятно как у вас там и у прочих ембедерщиков.
Но на большом и толстом сервер сайде вероятность того что код будет всегда работать так как задуманно — никогда не 100%

вероятность того что код будет всегда работать так как задуманно — никогда не 100%

Вот это было отражено? В каком именно ГОСТе и в какой форме?

Вот объясни с какой радости разименование NULL это UВ. Просто сделайте то, что написано. В тех системах, где оное допустимо всё пройдет нормлально, а тех где недопустимо сама ось обрубит прогу.

Объясняю. Там, где железо поддерживает hardware breakpoint или подобное поведение, ставится бряка на адрес памяти 0. Когда происходит разыменование 0, бряка срабатывает и рантайм кидает SIGSEGV (кстати, SIGSEGV — это чисто линукс, а Вы что предлагаете делать, стандартно для любой ОС или ее отсутствия?). А вот если нет хардварной бряки — то чтобы убедиться, что разыменовывается ненулевой указатель, надо в каждом месте разыменования вставлять код сравнения указателя с 0. Это будет основательно тормозить исполнение и увеличивать размер программы. И все скажут «плохой язык, надо писать новый».

А когда проц суется в чужую памать — разве это не UB?

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined .
www.open-std.org/...​2/wg14/www/docs/n1124.pdf page 95 clause 8.

Не пользуйся эзотерикой.
Разыменование указателя и запись в массив приводят к UB. Не надо его бояться. Если обкладывать ifами эти операции — просядет скорость.
Кстати, вот по линку с той самой википедии список UB www.open-std.org/...​w/docs/n1548.pdf#page=571 мне лень читать

В тех системах, где оное допустимо всё пройдет нормлально, а тех где недопустимо сама ось обрубит прогу.

А там, где ОСи нет (фирмварь), или вместо нее микрокернел (uC-OSII)?

Так везде все от него зависит. Это и есть UB — когда код не говорит, что получится, а оставляет это процу, ОСи или расположению страниц в адресном пространстве. Надо найти определение UB.

Что им мешало жестко задать исключительно слева-направо или справа-налево.

Во всяких Java и Go именно так и сделано.

Для C же... ну, я таки не вижу глубокого смысла. Или же это должно регулироваться, но позволять по умолчанию таки делать вычисления в любом порядке.

Вот ты отлаживаешься. Отладился, включаешь оптимизацию и получаешь код с совершенно другой логикой.

А потомучто нехер баловаться неинициализированными переменными. За такое, «плюсовые» кодерки должны депортироваться на джава-формошлёпство.

А потомучто нехер баловаться неинициализированными переменными.

Код тут и тут оба со всеми инициализированными переменными, зато результат оптимизации — противоположный.

За такое, «плюсовые» кодерки должны депортироваться на джава-формошлёпство.

За такое надменное верхоглядство, как Вы тут демонстрируете — надо депортировать даже не на Java, а куда-нибудь на Visual Basic под Excel.

Именно потому рулезные чуваки зашли через kotlin и groovy и наверняка через что-нибудь там ещё аля scala ))

То же и с signed int — это должно быть системно зависимо, а не UB.

Ну в пределах конкретных систем это вполне реализуется. Для GCC и Clang: есть общий -fwrapv, а есть -fstrict-overflow (который сам включается при -O2 (GCC), но можно его явно выключить). Кода, который «ниасилил» эти проблемы и применил -fwrapv, достаточно много (например, Python ещё во времена 2.6 был таким), а -fno-strict-overflow «стандарт» для ядра Linux и ещё кучи мест.
В Linux, кроме того, также включают -fno-delete-null-pointer-checks (я согласен, что это дурь, но, видимо, слишком много мест, где его хвосты...)

GUID уникален, а не рандомен.

У UUID высокая предсказуемость. А вообще марш все читать дискретку!

Так все же

высокая предсказуемость.

или

уникален

?

Вот честно, не интересно заниматься словоблудием. Эти понятия вообще ортогональны. Разбей поток рандома на 0 и 1 и у тебя будет хреновая уникальность и хорошая непредсказуемость.

Понял косяк.
Так все же что на счет уникальности GUID он уникален или нет?)
Потому что wiki говорит о какой-то вероятности коллизий.

Так все же что на счет уникальности GUID он уникален или нет?)

Уникален для чего? Для одного процесса, для операционки, для домена и т.д.?

До сих пор, насколько я слышал, ни на типе 1, ни на типе 4 коллизий не зафиксировано (для типа 1 — при условии правильной генерации timestampʼов).
Хотя вероятность — да, уже вроде бы высокая.

Предсказуемость — у типа 1. А сейчас в основном используют тип 4 (случайная генерация).

Почитай заодно, почему UUID не используют в качестве криптографических ключей.

Он не уникален. Но можно код писать и не пользуясь гуидами. В конце-концов, как-то индустрия работала без гуидов 30 лет до того, как мелкомягкие придумали ком.

В том, что оно было задизайнено как уникальность у него заложено в имени.

Просто люди не понимают понятия детерминированных систем. Никто не ожидает, что при чтения байта будет значение 257, так и не стоит ожидать чтения нулей там где они не были записаны, только и всего. Рандом никак не влияет на детерминированность, это всегда 0 или 1 при декомпозиции. Всё.

Так думали во времена дос. Потом поняли что все бажное, поделили на процессы нити, изолировали память, придумали разные штатные методики типа дог вотч и тд. И вообще, самое надёжное ПО сейчас на рынке это то, которое хорошо умеет себя реанимировать и максимально рассчитано на разного рода сбои. Включая сбои оборудования сети итд. И такой код пишут зачастую что да, а действительно мы прочитали значение 256 или нет. Ну или хотя бы контрольные суммы перепроверить прочитанных блоков.

И вообще, самое надёжное ПО сейчас на рынке это то, которое хорошо умеет себя реанимировать

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

Я подозреваю, что это и имеется в виду — просто собственно исполняющий код и контролирующий его код формально находятся в пределах одного продукта. Так, например, в Erlang/OTP с системой супервизоров. Но если кто-то хочет явно проверять условие падения — это тоже можно реализовать (грубо говоря, через try-catch).

«Как-то» это, да, ключевое. Например, есть монтирование разделов по uuid. Жутко вкусная штука, а без неё надо было искать вручную, где на каком пути какой раздел оказался. И таких примеров много.

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

На сколько я помню там указаны скалярные типы, массивы, юнионы, структуры, но типа указатель на функцию там нет.

C++11 last draft:

3.6.2/2:
Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place.

8.5/5:
— if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;

3.9/9:
Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types.

Так что — требует инициализации пустым (NULL, nullptr) указателем. Разницы между указателем на функцию и указателем на что-то другое тут нет.

Стандарт C требует то же самое, но без промежуточной отсылки на скалярные типы.

Может дело в typedef ? У меня нет clang этой версии, чтобы проверить.

Писали, что такое clang творил чуть ли не с самых первых версий. С 2.9 так точно.
Нет, typedef ни при чём. Работает логика по принципу
1. Вызывают по указателю не проверив его => считаем, что он гарантированно инициализирован непустым значением. Ибо иначе UdB.
2. Есть только одно место, где ему что-то присваивают => согласно п.1 оно вызывается. Ибо иначе UdB.
3. Присваивается согласно п.2 одно-единственное значение => можем не заглядывать в указатель, а вызвать безусловно.
По крайней мере пункт 3 это та же самая логика, что если компилятор видит, что по p типа A* вызывается виртуальная A::foo(), а он знает, что p инициализирован B() (потомком A) — он вызовет B::foo, не делая «лишнего» заглядывания по указателю.
Достаточно чуть-чуть разрушить эти гарантии — например, сняв слово static у указателя — и он резко становится «белым и пушистым», не делая таких предположений.
А людей смущает именно полная последовательность выводов — что компилятор сделал неожиданный вывод непонятно из чего.

Неожиданный вывод :) примерно того же уровня ожидаемости, что обсуждаемое решение компилятора :)

На один случай проблемы от инициализированности (если вообще считать, что проблема в этом) — 1000 от мусора в неинициализированных переменных.

У тебя смесь нескольких заметно разных вещей.

Для управления стержнями — вообще ни C, ни C++ не применимы, если серьёзно подходить к вопросу. Минимум — Ada с её правилами и гарантиями. А, возможно, вообще что-то самописное.

Для C/C++ уровень проблем от обсуждаемых случаев UdB, как (не)возможность доступа по nullptr, примерно сравним с уровнем проблем от доступа по испорченному указателю, который тоже UdB. Что одно — ошибка программиста, что другое. Но разница в том, что некоторые вещи компилятор может уловить, а некоторые (как use-after-free) — обычно нет. И ещё — в том, что курсы и книги C/C++, как правило, замалчивают проблемы UdB, и их выпускники предупреждены о доступе по кривому указателю, но не о переполнении знаковой арифметики и тому подобному.

А если еще учесть, что на С++ и С пишут не только люди уровня тебя, а много менее опытные и грамотные, то с С и С++ нынче всё очень плохо.

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

что не инициализированная переменная безопаснее, чем инициализированная.

И вот с этим я по-прежнему не согласен. Проблема не в инициализации. Если бы указатель был на стеке, оно имело бы право решить точно так же, потому что использование неинициализированного значения это такое же UdB.

За 20 лет столько раз менялось поведение компиляторов, что и не сосчитать.

Но оно менялось приблизительно в одну сторону — больше интеллекта (какой бы он ни был в деталях), больше кроссплатформенности и больше стандарта.

Порылся на en.cppreference.com по вопросу дефолтной инициализации

static Function Do;

.

Получается, что указатель на ф-цию попадает в -
«pointer types».
Все эти указатели группируются в — «scalar types».

А вот тут (en.cppreference.com/...​guage/zero_initialization) уже написано, что нулевая инициализация выполняется, если —
«If T is a scalar type, the object’s initial value is the integral constant zero explicitly converted to T.»

Так что вряд ли глобальная переменная — указатель на ф-цию, вряд ли будет чем то инициализироваться, кроме 0.

вряд ли будет чем то инициализироваться, кроме 0.

Разве что компилятор прошёлся по всем возможным точкам инициализации и оптимизировал единственную существующую её вынеся её вообще в глобальную инициализацию сегмента данных и убрав сам код (на что кстати надо бы глянуть что там получилось по результату в асме там где этот код должен быть в исходнике).

Вообще эта оптимизация — результат огромной, титанической работы над компилятором, в других языках об этом даже речи нет. Что касается undefined behaviour, достаточно выставить уровень предупреждений W3, или W4, и читать сообщения компилятора. Я лично обращаю внимание на все варнинги, и делаю по ним фиксы.

Что касается undefined behaviour, достаточно выставить уровень предупреждений W3, или W4, и читать сообщения компилятора.

False. Множество случаев компилятор не ловит. Там же на хабре были примеры.

Для мест, которые опознаны как подозрительные, специально придумали UB sanitizers с проверкой в рантайме.

Я лично обращаю внимание на все варнинги, и делаю по ним фиксы.

Это хорошо, так и следует делать.

Расшифруй, непонятно.

Это от гугла, санитайзер который.

В GCC и Clang есть в обоих, но с разными возможностями. Ты который смотрел?

вот только, как я понял при включении всех ворнингов он не включается, его нужно самому добавлять.

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

но когда сложность инструмента начинает требовать ИИ в широком смысле (поумнее человека) — это уже очень кривой инструмент.

Или просто отражающий реальную сложность мира и задачи.

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

Ну, я именно этот «проекционный» мир и имел в виду.

Вон у Майка жесткое ограничение С98 и не более. С++17, 20 даже не предполагается в будущем. Это и есть ответ бизнеса и людей на безумие в С и С++.

А это уже тупо. Я понимаю, C++17 не принимать, но C++11 это уровень просто доведения того же 98 до минимально логически замкнутого вида. Если они не могут сменить компилятор из-за внешних причин, вплоть до тупости процессов заказчика — ok, но если это внутреннее решение — то кого-то надо выкидывать в Днепр.

В ядре Линуха ты сам увидел кучу ручных ограничения для безумия компилятора С.

Это вообще-то режим для спецнужд, а никак не умолчание.

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

Так на это давно есть Java/C#/Go/etc.
хотя в embedded они достаточно медленно проникают.

А это уже тупо. Я понимаю, C++17 не принимать, но C++11 это уровень просто доведения того же 98 до минимально логически замкнутого вида. Если они не могут сменить компилятор из-за внешних причин, вплоть до тупости процессов заказчика — ok, но если это внутреннее решение — то кого-то надо выкидывать в Днепр.

С98 замість СХХ — правильне бізнес рішення в лонграні

C++11 — правильное решение. C++98 — уже давно нет.

я про старий добрий С замість підрихтованого С++

C98 не существует. Если вы про C99, то правильно называйте, чтобы не сбивать людей с толку.

Расскажите это Гуглу, который Хромиум делает, к примеру

Гугл закоснел уже в очень многих аспектах. Это — один из них.

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

Это и зовётся — закоснели. Потому что проблемы перехода с 98 на 11 ничтожны, касаются только очень специфических мест, методы решения отработаны и описаны на каждом углу. Надо просто взять и сделать. А польза — колоссальна.
(Кстати, я не проверял, так ли они запретили C++11. Тут временно доверюсь. Но был шумный запрет, например, исключений.)

А польза — колоссальна.

Где???

В продукте, где же ещё.

В чем конкретно польза для продукта?

Ну вот простой пример — emplace* для контейнеров. Ранее надо было или страдать от цены копирования, или идти через промежуточное «пустое» состояние, которое недопустимо с точки зрения логики программы. Сейчас имеем конструирование сразу на нужном месте, без затрат на ерунду.
Move-семантика, аналогично, убрала по сравнению с C++98 кучу лишних копирований.
constexpr — устранение пачки вычислений из рантайма.
Повторюсь, C++11 — это то, чем должен был изначально стать C++98, если бы его не заморозили.

Это не для продукта, а для программиста. Да и движок уже давно написан. А для продукта — все равно, на чем писали.

А для продукта — все равно, на чем писали.

Не всё равно, потому что заметное сокращение рантайм-затрат.

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

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

Для высоконагруженных частей системы используются структуры с тривиальными конструкторами и с оглядкой на расположение данных в памяти. Все эти классы и копирование — в тех 95% кода, которые выполняются 5% времени.

Все эти классы и копирование — в тех 95% кода, которые выполняются 5% времени.

В вашей специфике — может быть.

Move-семантика, аналогично, убрала по сравнению с C++98 кучу лишних копирований.

И сделала возможным создание unique_ptr, а также других некопируемых типов, которые теперь — о чудо! — таки можно куда-то передавать и хранить в контейнерах.
Да, можно было юзать shared_ptr, существовали реализации и до C++11. Но выделять лишнюю память на счётчик ссылок и прочую служебную ерунду для каждого объекта и платить за лишние атомарные инкременты/декременты по каждому чиху — ну, такое.
Ещё был auto_ptr. Он пытался имитировать «перемещение» через конструктор «копирования», принимающий не-const ссылку. Что часто приводило к неожиданным последствиям в рантайме. Неудачный эксперимент, который вышел неудачным именно из-за отсутствия move-семантик в языке.

Но если говорить о самой полезной на мой взгляд фиче C++11, то это override. Писать иерархии классов с виртуальными функциями без него — это полнейший мазохизм, как по мне.

Но если говорить о самой полезной на мой взгляд фиче C++11, то это override. Писать иерархии классов с виртуальными функциями без него — это полнейший мазохизм, как по мне.

Вообще, такие вещи неплохо ловят профильные IDE. У netbeans специальный знак возле функции, которая переопределяет базовую, без необходимости писать override.
(Кроме варианта, когда базовый класс меняют, конечно, и надо отследить все производные. Вот тут, да, лучше опереться на компилятор.)

Что именно им рассказать?
chromium-cpp.appspot.com
Что они неправы что используют множество фич C++11/14, потому что на доу кто-то считает эти фичи злом или C++ «мёртвым»?

Прикольно, они обновились)

Прост уже не первый раз вижу на ДОУ утверждение, что гугл якобы не юзают современный C++. А потом нахожу пруфы обратного :)

.

Так на это давно есть Java/C#/Go/etc.
хотя в embedded они достаточно медленно проникают.

Го -може повільно,
жаба давно в ембедеді, точніше вона з ембдеда вилізла,
Сдієз — теж має життя в ембдедеді
Хрести не нужни,
чистий С — лов\систем левел,
мови високого рівня для аплікейшенів

Ложка дёгтя делает куда больше чем бочка мёда.

На практике такие проблемы если и встречаются, то крайне редко.
Обратите внимание, как народ извращается, чтобы написать, какой компилер плохой.

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

В данном конкретном примере пишут, что при -О0 оно крешится.
Вы много знаете программ, боевой код которых ни разу не дебажили?
А в дебаге как раз -О0 ставят, чтобы оптимизации не мешали.

А в дебаге как раз -О0 ставят, чтобы оптимизации не мешали.

Не всегда

Ну да, если в фирмварь не влазит, то ставят -Os. Или когда профайлят. Других причин пока не знаю. Если есть — расскажите, интересно.

Я много знаю программ которые на этапе компиляции undefined behavior, а в рантайме всё пучком.

при -О0 оно крешится

Ну вкинь. В любой открытый большой проект. Пускай из-за тебя ядро линукса выкинут, или хромиум с его 10М строк на плюсах.

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

Не ты первый, не ты последний

Go вряд ли дальше этого зайдёт в замещении С++, ибо сборщик мусора.

Go пойдет настолько, насколько у гугла хватит денег.

ви говорите, бутто GC это плохо

Нет.
Если речь про то, что оно иногда подвисает на stop the world, то есть методы и без такого, и неплохие в остальном.

Нет. Доказано на практике, что если допускать достаточно времени перед собственно сборкой, вариант с GC может быть быстрее.
Выигрыш получается за счёт отсутствия затрат на структуру динамической кучи (объекты просто последовательно в RAM), на учёт free/delete для каждого возвращаемого куска, на гонки по кэшу (при типичной организации памяти в managed сборка идёт соседними областями).

без GC процесс уборки памяти детерминирован. Убить этот кусок памяти нужно прямо сейчас. И никого не интересует что этот кусок far jump для кешей. В гарбадж процесс недерминирован, он может убирать только то что считает выгодным сейчас (в том числе по contiguous логике). Поэтому GC может работать быстрее, это нормально.
Но есть и третий путь. Это функциональщина и вот там сборка будет даже поэффективней чем у этих двоих подходов. Гарбадж в Лиспе 50х годов задвигает по эффективности все выстраданные десятилетиями алгоритмы менегеров памяти в императивных языках. Такие дела.

Когда GC выделяет память, грубо говоря, это стоит какой-нибудь count++ по области памяти. Тоесть экстремально дешево. После этого GC может заняться shrink holes — перемещениями уплотнениями в background. От того и в менеджед языках вы и не видите реальный адрес обьекта. А если увидите то немедленно окантуйте его в fixed{} чтобы GC случайно его не переместил. Обычный алокатор при запросе куска памяти должен сразу быстренько проанализировать где есть дырка для нужного куска памяти, и после переместить уже ничего нельзя (С++). Вот хз как оно дальше работает на уровне TLB процессора и виртуализирования памяти, но в целом логика на прикладном уровне именно такая. Тоесть GC не только при удалении памяти выигрывает, но и прежде всего при ее выделении.
А делать то можно в ручную все что угодно, обычно это называется flyweight и стратегии пулов. Но это другая история.

Это прикол с совпадением локаций строк одновременно обрабатываемых матриц? Если да, то он хрестоматиен. Но и вляпаться в него достаточно легко.
Кстати, вот тут, например, с Java легче — потому что каждая строка аллоцируется отдельно :) и вероятность такого конфликта по всей матрице — падает практически до нуля.

И на новый процессор опять нужно будет переделывать алгоритм.

Уже меньше вероятности — по крайней мере для стандартных алгоритмов — и кэши больше, и 2-way реже... а вообще против такого можно и явный контроль в рантайме поставить.

Что-то мне подсказывает, что ты уперся в конвееры на SSE. И таки да, для Интеля-АМД они весьма по разному себя ведут.
Возился для ARM NEON для своих видеодекодеровых нужд — в конце концов психанул из-за его жуткой капризности и голландца, который доделал все что мне нужно в libav )

Я мож не совсем в теме, но по моему все,
что связанно с матрицами уже давно на GPU считается.

говорят, PyTorch умеет

Here we introduce the most fundamental PyTorch concept: the Tensor. A PyTorch Tensor is conceptually identical to a numpy array: a Tensor is an n-dimensional array, and PyTorch provides many functions for operating on these Tensors. Like numpy arrays, PyTorch Tensors do not know anything about deep learning or computational graphs or gradients; they are a generic tool for scientific computing.

However unlike numpy, PyTorch Tensors can utilize GPUs to accelerate their numeric computations. To run a PyTorch Tensor on GPU, you simply need to cast it to a new datatype.

pytorch.org/...​amples.html#warm-up-numpy

А зачем С++? На питоне писать в разы быстрее, а математика все равно на видяшке будет

А можно примеры того что можно сделать с матрицами на бумаге (оно же так проектируется, да?) и для чего вот, например этого youtu.be/...​qf8PaR8VB6gWU1nJ2IQ&t=924 будет не достаточно?

Непроизводительность затрат и сложность механизма там, где нет возможности её полноценно поддержать.

Я знаю софт, который работает на последовательной памяти. Типа выделил кусок в 30GB и пишешь в неё, пока не придёт всем глобальный flush. Но на то, чтобы переточить под него структуры данных, ушло столько времени, что ой.

А если ты пользуешься стандартными алгоритмами на традиционных структурах данных (а в большинстве кода таки вынужден так делать) — «последовательная память» не при делах.

А в чем проблема подложить линейный аллокатор под глобальный оператор new?

В нелинейном освобождении.

У линейного аллокатора delete() пустой, но есть reset().

Я знаю софт, который работает на последовательной памяти. Типа выделил кусок в 30GB и пишешь в неё, пока не придёт всем глобальный flush.

Вроде это оно и есть. Только операторы подменить надо было.

Это уже другой метод — может зваться, например, «временные арены». Главное — именно деструкция всей арены вместо каждого объекта по отдельности.
Против долгоживущих объектов — не поможет.

Это уже другой метод — может зваться, например, «временные арены». Главное — именно деструкция всей арены вместо каждого объекта по отдельности.

blog.molecular-matters.com/...​egies-a-linear-allocator

Против долгоживущих объектов — не поможет.

Долгоживущие выделяются до запуска системы при помощи обычного аллокатора.

Долгоживущие выделяются до запуска системы при помощи обычного аллокатора.

Мне бы такие простые системы, где так можно выделить заранее...

Линейный аллокатор подходит для циклических систем, вроде бекенда.

«для циклических систем, вроде бекенда.»

Эту фразу однозначно в цитатник. :crash:

Но уже возникновение Rust и Go говорит о многом

Говорит о том, что каждая контора хочет ограничить кодеров в своей тюрячке?

Говорим Rust — подразумеваем Mozilla
Говорим Go — подразумеваем Google
Говорим C# - подразумеваем Microsoft
Говорим Swift — подразумеваем Apple
Говорим Java — подразумеваем Oracle/Android

Чего ради? Такая же тюрьма как и все остальные.

У таких мов як раст проблема що він занадто молодий. Багато змін вносять в нього.

Ты забавный. Половина мира ещё использует С89, которому 28 лет, другая половина на острие хайпа использует супер-новинку С99, которому 18 лет. C11 был всеми дружно послан в жопу, т.к. gcc уже лет 20 имеет некоторые его фичи, остальные фичи С11 писал какие-то отбитые на всю голову чуваки-теоретики со своими нативными поддержками мнимых чисел и прочим трешем, которым никто не пользуется кроме лабораторных работ. Плюс отсутствие обратной совместимости. Вот тебе и приход замены -> сразу в утиль.

По сути сейчас у gcc есть три опции -std=c89, -std=c99, -std=gnu99. Всё остальное будет мёртворожденным по дефолту. Всё, что полезное будет, как расширение gnu99.

По сути сейчас у gcc есть три опции -std=c89, -std=c99, -std=gnu99

И как долго эта картина будет продолжаться, ещё 28 лет?

А почему бы и нет. Если всех всё утстраивает. Мне в gnu99 нравится очень многое, что я бы сделал стандартом в С, толко надо ссаными тряпками разогнать ту кодлу что там сейчас сидит и выстрадала С11. Мне реально в жизни не хватало трёх вещей:

1) case по зонам:

case 1 ... 20:
break;
case 21 ... 30:
break;

2) инициализация массива по енумам

static const GLuint gl_prim_to_hw_prim[GL_TRIANGLE_STRIP_ADJACENCY+1] = {
[GL_POINTS] =_3DPRIM_POINTLIST,
[GL_LINES] = _3DPRIM_LINELIST,
[GL_LINE_LOOP] = _3DPRIM_LINELOOP,
[GL_LINE_STRIP] = _3DPRIM_LINESTRIP,
[GL_TRIANGLES] = _3DPRIM_TRILIST,
[GL_TRIANGLE_STRIP] = _3DPRIM_TRISTRIP,
[GL_TRIANGLE_FAN] = _3DPRIM_TRIFAN,
[GL_QUADS] = _3DPRIM_QUADLIST,
[GL_QUAD_STRIP] = _3DPRIM_QUADSTRIP,
[GL_POLYGON] = _3DPRIM_POLYGON,
[GL_LINES_ADJACENCY] = _3DPRIM_LINELIST_ADJ,
[GL_LINE_STRIP_ADJACENCY] = _3DPRIM_LINESTRIP_ADJ,
[GL_TRIANGLES_ADJACENCY] = _3DPRIM_TRILIST_ADJ,
[GL_TRIANGLE_STRIP_ADJACENCY] = _3DPRIM_TRISTRIP_ADJ,
};

3) анонимные структуры и юнионы.

Теперь у меня это всё есть в gnu99, а всё остальное пусть догорает.

1) case по зонам:
case 1 ... 20:
break;
case 21 ... 30:
break;

Лучше тогда case по булевому выражению:
switch {
case i < 0:
...
case i > 0;
...
}
как реализовано в Go. Ещё там через запятую можно перечислять условия в одном case.

Оригинальная конструкция switch — прямое отображение процессорных комманд. Что там может быть кривого?

Область видимости чего? Кода? Данных?

Так что тебя не устраивает, покажи пример?

en.wikipedia.org/wiki/Duff’s_device ? Кстати хороший тест на собеседовании — объяснить что делает этот код.

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

Оно не было никогда «прямым отображением», это такой себе оптимизированный if с возможностью лукапа по таблицам. Для компиляторов тех времён это надо было писать отдельным конструктом.

Оно не было никогда «прямым отображением»,

Jump по таблице из регистра старее проституции.

Вот я и говорю — возможность лукапа по таблицам. Там, где она реализована в самом наборе рассматриваемых констант.
Если у тебя в switch() будут значения 1, 2, 99 и 286, то таблицы как-то не получится.
А с другой стороны — если записать пачку ifʼов, то компилятор тоже может свернуть их до таблицы.
Разницы между if и switch для современных средств — никакой, кроме синтаксиса. Отсюда и идут варианты как в Go, где switch это сжатая цепочка if-elif.

Когда свитч в С появился — это была эра 8 битовых компов и различных микроконтроллеров. Свитч был незаменим при обработке последовательных протоколов а ля BSC для имплементации машины состояний, где 0-31 ASCII таблицы были управляющими символами. Просто открой программы того времени и посмотри для чего оно применялось.

А с другой стороны — если записать пачку ifʼов, то компилятор тоже может свернуть их до таблицы.

Компилятор времен создания? %) В те времена любой высоуровневый язык был прямым указанием для компилятора как делать код и если ты хочешь switch — значит ты хочешь табличный джамп. Только и всего.

Свитч был незаменим при обработке последовательных протоколов а ля BSC для имплементации машины состояний, где 0-31 ASCII таблицы были управляющими символами. Просто открой программы того времени и посмотри для чего оно применялось.

Спасибо, кэп.

Компилятор времен создания?

Нет, современный. Тут я уже говорил о современных.

Спасибо, кэп.

You’re most welcome %)

Путь развития С++ меня уже давно не волнует, мой ответ касался только С. Тем более после переезда тут неожиданно выяснилось, что, например, сейчас есть аж три вакансии среди десятков тысяч: C++/BigData, C++/CAD, C++/Security. И ни в одном из трёх направлений мне не интересно. Во всех остальных вакансиях рекрутеры ошибочно называют С С++’ом. Это болячка интернациональная. Знания последних стандартов требует только одна позиция в C++/BigData. Вторая позиция — это суппорт, третья — работа на правительство, со всеми вытекающими.

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

Народец не показатель. 99% кодерков и на басике с удовольствием программировали бы, если бы под это были проекты.

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

Вы так говорите, как будто если бы те кодерки вместо Шарпа или джавы на Си писали, багов было бы меньше и код читаемый. Лол

Вы так говорите, как будто если бы те кодерки вместо Шарпа или джавы на Си писали, багов было бы меньше и код читаемый

Те кодерки, вместо «шарпов» — шли бы не в «плюсы», а в торговцы на базарах и дворники.
К несчастью, порог входа в «джавы» с прочими «шарпами» оказался настолько низок — что отличные потенциальные дворники оказались говно-кодерками.

П.С. Хотя, почему «к несчастью»? Говно-кодерки ежедневно колбасят столько говнокода, что владельцам этого говнокода не остаётся ничего другого, как приглашать мэтров это всё разгребать. За хороший рейт. :)

Щось я подібне чую весь час як вчив і користувався С++. Але все ніяк він нікуди не дівається. Да, в певних областях під певний спектр задач з’явилась купа більш зручних мов програмування, але С++ забити до смерті буде дуже тяжко.

С наврядчи замінять,
а С++ немає майбутнього крім того щоб обновлювати коден рік стандарт , поки не набридне реанімувати труп

а С++ немає майбутнього крім того щоб обновлювати коден рік стандарт , поки не набридне реанімувати труп

Как знать. Быстродействие процессоров уже лет 10 не увеличивается, новых технологий на замену старому-доброму кремнию пока не видать, написание распаралелленных приложений слишком дорого — а сложность приложений растёт... :)

ага, запусти доу.уа на 10 річному ПК, нічого ж не помінялося

По быстродействию ядра, ничего за 10 лет особо не изменилось. Ядер стало больше — но это приложениям не поможет.

По быстродействию ядра, ничего за 10 лет особо не изменилось

Вот именно что и по одному ядру есть рост (рядом писал подробнее)

Ядер стало больше — но это приложениям не поможет.

Поможет, хоть и не сразу. Медленнее, чем хотелось, но процесс идёт.

так ти загрузув доу.уа на старий комп?

так ти загрузув доу.уа на старий комп?

Ты всерьёз полагаешь, что до 2005 интернет-сайты не открывались? :)

ти дістав свій покритий пилью ПК 10 річної давності і написав пост оттуда?

dou как раз достаточно лёгкий сайт. Тяжёлый — например, FB. А вообще, реплика явно не по адресу.

чувак заявляє, що нікуя не міняється в світі процесорів.
Я ж бачу, що в ембедед уже коробки з процами овер 1ГГЦ та пам"ять овер 4ГБ.
А ти що хоч сказати, що кроме С та С++ в ембед нікуя нема і не буде??

А ти що хоч сказати, що кроме С та С++ в ембед нікуя нема і не буде??

Я хочу сказать, что при останавливающемся росте количественных показателей (для не-читателей: ещё не остановившемся, но останавливающемся) — ценность средств, которые максимально эффективно используют «железо», уже растёт. Когда/если он совсем остановится — будет расти насколько, что все прочие средства будут вымываться к лешим.
А будет то, у чего подскочит популярность, C++, Rust или кем-то ещё — вопрос ортогональный этому.
Но для начала «под нож» пойдут те средства, в которых неустранимо динамическая типизация. Как минимум после этого Python, Ruby, Perl, NodeJS и много других — в лучшем случае станут управляющим клеем над реально исполняющим кодом. Чуть-чуть позже аналогично с Javascript в браузере — и не как Webassembly нынче, а посерьёзнее по нему проедутся.

Он микроскопический в сравнении с ростом более 10 лет назад.

Не микроскопический. Но заметно меньше — факт. Если раньше рост был для всего при любом качестве и стиле написания, то сейчас для него надо уметь писать. Причём если (для Intel) для периода до Haswell надо было уметь просто не транжирить ресурсы, то после — надо уметь думать о внутренней параллельности через векторизацию.

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

Шаманством оно было 10-20 лет назад. Сейчас это просто ремесло. Ну а что большинство людей морально остались в старом, и половина фреймворков не поддерживает — будет постепенно лечиться.

На одном ядре или 8?

На одном.

На блокировках он большую часть времени висеть будет.

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

1. Думаю, даже градиентный спуск можно распараллелить :), точнее, не сам спуск, а задачу над ним. Это ведь обычно поиск экстремума на множестве аргументов? Если разрезать область поиска, можно несколько поисков запустить впараллель. И блокировок тут будет самый минимум.

2. Задачу ты нашёл показательную, не спорю. Но какая её доля от всех решаемых на всём множестве хотя бы рабочих станций? Я боюсь ошибиться в количестве нулей после точки...

Остальные примеры где-то в том же духе. Я всё-таки ожидал, что ты попробуешь предсказать именно типичные «по больнице» задачи...

В мой больнице такие.

Да, но твоя ужасно редка.

А в остальном мире таки возможностей больше. Например, тормозящий браузер можно разделить на несколько профилей :)
да и собственно однонитевость JS всяких Firefox это legacy от общего состояния, зафиксированное в API. Скоро всем надоест и его запретят к лешим.

Современная архитектура универсальных CPU сильно неудобна для многопоточности.

Не «современная», а её удобной вообще не бывает.
GPU не счёт, оно не MIMD, а SIMD.
И надо выкручиваться в этих условиях. Прогресс есть — вон промисы с awaitʼами изобретают.

И вопрос даже не в SIMD или MIMD, а в собственно железной организации взаимодействия ядер.

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

Отсюда и такая сложность многопоточного программирования.

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

То что в массовом железе не реализовали, это не говорит о том, нет идей и ничего не придумали.

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

Я нет, надо почитать, что это такое и в чем отличие от мютексов.

Там всё банально.
Делай раз: объект, которому передаёшь функцию действия и функцию реакции. Действие себе где-то исполняется в фоне. Реакция получает ответ этого действия или признак, что то сломалось (по исключению).
Обычно обе оформляются замыканиями.
Делай два: есть стандартный метод навесить в качестве реакции порождение нового промиса (со своим действием).
В результате построение становится 1:1 рисованием блок-схем с возможностью дорисовки или заранее, или на ходу. С последовательными действиями, развилками и т.п., но всё это исполняется где-то асинхронно, а тебе дёргают коллбэки, в которых ты можешь эту блок-схему достраивать.
Обычно где-то снаружи есть то, что вызывает вход в эту конструкцию, и получает из неё выходной результат. Например, веб-сервер: по получению запроса он порождает промис с обработкой запроса, а когда приходит ответ — отдаёт его.

и в чем отличие от мютексов.

Отличие в режиме работы с данными.
С тредами и мьютексами ты, например, лочишь данные, работаешь с ними, снимаешь лок.
Тут тебе залочить ничего нельзя, ты работаешь порциями. Если несколько таких одновременно в несколько порций — рисуешь изоляцию транзакций.
Зато можно в любой момент посмотреть, сколько таких и что они изолируют.

Но с разделением данных всё та же лажа осталась.

И её тоже принципиально устранить невозможно.

pipes and filters как раз для этого. Конечно не любой код туда ложится, но много высоконагруженного как раз таки да.

docs.microsoft.com/...​atterns/pipes-and-filters
www.dossier-andreas.net/...​ture/pipe_and_filter.html
Вообще — базовый метод распараллеливания не влазя в алгоритм. Используется в обработке видео, компьютерном зрении и других многошаговых задачах.

кажется, нигде. самому можно написать. gstreamer типа юзает.
впрочем, значек «|» в шелле — тоже оно.

Шаманством оно было 10-20 лет назад. Сейчас это просто ремесло.

Это всё ещё такое ремесло, что умеющие люди предпочитают в приложениях ремеслом не пользоваться, а неумеющие пользуются и натыкаются на кучи вил.

ага, запусти доу.уа на 10 річному ПК, нічого ж не помінялося

Запустил на тачке 2004 года, и?

Быстродействие процессоров уже лет 10 не увеличивается

Увеличивается. Если для Nehalem считалось нормальным, что оптимальный код работает на 1 IPC (instructions per cycle), то для Haswell уже нормально целиться на 3-5 в плотных циклах. И это ещё до векторизации. Всё ранее SandyBridge уже вредный отстой, Haswell где-то средненормально. Для AMD картина похоже, но чуть задержалась. ARMʼы тоже неплохо развиваются. IBM показывает огромные успехи с z14. А ещё DDR4, которая ускоряет типичные нагрузки.

Хотя по сравнению с темпами до 2005 и для слабооптимизированного софта это всё, конечно, мышкины слёзы.

Нужна возможность стандартизованного, кроссплатформенного применения этих самых правил, которые по умолчанию ведут к UdB.
Например, переполнение в операциях чисел со знаком. C# дал хороший пример, но слишком общий. Представим себе пометку куска кода знаком вида [[arith(signed, truncating)]]. Нужно кому-то в этом куске работать, как в Java или Go — остаются только младшие биты результата? OK, распишитесь. Умолчание — оставить прежним.
Главное — практически всё для этого уже есть. GCC уже давно научился, например, такой штуке, как #pragma GCC optimize(ключ). А на общем уровне есть -fwrapv или -ftrapv. Вот просто совместить их. Ну и протащить через стандарт (самое сложное, займёт лет 5 и кучу седых волос).

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

Пока же это всё на 90% тупая историческая нелепость (я как-то вытащил из khim@habr признание, что UdB для операций знаковых целых вызван именно тем, что нет гарантии, что отрицательные будут в дополнительном коде — а ведь железа с другой реализацией сейчас тупо не встретить от слова «совсем»).

Кому это все на практике надо? Либо берется размер с запасом, чтобы не влететь в оверфлов, либо ты четко представляешь, что делаешь, и что должно получиться.

Либо берется размер с запасом

Ага-ага. Вот брали такой размер с запасом, умножая на некоторую константу. Теперь вдруг в соседнем отделе эту константу увеличили, например, в 10 раз, и весь запас съелся. ИЧСХ — человек не успевает заметить такое изменение обстановки, а компилятор — замечает, но оборачивает в свою сторону — решает, что x никогда не будет больше 100, и поэтому цикл по x до 200 превращает в вечный (x<=200 заменяет на true).

либо ты четко представляешь, что делаешь, и что должно получиться.

«Ты» (программист без компьютера в голове) не может охватить сознанием весь код, если он больше нескольких страниц. Отдельные гении только подтверждают существование статистически подавляющего большинства.

Ну чувак, если у вас код шарится между отделами — пишите юнит-тесты. Так делают все, например, Гугл.

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

Не хочешь юнит-тесты — обложи ассертами. Правда, тогда оно может упасть в продакшне. Но хоть глючить не будет.

Даже параноидальное тестирование не гарантирует тебе отсутсвие багов.

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

Да, а твое распознавание речи может не распознать речь из-за сбоя в оперативке, вызванного космическим излучением.
Все глючит, часто или редко или когда железо разваливается. Ассерты смещают в сторону «редко».

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

То, что тесты не проверяют все случаи — по сравнению с этим уже второстепенное.

Собирай все в либу, и подключай одну и ту же версию к основному модулю либо к юнит-тестам.

И потеряю в производительности. А если речь идёт о решении на шаблонах C++ — то или вообще о библиотеке речи нет (всё инлайнится), или потеря производительности даже не в разы, а на порядки.

Ну чувак, если у вас код шарится между отделами — пишите юнит-тесты.

Одно из первых, что делают люди, которые разъясняют особенности UdB — что никакими юнит-тестами это не ловится.

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

Мышки, станьте ёжиками ©

Боишься — опять-таки обложи юнит-тестом.

Не проверяется случай UdB тестом. И не надейтесь.

Не хочешь юнит-тесты — обложи ассертами. Правда, тогда оно может упасть в продакшне. Но хоть глючить не будет.

На это стандартный ответ обычно бывает — с такой скоростью результата, как получается при assertʼах на любой чих, лучше писать на Go. Там хоть до порчи памяти не доводит.

Одно из первых, что делают люди, которые разъясняют особенности UdB — что никакими юнит-тестами это не ловится.

Запись за пределы массива ловится включением отладки в менеджере памяти (если массив динамический) или добавлением байта за пределами массива и проверкой, что он не перетерся. Cловили юнит-тестами или ассертами UB)

Видимо, кое-кто ни разу не сталкивался со случаем, когда сбой в индексации не на 1-2, а на несколько тысяч :)

А под Valgrind запустить?

А вот это зря. Наиболее типичные ошибки им таки ловятся отлично.

Нет, если течет — оно как раз для того. Там еще Helgrind есть и cppcheck

Ну, 90% случаев таки отловит. Но это никак не гарантия.

А мне даже понравилось. На редкость правильная оптимизация, просто недоработана идеологически. Идеология для языка — не мелочь. Это его основная задача, поскольку языки пишутся не для машины, а для человека.

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

И что ещё более мерзостно, так это то что код перестаёт отражать то, что происходит на самом деле. Более невозможно дебажить код на C++, его приходится дебажить уже на куда более низком уровне, читай очень дорого.

Правильная оптимизация — это ЕСЛИ результатом её будет код на C++. То есть можно свободно читать чего он там наоптимайзил, и даже копипастить это в исходник. Для Java баги подобного толка заманухи тоже имеются (хотя разумеется куда реже), но там ты можешь честно декомпилировать байткод. А здесь как — ассемблер читать предлагают?

// Хотя декомпилить Stream API с лямбдами — то ещё удовольствие, узреть что же на самом деле оно творит. Потому и стараюсь сразу писать нормальный код, чтобы читатель сразу видел что происходит.

Правильная оптимизация — это ЕСЛИ результатом её будет код на C++. То есть можно свободно читать чего он там наоптимайзил, и даже копипастить это в исходник.

en.wikipedia.org/...​ki/Instruction_pipelining уже ни на какой С++ не ложится. А без него — никак.
Это не значит, что компилер в примере не кривой.
Это не значит, что такие примеры встречаются в жизни.

Это значит что тупо поставили в продакшен откровенно сырую версию, назвав баги фичами, да ещё и поставив по дефолту в пакет оптимизации.

Мне нравится сама идея. Но уж никак не реализация.

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

Вообще, pipelining — это самая основа. Для гипертрединга он не годится, там нужна полноценная суперскалярность, причём с Tomasulo в основе.

Без этого вообще ничего не наоптимизируешь. Причина тормозов на if`ах — как раз сброс пайплайна.
www.agner.org/optimize/optimizing_cpp.pdf

т.е. го, руст и иже с ними автоматом рашат вопросы с оптимизацией? можно спросить — как?

если принять во внимание, что магии не существует, я не виже путей как добиться и того и того одновременно

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

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

Ось вам ще трохи С-кріпоти від мого колишнього одногрупника.
habrahabr.ru/post/136283

Ну, на захист плюсів можна сказати, що приклади там на чистом С, а плюси, особливо нові стандарти де є std::function(), дозволяють піти від вказівників на функції і скоротити кількість UB.

Підписатись на коментарі