Как стать автором
Обновить

Комментарии 296

Современный процессор Intel выполняет до 180 инструкций одновременно (в отличие от
последовательной абстрактной машины C

А точно эта и куча перечисленных сложностей из-за С?
Мне всегда казалось, что из-за обратной совместимости "ассемблера" (если мы уж говорим о языках программирования). Иными словами уже написанные и скомпилированные программы
нужно с каждой ревизией процессора выполнять все быстрее и быстрее. А никак не для того,
чтобы облегчить разработчикам С компилятора жизнь.

Отчасти да, отчасти нет. Библиотеки на ARM'е (на сотовых) — обычно перекомпилированные версии x86 библиотек… но они всё равно написаны на C.

Числодробилки часто оптимизируют под VFP и NEON, так что там тоже уже платформо-зависимый код на асме или интринсиках

Так всегда было. И всегда этого «платформно-зависимого кода» было несколько процентов.
С таким же успехом и ассемблер можно назвать высокоуровневым — все перечисленные в статье аргументы относятся и к нему (параллельное исполнение, кеши, выравнивание и т.п.). Но ближе к железу ничего нет. Поэтому все эти доводы весьма сомнительны.

Ассемблер в x86 — это тоже высокоуровневый язык. Автор оригинальной статьи нацеливается на C (один из двух-трех языков, которые дожили от 70х до наших времен, и наверняка самый из них популярный), но большинство аргументов применимы ко всем языкам подобной идеологии, включая даже ассемблер и машинный код. В современном x86 процессоре есть встроенный оптимизирующий компилятор, который преобразует "высокоуровневый" ассемблер во внутренний низкоуровневый — включая SSA (переименование регистров) и параллелизацию. Посыл статьи (если отбросить C-специфичные вещи про выравнивание, неопределенные биты и тд) в том и был, что C (и все другие похожие языки) был простым языком, который напрямую ложился на реальное железо, а теперь инженеры пытаются всячески вбить новое железо (которое имеет принципиально другие допущения!) в видение мира старого языка (а разработчики компиляторов в то же время пытаются как-то приспособиться к новому железу, которое пытается выглядеть старым). Строго та же фигня происходит и с, к примеру, архитектурой ОС — до сих пор программы притворяются, что весь компьютер принадлежит им, а ОС всячески им в этом потворствует ценой приличных накладных расходов. Обратная совместимость — главное зло мира...

Да, но в данном контексте низкоуровневых языков (позволяющих напрямую манипулировать кешем, к примеру) не существует в принципе, и тогда делить на высоко/низкоуровневое вообще нечего. В реальности же, если расположить существующие языки лесенкой, то С стоит на втором месте, после ассемблера, никаких фортранов и эрлангов там близко нет (автор их захотел втащить на пьедестал, как мне показалось). Кстати, некоторые аргументы вообще ложные, например про выравнивание.

Правильно, нечего их делить, но делят же. Тут вот ниже про микроконтроллеры упомянули правильно — они примерно соответствуют тем машинам, для которых разрабатывался C. В прицеле на них C будет низкоуровневым, а Java — не очень. В прицеле x86/ARM/POWER — какой-то большой разницы между С и Java уже нет.
Про фортран -он такой же, как и C, просто автор еще и выискивает в C любые вещи, которые мешают компилятору делать переход 1-1 из исходного кода в машинный (как то выравнивание и алиасинг). Эрланг же был упомянут как пример принципиально другой идеологии (многопоточного) программирования, которая может казаться странной и сложной для приверженца С-подобных языков и POSIX-подобной share-everything многозадачности, но современное железо может оказаться больше заточенным под модель эрланга, чем под модель C.
Большая часть этой статьи — это вопль в пустоту о том, что если делать все с нуля — включая парадигму программирования, которая учитывает текущие реалии в железе — то можно сделать процессор значительно проще, дешевле, холоднее и быстрее тех, что есть сейчас. Но такой процессор не сделают, потому что программы на C на нем будут работать медленно.

Просто нет языков под специализацию процессора. Вот была статья про Мультиклет — на который С-парадигма нормально не ложится. Но не ложится и ничего более из языков.

Еще и мир устроен лентяями — стоит попробовать спросить J-скриптера, во сколько тактов развернется его лямбда?

Как раз об этом думал по время прочтения статьи. Какой-нибудь .map() по массиву в JS теоретически может быть быстрее обычного цикла с вызовом функции, так как, все эти лямбды могут выполняться параллельно над всеми элементами массива сразу.

Да конечно, теоретически смогут. Если на этом (любом) языке смогут быть явно вычислены/выражены условия параллелизма конкретного процессора.

Иначе язык отдает это на откуп компилятору, который ...., дом который построил Джек
НЛО прилетело и опубликовало эту надпись здесь

Только не в JS. В этом языке функция map явно однопоточна: стандарт предписывает вызывать переданную функцию строго последовательно...

Если что то тормозит, запускай профайлер.
Кстати сегодня он мне сэкономил тучу времени. Получать культуру в цикле было плохой идеей.
var ci = (CultureInfo)CultureInfo.CurrentCulture.Clone();
НЛО прилетело и опубликовало эту надпись здесь
Статью прочитал, а комментарии почти нет. Вопрос к вам и к знающим людям: принимая во внимание все вышесказанное, можно ли сделать какой-нибудь функциональный язык, который будет решать задачи принципиально быстрее, чем С, и будет в каком-то смысле низкоуровневым? Имеются в виду как бенчмарковские задачи вроде сортировок, так и реальные задачи вроде HTTP-сервера.

Потому что если я правильно понимаю, сейчас ФП сильно проигрывает в производительности, а с учетом оптимизаций процессора для С – даже в задачах, которые легко распараллелить.
НЛО прилетело и опубликовало эту надпись здесь
Существуют расширения для С (CUDA, например) которые умеют работать с различными уровнями памяти на GPU — shared, local, global. Что в принципе схоже с организацией кешей в x86 и x64.
На самом деле задач, где имеет смысл использование shared памяти на gpu не так уж много. Nvidia в свое время сделало кэширование global памяти, такое же как в обычных процессорах, но потом в один прекрасный момент молча это выпилили. В результате производительность в конкретной задаче конкретно просела. В замен они добавили специальную инструкцию, для чтения глобальной памяти, которая гарантировала попадание данных в кэш. Но только это работало для константных данных и следить за тем, чтобы они не менялись должен был сам разработчик.
задач, где имеет смысл использование shared памяти на gpu не так уж много
Расскажите, пожалуйста, по-подробнее с этого места. Какого типа операции или задачи над данными на GPU не имеет смысла выполнять с использованием shared memory? И какие имеет смысл?
По моему опыту, не имеет смысл использовать shared memory в большинстве задач, потому что просто нечего там хранить: прочитал элемент из global или текстуры, обработал, сохранил туда же или в другой буфер.
Классическим примером, где нужно использование shared, является перемножение матриц, там это дает большое ускорение, но это весьма специфический пример.
В своих задачах я использую shared memory в основном при всякой редукции, тут она нужна чисто для обмена данными между потоками в блоке. Но для этого в той же CUDA есть и другие механизмы, можно обойтись и без нее.
Еще shared memory нужна, когда каждому потоку нужен какой то локальный буфер для своих вычислений. Но здесь размер буфера получается весьма ограничен, да и честно сказать это не очень эффективно. Если в задаче требуется буфер, то обычно ее достаточно сложно реализовать на gpu.
Больше что-то ничего не приходит в голову, для чего может shared memory понадобиться. Скорее в остальном это специфические задачи.
А вот у меня обратный случай — большинство задач связано с некоторым состоянием, которое претерпевает множество изменнеий в процессе работы приложения. При этом это состояние выгодно разбить на кусочки и хранить в shared memory. При этом если размер состояния не помещается в shared — то практически всегда есть возможность реализации ручного кеширования части этого состояния.
>> Скорее в остальном это специфические задачи.
Да.
Обратную совместимость я бы лучше заменил банальным словом «лень»…
Обратная совместимость — продукт нежелания миллионов программистов переписывать миллиарды строк кода(знаменитое Java-вское «написано однажды — работает всегда»). Каждому из нас не хочется этого делать… Но проблема тут не в программисте, а в языке, на котором написан код.
Язык С, как и все его компилируемые наследники(C++, Java, ..., но не Python, хотя там свои проблемы) в нынешние дни имеют ряд коренных недостатков, которые были не видны людям из далеких 70-x. Например, в С/C++ нельзя просто взять и написать ассемблерные вставки или функции. Это просто отсутствует в стандарте. И что прикажете с этим делать? А ведь даже такая малая стандартизируемая возможность в корне может ускорить выполнение кода…
Обратную совместимость я бы лучше заменил банальным словом «лень»
А когда этим заниматься миллионам программистов, если им надо работать по 8+ часов в день, что бы семью кормить?
Но проблема тут не в программисте, а в языке
Нет, проблема в суровой реальности — людишкам, которые не бум-бум в компуктерах, нужны новые интернет-магазинчики, а не стоп-кадр на 10 лет, что бы переделать все по-нормальному.
Например, в С/C++ нельзя просто взять и написать ассемблерные вставки или функции.
Можно практически во всех популярных реализациях.
А ведь даже такая малая стандартизируемая возможность в корне может ускорить выполнение кода…
В 0.001% случаев, потому что компиляторы уже давно умнее нас.
НЛО прилетело и опубликовало эту надпись здесь
Да, но в Стандарте понятие ассемблерных вставок, ассемблера вообще и прочих FFI не описано.

Ну как же. Раздел 9.9 как раз и описывает понятие ассемблерной вставки:image
НЛО прилетело и опубликовало эту надпись здесь

Вы путаете значение слова "понятие" с чем-то другим.

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

Ну, может и без стоп-кадра возможно… Например, начать с серверов… Сделать ЯП мимо C, под современный проц, и переписать на нём BEAM (виртуальную машину Erlang). Кто бы это только профинансировал?..

Для начала надо будет переписать на новом ЯП операционную систему — ибо она точно написана на С. Иначе все это полумеры и косметика над трупом. :)
Например, в С/C++ нельзя просто взять и написать ассемблерные вставки или функции. Это просто отсутствует в стандарте.
Стандарт описывает и разрешает ассемблерные вставки. Единственное что он не описывает, так это синтаксис внутри самой вставки.
НЛО прилетело и опубликовало эту надпись здесь
Да. Я в курсе. Но речь шла не об этом. Речь была о том, что:
… Это просто отсутствует в стандарте. ...

Как сказали выше, каждый современный компилятор подерживает вставки по-своему. Документации об этом навалом в свободном доступе.
Обратная совместимость — продукт нежелания миллионов программистов переписывать миллиарды строк кода(знаменитое Java-вское «написано однажды — работает всегда»).

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

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

А как надо?

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

Я имел в виду и саму вытесняющую многозадачность с внезапным прерыванием выполнения и сбросом контекста выполнения куда-то в память, и раздельные адресные пространства для приложений, которые влекут за собой еще большие накладные расходы на переключение, и переходы в ядро и обратно, и прочие "особенности" привычных нам ОС.
Как надо? Не знаю, я сам заядлый C++ программист ) Но подозреваю, что куда-то в сторону managed языков и кооперативного шедулинга мелких задач/вытесняющей многозадачности, но не в любой точке кода, а в определенных точках останова (VM может гарантировать, что одна программа не перетрет другую и не захапает себе все время)

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

Встречное предложение — оцените ресурсы, потраченные на борьбу с имеющимися ОС. Сколько человеко-лет ушло на написание TBB и всех ему подобных диспетчеров (включая что-то похожее в каждом первом игровом движке), которые суть подменяют встроенный в ОС диспетчер потоков на свой диспетчер задач — потому что встроенный механизм переключения потоков слишком накладный? Сколько ушло на разработку lockless структур — потому что локи/спинлоки использовать нельзя, потому что а вдруг вытеснят в критической секции?

Когда вы пишете «слишком накладный» и не пишете, что именно это значит, то это совершенно бессмысленное выражение. То ли это 0,1%, то ли 99%. Кому-то суп жидок, кому-то жемчуг мелок.
Неважно сколько человеко-лет ушло на написание того, что вы перечислили. Важно, что априори оно много меньше того, сколько человеко-лет понадобится на реализацию ваших предложений.
Когда действительно большому количеству пользователей (в данном случае программистов) что-то становится действительно необходимым, то появляется библиотека, фреймворк и т.п. Этот ваш intra-app «диспетчер задач» по сути реализация user-level threads, давно известной концепции. Раз нет одной или нескольких популярных реализаций такой концепции, следовательно это проблемы явного меньшинства, которое и вынуждено строить свои велосипеды. А менять ВСЁ ради решения проблем меньшинства как-то нерационально.

Реальный пример — наивная реализация flow based programming, где каждая нода — это поток, обмен друг с другом сообщениями через очереди. Делаем несколько тысяч нод-потоков, запускаем на большой машине с кучей ядер, набрасываем миллионы сообщений в секунду и получаем >50% накладных расходов чисто на переключение потоков.
Про явное меньшинство — TBB, Cilk, часть OpenMP, Microsoft's PPL/Concurrency, встроенный в OS X GCD, встроенный в Go goroutines, встроенные в Erlang процессы, TAP в .NET, десятки самописных для разных приложений и библиотек. Как только мы переходим от use case "несколько долгоживущих независимых потоков, которые долго-долго долбят числа или висят в I/O" — т.е. однопоточной программы x N, к параллельному решению относительно маленьких задач, как абстракция "бесконечного количества потоков" становится нежизнеспособной. Все эти "библиотеки и фреймворки" позволяют обойти накладные расходы от ОС путем отказа от этой абстракции; вместо этого создается один или несколько потоков (но не больше количества процессоров!), иногда они еще прибиваются жестко к процессорным ядрам, и все задачи кооперативно выполняются в них.
Интересно, что любой чисто асинхронный код естественным образом порождает задачи, а не потоки; если не ошибаюсь, то, например, все UWP программы такие.

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

