LINUX.ORG.RU

Функциональщина на C++

 , ,


0

5

По мотивам: Си с классами

бери с++20 с концепциями, корутинами и ренжами. игнорируй всё из с++17, сфинае, не пиши упоротые шаблоны, вообще шаблоны старайся не писать, и всё будет ок.
концепции уже вроде работают, ренжи тоже есть, корутины ещё не подъехали, но в будущем пригодятся, генераторы там всякие, всё такое. ещё будет проще потом перелезть на экзекюторы и т.д. потом ещё модули затащишь.

Как эффективно учиться? (комментарий)

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

Конкретно мне интересен функционально-процедурный подход к написанию кода на крестах, что-то похожее на Rust, только без абсурдной помешанности на безопасности памяти, так сказать «си с плюшками», но совсем НЕ «си с классами», как было в упомянутом треде. Для примера: Qt и UE — это примеры плохой архитектуры в данном контексте. Например, fstream — это плохая реализация файловых операций, поскольку скатывается в классы и исключения, в ней даже нельзя без исключений получить конкретную ошибку файловых операций.

Итак: какие есть конкретные хорошо проработанные приемы и библиотеки для писания на крестах в функционально-процедурном стиле?

★★★

Ответ на: комментарий от LamerOk

С появлением перестало подстраиваться?

С её появлением производители железа стали ориентироваться на эту стандартную модель памяти, а раньше ориентировались на неявно задаваемую софтом. Ну и сейчас тоже продолжают, потому что еще много софта, написанного задолго до С11. Сильно жить это не мешает, поскольку тот же х86 продолжает тащить «весьма строгую» модель памяти ради совместимости с древним софтом. Особенно это создает проблемы на многоядерных CPU. Потому что когда у тебя 64+ ядра, то держать все кэши когерентными мало не покажется. В акселераторах так не делают, ибо нафиг такое счастье не надо. Там массивы PU с локальной памятью и мессаджинг между ними.

Времена, когда CPU был центральным и единственным, заканчиваются. А с ними заканчивается и «модель памяти С».

aist1 ★★ ()
Ответ на: комментарий от anonymous

Раньше скорее софт подстраивался

Это когда IBM System/360 1964-ого года эмулировал IBM 1401 1959-ого года?

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

Дубль 2: все эти тезисы - ложная дилемма яйца и курицы.

LamerOk ★★★★★ ()
Последнее исправление: LamerOk (всего исправлений: 1)
Ответ на: комментарий от aist1

х86 продолжает тащить «весьма строгую» модель памяти ради совместимости с древним софтом. Особенно это создает проблемы на многоядерных CPU. Потому что когда у тебя 64+ ядра, то держать все кэши когерентными мало не покажется.

Именно по этому в армах её выкинули, а в крестах ради армов впилили std::memory_order.

И весь тезис

С её появлением производители железа стали ориентироваться на эту стандартную модель памяти

превращается в 4.2.

LamerOk ★★★★★ ()
Ответ на: комментарий от LamerOk

Если ты хочешь, чтобы твой С-код скомпилялся и заработал на некотором условном многоядерном процессоре, то этот процессор должен тебе дать две вещи:

1. Атомарные операции над переменными. 2. Единое адресное пространство для всех ядер.

Иначе на таком процессоре существующий многопоточный С-код не заработает.

Это нормально для MCU, это нормально для акселераторов, на которых «существующий многопоточный код» выполняться всё равно не будет.

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

Так понятнее?

Если не понятнее, то попробуй прикинуть, сколько CPU нужно выполнить разных операций, чтобы переслать 4 байта с адреса A на адрес B.

aist1 ★★ ()
Ответ на: комментарий от aist1

поскольку тот же х86 продолжает тащить «весьма строгую» модель памяти ради совместимости с древним софтом

Это не для C. Это для того, что руками на асме писалось. У C модель памяти как раз не строгая(и ранее таковой не была). Про необходимость единого адресного пространства согласен.

anonymous ()
Ответ на: комментарий от aist1

Если ты хочешь, чтобы твой С-код скомпилялся и заработал на некотором условном многоядерном процессоре, то этот процессор должен тебе дать две вещи: 1. Атомарные операции над переменными. 2. Единое адресное пространство для всех ядер.

Недопонял. (a) Разве только C-код, а не любой императивный (с масляным маслом мутабельным состоянием)? (b) Достаточно единого виртуального адресного пространства для всех потоков процесса; но опять же, это про императивный код с ручным параллелизмом, не? Однако в светлое чисто-ФП будущее всего человечества лично я как-то не верю.