Да-да, все так и есть. Никто не будет делать новые ОС по другим принципам, раз уже есть старые и привычные. Никто не будет делать новые процессоры по другим принципам, раз уже есть старые и привычные. Пусть неоптимальные, но мы к ним привыкли, кода понаписали… Я уже говорил про обратную совместимость, и вы подтверждаете мою точку зрения ))
На самом деле иногда пытаются — Itanium должен был давать лучший доступ до "настоящего" железа, чем текущие лидеры, Project Singularity пытался избавиться от накладных расходов на изоляцию ядра ОС — увы, они не выжили.

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

У меня — пара дней, причём я ещё получил до кучи выигрыш в виде сокращения накладных расходов по сравнению с TBB: универсальные средства не являются оптимальными для каких-то определённых задач.

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

Языковые VM тут ничего принципиально не меняют: с точки зрения пользовательского кода, работающего в песочнице, многозадачность всегда будет вытесняющей. Независимо от того кто это самое вытеснение будет обеспечивать — ОС или среда исполнения.
Раздельные адресные пространства необязательны (.NET может запускать недоверенный код в общем адресном пространстве).

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

Также, ради оптимизации, jit-компилятор может разбивать цикл по 1e6 итераций на 1e2 циклов по 1e4 итераций и между ними ставить yield, что минимально затронет производительность.
1. В каком «общем» адресном пространстве??? Адресное пространство процессов всегда изолировано.
И речь не о том, что общее адресное пространство невозможно, а о том, что оно небезопасно. Поэтому если есть недоверенный код (а он есть всегда), то изоляция адресных пространств обязательна.
2. В чем смысл передачи функций ОС компилятору? И когда появится универсальный компилятор для любого языка?
Разные AppDomains могут находиться в одном процессе и тем не менее, быть изолированными.

В чем смысл передачи функций ОС компилятору?
А в чем смысл идеи VLIW, отдать шедулинг команд компилятору, когда и процессор неплохо справляется. Моё замечание о том, что такая реализация в принципе возможна. Может, с какими-то дополнительными факторами она станет выгодной.
1. Ключевой момент в том, что в одном процессе. Разные процессы обязательно должны быть изолированными.
2. Среди процессоров общего назначения VLIW-разновидностей как-то не очень много. Все-таки это скорее для специализированных процессоров.
В то же время вопрос зачем отдавать scheduling компилятору не лишен смысла и для «обычных» процессоров. «Окно», в пределах которого способен манипулировать исполнением процессор, минимально. В то же время компилятор может делать это в масштабах всей программы, к тому же это снижает сложность, а соответственно, стоимость и энергопотребление процессора.
Но одно дело переносить функции железа на софт, и совсем другое функции софта на тот же софт. Причем ваш вариант однозначно ухудшает результат и вот почему.
В случае с переносом планирования инструкций из процессора в компилятор мы один раз оптимизируем и неограниченное количество раз исполняем уже оптимизированный двоичный код. Миллиарды процессоров экономят при этом буквально гигаватты мощности.
А в случае с переносом вытесняющей многозадачности из ОС, которая исполняет готовый машинный код, в VM, которая исполняет байт-код, мы, наоборот, получаем миллиарды лишних компиляций. И, поскольку такая jit-компиляция должна быть достаточно быстрой, то она, однозначно, будет хуже, чем традиционная aot-компиляция. Т.е. мы будем тратить лишние гигаватты и на саму компиляцию и на хуже оптимизированный код.
И вы не убедите меня, что современные jit-компиляторы очень быстрые и выдают отличный машинный код, пока я наблюдаю ужасные тормоза на многих сайтах, завязанных на js.
«Окно», в пределах которого способен манипулировать исполнением процессор, минимально. В то же время компилятор может делать это в масштабах всей программы, к тому же это снижает сложность, а соответственно, стоимость и энергопотребление процессора.

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


Дело в том, что огромный выигрыш в производительности достигается с помощью динамических оптимизаций предсказателя условных переходов в реальном времени:


https://stackoverflow.com/questions/11227809/why-is-it-faster-to-process-a-sorted-array-than-an-unsorted-array


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

Языки с динамической типизацией не могут быть быстрыми.
И, кстати, тормоза обычно вызваны не интерпретацией JS, а сложностью обработки DOM самим браузером, пересчёта расположения элементов и отрисовкой графики.

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

Мы его уже получаем.

Дело в том, что огромный выигрыш в производительности достигается с помощью динамических оптимизаций предсказателя условных переходов в реальном времени

Предсказание переходов — это не оптимизация кода.
Вообще-то предсказание переходов — то с чего начинается подавляющее большинство оптимизаций.

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

То же самое — встраивание функций. И далее везде.
Начнем с того, что вообще-то динамическое предсказание переходов — это функция процессора, и к компилятору как таковому не имеет ни малейшего отношения.
Другое дело, что компилятор может организовать код таким образом, чтобы уменьшить количество переходов вообще (как в случае с встраиванием функций) либо сделать переходы более предсказуемыми для аппаратных предсказателей. Но непосредственно «динамическим предсказанием переходов», о чем шла речь в комментарии, на который я отвечал, компилятор, повторюсь, не занимается и заниматься принципиально не может. Я за точность и однозначность терминологии.
Компилятор занимается статическим предсказанием переходов — например для того, чтобы в более вероятную ветвь условия выполнение «проваливаливалось» (fall through) без перехода.
Компилятор не занимается предсказанием переходов. Он занимается генерацией такого кода, который облегчает работу предсказателя переходов в процессоре.
Даже с точки зрения языка невозможно считать, что кто-то занимается «предсказанием», если этот кто-то «знает».
Я читал Intel® 64 and IA-32 Architectures Optimization Reference Manual, да.
kristerw.blogspot.com/2017/02/branch-prediction.html
The predictors are defined in predict.def (some of the definitions seem reversed due to how the rules are implemented, e.g. PROB_VERY_LIKELY may mean “very unlikely”, but the comments describing each heuristic are correct). You can see how GCC is estimating the branch probabilities by passing -fdump-tree-profile_estimate to the compiler, which writes a file containing the output from the predictors for each basic block

Predictions for bb 2
  DS theory heuristics: 1.7%
  combined heuristics: 1.7%
  pointer (on trees) heuristics of edge 2->4: 30.0%
  call heuristics of edge 2->3: 33.0%
  negative return heuristics of edge 2->4: 2.0%

as well as (when using GCC 7.x) the IR annotated with the estimated probabilities.

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

Меня слегка опередил комрад Druu.
Именно это я и имел в виду.
Странный спор получается. С одной стороны, вы за то, чтобы упростить процессор за счёт усложнения компиляции. С другой, вы аппелируете к существующему положению дел в современных процессорах, от которых хотите отказаться.
Нет, просто вы слегка упустили контекст.
Я лишь говорил, что перенос значительной степени оптимизации в компилятор из железа имеет смысл не только для VLIW (которые мой оппонент привел как пример), но и для более популярных архитектур, таких как x86/x64.
Но это было сказано не потому, что я горячий сторонник такого переноса самого по себе, а как пример именно оправданности в противовес неоправданности переноса функций ОС к ВМ.
Видимо из-за этого вы и начали со мной спорить, хотя, судя по другим комментариям, вполне разделяете мое мнение о явном преимуществе качества aot-компиляции перед jit-компиляцией, например.
Вполне разделяете мое мнение о явном преимуществе качества aot-компиляции перед jit-компиляцией, например.

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


И я вообще не рассматриваю архитектуру x86/x64 как удачную. За ней тянется ужасный шлейф обратной совместимости.

Тем не менее на сегодня она обеспечивает максимальную скорость однопоточного приложения. А если приложение портируется на GPU, то все CPU «отдыхают».

Потому Top500 и выглядит так, как он выглядит…
По моему этот спор давно разрешил Интел поставляя свой оптимизирующий компилятор. Размер бинарника у него раздувается, но скорость практически всегда выше.
Мы его уже получаем.

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


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


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


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


Предсказание переходов — это не оптимизация кода.

Ничего себе заявление. А profile-guided optimization — это что, по-вашему?

рограммы придётся распространять не в виде бинарников
Возможно, маркетинг просто перейдёт от загрузки/поставки носителя к on-demand compilation: пользователь скачивает аналог CPU-Z, отправляет отчёт, платит, скачивает программу, скомпилированную под данную конкретную конфигурацию систему.

Сменил систему — переклмпилируй всё заново.


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

Сменил систему — перекомпилируй всё заново.
И плати повторно, или оплачивай поддержку. Как вариант — дефальтная компиляция, универсальная, но неоптимальная.
ещё большие гигаватты электроэнергии будут тратиться
Когда это кого останавливало, если отбивается по деньгам?
Дык это… Все ваши предложения уже были реализованы три десятилетия назад.

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

Как насчёт «распространять в виде переносимого байт-кода, компилировать в машинный код при первом запуске»? Как-то так делается на Андроиде, емнип.

Потому что JIT-компиляция, работающая в реальном времени, никогда не будет насколько же эффективной, что и оффлайн-компиляция.

Она не в реальном времени. После первого запуска сохраняется машкод, для всех последующих запусков он берётся уже готовым.
То есть пользователь запускает программу, офигевает от тормозов, потому что компиляция не в реальном времени, удаляет. Так, что ли?
Как-то так, да. Ничем не отличается от тормозов при автоапдейте: прогресс-бар и «please wait while we're improving your experience» без указания, сколько времени осталось ждать. К автоапдейтам же вроде все привыкли?
Нет, я не привык.
В некоторых программах это происходит на этапе установки. За это отвечает модуль docs.microsoft.com/ru-ru/dotnet/framework/tools/ngen-exe-native-image-generator

Правда я не часто вижу его использование. Похоже всем плевать…
Как ни странно, в ряде случаев она может быть более эффективной, чем оффлайн-компиляция, поскольку у JIT-компилятора могут быть данные от рантайм-профайлера относительно статистики реального использования кода, что позволит «на лету» подправить машинный код в нужных местах. Но в общем — да, оффлайн качественнее из-за требований к скорости работы JIT.
И сколько сотых долей процента производительности потеряет программа скомпилированная с другим таймингами?
И сколько займет jit-компиляция какого-нибудь «Хрома» или «Офиса»?
Ничего себе заявление

Смотрите комментарий выше.
НЛО прилетело и опубликовало эту надпись здесь
Эта идея, прямо скажем, не нова.
НЛО прилетело и опубликовало эту надпись здесь
А разве P-код кто-то перекомпилировал? Его интерпретировали или JIT'или вроде.

Перекомпиляция — это TIMI, 1988й год…
Ключевой момент в том, что в одном процессе. Разные процессы обязательно должны быть изолированными.
Я потерял мысль, которую вы хотите донести. Мой ответ был возражением на
раздельные адресные пространства и прочее — это единственный вариант в мире где возможно исполнение недоверенного кода
В качестве контрпримера — vm .net, когда недоверенный код загружается в общее адресное пространство.
Я тоже не совсем понимаю с чем именно вы спорите. Начиная с терминологии. Кроме того вы повторяетесь.
В Windows нет никакого «общего адресного пространства», адресное пространство (АП) каждого процесса отделено от АП любого другого процесса.
То, что вы можете загрузить недоверенный код внутри АП вашего же процесса неудивительно и естественно. Но получить доступ к АП других процессов (без помощи ОС) вы не можете.
И это
раздельные адресные пространства и прочее — это единственный вариант в мире где возможно исполнение недоверенного кода
вы просто не поняли. Естественно, что можно сделать систему, в которой АП будет общим. С этим никто и не спорит. Только по соображениям безопасности этого делать никто просто не будет. Поэтому, если из контекста для вас неочевидно, то я чуть-чуть переформулирую «раздельные адресные пространства и прочее — это единственный разумный вариант в мире где возможно исполнение недоверенного кода»
это единственный разумный вариант
Не слишком ли категорично? Какие фундаментальные проблемы с безопасностью есть у Application Domains в .net?
Речь не о фундаментальных проблемах с безопасностью, а с тем, что Application Domains — это тот же механизм изоляции одной части кода от другой, просто использует не специально предназначенные для этого фичи процессоров, а собственную реализацию.
Я тоже не совсем понимаю с чем именно вы спорите
С тем, что общепринятые способы разделения доступа и ресурсов единственно верные и всегда будут эффективнее.
А в чем смысл идеи VLIW, отдать шедулинг команд компилятору, когда и процессор неплохо справляется.

Там смысл в том, что компилятор обрабатывает программу один раз (и может работать хоть час), а процессор — при каждом запуске (и должен делать это очень быстро).
При передаче функций ОС компилятору такой мотивации нет.
Разные AppDomains обладают отдельными кучами и сборщиками мусора, и между ними нельзя передать объект минуя сериализацию с десериализацией. Вполне себе отдельные адресные пространства с точки зрения управляемого кода.
Логически — да, но для процессора это одно адресное пространство.

И тут нет тех накладных расходов на переключения, которые есть у адресных пространств процессов.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
С каких пор язык си низкоуровневый? Тем более общепринято?
А какой — общепризнано?
Мне попалась на глаза статья Дэвида Чизнэлла, исследователя Кембриджского университета, в которой он оспаривает общепринятое суждение о том, что С — язык низкого уровня, и его аргументы мне показались достаточно интересными.

Ну, в те годы, когда я учил Си — его называли языком высокого уровня, изредка говоря "среднего" за некоторые низкоуровневые фишки типа адресной арифметики.

НЛО прилетело и опубликовало эту надпись здесь
1. Так я и говорю — не называли его языком низкого уровня.
2. Кроме PDP-11 было ещё много архитектур, на которые C ложился «достаточно прямо». В том же 8086 была сломана только адресация (сегмент-смещение, нигде больше не видел подобного ада), но она была сломана для всех языков. Зато были базовые регистры, в частности регистр BP, в который копировался SP — т.е. нормальная поддержка стек фреймов, что очень ценно для наивной реализации компилятора Си. И никакого предсказания переходов.
Или hitachi sh3 — на котором я впервые увидел, как компилятор Си выдаёт предельно оптимизированный код. С delayed branches вместо предсказания переходов — гениальное решение.

P.S. И ведь статья-то на итог вообще не об этом, и гораздо интереснее темы из заголовка…
There is no official definition, but historically assembler/machine code was considered low-level and any language more abstracted was high-level. But C is one of the high-level languages which is closest to the machine level, which is why it is sometimes designated «mid-level», while scripting languages like Python have sometimes been designated «very high level». But these are all informal categories and somewhat subjective.
++ www.quora.com/Why-is-C-considered-a-low-level-language
Вас еще кто-то минусует. Тоже удивлен существованию дискуссии о высокоуровневости Си.

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

По всей видимости, те, кто называет Си низкоуровневым подразумевают некоторый неформальный контекст.
Всё-таки ARM SVE — это *Scalable* Vector Extension, а не Scalar.
Чего только на волне хайпа с этими практически умозрительными багами не напишут. И С уже стал резко высокоуровневым, и замедление от патчей отбросило перформанс на годы назад, аяй…
Не можете понять как компилируется ваш С-шный код? Включите трансляцию в ассемблер и читайте. Придется правда в C ABI разобраться, но то такое.
Не нравится что компилятор слишком умный? Поставьте -О0 и вперед — оптимизируйте руками.

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

>>Эта информация в C гораздо более ограниченна, чем в таком языке, как Fortran, что является основной причиной того, что C не сумел вытеснить его из сферы высокопроизводительных вычислений.
Фига с два. Основной причиной является то, что на фортране дочерта и больше Legacy кода, который тянется с мохнатых 80-х или 70-х, в том что в тех же годах математики и физики очень привыкли фигачить все на фортране. Практически до 2000-х, когда видимо начинать новый проект на фортране стало уж совсем моветон. Реализация С-шных и С++ вещей на фортране часто вызывает улыбки. Например чтобы преобразовать 2-д в 1-д массив без вызова решейпа (затратной операции, которая реально перекладывает данные) — фиганем его через функцию, в которой массив определим «как придет» — потеряем дескриптор, но зато можем заюзать его как угодно. Или реализация полиморфных объектов через интерфейсы…
Короче не от хорошей жизни там извращаются.
Не понимаю, чего все привязались к названию статьи? Понятно, что это действительно не совсем корректное заявление.
Мне кажется, что главный посыл автора не в том, что все сишники на самом деле джависты (не в обиду последним), а в том, что возможности современного железа очень сильно упираются в сишное представление вычислительной системы. Поэтому автор предлагает взглянуть на мир под другим углом. Мне вот понравились мысли про кеш — хоть я еще не залезал в его когерентность, я слышал, что там все плохо, и возможно то, что предлагает автор, действительно улучшило бы ситуацию. Но, как уже писали выше, легаси есть легаси…
С другой стороны, про параллельные системы я не согласен. Видеокарты, которые так восхваляет автор, хороши в своих задачах, но если попробовать реализовать на них какую-то бизнес-логику, то, мне кажется, получится что-то страшное и непонятное, и оно будет медленнее, чем простой C на обычном процессоре.

P. S. Про Фортран — полностью с Вами согласен.
Не можете понять как компилируется ваш С-шный код? Включите трансляцию в ассемблер и читайте. Придется правда в C ABI разобраться, но то такое.

А если не можем понять, как (и сколько времени) выполняется наш ассемблерный код, потому что внутри процессора он транслируется в нечто недокументированное, тогда что читать? :-Р
Документацию на процессор. В конце мануала вы найдете таблицу со всеми коммандами, у которых указан Latency и Throughput.
Дык статья ровно о том, что последний процессор, для которого был такой мануал — это 80486. Появился в 1989м, не выпускается с 2007го.

А если вы про эту — так она не из мануала, а из экспериментов и, кроме того, в ней масса ньюансов не описана.
Всегда значит был, а тут вдруг не стало.
Я не припомню процессора для которого не было таблицы с латентностями-трупутами, а занимаюсь я оптимизацией ПО уже более 10 лет.
Вот вам по скайлейку несуществующая табличка от производителя:
software.intel.com/sites/default/files/managed/ad/dc/Intel-Xeon-Scalable-Processor-throughput-latency.pdf
В вашей табличке даже не написано на каких блоках какие команды можно исполнять впараллель. А это — очевидно необходимое (но не достаточное!) требование для ответа на вопрос «как (и сколько времени) выполняется наш ассемблерный код»!

Все эти описания, увы, это не чёткое, точное и жёсткое описание, как для 8086 или 80486, а, скорее, некоторые эвристики, позволяющие с некоторой точностью сравнивать куски ассемблерного кода. Для окончательной оценки — всё равно нужно запускать на железе и мерить. Ибо… «возможны варианты».
Я, видимо, несколько потерял нить беседы. Зачем вам знать «как (и сколько времени) выполняется наш ассемблерный код» с точностью до такта? Особенно с учетом того, что реальный код все равно взаимодействует с памятью, скорость которой заранее неизвестна, а, следовательно, с иерархией кэшей.

Еще бы эти значения гарантированы были, да. Вы же должны быть в курсе в каких условиях и как они считались. Что это был отдельный урезанный линукс и часть функций процессора была отключена. Эти значения не дают вообще ничего, на реальной системе они будут практически всегда выше, а отношения между ними сохраняться не будут. То есть то, что по этой таблице выполняется в три раза быстрее может легко оказаться медленнее на реальной задаче.
Впрочем я не нашел конкретно в этом документе замечаний об этом, что странно. Я занимался этой темой последний раз вроде бы в 2014 году, возможно что они нашли способ что-то гарантировать за последние годы, но как-то это сомнительно.

Тогда нужно устраиваться в компанию разработчик процессора и читать внутреннюю документацию. Или сломать их систему и достать доки нелегально. Или реверснуть логику процессора. Какой вариант вам больше нравится? =)

Могу судить по сфере встроенного ПО для микроконтроллеров разных архитектур.

Самое низкоуровневое, что доступно программисту — это машинные коды. Машинные коды можно хоть по бумажной таблице перевести в ассемблер (и обратно) даже вручную.
При этом компиляторы C выдают соответствующий ассемблерный код такого качества, что даже если бы я переделывал эти задачи вручную на ассемблере, доля совпадений была бы порядка 90%, если не более.

Проще говоря, программист на С и программист на машинных кодах выдадут исполняемый бинарник с совершенно минимальными отличиями.
Куда же низкоуровневее?
Да как бы, уже очень давно сами по себе х86 инструкции для процов — это такой же байткод как и Java и .Net
Я намеренно указал, что архитектуры не ограничиваются x86
Ну так в статье как раз наоборот, для архитектур есть ограничения. Так то понятно, что при желании можно найти проц который будет простой машиной Тьюринга.

Нельзя, у машины Тьюринга бесконечная лента.

Байт-код Java это язык низкого уровня, а ЯП Java это язык высокого уровня.
Куда же низкоуровневее?
Так вы же сами и ответили:
Самое низкоуровневое, что доступно программисту — это машинные коды. Машинные коды можно хоть по бумажной таблице перевести в ассемблер (и обратно) даже вручную.
Есть еще микрокод процессора.
Он же не доступен для программиста, поэтому, думаю, рассматривать его в качестве ЯП не очень корректно.
Думаешь, на нем офисные работники пишут, а не программисты? =)

Вполне реальная идея, чтобы компилятор генерил микрокод (мне даже кажется, что LLVM затачивался в т.ч под это). Но по ряду каких то причин (я бы предположил ноухау/патенты, невозможность разделения ответственности итп), этого делать не дают.
На нем пишут разработчики конкретного ЦПУ в процессе его создания, это совершенно не то же самое. Т.е. микрокод является его неотъемлемой частью и рассматривать его как язык способный программировать ЦПУ в целом — это уже какой-то уроборос получается
Нет уробороса. Ниже уже электросхемотехника, так что все — .

Микрокод это неотъемлемая часть конкретного ЦПУ, меняя микрокод вы меняете ЦПУ а не программируете его.

Микрокод не является неотъемлемой частью процессора, потому что относительно легко заменяется. (Без физических манипуляций).


WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

amd64-microcode/bionic-updates,now 3.20180524.1~ubuntu0.18.04.2 amd64 [installed,automatic]
intel-microcode/bionic-updates,now 3.20180425.1~ubuntu0.18.04.2 amd64 [installed,automatic]
microcode.ctl/bionic 1.18~0+nmu2 amd64
Легкость замены части чего-то не отменяет принадлежности этой части к целому. Вы можете относительно легко поменять колеса на автомобиле и это изменит характер его движения, но меняя колеса вы не управляете автомобилем в смысле езды.
Хотя, конечно, обновляемый микрокод смазывает границы, но все же наличие микрокода не делает машинный код высокоуровневым языком.
Это очень старая идея. Когда-то мой отец занимался тематикой трансляции Фортрана в микрокод, еще в советское время.

Один чешский деятель, сейчас не вспомню университета и регалий, реализовал это году в 2008 — трансляция С в микрокод (не помню, какой именно, не суть).

Моя дипломная работа посвящена в чем-то аналогичной идее — доступу напрямую к микрокоду.
Почему недоступен?
Зависит от процессора и от программиста.
Раз уж там упоминался SPARC.
Весной к нам в контору приезжали представители МЦСТ и рекламировали помимо своих VLIW Эльбрусов свой же компилятор С.
Так вот судя по их словам, обогнать себя самого на VLIW-ассемблере либо нельзя, либо на считанные проценты, относительно того же грамотно написанного С-кода в связке с оптимизирующим компилятором.
Так что будь то ARM, SPARC или что-либо ещё — суть не меняется.
Ниже машинных кодов ни в одной архитектуре не опустишься. И С даёт результат чрезвычайно близкий к вручную написанным машинным кодам.
Ниже машинных кодов ни в одной архитектуре не опустишься.

Ну так речь и идёт про то, что надо в консерватории машинные коды подправить в духе параллелизма.
И С даёт результат чрезвычайно близкий к вручную написанным машинным кодам.