dimgel ★★★★ ()
Ответ на: комментарий от aist1

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

Если ты хочешь, чтобы твой С-код скомпилялся и заработал
1. Атомарные операции над переменными.

Я хочу консистентных операций над данным на ЛЮБОМ языке, которым с ними работаю. Меня ни на каком языке не устроит, если при сохранении данных в ячейку памяти, потом оттуда будет прочитано хер пойми что, лишь случайно совпадающие с записанным.

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

2. Единое адресное пространство для всех ядер.

У меня даже в рамках одного ядра может не быть единого адресного пространства - см. сегментные модели адресации. И да, с ними тоже можно было работать на С.

LamerOk ★★★★★ ()
Ответ на: комментарий от dimgel

Ну вот смотри. У меня R9 5950X и в его кремниевом бюджете я мог бы сделать 10000 маленьких RV64 ядер с локальной scratchpad memory и явными пересылками между глобальной памятью и локальной. Высвободившийся кремниевый бюджет я мог бы потратить на те же ядра или проблемно-специфичные акселераторы (сжатие, шифрование, битовые операции, векторные операции и т.д.).

Цена вопроса:

  • Я уже не могу использовать ссылочные структуры данных, так как нет единого адресного пространства. Нужна или ручная трансформация пространств при перемещении данных, или какие-нибудь продвинутые кодировки указателей, например относительные. Оно решается, но софтовый стек должен всё это уметь. А, как показывает практика, софтовый стек оказывается очень ригидным.
  • Мне теперь вручную надо программировать все эти тыщи ядер, что нереально. Частные случае типа data-parallel слишком частные.

Вот это и есть «модель Си» в широком смысле:

  • небольшое количество «быстрых» универсальных OoOE-ядер
  • единое на всех линейное адресное пространство
  • неявные, управляемые железом и ОС пересылки в память, контроллируемые через блокировки и атормарные операции.

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

Поэтому, модель памяти тащит потом за собой буквально всё.

aist1 ★★ ()
Ответ на: комментарий от anonymous

У С и С++ ранее модели памяти не было вообще, и многопоточные программы по факту не были кроссплатформенными. Они и сейчас не особо, потому что не все сейчас пользуются барьерами правильно. Оно может работать сейчас на x86/ARM/RV, но выйдет какая-нибудь новая хитропопая архитектура, и начнутся проблемы.

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

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

aist1 ★★ ()
Ответ на: комментарий от LamerOk

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

А я и не привязываю. Она так называется: «модель памяти Си». Все остальные языки с явно заданной моделью памяти, так или иначе её воспроизводят или эмулируют (если могут). И вот это «если могут», ложится на разработчиков железа. Они эту возможность должны поддержать, чтобы эти программы могли быть портированы на это железо.

aist1 ★★ ()
Последнее исправление: aist1 (всего исправлений: 1)
Ответ на: комментарий от dimgel

однако в светлое чисто-ФП будущее всего человечества лично я как-то не верю.

ФП ограничивается необходимостью работы с ФСД, которые строятся над CoW-деревьями и имеют асимптотипку O(Log N) в худшем случае. Там можно амортизировать, если есть хорошая кэш-локальность, или вообще элиминировать средствами компилятора. Но, в общем случае, оно все равно имеет логарифмическую асимптотику для случайного доступа.

И тут есть два момента.

1. Динамические структуры данных (тот же динамический массив) тоже строится над деревом, так что получить из него ФСД можно, просто добавив CoW-семантику. Асимптотика при этом не ухудшится.

2. ФСД можно поддерживать аппаратно в иерархии памяти. И я думаю, что к этому придут. Потому что сейчас есть избыточные кремниевые бюджеты, которые хочется пускать на что-то более полезное, чем еще больше OoOE-ядер. Асимтотику это не снизит, но уменьшит скрытые константы.

Я думаю, что функциональный стиль со временем победит (через структуры данных), и я приложу к этому свои шаловливые ручки. Гы. У меня в Мемории будет площадка для экспериментов RISC-V акселераторов для ускорения операций с продвинутыми структурами данных. Для внешней памяти ФСД вполне себе натуральны и очень хорошо ложатся на физику.

aist1 ★★ ()
Ответ на: комментарий от dimgel
Ответ на: комментарий от aist1

У С и С++ ранее модели памяти не было вообще, и многопоточные программы по факту не были кроссплатформенными

Это не совсем так. Формально, на уровне стандарта - да, как бы не было. Но C/C++ тем и отличаются, что даже стандарт там расширяемый и фактически модель была(в реализациях, без неё никак).