Но это не точно. (с) По крайней мере в тех кусках, с которыми я ковыряюсь на предмет оптимизации на asm-е.
Именно поэтому я и написал про 90%. В тех редких случаях, когда компилятор всё-таки так и не догадался, как именно можно было срезать углы. И это вопрос качества С-кода и качества компилятора.
Существенно менее 90%.
Ложь скрывается в том, что это только для программ написанных квалифицированными программистами, знающими архитектуру для которой пишут, как минимум на уровне ассемблера. Код скопированный со «стековерфлоу» без «творческой» обработки на 100%, или из-за не знания алгоритмов, или неадекватности алгоритма под конкретную архитектуру (где важны учет операций пересылки, ветвления, а не количества арифметики или количества сравнений, и прочая, и прочая...).
Ложь заключается в том, что си низкоуровневый только в том, что он не предоставляет инструментария, доступного в языках высокого уровня: он не дает никаких гарантий; код на ассемблере или блисе, чуть более низкоуровневый, только потому, что там работа с типом еще менее ограниченна (есть, но никто, ничего гарантировать не может!).
Языки низкого уровня — это языки которые выбирают ССЗБ, разменивая простоту и ограничения абстрактных машин ( DSL[language] -языков ) на максимальную доступность функциональности архитектуры на текущих ЦПУ, минимизируя ответственность за качество кода, выразительность, обозримость, управляемость коллективной разработки (всем все доступно, можно трюкачить, зная «что внутри»; как результат было больше ограничений встроено в си++, java… языках высокого уровня и ООП, с их инкапсуляцией… правда с тех пор мы потеряли Пакеты Прикладных Программ(этакий сорт библиотек, которые мы тоже потеряли)… ССЗБ).
Язык — это всегда! система ограничений [как и языки человеческого общения], я говорю не только о синтаксисе/семантике (да, и они входят в эту систему), но о том, что эта система строится для удобства получения конкретного! результата, через высокоуровневую систему типов (функциональность, в си реализовано только покрытие модальных целых, реализованных в ЦПУ, даже с float/double принято решение в пользу усеченных, в сравнении с фортрановскими или ди, SIMD — чисто архитектурные, а матрицы реализованы только в языках-матпакетах, работа с временем, цветом, видео… скоро (видимо) будет только через #nodejs, на amazon cloud, а пользователю будет отсылаться только картинка со звуком, получая управленческие импульсы прямо из мозга человека-обезьянистого [сарказм] ). Си — это язык unix, как было задумано, использование его иначе чревато овердрайвом, OS обязаны (теперь!) предоставлять всю (открытую) функциональность операционки из конца 60х, ее безответственность по отношению к пользователям, требуя высокой квалификации и обоймы программ накладывающих ограничения на это пиршество свобод…
Конечно, идеалом не является программа с единственной кнопкой «сделай мне хорошо», но язык не определяется свободами, язык — это средство общения, как идеал, си не подходит более чем полностью, он не может даль ничего более (в сравнении с ассемблерами, тем более с макро-… или тем же occam-2, а тем более с ada или c++). Единственное, что дает си — это качественные (на текущий момент) оптимизирующие компиляторы, и легаси, накопившееся с доисторических времен, когда компьютеры были большими.
Так вот судя по их словам, обогнать себя самого на VLIW-ассемблере либо нельзя, либо на считанные проценты, относительно того же грамотно написанного С-кода в связке с оптимизирующим компилятором.


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

В своё время нам так разъясняли, чем отличаются языки высокогоуровня от низкого — наличием абстракции. По скольку типы данных и операции языка С абстрагированы от архитектуры — он является языком высокого уровня по определению. Например, такие вещи, как операции с плавающей точкой, являются частью стандарта языка С, однако некоторые архитектуры имеют только целочисленные АЛУ, но благодаря компилятору, программисту не надо об этом заботиться. Разработчики, использующие языки с бОльшим уровнем абстракции, как GO, например, считают С языком низкого уровня, но это не так.

НЛО прилетело и опубликовало эту надпись здесь

Не нужно путать мнемокод ассемблера и машинный код. Машинный код это двоичные данные, которые непсредственно исполняет процессор, и ничего там уже не переименовывается и не изменяется. Есть ещё байт-код, например Java, но он исполняется не непосредственно процессором, а run-time интерпретатором — специальной программой, которая превращает байты этого кода в вызовы специальных подпрграмм — он так же не является машинным кодом!
Если Вы на assembler напишете программу, то каждая инструкция будет однозначно преобразована в машинный код, и если дезассемблировать полученный дамп данных, вы получите практически тот же исходный код, единственное — это символические имена меток и адресов памяти будут в виде цифр. Поэтому assembler и является языком низкого уровня. Вам нужно знать все спецификации процессора, для которого пишетсчя программа — никакой абстракции, bare metal!
Чтобы пояснить наглядно разницу низкоуровневого ассемблера и высокоуровнего С, приведу маленький пример из жизни 8-битных микропроцессоров AVR. В серии megaAVR есть блок умножения и соответствующая инструкция mul, а в tinyAVR такого блока и инструкции — нет. То есть, написанная программа на ассемблере, для megaAVR с использованием инструкций mul не может быть скомпиллирована для tinyAVR. Все операции умножения прийдется заменить подпрограммой умножения, которая использует, допустим, сложение в цикле. Это говорит о том, что программист должен знать сиецификацию целевого процессора, набор команд, регистров итд. В С можно просто написать a=b*c; и это без переделок можно скомпиллировать для любого процессора, даже не задумываясь, может ли его АЛУ перемножать числа или нет. Компилятор языка С сам заменит умножение на нужрую инструкцию или подпрограмму, в зависимости от используемого целевого процессора.

НЛО прилетело и опубликовало эту надпись здесь
Машинный код это двоичные данные, которые непсредственно исполняет процессор, и ничего там уже не переименовывается и не изменяется.
На календарь смотрели? Последний x86 процессор, где ничего не переименовывалось и не изменялось (хотя кеши были уже и там) — это Pentium! Вышедший четверть века назад!

И для ARM-процессоров — это тоже уже не так (про Denver почитайте, что ли).

Да, в embedded это ещё временами так, ну так в статье не про это речь…
Процессор не исполняет данные. Он из машинного кода берёт адреса инструкций и данных. Суперскалярные процессоры исполняют код магическим (для программиста) образом, совершая несколько переходов в конечном автомате программы одновременно.
----а делали быстрые процессоры с интерфейсом PDP-11.

Мне кажется автор имеет весьма смутное понимание о машинах PDP-11.

Мало того что ассемблер на интелле несколько другой, но и аппаратная часть разительно другая: IO порты маппируются на адресное пространство, одной командной данные с одного устройства, например, копируются на другое без участия в этом процессора.

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

Слово эмуляция, мне кажется, неуместно.
НЛО прилетело и опубликовало эту надпись здесь
VLIW Эльбрус основан на архитектуре E2K, а не SPARC.

И С даёт результат чрезвычайно близкий к вручную написанным машинным кодам.

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

А Эльбрус — это ещё и R-серия, которая чистый SPARC.
практически идентичный по качеству тому машинному коду, который я могу написать вручную, зная все особенности данной архитектуры.

Но, увы, не всегда. Как человек, вручную оптимизировавший обработчик прерывания на C2000, сокращая его на пару микросекунд, скажу, что компилятор, безусловно, умный, но иногда допускает пикантные деградации (об одной даже получилось отрепортить разработчику компиля).


Что же до "90% идентичный", то компиль обладает замечательной возможностью перемешивания инструкций на конвейере, что позволяет выполнять код быстрее его прямолинейного составления и в итоге он должен стать даже быстрее того, что пишет человек

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

С тех пор было создано огромное множество языков, и все они — одинаково высокоуровные согласно этой классификации. C, C++, C#, JavaScript, Python, да даже LLVM — всё это высокоуровневые языки. В этом случае подобная классификация не имеет никакого смысла.

То есть имеет смысл в относительной классификации, а не в абсолютной, или в нескольких уровнях градации высокоуровневых языков:
— абстрагирование от работы с регистрами и машинным представлением данных (C­);
— наличие структур данных: объектов, функций высших порядков (C++)
— абстрагирование от прямой работы с памятью (.NET, JVM);
— абстрагирование от операционной системы (JavaScript).
Если javascript решает задачу абстрагирование от операционной системы — то он её ниразу не решил.

Как минимум один раз решил — моё приложение одинаково работает на айфоне, андроиде, в файрфоксе на убунту, в сафари на маке и хроме на винде.
Так что ваше заявление ложно.

Да-да, моя Qt программа тоже без проблем собирается на винде и убунту, но есть нюансы

Для точекнету и жабомашины ОС — виртуальная машина. Всё было круто и однородно чуть ли не до омерзения, когда мобильные апплеты можно было запускать на пеке. А потом появились OpenJDK и Mono — и ряд программ перестал нормально работать в одной из реализаций. Не говоря уж о том, что не только язык, но и байткод обоих терял обратную совместимость в ряде аспектов.

Для бинариков тоже есть разные приблуды. Вроде WINE, которые симулируют x86 и x64 окружение Windows, сохраняя весьма приличную производительность. Или обратную фишку для POSIX от мелкософта. Всё вроде работает, но нет совместимости баг-в-баг.

Для жабоскрипта ОС — это среда выполнения, обычно, браузер. Внезапно в хроме, огнелисе, сафари и эдже есть куча собственных костылей, которые нужно учитывать, а ведь есть ещё node и другие ребята. Дабы далеко не ходить, как давно в них во всех появилось общее имя для корневого элемента? У вас одинаково — или у меня вся вёрстка рассыпется на левом браузере с кастомной темой? Да ладно, у меня в Вивальди или, ть, Амиго точно всё работать будет?
Да ладно, у меня в Вивальди или, ть, Амиго точно всё работать будет?
А что это за операционные системы такие? Я таких не знаю.

Зависимости от операционной системы таки больше нет. А от браузера — да, таки есть. Что вас удивляет?
А что это за операционные системы такие? Я таких не знаю.
Я надеюсь, это сарказм. А так, нет, не удивляет — забавляет. Люди говорят об избавлении от зависимостей, а потом пишут многотомные проверки на версии браузеров и поддержку фич. Нуок, «везде всё одинаково», «вёрстка почти не поехала».
Люди говорят об избавлении от зависимостей, а потом пишут многотомные проверки на версии браузеров и поддержку фич.
Увы, избавиться от зависимости нельзя, если ты хочешь общаться с реальным миром. Можно только передвинуть их. JS успешно заменяет зависимость от операционки на зависимость от браузера. Это всё равно большой шаг вперёд, так как, к примеру, на мобильнких и PC есть одинаковые браузеры, а одинаковых операционок — нету.
Нет на мобильниках и ПК одинаковых браузеров — есть только одноимённые
Это уже расщепление волос. Мобильный и настольный Хром имеют на 90% общую кодовую базу (хотя некоторые вещи выключены на мобильника на стадии компиляции для экономии места).

То, как реализованы табы — разработчиков приложений волнует мало.
Для точекнету и жабомашины ОС — виртуальная машина.

Но и там, и там есть возможность взаимодействия с нативным кодом, возможность вывоза функций системы и взаимодействие с её сущностями.


А в JavaScript и этого нет, взаимодействие идёт только с браузером.


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

Если бы существовала серебряная пуля, было бы легче, правда?

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

*Hе касается Вивальди.
С тех пор было создано огромное множество языков

Но и классификация языков не стояла на месте. Все перечисленные вами языки вполне классифицируются без необходимости введения нового способа классификации.
.NET и JVM тоже абстрагированы от операционных систем, во всяком случае десктопных.
Да, поэтому такие уродские иконки в трее и окно сохранения файлов.
Или всеми любимый COM или ADO драйвера не той разрядности.
В общем не обобщайте. На серверах все немного проще.
  1. Вообще не понятно причет тут язык в частности C. Это проблемы другого плана.
    Современные программы тормозят совсем по другим причинам. Компилятор тут вообще не причем. Главная причина экономическая. Надо получать прибыль быстро, поэтому разработчики имеют топовое железо, что бы быстрее выкатить хоть как-то рабочую версию. У них работает приемлемо и ладно. То что у пользователя тормозит — это проблемы негров. Пусть покупают новое железо. Взгляните на java — академически правильный язык, быстрый, хорошо структурированный автоматически следит за память… всё шикарно. Но программы на java приобрели славу тормозного, жрущего ресурсы по. Почему да потому что там тысячи абстракций, куча библиотек обёрнутых в другие библиотеки да еще всё это разных версий.
    Если старый word 2003 запустить на новой машине. Всё работает просто мгновенно по стравнению с word 2016, хотя решают они одинаковые задачи набор и печать текста и компиляторы стали лучше и делают массу трансформаций, и операционка новее и ядер больше. Но когда курсором мигают через javascript это всё коту подхвост
  2. Паралельный код писать очень сложно и поддерживать дорого. Особенно на языках к жтому не приспособленных. А использовать всю вычислительную мощность конкретной системы иногда вообще невозможно. Плюс массивно паралельных систем сейчас очень много видов. Поэтому приходится использовать абстракции openmpi,opencl,openal,… и готовые готовые библиотеки IPP, gpu-accelerated-libraries… Более того некоторые алгоритмы не имеют общих рецептов распараллеливания.
  3. В модель акторов не панацея она создаёт больше проблем чем решает.
  4. Высокоуровневый язык это sql вы ему пишете что хотите, а он уже сам строит план выполнения, выполняет и возвращает вам результаты.
  5. Язык C полный по тюрингу и на нём теоритически можно написать что угодно. C компилятор есть почти под любую платформу. Если нет то его просто написать, т.к. сам язык простой.
  6. Сам язык прост в изучении. Главное отличие C от C++. C необходим для написания маленьких утилит которые потом объеденяются в рабочую систему языками склейки (bash,python,lua,java...). C в отличии от C++ имеет вменяемый бинарный интерфейс. С другой стороны C++ претендует на то чтобы писать огромные «титаники» в которых весь функционал пихают в одну программу.
  7. Постоянное развитие C++ ведёт его в сторону усложнения, как для изучения так и для написания компиляторов. И к сужению количества платформ под которые можно компилировать. При этом не решая практических задач. (например php из коробки умеет общаться с базами данных, работать с архивами и т.п.)
  8. Более того не всегда ясно какое наречие языка C++ надо использовать, что бы не огрести проблем.
  9. Для решения задач распараллеливания есть экспериментальные языки типа halide-lang.org которые пытаются дать инструмент для анализа и поиска компромисса локальности, параллельности и порядка вычислений.

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

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

1.1. Я эту старую песню о главном слышу с 2003 года. «Процессоры стали сильно мощные, их нужно замедлять искусственно, чтобы покупали новые.» Это очень красивые слова, которые нужно доказать метриками. А ещё лучше, разбором топологии. А с этим будет очень непросто. А пока это не сделано, к подобным фразам можно относиться так же, как к вою о вреде ГМО.
1.2. Очень красивый пример с вордом. Но давайте ближе к нашим тушкам. Давайте сравним студию 2003 и 2017. Начните с тормозной новой, а потом перейдите на быстролётную старую. Только попытайтесь не блевать в процессе.
2. Да и нет. MPI и прочие — они не про параллельность выполнения операций. Они о командной работе. Про параллельность — OpenMP. ИМХО, там достаточный набор команд для нормального распараллеливания задачи. Если вам нужно больше — вы пытаетесь решать несколько задач одновременно.

Остальные пункты вообще непонятно о чём. Вы не расчёсывайте плюсы — они и чесаться у вас не будут.

Настоящая проблема в том, что разработчики процессоров не дают программистам прямого доступа к управлению процессором. Да что там, даже косвенного не дают. Понятное дело, безопасность, все дела… Но в чём проблема добавить определение расположения переменной в памяти на уровне прагм и деклараторов?
int __declspec(allocate(L3Cache)) bigRoutineCounter; 

Но нам, почему-то, не доверяют делать вот такие советы. Вот это, на самом деле, печально и обидно.
Проблема не в языках, а в тех, кто на них пишет. Разработчики процессоров фактически тоже не при делах. Современные разработчики серьезно игнорируют проблемы оптимизации своего кода в пользу скорости и удобства разработки. Компилятор все за всех не заоптимизирует. В случае интерпретируемых языков — все еще хуже.
Говнокодеров в процентном отношении за последние 30 лет сильно больше не стало. Просто раньше не было гитхаба и перестека, поэтому насладиться кодом надмозгов было куда сложнее, как и плодами их трудов — ибо чаще всего они не могли выбраться за пределы их собственной пеки.

К тому же, что это за аргументация? Это ж бред! Как минимум, разработчики компиляторов, игр и высоконагруженных систем кровно заинтересованы в каждой доле процента производительности. Если размещение простых ничего не обязывающих прагм работы с кешем позволит повысить производительность на 1% — это уже серьёзно. Если дать возможность программисту задавать вероятности выполнения условий — и передавать их прямо в процессор, можно ожидать огромный скачок производительности всего кода, особенно, JIT.

Пускай это будет расширение Intel или AMD. Пусть это будут «рекомендации» типа inline. Пускай 99% кладут на оптимизацию. У оставшегося процента программы будут значительно быстрее, а зарплаты — выше. И это прекрасно, даже если меня в этом проценте нет и не будет.
> Давайте сравним студию 2003 и 2017

Кстати предпочитаю использовать Visual Studio 2005, а не 2017 (там где требуется старый уровень С++). Заметно быстрее работает и все что надо в основном есть (отладка, редактирование, навигация). Там где можно использовать новый С++, там понятно лучше новая студия (ибо старая перестает работать как IDE), но каких-то особых плюсов я не вижу (кроме «Rename» — может чего не нашел?), а минусы в виде пониженной производительности и повышенного потребления ресурсов я замечаю.
Давайте немного отвлечёмся от плюсов, так как MS явно не горит пламенной любовью к крестовикам и поддерживает их сравнительно бедно. Сравните редактор C#. Сравните подсветку и автодополнение, построение схем кода и тп. Они разительно отличаются. Тот же IntelliSense превратился из «о, боже мой, как это отключить» во вполне себе полезный инструмент, которого вполне хватает. И так во всём.

Нет, меня самого радует сверхотзывчивая среда разработки. Но меня так же радует комфорт. Если отключить все подсветки и украшательства, окажется, что новая студия быстрее старой и может оперировать бОльшими файлами. Вот только зачем нам нужен очередной блокнот?
1.2. Очень красивый пример с вордом. Но давайте ближе к нашим тушкам. Давайте сравним студию 2003 и 2017. Начните с тормозной новой, а потом перейдите на быстролётную старую. Только попытайтесь не блевать в процессе.
Блевать может захотеться от ограничений старых версий компилятора, но вот UI у старой версии — таки приятнее и удобнее.

Я эту старую песню о главном слышу с 2003 года. «Процессоры стали сильно мощные, их нужно замедлять искусственно, чтобы покупали новые.» Это очень красивые слова, которые нужно доказать метриками.

Я пишу софт на Паскале для большого зоопарка систем от первых Селеронов и Атлонов (даже без SSE) до современных 18-ядерных. Который должен работать годами без остановки. И так получается, что если отвязать логику от гуя и от ввода-вывода (через очереди команд), то многое можно сделать асинхронным и легко распараллелить. И тогда проблем с быстродействием процессора практически не возникает, все начинает упираться в диск, сеть и память. И разница между системами 15-летней давности и современными оказывается не столь велика — везде есть какие-то узкие места, то в рейд-контроллере через неделю работы деградирует кеширование запись, то запись в SSD деградирует, то вдруг ОС решит дисками пошуршать по своим делам, то память фрагментируется до неприличия, не может найти непрерывный кусок в десяток мегабайт при свободном десятке гигабайт… А у процессора на самом деле моща гигантская, сотни миллионов операций в секунду. И эта моща может легко уйти в свисток в каком-нибудь банальном цикле поиска или сравнения строк, выполняемого для каждой ячейки таблицы. Для которых есть специальные команды процессора, но компилятор и библиотеки языка программирования решили, что для уникода это не годится.
Пардон пур мон франсе, а разве паскаль ещё жив? Последний стандарт был в прошлом веке. Быть может, вы о делфи? Но и с ним всё не так хорошо. Насколько я знаю, он последние лет 6 мучительно агонизирует. Тем более, ни первый, ни второй никогда не претендовали на поразительную скорость и близость к железу. Так чего ждать от старых компиляторов?

И, вы серьёзно? То, что вы описываете — отсутствие гигиены, причём, принудительное. Та же дефрагментация по умолчанию выполняется по расписанию еженедельно. Если перестать убирать пыль, любой ПК вышедший после 1996 года рано или поздно вызовет пожар. То же в ОС. У меня на моих задачах даже между Core2Duo 2008 года и Athlon2x4 2011 года разница ощущается весомая. Если вы i9 увайдохиваете в днище… Остаётся только снять шляпу перед вашим мастерством!
И, вы серьёзно? То, что вы описываете — отсутствие гигиены, причём, принудительное. Та же дефрагментация по умолчанию выполняется по расписанию еженедельно.

Как дефрагментировать память в Windows без перезапуска приложения? =)

На процессоре i3 обычно нагрузка 1-3%, на старых 10-20%. Там просто данные из разных портов обрабатываются и отправляются в сеть + кладутся на диск в виде логов и БД. Но данных много, в сутки до 2 Гб логов.
НЛО прилетело и опубликовало эту надпись здесь
Ну и зачем заниматься дефрагментацией на 64 битах, скажем, я не очень понимаю. Да, есть TLB, есть page cache, но это уже всё эффекты следующего порядка малости.

Это в теории на 64 битах все должно быть шоколадно, а на деле оно вафельно. Вот у чувака похожие проблемы: habr.com/post/420579
НЛО прилетело и опубликовало эту надпись здесь
Ну и зачем заниматься дефрагментацией на 64 битах, скажем, я не очень понимаю

Каким образом разрядность ОС влияет на проблему фрагментации памяти?

Размером виртуального адресного пространства процесса.
В случае 64-битного процесса вы всегда можете выделить непрерывный кусок памяти любой разумной длины, в случае 32-битного — не можете.

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

А почему фрагментация физической памяти — это проблема? И зачем её решать? Или вы пользуетесь операционной системой без виртуализации доступа к памяти?

НЛО прилетело и опубликовало эту надпись здесь
то память фрагментируется до неприличия

При частом выделении/освобождении памяти обычно применяют пулы объектов вместо кучи, не?


в каком-нибудь банальном цикле поиска

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


для уникода это не годится.

Ну, в общем и целом, если вы не сравниваете две строки юникода как последовательность октетов, то там действительно может быть сложно.


Который должен работать годами без остановки

ОС решит дисками пошуршать по своим делам

У меня возникает ощущение, что где-то взяли не подходящий инструмент для решения задачи.

При частом выделении/освобождении памяти обычно применяют пулы объектов вместо кучи, не?

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

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

В моем случае это не критично, там больше проблемы с ОСью.

У меня возникает ощущение, что где-то взяли не подходящий инструмент для решения задачи.

А какой инструмент поможет, если слабым звеном оказывается ось и ввод-вывод? Пробовали свою железку делать, на микроконтроллере со своей прошивкой и на нее переложить часть забот, так там свои проблемы — то флеш глючит, то памяти мало, то пропускной способности не хватает.
Дают доступ к тому функционалу, который не отвалится в обозримом будущем.
openal
— это библиотека для работы со звуком.
java — академически правильный язык
Ой, ну скажите еще, что С — не низкоуровневый язык))) Чем java «академически правильная»?

А вообще все Ваши пункты никак не опровергают и не подтверждают сказанного автором.
Си — высокоуровневый язык программирования.
Изначально был высокоуровневым и им и остался.

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

Если уж совсем докапываться до фразы, то программы пишут на языках программирования, с использованием IDE и других инструментов.
Не спорю, это лишь отсылка к названию статьи.
Извините ночью писал: Не openal а openacc
Я вообще не понял что хотел сказать автор. Но категорически с ним не согласен.
НЛО прилетело и опубликовало эту надпись здесь
На современных плюсах легче писать, чем на плюсах 10-летней давности. И выучить современное подмножество, наверное, проще.

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

НЛО прилетело и опубликовало эту надпись здесь
И правда, давайте не будем использовать языки, по которым можно придумать сложные собеседования. Всем бегом учить Go!
НЛО прилетело и опубликовало эту надпись здесь
1. Должен.
2. Вторая сразу станет искать конструктор, а первая поищет оператор приведения.
3. Думаю, не определено.

Уверен, что ошибся минимум в двух пунктах из трех. Так что, учим Go?
НЛО прилетело и опубликовало эту надпись здесь
2. Initializer list'а к T.
3. Не знаю, у меня три варианта и все «равноправные»))

Но опять же, наличие такой вот фишки, которую никто не использует дальше инициализации константной plain-даты, не создает мне головной боли. А вот от Rust'а, я уверен, голова заболела бы еще до попытки что-либо скомпилировать (я про синтаксис).
НЛО прилетело и опубликовало эту надпись здесь
Не, ну как, это следствие зоопарка из способов инициализации и количества различных видов типов данных (user-provided ctor, user-declared ctor, вот это всё). И это иногда выстреливает. По крайней мере, на code review на некоторые частные случаи этого всего мне указывать приходилось.


Но опять же, наличие такой вот фишки, которую никто не использует дальше инициализации константной plain-даты, не создает мне головной боли. А вот от Rust'а, я уверен, голова заболела бы еще до попытки что-либо скомпилировать (я про синтаксис).

Почему-то в каждой статье где вспоминают раст говорят про его ужасный синтаксис. Я последний год его изучаю, полгода пишу всякий опенсорс, и никакого отличия от синтаксиса того же C# не вижу, за исключением сокращений типа function -> fn (хотя в том же F# fun)… Можете рассказать?
НЛО прилетело и опубликовало эту надпись здесь
А если чуть серьёзнее, если я на раст смотрю как сиплюсплюсист, то там слишком много всяких закорючек.

А можно пример? Основные претензии, что я видел, связаны с лайтаймами и ассоциированными типами. Только там аргументация в стиле «вот в расте лайфтайм есть, а вот в С++ нет, лишняя писанина». Хотя если мы передаем компилятору одинаковое количество инфы (например, гарантируем, что параметр функции А должен пережить параметр функции Б) то оказывается, что синтаксис вполне компактный и понятный.
НЛО прилетело и опубликовало эту надпись здесь
Все мейнстрим ФП языки это GC, там лайфтаймы тоже не сильно нужны.

Раст требует как синтаксиса для высокоуровневых деталей, так и более низкоуровневых. Получается слегка перегружено как для тех, так и для других.

Но для меня освобождение от GC просто как глоток свежего воздуха, ибо работа с любыми ресурсами сводится к соглашениям семидесятых в стиле «caller обязуется освободить ресурс». Автоматизация — наше все. Пусть приходится описывать отношения времен жизни, всё же это декларативное описание, достаточно высокоуровневое.
НЛО прилетело и опубликовало эту надпись здесь
Clean, например, в отличие от хаскелевских монадок линейными типами (которые суть обобщение лайфтаймов) решает проблему IO, например. В хаскель потихонечку завозят линейные типы. Линейные типы позволяют гарантировать, что вы закроете файл, но сделаете это не более одного раза. Линейные типы позволяют статически определять всякие интересные вещи об использовании данных и генерировать более оптимальный код (например, мутирующий структуру in-place вместо порождения копии даже в чистом ФП). Короче, оно везде полезно.


Круто. Правда, в работающем коде пока не встречал.

Но ведь GC — это только про память, а ресурсы — это далеко не только она!

Но это в каком-то смысле просто ещё раз мой тезис выше.

О чем и речь. Растовые лайфтаймы на моей памяти первый удачный механизм автоматической работы с любыми ресурсами, а не только с памятью. После всех этих try-with-resource и IDisposable радует до невозможности.
Со вторым ошиблись. А это ведь как раз прекрасная иллюстрация обсуждаемого тезиса. В C++17 между этими двумя вариантами нет разницы, а в C++14 есть.

Вот расширенный пример, показывающий разницу:
$ ./clang++ -std=c++14 -fno-elide-constructors tst.cc -o tst && ./tst
Foo() constructor called!
Foo() constructor called!
Foo(const Foo&) constructor called!
$ ./clang++ -std=c++17 -fno-elide-constructors tst.cc -o tst && ./tst
Foo() constructor called!
Foo() constructor called!
$ cat tst.cc
#include <stdio.h>

class TraceFoo {
 public:
  TraceFoo() { printf("Foo() constructor called!\n"); }
  TraceFoo(const TraceFoo&) { printf("Foo(const Foo&) constructor called!\n"); }
};

template<typename T>
T mkFoo1() { return {}; }

template<typename T>
T mkFoo2() { return T {}; }

int main() {
  mkFoo1<TraceFoo>();
  mkFoo2<TraceFoo>();
}

НЛО прилетело и опубликовало эту надпись здесь
А со вторым, более того, разница не только в вызываемых конструкторах, но и вообще в поддержке noncopyable-типов. То есть, с noncopyable-типом первая функция протайпчекается, а вторая — нет. Даже без -fno-elide-constructors.
В C++17 обе прочекаются. В этом и был смысл изменения.
НЛО прилетело и опубликовало эту надпись здесь
Последний вопрос — он вообще какой-то странный. Там же undefined behavior, так что она вообще всё, что угодно может вывести.
НЛО прилетело и опубликовало эту надпись здесь
1. Ответ зависит от версии компилятора. Берём компилятор, собираем. Может собраться, а может и нет.

2. Ответ зависит от версии компилятора.

3. Вообще дичь какая-то.
НЛО прилетело и опубликовало эту надпись здесь
  1. Нет, пока что ответ от версии компилятора не зависит. Это aggregate initialization, поэтому соберётся во всех conforming-компиляторах для всех С++11, 14 и 17.