Оно может работать сейчас на x86/ARM/RV, но выйдет какая-нибудь новая хитропопая архитектура, и начнутся проблемы.

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

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

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

anonymous ()
Ответ на: комментарий от LamerOk

Пардон, не хотел мешать царю показывать свою гениальность. Теперь мой ход.

х86 продолжает тащить «весьма строгую» модель памяти ради совместимости с древним софтом. Особенно это создает проблемы на многоядерных CPU. Потому что когда у тебя 64+ ядра, то держать все кэши когерентными мало не покажется.

Именно по этому в армах её выкинули, а в крестах ради армов впилили std::memory_order

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

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

Linux только пару лет назад наконец пришел к системным вызовам и вводу-выводу через кольцевой буфер, что является самым оптимальным методом с точки зрения процессора x86 и высокопроизводительных ARM (и еще каких-то других), но работать с ним в Си намного сложнее:

https://github.com/shuveb/io_uring-by-example/blob/master/01_regular_cat/main.c — обычный синхронный код
https://github.com/shuveb/io_uring-by-example/blob/master/02_cat_uring/main.c — код с io_uring

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

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

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

Здесь я задел еще и вопрос кэш-линий процессора. Они оптимизированы под линейное чтение, которое характерно именно для Си, и которое не характерно для многих класс-ориентированных и просто динамических языков, вроде Java, JS, Python, etc. Как выше упоминали, писать операции на гомогенных массивах на той же жаве — это боль и страдание. Если средняя операция в вакууме читает 8-16-24 байт, то зачем иметь линию кэша 64 байта? Все остальные прочитанные в линии байты будут бесполезны.

byko3y ★★★ ()
Ответ на: комментарий от EugeneBas

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

Я предлагаю следующие варианты:

1. Любой «неправильный дизайн» будет содержать dynamic_cast (необходимость).

2. Наличие dynamic_cast делает дизайн «неправильным» (достаточность).

3. Некоторые «направильные» дизайны содержат dynamic_cast.

4. Нектоторые дизайны, содержащие dynamic_cast, «неправильны».

5. Наличие dynamic_cast хорошо коррелирует (условно, более 0.75) c «неправильностью» дизайна (статистический критерий).

Выбери наиболее близкий вариант (1-5) или предложи свой. потом раскроем понятие «неправильный».

Аргументы вида «dynamic_cast медленных, поэтому всё, что его требует, стоит избегать на уровне дизайна» не рассматриваем, потому что это совсем другой тезис будет.

aist1 ★★ ()
Последнее исправление: aist1 (всего исправлений: 2)
Ответ на: комментарий от LamerOk

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

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

byko3y ★★★ ()
Ответ на: комментарий от LamerOk

Раньше скорее софт подстраивался

Это когда IBM System/360 1964-ого года эмулировал IBM 1401 1959-ого года?

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

byko3y ★★★ ()
Ответ на: комментарий от aist1

Все остальные языки с явно заданной моделью памяти

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

, так или иначе её воспроизводят или эмулируют

Полная ерунда. Это 11-ые кресты взяли спецификацию из Java.

LamerOk ★★★★★ ()
Ответ на: комментарий от anonymous

фактически модель была(в реализациях, без неё никак).

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

И сейчас очень даже можно на С писать для массивов PU с локальной памятью и явными пересылками. И, может быть потом, такая модель войдет в стандарт памяти. А пока что оно будет платформо-зависимо.

aist1 ★★ ()
Ответ на: комментарий от dimgel

Если ты хочешь, чтобы твой С-код скомпилялся и заработал на некотором условном многоядерном процессоре, то этот процессор должен тебе дать две вещи: 1. Атомарные операции над переменными. 2. Единое адресное пространство для всех ядер.

Недопонял. (a) Разве только C-код, а не любой императивный (с масляным маслом мутабельным состоянием)?

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

byko3y ★★★ ()
Ответ на: комментарий от byko3y

любая среднеблочная организация памяти, вроде современной виртуальной памяти в операционных системах, является наследием Си, где с одной стороны часто применяется работа со сплошными массивами байт, а с другой стороны интенсивное использование менеджера памяти (malloc)

Бох мой, какая дичь.

Си никогда не имел векторных инструкций.

Я открою лорошкольником страшную тайну: виртуальные адресные пространства, защита памяти и векторные инструкции появились ДО возникновения языка Си.

Прекратите уже пороть чушь - ей же больно!

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

Бох мой, какая дичь - 2.

Ты и правда думаешь, что за 70 лет не было любителей слепить аппартных лисп-машин?

LamerOk ★★★★★ ()
Ответ на: комментарий от EugeneBas