Абсолютно нелогичное поведение, кстати. И хорошо, что в С++20 это пофиксят.


P.S. Не удивлюсь, если скоро в первой строчке файла придётся писать директиву типа #language c++20.

НЛО прилетело и опубликовало эту надпись здесь
Модель акторов внутри себя имеет очереди которые имеют свои особенности. Так вот когда они всплывают становиться очень не смешно.

> На современных плюсах легче писать, чем на плюсах 10-летней давности
Точно берёшь какую нибудь библиотеку и хочешь её собрать и тут выяснятся что она требует только определённую версию компилятора иначе она или не собирается или не проходит тесты. Еще прикольней когда компилятор сообщает что у вас префис Q не поддерживается и 128битные флоаты (которые вы не используете) не работают. Да и раздел depricated постоянно растёт. Если вы пишете на современном C++ оно через пару лет может уже не собираться на новых компиляторах. Современные плюсы это страх и ужас. Вместо того что бы выкатить новый язык они пытаются сделать из говна конфетку сохраняя обратную совместимость с переменным успехом. Вспомните с чего начинался C++. Это был препроцессор языка C. Так вот эти препроцессоров могло быть сколько угодно. И обычные скриптовые языки элементарно справляются с подобной задачей (генерацией из DSL C кода). И в отличии от C++ не надо ждать выхода нового стандарта, что бы получить например обычную рефлексию кода или аспекты.

>Пхп-код уже можно компилировать под attiny?
дело не в php. А в инструменте который из коробки позволяет просто решать типовые задачи и разбивать на простые сложные. Что C++ из коробки умеет парсить аргументы командной строки как gargs или читать конфигурационные файлы, или упаковывать логи, работать с изображениями, звуком, сетью, базами данных, печатать на принтер, gui и т.п. даже работа с микрософтовскими COM интерфейсами IDispatch для C++ это грусть, печаль и насилие над здравым смыслом.
Вместо решения проблем которые возникают при реализации практических задач C++ предоставляет разнообразные инструменты программирования ради программирования.
Вот например какое отношение к реальным задачам имеет stric aliasing? Даже сам термин UB для стандарта, которого все должны придерживаться, выглядит как издевательство. Вместо нормального языка запросов для анализа и преобразования кода использовать метапрограммирование на плюсовых шаблонах это вообще садизм.
Cи гарантирует, что структуры с одинаковым префиксом могут использоваться взаимозаменяемо, и предоставляет доступ к смещению полей структур в языке. Это означает, что компилятор не может изменить очерёдность полей в структуре или добавить выравнивание для улучшения векторизации (например, трансформировав структуру из массивов в массив структур или наоборот).

Что такое «одинаковый префикс» и куда нельзя добавлять выравнивание (в оригинале там «compiler is not free», то есть всё таки можно, но ограниченно)? Я сломал голову, пытаясь понять этот отрывок.
Имеется в виду, что если структуры начинаются с одинакового набора полей, то эти поля внутри структур будут размещаться одинаково. (В частности, если между ними будет добавлено выравнивание в одной из структур, то во второй будет добавлено в точности такое же выравнивание.) Подробнее: stackoverflow.com/a/19805187
Думаю, подразумевается, что в целях оптимизации, компилятор в теории мог бы перекомпоновать структуры данных, например, вот так и получить выигрыш от этого.
#include <stdio.h>
#define N 1000
struct StructA {
    int  a;
    char b;
};

StructA VectorA[N];

struct StructB {
    int  a [N];
    char b [N];
};

StructB VectorB;

int main(void)
{
    printf("VectorA: %lu \n", sizeof(VectorA));
    printf("VectorB: %lu \n", sizeof(VectorB));
}

VectorA: 8000
VectorB: 5000
Как видим, по памяти аккуратней может выйти. Также, например, найти сумму полей a будет быстрее в альтернативной структуре данных. Однако, компилятор не имеет права так поступить.

Последний абзац про потоки и GPU это бред какойто… очень много алгоритмов невозможны без операций над шаред памятью даже пресловутые lock-free.
А в GPU вообще всё иначе.

НЛО прилетело и опубликовало эту надпись здесь
А очень просто: на этих самых AMD и ARM однопоточная производительность сильно ниже, чем «у некоторых производителей»… и теперь понятно — почему.

А «заигрались»… вся индустри заигралась. Процессоры можно сделать в 10 (если не 100 раз) проще, если не пытаться делать вид, что вы работает на «очень-очень быстрой PDP-11»… об чём и статья…
НЛО прилетело и опубликовало эту надпись здесь
Собственно, на всех своих машинах в ядре соответствующие опции для защиты я выключил.

А что с защитой, которую встроили в программное обеспечение разработчики? То, что в Хроме и Файрфоксе? Это можно отключить?

А вы хотите, чтобы JS из рекламы читал память ядра?

Не совсем понимаю, почему тут GPU противопоставляется CPU в плане ILP. На GPU точно так же есть ILP, достаточно глянуть даже на эту старую презентацию по CUDA 10-го года, чтобы убедиться в этом: Better Performance at Lower Occupancy by Vasily Volkov
«C» — достаточно низкоуровневый, чтобы не терять прямой контроль над существенными железными ресурсами и достаточно высокоуровневый, чтобы не тратить человекогоды на аллокацию регистров и стека процессора, линеаризацию ветвлений алгоритма, и экономить слова при написании программ.
Он идеален для небольших проектов, общающихся с системой на низком (аппаратном) уровне, требующих выжимать проценты быстродействия, экономить ресурсы с помощью хитрых структур данных, рассчитанных до байта памяти.
Бизнес-логику, веб, утилиты, на сегодняшний день, на нём писать нелогично, существуют более эффективные, более высокоуровневые языки.
Си — просто высокоуровневый язык программирования.

Его уровень может быть можно мерять относительно других языков программирования, например с java/python/javascript.

То есть да, можно считать, что Си — менее ровный, чем многие другие компилируемые и транслируемые языки. Можно даже считать, что из всех живых/популярных языков, он один из самых близких к низкоуровневым языкам. Но при этом он никогда не был и не станет просто низкоуровневым.
Не сочтите за сарказм или иронию, а можно сюда список реально низкоуровневых языков? P.S. Я – интересующийся юзер. В начале было сказано, что ассемблер тоже не подходит.
Там же сказано, что хитрожопому CPU твой ассемблер все равно что JavaScript, он его по-своему интерпретирует.
Verilog))) других вариантов в голову не приходит

Скорее не верилог, а результат синтеза из его исходников

Так речь ведь о языке шла

А brainfuck? )
Что такое язык низкого уровня?
Ну, я бы определил уровень языка по тому, сколько сил надо для портирования программ на другую платформу. Ассемблер — низкий, т.к. требует много работы при портировании на другой процессор. ABI для работы с периферией («программирование контроллеров») — пожалуй, ещё более низкоуровневый, т.к. требует много работы при смене периферии (например, при смене сетевой карты).

Язык Си — низкоуровневый из-за того, что там масса случаев неопределённого поведения. Из-за этого возможны проблемы при смене компилятора (на том же процессоре и операционке); и возможны ещё более другие проблемы при смене платформы, особенно процессора (например, при переходе от плоской адресации к сегментной).

«Язык программирования является низкоуровневым, если написанные на нём программы требуют внимания к несущественному».
Ну, это зависит от того, что считать существенным.

Легко доказать, что С был низкоуровневым языком в PDP-11.
Ну так большинство языков того периода — были низкоуровневыми языками платформы, на которой разрабатывались. Например, в Фортране был «условный арифметический оператор» — явная калька с ассемблерной команды.

Ключевая причина появления уязвимостей Spectre и Meltdown в том, что создатели процессоров не просто делали быстрые процессоры, а делали быстрые процессоры с интерфейсом PDP-11.
Чтобы доказать это — надо привести пример более другого процессора, на котором хорошо исполняется более другой язык программирования. Ну и оценить перспективу применения этого процессора с традиционными (широко распространёнными) языками программирования; и этого языка программирования с другими процессорами. Прикинуть стоимость портирования на этот процессор существующих операционок и программ.

Лично мне кажется, что гораздо более неприятную проблему представляет необходимость выполнять программы, скомпилированные для *86-й архитектуры, и прежде всего — Windows, которую MicroSoft сама не портирует на другие процессоры и другим не позволяет.

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

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

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

Эти выброшенные данные имеют видимые косвенные результаты
Ужасная формулировка.
1) Косвенные результаты — имеют не выброшенные данные, а совершённые действия (вычисления), которые оказались ненужными.
2) Косвенные результаты всё-таки являюся скрытыми; но при этом их можно разглядеть.

Переименование регистров потребляет много энергии и площади кристалла в современных процессорах.
Очевидно, необходимость переименования регистров вызвана неудачной архитектурой набора команд процессора. Если предусмотреть много регистров (столько, сколько их будет реально в процессоре) — то программа (сформированная человеком или компилятором) будет обращаться к тем процессорам, которые есть — а не к виртуальным, которые используются ради совместимости тридцатилетней давности.

Кеш скрыт от программиста и потому недоступен из C.
Если сделать кэш доступным программисту (например, расположить егно на определённых адресах адресного пространства) — то мы получим массу проблем при попытке внедрения кэша другой архитектуры. Т.е., гляда на нынешнюю компьютерную индустрию, я могу сказать, что при Вашем подходе — ради совместимости будет зафиксирована ещё и архитектура кэша. Спасибо, нам более чем хватает проблем с фиксацией системы команд процессора!

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

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

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

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

во FreeBSD реализация malloc() сообщает системе, что страницы более не задействованы, а система использует первую запись в страницу как доказательство того, что это не так
Это что вообще значит?

Представьте оптимизацию по размыканию цикла, где цикл выполняется ноль раз. В оригинале весь цикл является мёртвым кодом. В разомкнутой версии теперь есть условие с переменной, которая может быть не инициализирована.
Ну и что? Цыкл внутри условия всё равно не выполняется!

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

Если вы заполните эту структуру нулями и потом укажете значение некоторым полям, будут ли в битах выравнивания нули?
1) Как кименно я заполню структуру нулями?
2) Обращаться к битам выравнивания — запрещено. А если туда не обращаться — то их содержимое неважно.

GCC и Clang, например, отличаются в том, сохраняет ли своё происхождение указатель, который конвертировали в целое и назад.
А разве так разрешается делать?

Компиляторы могут решить, что два указателя на результаты malloc() при сравнении всегда дают отрицательный результат, даже если они указывают на один и тот же адрес.
А можно пример?

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

Есть множество примеров архитектур, которые не были сфокусированы на традиционном коде C и из которых можно черпать вдохновение. Например, такие ориентированные на многопоточность процессоры, как Sun/Oracle UltraSPARC Tx, не требуют столько кеша, чтобы держать занятыми свои исполнительные устройства.
1) Что такое «традиционный код C»?
2) Почему Вы считаете, что архитектура *86 фокусировалась именно на C? Мой опыт изучения истории *86 говорит о том, что она формировалась в первую очередь ради совместимости; а C и типа того были не столь важны, ибо в начале существования *86 C не играл на ней особо важной роли.
3) Мне кажется, что главное отличие Sparc — в большом количестве регистров и в наличии регистрового файла.

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

Проблема же состоит в том, что программы на C обычно имеют очень мало потоков.
А что мешает программисту вызывать подпрограммы (процедуры, функции) с указанием «эту подпрограмму можно выполнять в отдельном потоке»?

Использовать это в C сложно, потому что автовекторизатор должен рассчитать параллелизм на основании циклов в коде.
А где просто — в Паскале, в Фортране, в Питоне?

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

Я полагаю, что производителям вообще надо смотреть в сторону архитектуры «у каждого процессора — своя собственная память». При этом между процессорами д.б. очень быстрая шина — примерно как PCI-X по скорости, но без её глупых заморочек.

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

В сфере разработки программного обеспечения есть миф о том, что параллельное программирование — это сложно.
Да вопрос даже не в сложности. А в существовании массы готовых программ и алгоритмов (алгоритм — это как бы неготовая программа), написанных в парадигме последовательного программирования. И переделать это всё на параллельное программирование — реально сложно.

Алан Кэй был бы очень удивлён, услышав это: он научил детей использовать модель акторов, с помощью которой они писали программы на более чем 200 потоков.
Замечательно. А что делают эти программы? Может, там есть какие-то новые алгоритмы?
Написать программу на 200 потоков — мало. Надо, чтобы эта программа ещё и делала что-то полезное. Ну, хорошо бы написать офисную программу (Word, Excel), графический редактор (PhotoShop), браузер или Web-сервер — и сравнить с существующими. Даже не обязательно воспроизводить полностью функциональность — хотя бы основную.
НЛО прилетело и опубликовало эту надпись здесь
Стандарт в этом месте не очень понятен, насколько я понимаю.
Достаточно понятен:
The following type designates a signed integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
intptr_t
The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
uintptr_t
These types are optional.
Если типа, куда «влазит» указатель нет — тогда нет и соотвествующих типа intptr_t/uintptr_t, но если они есть… то да, можно преобразовывать. На этом разработчики E2K обожшлись, так как эта «фича» всё тегирование им поломала…
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Статистика — из популярности облачных вычислений, хостинга и всего такого. Там — виртуалки.

Много ли людей запускают у себя на машине считалку на много потоков?
И при этом их считалки не способны распараллеливаться на несколько компьютеров (кластер)?
НЛО прилетело и опубликовало эту надпись здесь
Язык Си — низкоуровневый из-за того, что там масса случаев неопределённого поведения. Из-за этого возможны проблемы при смене компилятора (на том же процессоре и операционке); и возможны ещё более другие проблемы при смене платформы, особенно процессора (например, при переходе от плоской адресации к сегментной).
Что за бред? Неопределённое поведение существует только и исключительно для того, чтобы облегчить перенос на другую платформу. В том числе на платформу с сегментной адресацией, если нужно.

Если ваша программа вызывает неопределённое поведение, то не нужно искать «правильный» компилятор или «подходящую» версию. Нужно программу исправить — только и всего. И тогда никаких проблем с портированием у вас не будет.

Лично мне кажется, что гораздо более неприятную проблему представляет необходимость выполнять программы, скомпилированные для *86-й архитектуры, и прежде всего — Windows, которую MicroSoft сама не портирует на другие процессоры и другим не позволяет.
Проблема курицы и яйцы, на самом деле. Windows за время своего существования была портирована на полдюжины платформ точно. Только никому это оказалось не нужно, потому что программы все скомпилированы под x86.

В процессоре ARM есть возможность сделать любую команду условный — т.е. резко снизить количество условных переходов.
В AArch64 уже нет.

Очевидно, необходимость переименования регистров вызвана неудачной архитектурой набора команд процессора. Если предусмотреть много регистров (столько, сколько их будет реально в процессоре) — то программа (сформированная человеком или компилятором) будет обращаться к тем процессорам, которые есть — а не к виртуальным, которые используются ради совместимости тридцатилетней давности.
И снова «мимо». Проблема в том, чтобы сделать много архитектурных регистров не в том, что поломается совместимость, а в том, что их нужно будет адресовать — то есть увеличивать размер программ. Что приведёт к тому, что вместо того, чтобы тратить место на железо, осуществляющее переименование регистров вы будете тратить его на кеш.

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

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

2) Обращаться к битам выравнивания — запрещено. А если туда не обращаться — то их содержимое неважно.
Как это запрещено? Вы всегда можете сделать memcpy!

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

А разве так разрешается делать?
Есть в реализации есть опциональный тип intptr_t — то да.

Компиляторы могут решить, что два указателя на результаты malloc() при сравнении всегда дают отрицательный результат, даже если они указывают на один и тот же адрес.
А можно пример?
Примерно что-то подобное недавно обсуждавшемуся. Хотя конкретно с malloc'ом у меня не получилось.

3) Мне кажется, что главное отличие Sparc — в большом количестве регистров и в наличии регистрового файла.
Он говорит не про Sparc, а про Ниагару. Где отсутствие всяких спекуляций компенсируется тем, что каждое ядро исполняет 4 потока. POWER9 умеет 8 потоков на ядро исполнять.

Исследовательские процессоры расширили этот концепт до очень большого количества аппаратно-планируемых потоков. Ключевая идея состоит в том, что с достаточным количеством потоков процессор может приостановить те потоки, которые ожидают данных, и наполнить исполнительные устройства инструкциями из других потоков.
Это же HyperThreading!
Да, но HyperThreading — это очень ограниченный вариант. Речь идёт о всяких процессорах, исполняющих сотни потоков в параллель.

Проблема же состоит в том, что программы на C обычно имеют очень мало потоков.
А что мешает программисту вызывать подпрограммы (процедуры, функции) с указанием «эту подпрограмму можно выполнять в отдельном потоке»?
То что процессоры имеют мало потоков, в основном. Да — это проблема курицы и яйца классическая…

Использовать это в C сложно, потому что автовекторизатор должен рассчитать параллелизм на основании циклов в коде.
А где просто — в Паскале, в Фортране, в Питоне?
Хотя Occam, хоть Haskell. Да и даже, отчасти, Go.

Протокол поддержки когерентности кеша — одна из сложнейших составляющих современного процессора.

Это — проблема не процессора, а общей архитектуры компьютера.

От языка программирования тут вообще ничего не зависит.
Зависит. Если вы на этом железе хотите запускать что-то, что считаете, что у делеза есть только одна память (типа C), то вам нужно поддерживать когерентность кешей, если языки этого не требуют (в них, например, нельзя передавать из одного потока в другой указатели) — то всё это не нужно.

Я полагаю, что производителям вообще надо смотреть в сторону архитектуры «у каждого процессора — своя собственная память». При этом между процессорами д.б. очень быстрая шина — примерно как PCI-X по скорости, но без её глупых заморочек.
Infiniband плюс GPGPU, да. Типичная прхитектура суперкомпьютеров сегодня. Но программы на C там не запустить.

В сфере разработки программного обеспечения есть миф о том, что параллельное программирование — это сложно.
Да вопрос даже не в сложности. А в существовании массы готовых программ и алгоритмов (алгоритм — это как бы неготовая программа), написанных в парадигме последовательного программирования. И переделать это всё на параллельное программирование — реально сложно.
Однако выбора-то и нету! Иначе дальнейшее увеличение количества транзисторов окажется бессмысленным.

Алан Кэй был бы очень удивлён, услышав это: он научил детей использовать модель акторов, с помощью которой они писали программы на более чем 200 потоков.
Замечательно. А что делают эти программы? Может, там есть какие-то новые алгоритмы?
Написать программу на 200 потоков — мало. Надо, чтобы эта программа ещё и делала что-то полезное. Ну, хорошо бы написать офисную программу (Word, Excel), графический редактор (PhotoShop), браузер или Web-сервер — и сравнить с существующими. Даже не обязательно воспроизводить полностью функциональность — хотя бы основную.
И снова проблема курицы и яйца: подобная программа на существующем железе будет медленнее того, что у нас есть… а другое железо без соответствующих программ «не взлетит».

Основная проблема — как раз даже не в C, а в JavaScript: это убожество было изначально заточено под однопоточное исполнение и так просто от этого не отойти. Очень забавно что такой «типа высокоуровневый» язык оказался де-факто более «низкоуровневым», чем C…
НЛО прилетело и опубликовало эту надпись здесь
В процессоре ARM есть возможность сделать любую команду условный — т.е. резко снизить количество условных переходов.
В AArch64 уже нет.

Добавлю, что это изменение как раз и было связано с предсказателем переходов: предсказывать ветвления, которые встречаются одно на семь инструкций, проще и дешевле, чем предсказывать про каждую инструкцию, выполнится она или нет.
Т.е. предложенный Karpion‌-ом способ «резко снизить количество условных переходов» — не снижает нагрузку на предсказатель, а наоборот, увеличивает.
Добавлю, что это изменение как раз и было связано с предсказателем переходов: предсказывать ветвления, которые встречаются одно на семь инструкций, проще и дешевле, чем предсказывать про каждую инструкцию, выполнится она или нет.
Никто и никогда не предсказывал ничего про отдельную инструкцию. Там всё проще: выполняем всегда, если флаги говорят «не исполнять» — не записываем результат обратно в регистр. Всё. Предсказатель действительно оказался не при делах.

Проблема там была ровно в том, что это тратило 4 дефицитных бита в кодировке инструкции. А выигрыш, при приличном предсказателе ветвлений, невелик.

Т.е. предложенный @ Karpion‌-ом способ «резко снизить количество условных переходов» — не снижает нагрузку на предсказатель, а наоборот, увеличивает.
Нет-нет, он работает. Просто оказалось, что гораздо полезнее добавить регистров и реализовать то, что требуется, с помощью отдельной инструкции: conditional move.

Но фундаментально это проблемы не решает: можно спорить что выгоднее — 16 регистров или 32, но оптимум где-то тут. 128 или 256 архитектурных регистров себя не оправдывают. А 256 физических — вполне могут оказаться при делах.

Но, в конечном итоге, мы получаем следующий результат: увеличив количество транзисторов в ядре раз так примерно в 100 мы получаем ускорение в 3-4 по сравнению с 80486м, который большинство инструкций исполнял за один такт. Ясно, что если бы писали код на языках, который хорошо параллелятся и потоков у нас было бы больше — то выигрыш был бы весьма неплох… но такие программы плохо работают на существующем железе!
Там всё проще: выполняем всегда, если флаги говорят «не исполнять» — не записываем результат обратно в регистр. Всё.

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

Инструкция addeq r1, r2, r3 реализуется как r3 = (r3 & !zf) | (zf & (r1 + r2)).

Никакого предсказания ветвлений и никаких спекуляций. Да, это вводит дополнительную зависимость от r3 (который мы теперь безусловно не только пишем, но и читаем), но это оказывается, как правило, эффективнее, чем предсказывать на каждую инструкцию ветвления…

P.S. Кстати это очень хорошо заметно на инструкциях, пересылающих из памяти, на x86, как раз. cmove (%rax), %rax — вызывает исключение если %rax == 0 даже если ZF не установлен.
Что будет с инструкцией, следующей за addeq и использующей её результат? Будет стоять в конвейере колом, пока addeq не дойдёт до retire?
Не понял вопроса. А что будет с инструкцией, следующей за обычно, безусловной, add?

Всё то же самое точно: спекулятивное исполнение, запись в теневые регистры и прочее, прочее, прочее. Просто инструкция принимает не два аргумента, как у обычной add, а четыре (два входных параметра, плюс выходной и флаг). Всё, больше никакой разницы.
А что будет с инструкцией, следующей за обычно, безусловной, add?

Начнёт исполняться, как только будет вычислена сумма — ещё до записи суммы в регистр.
А за условной — не может, пока не знает, будет сумма записана или не будет.
Неопределённое поведение существует только и исключительно для того, чтобы облегчить перенос на другую платформу. В том числе на платформу с сегментной адресацией, если нужно.
Я что-то не помню проблем неопределённого поведения с переносом программ на Fortran, Java, JS, Perl, Python и т.б. скриптовых программ для 1С.

Если ваша программа вызывает неопределённое поведение, то не нужно искать «правильный» компилятор или «подходящую» версию. Нужно программу исправить — только и всего. И тогда никаких проблем с портированием у вас не будет.
Я как бы в курсе этого. Но мы тут не обсуждаем правильное написание программ на C — а сравниваем языки по их «уровню». И я предлагаю в качестве критерия уровня — неопределённое поведение.

Windows за время своего существования была портирована на полдюжины платформ точно. Только никому это оказалось не нужно, потому что программы все скомпилированы под x86.
Не вижу проблемы в том, чтобы MicroSoft скомпилировала свои программы (в первую очередь — Windows и Office) под другую платформу. Ну и предложить другим разработчикам (типа Adobe) сделать то же самое.

В AArch64 уже нет.
Я слышал. Похоже, в ARM повывелись люди, умеющие мыслить оригинально.

Проблема в том, чтобы сделать много архитектурных регистров не в том, что поломается совместимость, а в том, что их нужно будет адресовать — то есть увеличивать размер программ.
Ну, сделаем грубую прикидку.
Допустим, у нас сейчас восемь регистров (в процессоре *86 как раз столько рабочих регистров). Допустим, средний размер команды = четыре байта. Допустим. средняя команда содержит упоминание двух регистров. Допустим, мы увеличим количество регистров в четыре раза — это потребует ещё четыре бита. Т.е. размер команды (и всего программного кода, если количество команд в программах не изменится; хотя при росте количества регистров — количество команд в программе можно понизить) увеличится на одну/восьмую, т.е. на 12.5%. Это разве много?

Кстати, размер кэша кода (на тех уровнях кэша, где кэш раздельный) вообще не вырастет.

Интел примерно это и делает {- с каждым новым чипом выпускался бы новый компилятор, предназначенный именно для этого чипа}.
А что с программами, откомпилированными прежними версиями компилятора? Под какой процессор оптимизируются популярные программы типа Windows?
Сколько производительности теряется на других процессорах — относительно того, как если бы компилировали именно под этот процессор?

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

Как это запрещено? Вы всегда можете сделать memcpy!
Исходный вопрос был: «Если вы заполните эту структуру нулями и потом укажете значение некоторым полям, будут ли в битах выравнивания нули?»
Во-первых, я не вижу, где заполнили структуру нулями, и где дали значения полям.
Во-вторых, я не представляю ситуации, когда осмысленная программа будет считывать биты выравнивания; и от их значения будет зависеть результат выполнения этой программы.

Есть в реализации есть опциональный тип intptr_t — то да.
Интуиция подсказывает мне, что если такой тип есть — то «указатель, который конвертировали в целое (только не просто целое, а именно в intptr_t) и назад» должен сохранять своё значение. Потому что если он не сохраняет — то нефиг и вводить такой тип.

Он говорит не про Sparc, а про Ниагару. Где отсутствие всяких спекуляций компенсируется тем, что каждое ядро исполняет 4 потока.
Мне всегда казалось, что в *86 ядра сделаны по образу процессоров — и предназначены для выполнения разных процессов; т.е. четырёхядерный процесс может исполнять четыре процесса или один процесс с четырьмя потоками (на и разные промежуточные варианты). А Ниагара пытается экономить на страничных дескрипторах, так что условный/примерный процессор может выполнять только один процесс с четырьмя потоками — благо потоки имеют одно адресное пространство.

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

То что процессоры имеют мало потоков, в основном.
Количество ядер в современных процессорах — немаленькое.

Если вы на этом железе хотите запускать что-то, что считаете, что у делеза есть только одна память (типа C), то вам нужно поддерживать когерентность кешей
А где в стандарте C сказано «только одна память»?

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

Infiniband плюс GPGPU, да. Типичная прхитектура суперкомпьютеров сегодня. Но программы на C там не запустить.
Есть два компьютера. На одном — Web-сервер, на другом — СУБД, на третьем — браузер. Фактически, они работают в связке. И всё написано на C. Разве плохо?