The difference between theory and practice is that in theory there’s no difference between theory and practice, but in practice there is.

Кстати, там где-то выше Аист упомянул даункасты в контексте type erasure – дык вот помню в каком проекте (lightweight ORM на скале с компиляцией scala-функций в хранимки – всё на макросах, разумеется), но убей бог не помню как и с какого боку, но я вляпывался в даункасты ровно из-за type erasure. Возможно, при трансформациях AST.

О, кстати! Pattern matching над типами – тоже по сути instanceof + даункасты. И используется вовсю и повсеместно, далеко не только при преобразованиях AST.

result match {
    case OK(data) => { ... }
    case Error(message) => { ... }
}
dimgel ★★★★ ()
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от byko3y

Я напомню, что Си проектировался именно как портируемый препроцессор над ассемблером.

Бох мой, какая дичь - 3!

Си - это типизированная дочка безтипового https://en.wikipedia.org/wiki/B_(programming_language) , который в свою очередь клон BCPL, где никакого ассемблера даже близко не было.

Не знать таких вещей хотя бы в рамках сраной википедии и ещё чего-то вещать в интернетах - ёбанный стыд!

LamerOk ★★★★★ ()
Ответ на: комментарий от LamerOk

Я хочу консистентных операций над данным на ЛЮБОМ языке, которым с ними работаю. Меня ни на каком языке не устроит, если при сохранении данных в ячейку памяти, потом оттуда будет прочитано хер пойми что, лишь случайно совпадающие с записанным

Далеко за контрпримером ходить не нужно — возьмем то же ФП: локальные данные, строго определенные связи между ними; читаешь полученные аргументы, выдаешь результат; две функции не могут записать значение в одну ячейку, потому атомарных операций не нужно; поскольку все данные локальны для очень ограниченного набора функций, то и глобальной памяти не нужно. Как делать большие хранилища состояний в такой модели? Делить состояния на маленькие и хранить их в локальных обработчиках.

Я даже не знаю, какой уровень аутизма нужно иметь, чтобы увязывать консистентность работы с памятью к одному единственному языку программирования

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

У меня даже в рамках одного ядра может не быть единого адресного пространства - см. сегментные модели адресации. И да, с ними тоже можно было работать на С

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

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Это когда IBM System/360 1964-ого года эмулировал IBM 1401 1959-ого года?

Довольно редкое явление того времени.

Да погугли уже в яндексе, едрить твою налево. Обратная совместимость на уровне машинных кодов - практически норма для любых широко распространённых архитектур, вплоть до PS2, исполняющей код PS1.

LamerOk ★★★★★ ()
Ответ на: комментарий от dimgel

Всё с instanceof проще. Информацию о типе приходится тянуть по цепочке использования объекта. Где-то она будет лишней, где-то нет. В случае С++ приходится или мономорфизировать код (быстрее, но больше кода), или делать type erasure, и платить циклами за диспетчерезацию в рантайме.

Чем меньше type erasure, тем больше специализации кода. Каждый случай тут отдельный.

aist1 ★★ ()
Ответ на: комментарий от byko3y

Как делать большие хранилища состояний в такой модели? Делить состояния на маленькие и хранить их в локальных обработчиках.

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

Более того, exactly-once семантика доставки сообщений требует по сути реплицируемой базы данных, в которой будет храниться информация о доставке и обработке сообщения.

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

aist1 ★★ ()
Ответ на: комментарий от aist1

А, вон ты в каком смысле. Эта дилемма очевидна, но я среагировал на термин: в терминологии java «type erasure» == «runtime type erasure», т.е. в рантайме JVM не знает типопараметры generic-классов; остаётся чистый LSP, за который по сути здесь ратует @EugeneBas (так-то в теории ратует правильно, LSP в ООП – вещь ключевая).

А вот в .NET CLR, «runtime type erasure» нету, там рантайм знает типопараметры generic-классов, в т.ч. например коллекций, в результате никакими кастами ты не добавишь в массив воронов письменный стол. А в жаве – раз плюнуть.

dimgel ★★★★ ()
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от byko3y

Далеко за контрпримером ходить не нужно — возьмем то же ФП

И? Контрпример будет тогда, когда у тебя все записанные данные никто не будет никогда нигде читать, расчитывая получить оттуда записанное. Хоть фп, хоть декларативное, хоть метапрог.

Пока у тебя нет такого, у тебя нет контрпримера.

Не так уж много ЯП работает на этом уровне абстракций.

Да насрать на ЯП и их уровни абстракций. Консистентность данных - это базовое требование к любым вычислениям. 2+2 должно давать 4, иначе аппаратуру и все языки программирования для неё можно засунуть туда, куда не светит солнце.

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

Боже мой, какая дичь - 5.

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

Телегу впереди лошади не надо ставить.

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

LamerOk ★★★★★ ()
Ответ на: комментарий от aist1

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

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

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

Выходов из западни немного — это уменьшение размера состояния и/или локализация этого состояния для отдельных вычислителей. Как ни странно, но маленькое состояние — это как раз «стиль Си», поскольку в Си большое сложное состояние организовывать весьма неудобно. Именно этот факт стал причиной кризиса IT последних лет, в результате которого пошла массовая миграция на всякие нейронные сети и решения задач перебором параметров, а также динамику, вроде Python/JS, и даже просто языки с GC, как то Go и JVM-based/CLR-based языки.

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

byko3y ★★★ ()
Ответ на: комментарий от LamerOk

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

Всё-таки совсем уж сладкое с мягким смешивать не нужно. Идиотская модель (сегмент << 4 + смещение) в реальном режиме 8086 имеет мало общего с замороченной адресацией памяти в защищённом режиме. И лично мне в своё время было чертовски приятно перейти от дичи «сегмент+смещение» к плоской модели, где процесс видит свою память как плоскую с адресами от нуля до скольки ему нужно, независимо от того, где в реальной физической памяти разбросаны его страницы.

dimgel ★★★★ ()
Ответ на: комментарий от dimgel

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

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

так-то в теории ратует правильно, LSP в ООП – вещь ключевая

Дело в том, что LSP — это частный случай других, более выскоуровневых, принципов. Которые тоже можно раскрыть в конструкции С++ и как-то отображать в объектную модель. Например, объекты С++ — это конечные автоматы. А можно их рассматривать как коды (в смысле теории информации). Т.е. иерархия становится способом кодирования информации в рамках средств языка. Много чего можно, и это — нормально. Хотя оно не обязательно будет идеально ложится на сам язык))

aist1 ★★ ()
Ответ на: комментарий от anonymous

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

Как там с NUMA на сишном наследии? Научились не лезть без повода в чужие блоки памяти?

byko3y ★★★ ()
Ответ на: комментарий от aist1

Дело в том, что LSP — это частный случай других, более выскоуровневых, принципов.

Недопонял. Каких именно? Форсировать бизнес-правила через систему типов умею, но как раз здесь-то dynamic_cast смертелен: объезжаем на хромой козе систему типов ==> объезжаем бизнес-правила.

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

dimgel ★★★★ ()
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от fsb4000

по пунктам:

  1. rethrow_if_nested - костыль, т.к. решили не менять изначальный интерфейс базового класса exception, по-хорошему функциональность std::nested_exception надо было вносить в базовый класс
  2. dynamic_pointer_cast - не считается, т.к. просто реализация dynamic_cast для умных указателей
  3. std::use_facet - как раз пример плохого дизайна, фасеты как таковые не нужны, интерфейс локали должен содержать методы фасетов, а реализации должны их переопределять, тогда и касты не будут нужны
  4. под рукой имплементации с++20 нет, чтобы посмотреть что там

но резюме такое - неубедительно

EugeneBas ★★ ()
Ответ на: комментарий от dimgel

Идиотская модель (сегмент << 4 + смещение) в реальном режиме 8086 имеет мало общего с замороченной адресацией памяти в защищённом режиме.

А где я говорю, что имеет?

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

LamerOk ★★★★★ ()
Ответ на: комментарий от aist1

5. Наличие dynamic_cast хорошо коррелирует (условно, более 0.75) c «неправильностью» дизайна (статистический критерий)

Я голосую за 5. Динамическое приведение типов редко нужно, но иногда оно таки нужно. Очень иногда. Правда, примерно то же самое можно реализовать на каких-нибудь интерфейсах, и тогда полностью убрать динамическое приведение.

byko3y ★★★ ()
Ответ на: комментарий от aist1

Я вот читаю-читаю, читаю-читаю, и не могут понять — что такое «модель памяти». Я подозреваю, что разные собеседники здесь по разному понимают это словосочетание, что добавляет путанности обсуждению. Речь идет про упорядоченность доступа к памяти, что ли? Ну то есть барьеры и атомарные операции.

byko3y ★★★ ()
Ответ на: комментарий от dimgel

Недопонял. Каких именно?

Теории алгоритмической информации, например. Там сложный развесистый матан, забей. Просто поверь мне на слово :)

Форсировать бизнес-правила через систему типов умею

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

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

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

aist1 ★★ ()