И снова проблема курицы и яйца: подобная программа на существующем железе будет медленнее того, что у нас есть…
А я не просил «быстро». Я просил «чтобы работало». Ведь мы обсуждаем «есть миф о том, что параллельное программирование — это сложно» — ну так покажите мне, как дети написали что-то полезное, и это им оказалось несложно. Но я хочу, чтобы оно стабильно работало.

Основная проблема — как раз даже не в C, а в JavaScript: это убожество было изначально заточено под однопоточное исполнение и так просто от этого не отойти.
А в разных окнах/вкладках — тоже последовательно?
И я предлагаю в качестве критерия уровня — неопределённое поведение.

А мне кажется, что UB не является прерогативой компилятора или стандартной библиотеки. Фактически, утверждение "X — UB", это:


  1. Х — баг
  2. Нет документированной обратной связи в случае, если допустили Х.

И библиотека может создавать собственные UB.

НЛО прилетело и опубликовало эту надпись здесь
UB не гарантирует результата, значит место, которое допускает UB — баг. То, что всякие линусы специально абузят UB какого-нибудь GCC этого не отменяет.
НЛО прилетело и опубликовало эту надпись здесь
Как без указателей-то?
Глобальные переменные. Уже настолько это «фу-фу», что и на ум не приходят?
Я как бы в курсе этого. Но мы тут не обсуждаем правильное написание программ на C — а сравниваем языки по их «уровню». И я предлагаю в качестве критерия уровня — неопределённое поведение.


UB есть в любом языке. Например тут — о том, зачем они нужны вообще и почему языков без UB по сути не бывает.
На самом деле, конечно, языки без UB бывают. В частности Java была сделана попытка сделать язык без UB. Не до конца успешная, впрочем, как и в Ada.

Но это очень «дорого» стоит. И обязательно требует GC (или жёсткого учёта времени жизни в переменной как в rust'е — хотя в самом rust'е UB, конечно, бывает).
во FreeBSD реализация malloc() сообщает системе, что страницы более не задействованы, а система использует первую запись в страницу как доказательство того, что это не так
Это что вообще значит?
Я это понял так, что malloc резервирует адресное пространство, но подставляет в него физические страницы при первом обращении на запись.

То есть, чтение памяти, выданной malloc, без записи в неё, это UB, и запросто может вызвать page fault, если выделение физических страниц привязано к записи по адресу, а к чтению не привязано.

Ну, я до сих пор полагал, что это делается немного совсем иначе:


  • В системе есть одна реальная страница памяти, забитая нулями. Она выделяется задачам только на чтение, поэтому она всегда остаётся такой "занулённой".
  • Когда задача просит у ядра выделить ей память — ядро выделяет ей эту страницу. Т.е. в таблице страничных дескрипторов проставляются ссылки на эту страницу — как я сказал выше, все ссылки разрешают только чтение (в дескрипторе страницы — содержится её адрес и права доступа).
  • Теперь при чтении — мы получаем нули. Заодно облегчается работа кэша.
  • А вот при первой же операции записи — происходит exception, и ядро понимает, что память надо выделить реально.

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


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


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

Вы слишком уходите в практические аспекты.

Пример этот был дан, чтобы продемонстрировать ситуацию, когда чтение перед записью, являясь UB, действительно может привести к неожиданным последствиям, даже к page fault.

Хотя, схема не имеет дыр. Можно при попытке записи в страницу подставлять на этот адрес новую занулённую страницу из пула страниц (а чтение будет fault-ить). Не вижу преимуществ к тому, чтобы сразу после malloc держать там занулённую read-only страницу.
Пример этот был дан, чтобы продемонстрировать ситуацию, когда чтение перед записью, являясь UB, действительно может привести к неожиданным последствиям, даже к page fault.
Быстро поднятое не считается упавшим. Чем этот page fault будет отличаться от page fault'а, который произойдёт если стараницу вытеснили на диск? Сигнал в приложение же не поступит!
Суть примера в том, что стандарт обязывает не передавать исключение в приложение при первой записи в страницу, но ничего не говорит, что делать, если первое обращение на чтение. Вполне позволительно бросить исключение в процесс — UB оно такое.
А! Понял наконец. Да. Теоретически такое возможно. Практически — так никто не делает.
Не знаю, делает или нет, но мотивация «в случае UB сразу же крэшиться, чтобы программист его заметил и исправил» вполне достаточная, на мой взгляд.
Только если вы что-нибудь типа ubsan используете. В противном случае всё наоборот: мы знаем что программист обеспечивает нам отсуствие в программе UB (это его работа) и за счёт этого делаем программу быстрее.

В ubsan… это интересная идея… хотя asan всё-таки куда надёжнее…
Так это получается практически бесплатно. Крашиться, если первое обращение на чтение.
Осталось понять как вы «забесплатано» выясните, что это первое обращение. Если вы запретите чтение или запись — то получите exception там, где его не было. Плюс будет необходимо два раз менять права доступа, вместо одного. Извините — но это очень далеко от понятия «бесплатно».

А если права доступа не менять — то никакого краха и не будет.
Зачем менять два раза права, наверное вы спутали меня с оппонентом, который предлагал подставлять r/o страницу с нулями и делать её Copy-on-write при записи?

Осталось понять как вы «забесплатано» выясните, что это первое обращение.
Это очевидно. Выделение памяти через malloc пусть даёт виртуальный адрес без маппинга на него физической страницы. Любое обращение вызовет page fault, и даст возможность подставить физическую страницу на этот адрес. Нюанс лишь в том, что если это обращение чтение, то можно страницу не подставлять, а закрашить приложение для надёжности.
Любое обращение вызовет page fault, и даст возможность подставить физическую страницу на этот адрес.
То есть вы можете обнаружить только одно обращение на страницу ценой существуенного усложнения всей системы.

Выделение памяти через malloc пусть даёт виртуальный адрес без маппинга на него физической страницы.
Это сработает только если вы не вызывали недавно free. Изменение таблиц распределённых страниц — весьма дорогая операция и аллокаторы стремятся её избежать. Вы же предлагаете через неё прогонять либо все аллокации (что очень дорого), либо делать это только тогда, когда нам и так нужно запросить память в ядре (что будет приводить к тому, что в 99% случаев это срабатывать не будет).

Я не уверен, что такая фича кому-то будет полезна, чесслово.
Я не предлагаю каждый выделенный через malloc регион памяти помечать как недействительный и ждать первого обращения. Я предлагаю в тех сценариях, когда malloc вернул указатель на адрес без физической страницы (а такие сценарии существуют) проверять тип первого обращения. Это практически бесплатно. Да, мы не превратим все UB в исключения, но хотя бы малую часть почему не сделать, раз всё равно бесплатно.
Я предлагаю в тех сценариях, когда malloc вернул указатель на адрес без физической страницы (а такие сценарии существуют) проверять тип первого обращения.
В данный момент требования стандарта (память выделенная malloc содержит значения, являющиеся либо валидными, хотя и неопределёнными значениемя, либо trap value) реализованы буквально и таких вариантов нет.

Я так понимаю вы имеете в виду случаи, когда malloc напрямую запрашивает информацию для выделенного участка через mmap? В этом случае начальная страница используется под служебную информацию всё равно, ну да ладно, это можно, в принципе, переделать.

Хуже то, что это происходит когда выделается большой участов в один приём: от 128K по умолчанию. Как правило таких буферов в программе немного и разобраться с ними несложно. Проблемы обычно случаются там, где аллокаций много, они маленькие и ваша техника неприменима от слова совсем.

Да, мы не превратим все UB в исключения, но хотя бы малую часть почему не сделать, раз всё равно бесплатно.
Потому что это не бесплатно. Это серьёзное усложнение кода, это уменьшение локальности данных, а проблемы с обработкой исключений, либо новая функциональность в ядро. И ради чего? Ради того, чтобы обнаружить ошибки с вероятностью, дай бог, 1/100?

Будете делать свою ОС — можете резвлечься, но я уверен, что ни в одной нормальной ОС такого не будет…
НЛО прилетело и опубликовало эту надпись здесь
И ради чего? Ради того, чтобы обнаружить ошибки с вероятностью, дай бог, 1/100?

Ради того, чтобы потом разгребать на 1/100 меньше баг-репортов, очевидно же.
Вы зря считаете, что помощь криворуким программистам — это благотворительность pro bono. Мейнтейнеры GCC считают, что она окупается.
Мейнтейнеры GCC считают, что она окупается.
Только в случаях, когда вопроизводимость достаточно высока, а затраты на «отлов» — малы.

В вашем предложении — всё наоборот.
НЛО прилетело и опубликовало эту надпись здесь
В противном случае всё наоборот: мы знаем что программист обеспечивает нам отсуствие в программе UB (это его работа) и за счёт этого делаем программу быстрее.

Как раз сегодня эту тему обсуждали на GNU Tools Cauldron, и — нет, большинство разработчиков GCC (те самые, кто разгребают баг-репорты на компилятор) считают, что программист заведомо неспособен написать нетривиальную программу без вероятности UB, и что когда компилятор может с разумными затратами обнаружить UB — лучшей практикой будет это обнаружение включить, и отключать его ради сверхагрессивной оптимизации только по специальному флагу наподобие -funsafe-math-optimizations.
«Разумная вероятность» — тут ключевой момент. Дело даже не в том, что описанный подход потребует некоторых затрат. А скорее в том, что он сложный, а ошибки обнаруживает с очень-очень малой вероятностью.
Если я правильно понимаю, то речь в статье не о поведении ОС, а о поведении стандартной библиотеки Си, которая «придерживает» освобождаемые страницы памяти для повторного использования, но обнуляет их только при первой попытке записи.
Тогда «прежний хозяин» данных — тот же самый процесс, и проблема безопасности не стоит.
Никакая библиотека, работающая в user-space, не может заблокировать страницу памяти от записи — для этого надо обращаться в ядро.

Я также не вижу смысля тянуть с обнулением страниц.

И наконец, библиотека должна оперировать малыми кусками памяти, а не страницами.
Никакая библиотека, работающая в user-space, не может заблокировать страницу памяти от записи — для этого надо обращаться в ядро.
Да, надо. И что ей мешает?
Я также не вижу смысля тянуть с обнулением страниц.
Экономия времени в случае, если выделенная память будет использоваться не вся.
И наконец, библиотека должна оперировать малыми кусками памяти, а не страницами.
У ОС она всё равно запрашивает память страницами, значит и внутренний учёт страниц должна вести.
Допустим, программа говорит free(), а потом malloc(). Обе операции обрабатывает библиотека. На операции free() — библиотека может обратиться в ядро и отдать ему эту память. Ну а может обратиться в ядро с просьбой пометить эту память как ro. Я как-то не вижу особой разницы в трудоёмкости.

«Экономия времени в случае, если выделенная память будет использоваться не вся.» — тоже очень сомнительно. Ибо обработка exception — довольно дорогая.
Вот экономия памяти — это да, интересно.
На самом деле всё ещё хуже: дорого не только exception обработать, дорого и права доступа менять тоже.
Кстати, а что именно там дорого? Ну, пробежались по таблице дескрипторов, поменяли битики…
Ну да, запись в память, занесение новых данных в кэш дескрипторов…

Платить эту цену — имеет смысл ради экономии памяти. И только на больших массивах.

Кстати, есть ещё одна экономия: когда программа реально хочет память, мы можем урезать ей приоритет и этим притормозить её выполнение. Зато когда приоритет всё-таки вырастет достаточно, чтобы программа из ready перешла в running — мы вернём ей временно отнятый приоритет. В результате программа быстро выполнит тот кусок, в хоте которого ей нужно много памяти; и есть вероятность, что она скоро отдаст память обратно.

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

Платить эту цену — имеет смысл ради экономии памяти. И только на больших массивах.
Как правило смысл возникает только в настолько больших массивах, что с ними дешевле программе предоставить самой управлять памятью. У стандартного malloc'а нет достаточной информации — будет выигрыш от перевода в ro и ожидания, пока память запросят повторно или нет.

В результате программа быстро выполнит тот кусок, в хоте которого ей нужно много памяти; и есть вероятность, что она скоро отдаст память обратно.
В современных системах «много памяти» — это десятки, а то и сотни, гигабайт. «Скоро» с ними ничего не сделать, даже просто чтобы заполнить их чем-нибудь требуются секунды, за которые программа будет много раз вытеснена и заменена чем-то ещё…

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

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

Подкачка интересна в случаях, когда есть какие-то программы, активизируемые редко. В принципе, их можно запускать и прибивать по мере необходимости. Но иногда лучше (а иногда — просто необходимо) запустить такую программу в режиме демона и выпихнуть на диск, отдав память под кэширование более актуальных данных.
Типичный пример такой программы = getty, особенно ночью.
а также и в кеши всех других процессоров в системе.

Как это обычно делается — push или pull система? обозначаем для других процессоров, что есть необходимость в перегрузке или есть push-alike механизм?
Push. Дёргается прерывание и в ответ на него нужный элемент все процессоры сбрасывают из своего Кеша. Это не просто дорого, на большой системе это может быть ОЧЕНЬ дорого.

Потому современные коллекторы «заказывают» у ядра большие блоки, а потом уже в них всё сами распределяют…
Всё мимо. Настоящая причина, по которой так никто не делает — это куча проблем при попытке помирить требования POSIX на работу функции signal, и такую вот «незаметную» обработку SIGSEGV.

Если же говорить не про библиотеку, а про программу — то так очень даже делают. В частности небезивестный Bourne shell так делает (не bash, а именно оригинальный, который тот самый Стефан написал), некоторые лисп-системы так делают…

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

Думаю, язык надо называть низкоуровневым не тогда, когда он соответствует железу, а тогда, когда он работает с низкоуровневыми абстракциями.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий