Привет! Меня зовут Александр Каленюк, и я крепко подсел на C++. Пишу на C++ 18 лет кряду, и все эти годы отчаянно пытаюсь избавиться от этой разрушительной зависимости.

Всё началось в конце 2005 года, когда мне довелось писать движок для симуляции 3D-пространства. В этом движке было буквально всё, чем язык C++ мог похвастаться в 2005 году. Трёхзвёздочные указатели, восьмиуровневые зависимости, C-подобные макросы повсюду. Кое-где – вкрапления ассемблера. Итераторы в стиле Степанова и мета-код в стиле Александреску. В общем, всё. Кроме ответа на самый важный вопрос: зачем?

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

В первое время я честно пытался вникнуть во все детали, вплоть до мельчайших. Это было дело совершенно неблагодарное, и в какой-то момент я сдался. Продолжал закрывать задачи и фиксить баги. Не могу сказать, что работал крайне продуктивно, скорее минимально продуктивно, чтобы меня не уволили. Но как-то раз начальник меня спрашивает: «хочешь в этом шейдере переписать часть кода с ассемблера на GLSL»? Я понятия не имел, что это такое — GLSL, но счёл, что он едва ли окажется хуже, чем C++ — и согласился. Действительно, он оказался не хуже.

Так сложился своеобразный паттерн. Я по-прежнему писал в основном на C++, но то и дело ко мне обращались с вопросом: «А хочешь вот эту задачу, она не на C++?» И я отвечал: «Давай!» А затем брался за задачу, какова бы она ни была. Я писал на C89, MASM32, C#, PHP, Delphi, ActionScript, JavaScript, Erlang, Python, Haskell, D, Rust, а однажды даже ввязался в авантюру с одним из рук вон плохим скриптовым языком, он называется InstallShield. Я писал на VisualBasic, на bash, на нескольких проприетарных языках, которые даже упоминать будет противозаконно. Как-то раз я даже случайно написал собственный язык. Это был простой интерпретатор в стиле Lisp, призванный помочь разработчикам игр автоматизировать загрузку ресурсов. После этого я ушёл в отпуск. Когда вернулся, оказалось, что они пишут на этом интерпретаторе целые игровые сцены, поэтому пришлось некоторое время его поддерживать.

Итак, в течение последних 17 лет я честно пытался завязать с C++, но всякий раз, попробовав какую-нибудь блестящую новинку, брался за старое. Тем не менее, считаю, что писать на C++ — плохая привычка. Этот язык небезопасен, не так эффективен, как считается, при работе с ним программисту приходится жутко ломать голову над вещами, которые никак не связаны с созданием ПО. А вы знаете, что в MSVC uint16_t(50000) * uin16_t(50000) == -1794967296? А знаете, почему? Да, вот такие мысли меня занимали.

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

Но почему нельзя просто взять и бросить? В чём же дело? Дело в том, что ни один язык, в особенности из числа так называемых «убийц C++» в современном мире ни в чём реально не превосходит C++. Фишка большинства этих языков — просто в стреноживании программиста, якобы, для его же блага. Это нормально, только вот проблема «как плохому программисту написать хороший код» коренится в XX веке, когда плотность транзисторов на кристалле удваивалась каждые полтора года, а абсолютное количество программистов удваивалось каждые 5 лет.

Мы же теперь живём в XXI веке. В мире настолько много опытных программистов, как никогда ранее в истории. И ещё нам сейчас более чем когда-либо ранее необходим эффективный софт.

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

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

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

А потом вдруг оказывается, что все эти «убийцы C++», в том числе, те, которых я всем сердцем люблю и уважаю, в частности, Rust, Julia и D, тоже не решают проблем XXI века. Да, на этих языках получается написать больше фич с меньшим количеством багов, но это не очень помогает, если вам приходится выжимать мощность арендованного вами оборудования вплоть до последнего флопса.

В принципе, у них в самом деле есть конкурентное преимущество над C++. Если уж на то пошло — и друг над другом. Большинство из них, взять, к примеру, Rust, Julia и Clang, даже работают с одним и тем же бекэндом. Невозможно определить победителя в автогонке, если все претенденты сидят в одной машине.

Так какие же технологии действительно дают конкурентное преимущество над C++ или, в более общем смысле, над всеми опережающими компиляторами?

Хороший вопрос!

Рад, что вы спросили.

Киллер C++ № 1. SPIRAL

Прежде, чем подробно обсудить, что такое SPIRAL, давайте проверим, не подводит ли нас интуиция. Как вы считаете, что быстрее: стандартная синусная функция C++ или четырёхчастная многочленная модель синуса?

// версия 1
auto y = std::sin(x);
 
// версия 2
y = -0.000182690409228785*x*x*x*x*x*x*x
    +0.00830460224186793*x*x*x*x*x
    -0.166651012143690*x*x*x
    +x;

Второй вопрос. Как получится быстрее — с использованием логических операций и вычислениями по короткой схеме, либо с преобразованием логического выражения в арифметическое?

// версия 1	
  if (xs[i] == 1 
   && xs[i+1] == 1 
   && xs[i+2] == 1 
   && xs[i+3] == 1) // xs – это булевы значения, сохранённые как целочисленные 
 
// версия 2
  inline int sq(int x) {
      return x*x;
  }
 
  if(sq(xs[i] - 1) 
   + sq(xs[i+1] - 1) 
   + sq(xs[i+2] - 1) 
   + sq(xs[i+3] - 1) == 0)

И последний вопрос. Как отсортировать тройки быстрее: при помощи пузырьковой сортировки с ветвлением или при помощи индексной сортировки без ветвления?

// версия 1
    if(s[0] > s[1])
        swap(s[0], s[1]);
    if(s[1] > s[2])
        swap(s[1], s[2]);
    if(s[0] > s[1])
        swap(s[0], s[1]);
 
// версия 2
    const auto a = s[0];
    const auto b = s[1];
    const auto c = s[2];
    s[int(a > b) + int(a > c)] = a;
    s[int(b >= a) + int(b > c)] = b;
    s[int(c >= a) + int(c >= b)] = c;

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

1. Многочленная модель в 3 раза быстрее стандартной синусной функции, если собрать её при помощи clang 11 и опциями -O2 -march=native, а затем выполнить на Intel Core i7-9700F. Но, если собрать её при помощи NVCC с опциями --use-fast-math и выполнить на GPU, а именно на GeForce GTX 1050 Ti Mobile, то стандартная синусная функция окажется в 10 раз быстрее многочленной модели.

2. Кроме того, на i7 имеет смысл отказаться от вычислений по короткой схеме в пользу векторной арифметики. Тогда этот листинг выполняется вдвое быстрее. Но на ARMv7 с теми же настройками clang и -O2 стандартная логика получается на 25% быстрее, чем при микро-оптимизациях.

3. Если же сравнивать индексную сортировку с пузырьковой, оказывается, что индексная втрое быстрее на Intel, а пузырьковая втрое быстрее на GeForce.

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

1. Даже если разрешить компилятору заново реализовать синус как многочленную модель, таким образом пожертвовав точностью ради скорости, компилятор всё равно не знает, какой точности мы добиваемся. В C++ нельзя указать: «разрешено, чтобы эта функция допускала ошибку». Всё, что у нас есть на этот случай — флаги компилятора вроде “--use-fast-math”, причём, только в области действия единицы трансляции.

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

3. А в третьем примере примеры кода различаются настолько сильно, что их даже сложно опознать как синонимичные. Мы слишком детализировали код. Будь это просто std::sort, компилятор был бы явно менее стеснён в выборе алгоритма. Но он бы не выбрал ни пузырьковую, ни индексную сортировку, поскольку оба этих алгоритма неэффективны при работе с большими массивами, а std::sort работает с обобщённым контейнером, ориентированным на перебор.

Здесь мы и подходим к SPIRAL. Этот проект совместно разработан университетом Карнеги-Меллон и Высшей технической школой Цюриха. Если коротко: экспертам по обработке сигналов надоело переписывать любимые алгоритмы под каждый новый образец оборудования, и они создали программу, которая делает это за них. Программа принимает высокоуровневое описание алгоритма и подробное описание аппаратной архитектуры, после чего оптимизирует код до тех пор, пока не отыскивает наиболее эффективную реализацию алгоритма для указанного аппаратного обеспечения.

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

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

SPIRAL — это исследовательский проект с ограниченной областью применения и ограниченным бюджетом. Но его результаты уже впечатляют. Так, при быстрых преобразованиях Фурье данное решение однозначно превосходит реализации MKL и FFTW. Код работает примерно вдвое быстрее даже на Intel.

Просто оцените масштаб этого достижения. MKL — это математическая библиотека функций ядра, разработанная самой компании Intel, а уж эти ребята лучше кого бы то ни было знают, как устроено их фирменное аппаратное обеспечение. В свою очередь, FFTW, известная в народе как «Самое быстрое преобразование Фурье на всём Диком Западе» — это высокоспециализированная библиотека, разработанная теми, кто лучше всех знает этот алгоритм. Обе команды – настоящие асы своего дела, и просто ошеломительно, что SPIRAL вдвое уделывает их обоих.

Вот GitHub-страница проекта SPIRAL: https://github.com/spiral-software/spiral-software. Если вышеприведённые цифры вас не убедили, можете всё перемерять самостоятельно.

Когда используемые в SPIRAL технологии оптимизации будут финализированы и доведены до коммерческого использования, не только C++, но и Rust, Julia, и даже Fortran столкнутся с ранее не виданной конкуренцией. Зачем вообще программировать на C++, если достаточно дать высокоуровневое описание алгоритма — и инструмент сделает вам код, в два раза обгоняющий код на C++?

Киллер C++ №2. Numba

Самый лучший язык программирования — тот, который тебе уже хорошо известен. На протяжении нескольких десятилетий пальму первенства удерживал язык C. Он до сих пор остаётся в высшем эшелоне рейтинга TIOBE, и в первой десятке с ним соседствуют другие C-образные языки. Правда, всего около двух лет назад произошло нечто неслыханное. C уступил первое место… кому?

Оказывается — языку Python. Это язык, который в 90-е никто не воспринимал всерьёз, его считали просто ещё одним скриптовым языком, каких уже насчитывалось огромное множество.

Вы можете возразить: «Фу, Python же медленный!», но в таком случае это будет терминологический нонсенс. Язык программирования — как сковородка или гармошка, просто не может работать однозначно «медленно» или «быстро». Скорость игры на гармошке зависит от мастерства гармониста, а «скорость» языка зависит от скорости компилятора.  

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

Был у меня однажды проект. Симулятор 3D-печати был исходно написан на Python, а затем переписан на C++ «ради увеличения производительности», потом портирован на GPU и только после этого достался мне. Я целые месяцы потратил, чтобы портировать эту сборку на Linux и оптимизировать код GPU для Tesla M60, поскольку на тот момент это было самое дешёвое предложение от AWS. Тем временем, я валидировал все изменения в коде C++/CU, следя за тем, чтобы не отступать от оригинального кода на Python. То есть, занимался чем угодно кроме той вещи, на которой специализируюсь, то есть, кроме разработки геометрических алгоритмов.

Когда же я, наконец, всё закончил, мне позвонил один студент из Бремена (совместитель) и сказал: «вижу, вы отлично справляетесь с разнородным материалом. Скажите пожалуйста, не могли бы вы помочь мне запустить один алгоритм на GPU?». Конечно же! Я рассказал ему про CUDA, CMake, сборку Linux, тестирование и оптимизацию; мы, может быть, час проговорили. Он очень вежливо меня выслушал, а в завершение сказал: «Всё это очень интересно, но у меня к вам есть специфический вопрос. Допустим, у меня есть функция, перед её определением  я написал @cuda.jit, а Python пишет мне что-то о массивах и не компилирует ядро. Не знаете ли, в чём здесь может быть проблема?»

Я не смог ответить. Но он примерно за день сам во всём разобрался. По всей видимости, Numba не работает с нативными списками Python, а просто принимает данные в форме массивов NumPy. Он это выяснил и запустил свой алгоритм на GPU. На Python. У него не возникло ни одной из тех проблем, над которыми я бился месяцами. Хотите, чтобы код работал на Linux? Не проблема, просто запускайте на Linux. Хотите, чтобы он был согласован с кодом Python? Не проблема, ведь это код на Python. Хотите его оптимизировать под целевую платформу? Опять же, никаких проблем. Numba оптимизирует код именно под ту платформу, на которой вы его выполняете, поскольку он не подвергается опережающей компиляции — нет, он компилируется по требованию, уже будучи развёрнут.

Разве это не чудесно? Ну, нет. Не для меня, по крайней мере. Я месяцами работал с C++, решая проблемы, которые в Numba просто никогда не возникают, а этот совместитель из Бремена справился с теми же задачами всего за несколько дней. А мог бы и за несколько часов, если бы предварительно ему не пришлось впервые разбираться в Numba. Так что же это за Numba? Может быть, тут не обошлось без колдовства?

Никакого колдовства. Декораторы Python за вас превращают любой фрагмент кода в его абстрактное синтаксическое дерево, после чего вы можете делать с ним что угодно.  Numba — это библиотека Python, готовая компилировать абстрактные синтаксические деревья под любой серверный интерфейс на любой платформе, которую она поддерживает. Если вы хотите скомпилировать ваш код Python так, чтобы он выполнялся на многих ядрах ЦП в стиле чрезвычайно параллельных вычислений — просто сообщите Numba, что его нужно скомпилировать именно так. Если вы хотите выполнять что-либо на GPU, опять же, просто попросите об этом.

@cuda.jit
def matmul(A, B, C):
    """Перемножить квадратные матрицы C = A * B."""
    i, j = cuda.grid(2)
    if i < C.shape[0] and j < C.shape[1]:
        tmp = 0.
        for k in range(A.shape[1]):
                tmp += A[i, k] * B[k, j]
        C[i, j] = tmp

Numba — один из тех компиляторов Python, из-за которых C++ морально устаревает. Но, теоретически, он ничем не лучше C++, поскольку работает всё с теми же серверными интерфейсами. Использует CUDA для программирования под GPU и LLVM для работы с ЦП. На практике решения Numba легче адаптировать к любому новому аппаратному обеспечению (поскольку они не требуют никакой опережающей пересборки), а также применять все доступные виды оптимизации.

Разумеется, привлекательнее был бы явный выигрыш в производительности, как в случае со SPIRAL. Но SPIRAL — в большей степени исследовательский проект, он, возможно, и вытеснит C++, но только и только в том случае, если ему повезёт. Numba с Python уже сейчас одолевают C++, прямо на наших глазах. Ведь если можно писать на Python и иметь производительность как на C++, то зачем вообще писать на C++?

Киллер C++ №3. ForwardCom

Теперь сыграем в другую игру. Я даю вам ещё три фрагмента кода, а вы угадываете, какой из них (возможно, не один) написан на ассемблере. Вот они:

Первый

invoke RegisterClassEx, addr wc     ; зарегистрировать класс окна
    invoke CreateWindowEx,NULL,
        ADDR ClassName, ADDR AppName,\
        WS_OVERLAPPEDWINDOW,\
        CW_USEDEFAULT, CW_USEDEFAULT,\
        CW_USEDEFAULT, CW_USEDEFAULT,\
        NULL, NULL, hInst, NULL
        mov   hwnd,eax
    invoke ShowWindow, hwnd,CmdShow     ; отобразить окно на ПК
    invoke UpdateWindow, hwnd           ; обновить клиентскую область
 
    .while TRUE                         ; Входим в цикл сообщений
        invoke GetMessage, ADDR msg,NULL,0,0
        .break .if (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
   .endw

Второй

(module
  (func $add (param $lhs i32) (param $rhs i32) (result i32)
        get_local $lhs
        get_local $rhs
        i32.add)
  (export "add" (func $add)))

Третий

v0 = my_vector  // хотим просуммировать это по горизонтали
int64 r0 = get_len ( v0 )
int64 r0 = round_u2 ( r0 )
float v0 = set_len ( r0 , v0 )
while ( uint64 r0 > 4) {
        uint64 r0 >>= 1
        float v1 = shift_reduce ( r0 , v0 )
        float v0 = v1 + v0
}

Если вы догадались, что все три примера написаны на ассемблере — поздравляю! Ваша интуиция уже гораздо лучше!

В первом примере имеем дело с MASM32. Это макроассемблер с операторами «if» и «while», на нём пишутся нативные приложения под Windows. Да, именно «по сей день», а не «когда-то».  Microsoft ревностно отстаивает обратную совместимость Windows с Win32 API, так что все программы, когда-либо написанные на MASM32, до сих пор могут работать на современных ПК.

Ирония судьбы в том, что язык C изобрели, чтобы упростить трансляцию UNIX с PDP-7 на PDP-11. Он проектировался как портативный вариант ассемблера, который мог бы пережить бурные события 70-х в области аппаратных архитектур, сравнимые по масштабу с Кембрийским взрывом. Но в XXI веке аппаратная архитектура развивается настолько медленно, что те программы, которые я писал на MASM32 20 лет назад, и сегодня отлично собираются и выполняются. Но я совершенно не уверен, что то приложение на  C++, которое я в прошлом году собирал с CMake 3.21, соберётся и на будущий год с CMake 3.25.

Второй фрагмент написан на WebAssembly. Это даже не макроассемблер, в нём нет операторов «if» и «while». Его правильнее назвать человеко-читаемым вариантом машинного кода, рассчитанным на работу в браузере. Концептуально, в любом браузере.

Код WebAssembly совершенно не зависит от аппаратной архитектуры. Можно сказать, что он обслуживает абстрактную, виртуальную, универсальную машину, называйте её как хотите. Если вы способны прочитать этот листинг, значит, он физически уже присутствует на вашей машине.

Но самый интересный фрагмент кода — третий. Это ForwardCom, вариант ассемблера, предлагаемый Агнером Фогом. Агнер Фог — прославленный автор мануалов по оптимизации C++ и ассемблера. Точно как и WebAssembly, этот вариант охватывает не столько ассемблер, сколько универсальный набор инструкций. Этот набор инструкций предназначен для обеспечения не только обратной, но и прямой совместимости. Отсюда и название. Полностью ForwardCom именуется «открытая прямо совместимая архитектура с соответствующим набором инструкций». Иными словами, это не столько предложение нового варианта ассемблера, сколько инициатива о мире.

Наиболее распространённые семейства архитектур хорошо известны: это x64, ARM и RISC-V. Во всех них — разные наборы инструкций. Но никто не сможет обосновать, почему ситуация должна оставаться именно такой. Все современные процессоры, кроме, возможно, самых простых, выполняют не тот код, который вы им скармливаете, а микрокод, в который они транслируют ваш ввод. Поэтому не только в M1 предусмотрен слой для обеспечения обратной совместимости с Intel; нет, в сущности, в любом процессоре есть такой слой обратной совместимости для взаимодействия с его же более ранними версиями.

Так почему бы проектировщикам архитектур не договориться о создании аналогичного слоя, но для обеспечения прямой совместимости? Если не считать конфликта интересов между корпорациями, прямо конкурирующими друг с другом — ничего. Но, если когда-нибудь производители процессоров станут работать с общим набором инструкций, а не реализовать новый уровень совместимости для взаимодействия с оборудованием каждого из конкурентов, то программирование на ассемблере благодаря ForwardCom вновь станет мейнстримом. Такая прямая совместимость вылечит тяжелейший невроз у любого специалиста по ассемблеру. «Что, если я единственный раз в жизни напишу код для данной конкретной архитектуры, и именно эта архитектура через год устареет»?

Если реализовать слой прямой совместимости, то ни один код в таком случае никогда не устареет. В этом и суть.

Кроме того, программирование на ассемблере не развивается из-за мифа, будто писать на ассемблере сложно и, следовательно, непрактично. Фог и этот момент учитывает в своём предложении. Если кому-то кажется, что на ассемблере писать сложно, а на C — нет, что ж, хорошо, давайте сделаем ассемблер похожим на C. Нет никаких причин, по которым современный ассемблер должен выглядеть так же, как и его дедушка в 1950-е.

Я только что показал вам три варианта ассемблера. Ни один из них не выглядит похожим на «традиционный» ассемблер, и не должен.

Итак, ForwardCom — это ассемблер, на котором можно писать оптимальный код, и этот код никогда не устареет. Более того, для работы с ним вам не придётся изучать «традиционный» ассемблер. С любой практической точки зрения, это C будущего. А не C++.

Когда же C++ окончательно отомрёт?

Мы живём в мире постмодерна. В этом мире больше ничего не умирает (кроме людей). Точно как латынь не вымерла окончательно, так не вымерли и COBOL, Algol 68, Ada. Язык C++ также обречён на такую полужизнь. C++ никогда окончательно не отомрёт, но его вытеснят из мейнстрима новые, более мощные технологии.

Даже не «вытеснят», а «уже вытесняют». На мою нынешнюю работу я пришёл как C++-программист, а сегодня мой рабочий день начинается с Python. Я пишу уравнения, SymPy решает их за меня, а потом транслирует это решение на C++. Затем я вставляю получившийся код в библиотеку C++, даже ничуть его не форматируя, ведь за меня это сделает clang-tidy. Статический анализатор проверит, не перепутал ли я где-нибудь пространства имён, динамический анализатор проверит, нет ли утечек памяти. Конвейер  CI/CD обеспечит кроссплатформенную компиляцию. Профилировщик поможет мне понять, как именно работает мой код, а дизассемблер — почему.

Если мне придётся променять  C++ на «не C++», то 80% моей работы никак не изменится. Язык C++ уже просто нерелевантен для большинства моих задач. Могу ли я в таком случае утверждать, что для меня C++ уже на 80% мёртв?

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


  1. SpiderEkb
    28.04.2024 11:35
    +16

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

    Но вот 6 лет назад (скоро уже 7 лет) занесло на узконишевую платформу IBM i и специфические задачи - работа с БД и коммерческие расчеты. И обнаружил что если есть язык, специально предназначенный для решения какого-то класса задач, то решать эти задачи на нем и проще и быстрее и эффективнее чем на любом языке "для всего". Потому что он специально под эти задачи сделан. И код на нем получается простоя и понятный. И работает он быстро и эффективно. И вот прям зашло... Хотя если что-то низкоуровневое-системное, то тут, конечно, С удобнее...


    1. alexandrustinov
      28.04.2024 11:35
      +17

      Numba

      что если есть язык, специально предназначенный для решения какого-то класса задач, то решать эти задачи на нем и проще

      Поздравляю парни, да вы открыли понятие DSL!

      А любой DSL можно на любом другом языке реализовать. Хоть на Python, хоть на C++, да хоть на JSON.

      И это не проблема "базового" языка, это проблема продуманности (или, что чаще встречается - непродуманности и/или излишней абстрактности-заумности) предлагаемых API и прочих специфичных задаче сниппетов и структур.

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

      И дело пойдет. Но никто пока не решается.


      1. SpiderEkb
        28.04.2024 11:35
        +1

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

        Ок. Выкинули все из С++. Теперь вопрос - вы работаете с БД. В больших объемах - лопатите десятки и сотни миллионов записей по многим таблицам.

        И вот простенькая задачка - прочитать запись из одной таблицы по ключу, как-то поработать с ее полями (а там могут быть date, time, decimal, numeric) и положить запись в другую таблицу. Сколько зависимостей вам придется подтянуть в С++ и сколько кода написать. А на советующем DSL это выглядит так:

        dcl-ds dsMyFileRec likerec(MyFile.MyFileRecF: *all);
        
        chain (KeyValue) MyFile.MyFileRecF dsMyFileRec;
        
        dsMyFileRec.numericFeild += 1;
        dsMyFileRec.dateField += %days(3);
        
        update MyFile.MyFileRecF dsMyFileRec;

        5 (пять) строк кода.

        1 - объявили структуру "такую же как формат записи MyFileRecF в таблице MyFile

        3 - прочитали запись для значения ключа KeyValue в эту структуру

        5 - увеличили в записи поле numericFeild (допустим, оно имеет тип NUMERIC - это тип данных с фиксированной точкой) на 1

        6 - увеличили дату в поле dateField на три дня

        8 - сохранили запись обратно в таблицу

        Заметьте - все это на 100% средствами языка и в рантайме не создано ни одного дополнительного объекта (чтобы работать с типами numeric и date) - все типы данных, что есть в БД, нативно поддерживаются языком.

        Это можно написать на С++. Но так написать не получится. Будет длиннее и много лишних телодвижений.


        1. Daddy_Cool
          28.04.2024 11:35
          +6

          Хитрые сипипишники скажут - просто надо воспользоваться каким-нибудь фреймворком или библиотекой на С++ для БД!


          1. alexandrustinov
            28.04.2024 11:35
            +15

            Хитрые сипипишники скажут -  просто надо воспользоваться каким-нибудь фреймворком

            Как хитрый C++ ник я могу сказать - ничего лучше INSERT INTO table2(...) SELECT ... FROM table1 до сих пор не придумано.

            Если не хватает базового SQL (и каких cross-database links), то есть понятие EXTERNAL TABLES, когда вывод некого скрипта можно представить для сервера баз данных как виртуальную таблицу (к примеру в .CSV формате) и далее опять куда там нужно тебе INSERT INTO SELECT FROM

            И то - это если у тебя какого PL/SQL нет под рукой (который вполне покрывает 99% задач по трансформации данных).

            Это если говорить о реалиях реальной жизни.

            А если говорить о "взагали по взагалях" и как оно неплохо было бы - то возможности C/C++ тебя не ограничивают, упарываться в DSL можно как угодно - к примеру из таблиц/вьюшек нагенерировать классов в стиле OORM. И взять любой из десятка доступных API - от преснокислого ODBC до прям специализированных, OCILIB какой к примеру очень и очень неплох.

            Вопрос то в чем собственно? Если не удалось найти достаточно выразительный и удобный DSL (а SQL и PL/SQL это как раз примеры очень удачных DSL), то напиши свой. И документацию к нему не забудь. Для себя-же, который будет это все потом через пару лет читать и материться, в духе "какой же баран это все так криво понаписал".


            1. SpiderEkb
              28.04.2024 11:35
              +3

              Если не хватает базового SQL (и каких cross-database links), то есть понятие EXTERNAL TABLES, когда вывод некого скрипта можно представить для сервера баз данных как виртуальную таблицу (к примеру в .CSV формате) и далее опять куда там нужно тебе INSERT INTO SELECT FROM

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

              Ну или вот задачка. Есть клиенты. Их 50млн. У каждого 5 типов адресов. Это с одной стороны. С другой стороны есть "субъекты списков росфина" (террористы/экстремисты всякие). Их... ну тысяч 300, положим. И у каждого по 2-3 адреса.

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

              И как решать будете? Прикиньте количество комбинаций (пар для сравнения).

              С учетов способа хранения всего этого в БД (в виде витрин, где все адреса уже разложены по элементам) можно сделать все это скулем. Запрос получается на три страницы. И работает очень долго (на тестовом юните, где данных кот наплакал - субъектов там реальные объемы, но клиентов всего-то тысяч 20) оно работает где-то 60-70сек. На бою это растянется на несколько дней, что неприемлемо.

              А на DSL, без скуля вообще все это работает в разы быстрее (на тех же данных на тесте 4.5сек). Хотя бы за счет того, что там большая часть поверок идет без чтения записи в БД. Просто по наличию соотв. ключа в индексе (да, DSL такое позволяет - просто посмотреть есть оно в индексе или нет, не лазя в саму таблицу за записью).

              Ну а если нужен скуль -

                     dcl-proc GetCustInf;
                       dcl-pi *n extpgm('CUS001');
                         inCusNo like(arCNum) const;
                         outName like(arName);
                         outAddr1 like(arAdd1);
                         outAddr2 like(arAdd2);
                         outCity like(arCity);
                         outState like(arStte);
                         outZip like(arZip);
                       end-pi;
                       exec sql select arName, arAdd1, arAdd2, arCity, arStte, arZip
                                into  :outName, :outAddr1, :outAddr2, :outCity, :outState,
                                      :outZip
                                from   ARMSTF1
                                where  arCNum = :inCusNo
                                fetch first 1 row only
                                with CS
                                use currently committed;
                       return;

              Просто вставляем SQL запрос в код на DSL. А всю сложную логику пишем на DSL. Никто не запрещает. Embedded SQL называется. Запросы любой сложности. С подзапросами и прочим. Можем из программы на DSL вернуть SQL ResultSet если надо. Можем принять ResultSet из другой программы.

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


              1. alexandrustinov
                28.04.2024 11:35
                +3

                Все эти "виртуальные таблицы в CSV" на таких объемах просто встанут колом.

                Почему они встанут колом? Эти виртуальные таблицы (в формате CSV) можно чем угодно делать - от bash до С/С++, да хоть на ассемблере. В базу данных подается просто готовый результат уже, который нужно сохранить (если нужно). Возможно сейчас не совсем было понятно, что такое EXTERNAL TABLES и как они работают, но... можно погуглить наверное. В простейшем случае это SELECT FROM stdout - а кто в этот stdout пишет и что именно - это вопрос десятый.

                А так - для современной базы данных сотни миллионов записей в день это не проблема. Там практический лимит был для типового не очень свежего 16 ядерного x64 сервера около 5 млрд. записей, по 100 байт каждая, в сутки. С парой индексов сверху. Без индексов сильно больше. И то оно все упиралось просто в хранилище. А если direct path insert какой взять INSERT /*+ APPEND */ в простонародье, и без логов - то там и сильно больше 5 млрд можно осилить. Не в один поток естественно это все.

                Другой вопрос - где вы такие объемы берете-то.
                Но С++ там точно не серебряная пуля, основные временные затраты идут вовсе не на клиентской стороне.

                Просто вставляем SQL запрос в код на DSL. А всю сложную логику пишем на DSL. Никто не запрещает. Embedded SQL называется.

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


                1. SpiderEkb
                  28.04.2024 11:35
                  +8

                  Другой вопрос - где вы такие объемы берете-то.

                  Да объемы не вопрос. Банк. 50млн клиентов. И у каждого куча данных. Счета (у крупного корпората их может быть сотня, у обычного физика и то десяток счетов, считая кредитные и депозитные, не редкость), карты (плюс держатели карт), доверенные лица всякие, клиентские данные - документы, адреса, доверенности, контакты... А еще всякие риски, платежные документы, проводки...

                  Сколько там всего этого трудно сказать. По самым грубым оценкам - несколько десятков тысяч объектов БД.

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

                  писать свой DSL для подобного

                  А кто говорил про самописный? Это не самописный язык. Речь про RPG. Он с 1959-го года существует. В целом, все, что можно написать на С, можно написать на RPG и наоборот. Но сравнивать их бессмысленно т.к. есть вещи, которые удобнее и быстрее писать на одном, а есть - на другом. Бизнес-приложения - это про интенсивную работу с БД и всякие расчеты, связанные с датами, временем и деньгами (а деньги считаются только в формате с фиксированной точкой). Поэтому вполне логично, что все это реализовано на уровне языка.

                  И да, вопрос производительности стоит крайне остро. Потому что в нормальных условиях нагрузка на сервер 50-60%, а в пиковых (например, предновогодний шоппинг) может резко подскочить до 90... Сервер - 120 8-поточных ядер Power9.

                  И на нагрузочном тестировании смотря на все - создаешь какие-то временные объекты - к тебе будут вопросы. Излишнее переоткрытие файлов - будут вопросы. Даже за слишком интенсивное использование динамической памяти спросить могут (предпочтительнее использовать статику - с памятью тут проблем нет, во-первых, тут одноуровневая память, во вторых на сервере 12Тб оперативки стоит).


              1. vdudouyt
                28.04.2024 11:35

                Насчет DSL-подхода (и, что характерно, на примере все той же задачи взаимодействия с БД) весьма неплохо написано вот в этой статье: https://habr.com/ru/articles/422667/

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


                1. SpiderEkb
                  28.04.2024 11:35
                  +1

                  Откуда уверенность, что DSL это обязательно какой-то самописный велосипед?

                  Вот есть у IBM платформа IBM i - middleware коммерческие серверы. Для бизнеса в первую очередь. История там длинная, если закапываться совсем вглубь, то можно дойти до System/3. "Новая история" - System/38 -> AS/400. Дальше уже больше ребрендинга. Сейчас оно называется IBM i (есть еще мейнфреймы IBM z, но они от System/390 идут, это другое).

                  И есть на этой платформе DSL - RPG Корнями уходит в 1959-й год (первая реализация эмулятора табуляторов для IBM 1401).

                  Так вот, на нем тут пишется более 80% кода. Де-факто на этой платформе это стандарт. Появился SQL - добавили в RPG возможность встраивать SQL в RPG код (exec sql ...).

                  И да, для решения задач в тех областях, где эта платформа используется, это самый эффективный путь. Можно пробовать (и пробовали) делать все то же на С. Или на С++. Но ни по скорости разработки, ни по качеству результата никакого выигрыша нет. Скорее даже наоборот. Будет прикручивать к тому же С++ кучу зависимостей, чтобы получить то, что в RPG уже есть на уровне языка. То, что в RPG реализуется compile time типами, в C++ будет реализовано создаваемыми в runtime объектами (а это лишнее время и лишние ресурсы).

                  Но если мне, например, нужно удобное API для работы с какой-нибудь User Queue (есть такой тип системного объекта), я сделаю это на С. А потом буду использовать из RPG приложений уже под бизнес-задачи. Ну и до кучи напишу на RPG UDF/UDTF чтобы можно было со всем этим из SQL работать.


              1. redfox0
                28.04.2024 11:35

                Синтаксис выглядит забавно. Мы в oracle apex подсмотрели такой подход, чтобы не объявлять десятки переменных l_out_name ARMSTF1.arName%type;:

                procedure get_custInf(
                    p_arcnum in ARMSTF1.arcnum%type
                ) as
                    cursor l_c is
                    select
                        arName,
                        arAdd1,
                        arAdd2,
                        arCity,
                        arStte,
                        arZip
                    from
                        ARMSTF1
                    where
                        arCNum = p_arcnum
                    fetch first 1 row only;
                begin
                    for i in l_c loop
                        -- i.arName etc
                        exit;
                    end loop;
                end get_custInf;
                


          1. SpiderEkb
            28.04.2024 11:35

            Минус фреймворка в том, что там слишком много чего делается в рантайме. В приведенном выше пример структура - статическая. Она "раскручивается" на этапе компиляции. Ровно так, как если ее просто руками наколотить в структуре записи в БД. И никаких маппингов, никаких дополнительных объектов в рантами не создается. И типами с фиксированной точкой, датами, временем работает точно также как в С работаем с int или double... Ну есть специфические операции типа операций с округлением - просто нужно специальный модификатор поставить который указывает что делать при уменьшении количества знаков после запятой - откидывать или округлять. Но это уже тонкие тонкости всякие. А если пишем a = b + c, то все три переменных могут быть любого числового типа - int, float, decimal, numeric...


            1. Foror
              28.04.2024 11:35

              >Минус фреймворка в том, что там слишком много чего делается в рантайме

              Сделайте фреймворк, который будет делать "слишком много" не в рантайме, в чём проблема?


              1. SpiderEkb
                28.04.2024 11:35

                Дело в том, что у меня нет нужды делать какие-то фреймворки.


                1. Foror
                  28.04.2024 11:35

                  >обнаружил что если есть язык, специально предназначенный для решения какого-то класса задач, то решать эти задачи на нем и проще и быстрее и эффективнее чем на любом языке "для всего"

                  Так речь не о вас, а о вашем ошибочном суждении. Конечно ошибочно оно не только у вас, если посмотреть на весь зоопарк DSL.


            1. Foror
              28.04.2024 11:35

              >И никаких маппингов, никаких дополнительных объектов в рантами не создается. 

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


        1. alexandrustinov
          28.04.2024 11:35
          +2

          dcl-ds dsMyFileRec likerec(MyFile.MyFileRecF: *all);
          
          chain (KeyValue) MyFile.MyFileRecF dsMyFileRec;
          
          dsMyFileRec.numericFeild += 1;
          dsMyFileRec.dateField += %days(3);
          
          update MyFile.MyFileRecF dsMyFileRec;

          5 (пять) строк кода.

          Простите конечно, но чем псевдокод лучше вот такого? Строчек ровно в два раза меньше.

          UPDATE myFile rec
             SET rec.numericField = rec.numericField + 1,
                 rec.dateField = rec.dateField + 3 -- ook, Oracle specific
          WHERE  1 = 1; --  likerec(MyFile.MyFileRecF: *all)

          Ну да, += в SQL так и не завезли, печаль печаль огорчение :) Но по сути, чем вот то что выше лучше? Возможности вставить произвольное процедурное в процессе - это тоже есть, можно на каждое UPDATE хоть JSON запросы на внешние вебсервисы дергать, и даже результаты в другие вебсервисы засылать, есть и такие средства.


          1. SpiderEkb
            28.04.2024 11:35
            +1

            Ну я привел пример из серии Hello Word. И это не псевдокод. Это реально скомпилируется и реально сработает. Оно действительно вот так просто все.

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

            Начать с того, что эта структура может вам не из БД прийти, а как параметр вашей процедуры (допустим, это некий актор). И вам нужно будет произвести несколько десятков манипуляций с разными ее полями, подтягивая дополнительные данные откуда-то. А потом отдать ее обратно уже в модифицированном виде. И так 100млн раз в день (примерно с такими объемами работает комплекс комплаенс-проверок в системе контроля платежей).

            Про возможность вставлять SQL запросы непосредственно в код написал выше. Иногда быстрее и эффективнее действительно тянуть данные из БД SQL запросом. А иногда наоборот - прямой доступ и по времени и по ресурсам будет выгоднее. Это уже на основе накопленного опыта понимаешь где что лучше.

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

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


            1. alexandrustinov
              28.04.2024 11:35

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

              В современном мире использовать SQL для highload runtime, да еще и с гарантией по времени отклика - да, это так себе идея. Тем не менее, о чем вообще спор был изначально?

              С++ вполне может быть адекватен даже для бизнес задач. Для тех самых типовых задачек вида "конвертируем что-то из одного условно-табличного вида в другой" (к которым сводится 99.9% всех айтишных задач).

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

              В современной разработке что C++, что Java подходы из коробок/книжек - они, изначально, мягко говоря, не про бизнес задачи. Сильно много слов заставляют писать не по "теме".

              LINQ вот еще подавал надежды, но тоже сдулся. lsFusion еще тут мелькал, как альтернатива 1С, но... не будем больше о грустном.

              По хорошему нужно что-то, что может в одном адресном пространстве (без всяких IPC/TCP/etc) иметь сразу и ORM, и SQL, и сразу HTTP(s)/JSON, и чтоб быстро и чтоб надежно, и еще column-storage. И без лишних конвертаций туда-сюда. И чтоб язык был хороший для "скриптов".

              Tarantool еще, ах да. Интересно, так и пытаются LUA продавать?


              1. KReal
                28.04.2024 11:35
                +4

                LINQ вот еще подавал надежды, но тоже сдулся

                нельзя ли поподробней? пропустил похороны поциента.


              1. SpiderEkb
                28.04.2024 11:35

                Тем не менее, о чем вообще спор был изначально?

                В целом - про то, что бессмысленно притягивать за уши С/С++ "до уровня DSL" чтобы получить то, что уже есть в DSL, но с потерей времени. Т.е. оно ни работать быстрее не будет и писать быстрее не получится.

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


          1. SpiderEkb
            28.04.2024 11:35

            Вот вам еще про скуль.

            Есть таблица "дата актуализации клиента". Таблица "историческая" - когда у клиента меняется дата актуализации, туда просто добавляется запись с новой датой (бизнесу зачем-то это нужно).

            По этой таблице есть индекс по ID клиента и DT - дате его актуализации.

            Очень много ситуаций когда в задаче требуется получить текущую (последнюю) ДА по клиенту. Так вот, "скулевое" решение

            select ID, max(DT)
              into :id, :dt
              from DATbl
             where ID = :id
             group by ID;

            работает ощутимо медленнее, чем решение с прямым доступом

            dcl-ds dsDATblRec likerec(DAIdx.DATblRecF: *all);
            
            setgt (id) DAIdx;
            readp DAIdx.DATblRecF dsDATblRec;

            Это подтверждается PEX (Performance EXplorer) статистиками. Скуль пытается что-то там агрегировать в группе, в прямом доступе SetGT тупо по индексу ставит указатель после последней записи с заданным значением ключа, а потом ReadP читает предыдущую запись. Которая и есть в данном индексе запись с максимальным DT для заданного ID.


            1. netch80
              28.04.2024 11:35

              в прямом доступе SetGT тупо по индексу ставит указатель после последней записи с заданным значением ключа

              Это вам повезло, что ставит указатель именно после последней записи. А если бы ставил перед первой? А если бы после последней, но задача была бы `select id, min(dt)` ?

              Конечно, легко найти один частный случай...

              А если в SQL варианте для max() что-то "агрегируется" с заметными тормозами, это просто недоработка оптимизатора конкретного движка. Вы будете давать зуб, что все движки тут недорабатывают, или кроме DB2 ничего не проверяли?


              1. Cerberuser
                28.04.2024 11:35

                Это вам повезло, что ставит указатель именно после последней записи. А если бы ставил перед первой?

                Если я правильно понимаю, что означает "SetGT", то "поставить перед первой" было бы "SetLT".


                1. netch80
                  28.04.2024 11:35

                  то "поставить перед первой" было бы "SetLT".

                  Может, да, может, нет... надо смотреть в детали. Тем более что там есть ещё над чем подумать:

                  • Откуда взялась именно такая сортировка, кто-то создал курсор по составному индексу? Это не сказано.

                  • Если этот id последний, SetGT станет на последнюю позицию или пустую за последней?

                  • А для SetLT, если это первая, станет на первую или перед первой? Нужен readn (по аналогии с readp) или нет? Эти случаи кто-то отработал?

                  • Зачем сначала становиться за последней позицией, а потом возвращаться назад?

                  Я писал когда-то под Clipper и FoxPro и помню эту беготню курсорами... да, оно просто на совсем простом. Но затем рост сложности, в отличие от SQL, практически экспоненциальный...


              1. SpiderEkb
                28.04.2024 11:35

                Это вам повезло, что ставит указатель именно после последней записи. А если бы ставил перед первой? А если бы после последней, но задача была бы `select id, min(dt)` ?

                dcl-ds dsDATblRec likerec(DAIdx.DATblRecF: *all);
                
                setll (id) DAIdx;
                read DAIdx.DATblRecF dsDATblRec;

                Вместо SetGT используем SetLL - он ставит указатель перед первой записью с указанным значением ключа.

                Вместо ReadP (чтение предыдущей записи) используем Read - чтение следующей записи.

                Если нужно проверить, есть ли в таблице запись с нужным значением ключа (но сама запись не интересует), пишем так

                SetLL (KeyValue) MyIdx;
                if %equal(MyIdx);
                  // запись существет
                else;
                  // Такой записи нет
                endif;

                При этом не тратим время на чтение записи из таблицы, проверяем только наличие ее в индексе.

                И там таких моментов очень много. Мелочь, но на скорость на больших объемах влияет ощутимо.

                Я уже упоминал задачу про поиск соответствия в больших объемах строк.

                Есть две таблицы - "витрина элементов адресов субъектов" - таблица где содержится Id адреса, Id элемента и его порядковый номер в строке. И таблица где содержатся сами элементы - Id элемента и сам элемент (слово).

                Аналогичная витрина есть для адресов клиентов.

                Нужно найти соответствия - когда все уникальные элементы адреса субъекта входят в адрес клиента.

                Можно решить скулем. Один запрос, находящий пересечения в группах по витринам адресов субъектов и клиентов и отбирающий те, где количество пересечений в группе равно количеству уникальных элементов в адресе субъекта. Но работает достаточно долго (на промсреде это может занимать 7-10 часов для всех клиентов-субъектов).

                Длиннее по коду, но эффективнее:

                • Составляем список уникальных элементов адреса субъекта

                • Берем первый элемент списка, находим его Id в таблице элементов адресов клиентов

                • По этому Id находим Id всех адресов клиентов, содержащих этот элемент и заносим их в массив

                • Дальше цикл - берем очередной элемент адреса субъекта, находим его Id в элементах адресов клиентов и потом проходим по нашему массиву, проверяя наличие связки Id адреса (из массива) и Id элемента в индексе (без чтения самой записи, нам важно только есть оно или нет). Если есть - адрес "проходит в следующий тур", если нет - удаляется из массива (отсеивается).

                • С каждым элементом длина массива сокращается и после последнего там остаются только те Id, которые соответствуют адресам клиентов куда вошли все элементы проверяемого адреса субъекта.

                Это работает в несколько раз быстрее, чем скулевый запрос "в лоб".

                Итого

                • Чтение элементов адреса субъекта (3-5-7 записей)

                • На первом "обороте" цикла заполнение массива - чтение всех записей, содержащих первый элемент (там много - может быть несколько сотен тысяч)

                • На всех оборотах цикла чтение одной записи по уникальному ключу - получение Id очередного элемента адреса субъекта в таблице элементов адресов клиентов.

                • Все остальное - без чтения записей, только проверка наличия значений в индексе.

                Никаких группировок и агрегирования скулем (это достаточно "дорогие" операции). Однократное заполнение массива с последующим его просеиванием.

                Если еще чуть оптимизировать и добавить "ключевое слово" - элемент адреса субъекта, имеющий минимальное количество вхождений в таблицу элементов адресов клиентов, и использовать его на первом обороте цикла (при заполнении начального массива), то сразу получаем массив минимальной длины и ускорение еще примерно в два раза.

                Итого - на промсреде уже не 7-10, а 1-2 часа работает.


                1. netch80
                  28.04.2024 11:35
                  +1

                  Вместо SetGT используем SetLL - он ставит указатель перед первой записью с указанным значением ключа.

                  Перед записью или на записи?

                  SetLL (KeyValue) MyIdx; if %equal(MyIdx);

                  И вылетаем на пустом ответе?

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

                  Если select только полей из индекса в SQL, можно тоже не тратить время на чтение записи. Есть такая оптимизация. DB2 её не умеет?

                  Длиннее по коду, но эффективнее:

                  Реализуемо в большинстве "взрослых" движков SQL как хранимая процедура. И с перебором курсора, и с промежуточными представлениями результатов выборки.

                  Пока что весь этот рассказ выглядит как пример типа "DB2 туповата, обходим вручную"...


        1. Kelbon
          28.04.2024 11:35
          +11

          а если на С++ написать функцию которая это делает, то окажется можно сделать за одну строку:

          make_what_we_need();

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


          1. SpiderEkb
            28.04.2024 11:35

            А если он уже реализован? И развивается постоянно?


      1. Writer4
        28.04.2024 11:35

        Язык я-ля DSL это хорошо, но с ним есть проблема. Кто на нем будет писать. Если он нишевый, но более-менее распространенный, типа того же RPG или ABAP/4, то в целом людей можно найти. Если же это совсем узкая штука конкретной конторы, то писать на нем прикладуху мало кому интересно, просто в силу того, что за пределами конторы это никому не нужно. То есть свой DSL лучше делать, чтобы могли на нем писать кто-то вроде аналитиков.


        1. alexandrustinov
          28.04.2024 11:35
          +2

          То есть свой DSL лучше делать, чтобы могли на нем писать кто-то вроде аналитиков.

          Совсем не обязательно писать свой именно отдельный язык. Есть же пример Lisp/Scheme, где и языка как такового нет и ты сразу пишешь просто в AST.
          Ну так AST можно описать чем угодно - хоть в YAML, JSON или и вовсе на С/C++ макросах, которые будут убирать лишнее под капот.

          К примеру вот пример C/C++ кода. Ниже FOR(), SELECT(), FROM(), WHERE(), IS_NOT_LIKE() - это макросы.

          int main() {
            FOR(record, 
              SELECT(INDEX_DESC(persons_name)) 
              FROM(persons)
              WHERE(IS_NOT_LIKE(persons.name, "Сидоров%") && (persons.age > 40))
            ) {
              printf("%s %d\n", record.name, record.age);
            }  
            return 0;
          }

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

          Только не надо говорить что макросы это зло - вы просто не умеете их готовить (с)

          P.S. А еще M4 существует :)


          1. vkni
            28.04.2024 11:35
            +1

            К примеру вот пример C/C++ кода. Ниже FOR(), SELECT(), FROM(), WHERE(), IS_NOT_LIKE() - это макросы.

            Это eDSL. ;-)


          1. pooqpooq
            28.04.2024 11:35
            +2

            Только не надо говорить что макросы это зло - вы просто не умеете их готовить (с)

            1. Насколько хороши здесь будут сообщения об ошибках, если аналитик случайно напишет `persons.age > "4O"`? Или, например, если аналитик случайно забудет в insert обязательные для заполнения поля?

            2. Насколько легко здесь будет реализовать «межмакросную» зависимость — ну там, про агрегатные функции и group by, скажем?


            1. Writer4
              28.04.2024 11:35

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


        1. SpiderEkb
          28.04.2024 11:35
          +1

          Ну наконец-то :-)

          Да, речь именно об RPG. У нас в стране как минимум три банка (Альфа, Райф и Росбанк) его используют. Плюс как минимум три (которых я знаю) компании - BTC, Cinimex и РМС-Лаб предлагают услуги по разработке на нем. На SO есть теги #rpg (старые диалекты) и #rpgle (современный диалект). В LinkedIn есть группы. Есть форум на Code400, есть блоги - Nick Litten, Scott Klement, Simon Hutchinson (RPGPGM). Есть IT Jungle, MySampleCode, WisdomJobs... Т.е. вполне устойчивое сообщество где можно и поделиться чем-то и спросить.


      1. isadora-6th
        28.04.2024 11:35

        Так а чем вас так STL не устраивает? Да и "стандарт"?

        Вот было круто то, когда был borland C++, embarcadero C++, и очередной закрытый компилятор от странной конторы которая не понятно вообще будет ли жива через 2 года.

        Ну и на самом деле никто не мешает вам не линковаться с -lstdc++ и жить себе прекрасно на рукописных примитивах с -fno-exceptions в счастливом мире си с классами.

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


      1. Contender
        28.04.2024 11:35

        да хоть на JSON

        Это как?


        1. netch80
          28.04.2024 11:35

          Ну например вот функция сложения двух чисел

          {"type": "function", "name": "adder", "args": [["x", "int"], ["y", "int]], "result_type": "int",
           "body": [
             {"st": "return", "expr": ["+", "x", "y"]
             }
          ]}

          Такой себе AST в виде списков и деревьев, только на JSON.

          В реальном, конечно, будет много отличий (например вместо "x" в выражении, скорее всего, будет что-то вроде {"local":"x"}), но внешне будет достаточно похоже.


    1. atues
      28.04.2024 11:35

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


    1. sdramare
      28.04.2024 11:35
      +5

      Если вы про RPG, то это не язык, заточенный под задачи "работы с БД и коммерческие расчеты", а платформа мейнфреймов ΙΒΜ заточенна под глубокую интеграцию с этим прориетарным языком(вроде возможности прямого чтения данных из бд). Шаг в сторону от ΙΒΜ мейнфреймов и этот язык теряет всякий смысл в отличии языков общего назначения типа С/С++/Java и т.д.


      1. SpiderEkb
        28.04.2024 11:35
        +1

        И да и нет.

        IBM i - это не мейнфреймы. Мейнфреймы - IBM z. i - это middleware. Изначально позиционировались как сервера для малого и среднего бизнеса. Но получилось настолько мощно и масштабируемо, что успешно используется и в крупном

        Да, RPG интегрирован в систему. Но тут все интегрировано. БД (DB2) тоже часть системы. Компиляторы (CL, COBOL, C, C++, RPG) - часть системы. Все это получаешь сразу из коробки как единое целое.

        При этом есть реализации и для других платформ. Тот же ASNA VisualRPG (в т.ч. и для .NET)

        В С/С++ тоже есть поддержка decimal (правда, только ее, нет ни varchar, ни numeric, ни date, ни time, ни timestamp). И есть библиотека для прямой работы с БД - RECIO. Правда, в чисто "Сишном" стиле - куча опций, параметров, 90% которых в обычной работе нафиг не нужны.

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

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

        Лично я до банка совсем другими задачами занимался. И 25+ лет писал на С/С++. И сейчас иногда пишу когда это оправданно задачей. Но вот столкнувши с RPG быстро понял что раз он тут есть, то использовать его для решения банковских задач эффективнее как с точки зрения разработки, так и с точки зрения конечного результата.

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


        1. eao197
          28.04.2024 11:35
          +2

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

          Замечательно. Осталось понять при чем здесь C++.

          Ведь из ваших слов логичным образом вытекает, что в ваших условиях и на ваших задачах RPG будут сливать и Java (со Scala-ми и Kotlin-ами), и C# с F# впридачу, и другие высокоуровневые универсальные языки (ну, может быть, за исключением COBOL-а).

          Но "на помоечку" (с) отправить нужно именно C++.


          1. SpiderEkb
            28.04.2024 11:35
            +1

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

            С/С++ тут только при том, что здесь он тоже есть и есть возможность сравнить решение одной и той же задачи на них или на RPG. По затратам времени на разработку и по эффективности полученного результата (объективно, через снятие PEX статистик на одних и тех же данных).

            С COBOL вопрос открытый. С одной стороны, он также специализирован для этого же класса задач, с другой - он не развивается давно (а IBM его никогда и не развивала, им MicroFocus занимался). И в нет много того, что сейчас появилось и появляется в RPG. Чисто на уровне "синтаксического сахара".

            Хотя, если стоит задача портирования, то COBOL будет лучшим решением т.к. его можно перенести на другие платформы (тот же GNU COBOL).


            1. eao197
              28.04.2024 11:35

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

              Тогда зачем продолжать полоскать C++?


              1. SpiderEkb
                28.04.2024 11:35

                Ну хорошо, не будем, раз это так оскорбляет чьи-то чувства.


                1. eao197
                  28.04.2024 11:35
                  +3

                  Речь не о чувствах, а о том, чтобы картина была объективна.

                  Ваше восхваление RPG можно было бы понять, если бы вы пришли на платформу IBM i и начали решать свои специфические задачи на C++, долго от этого страдали, пока кто-то не раскрыл вам глаза на существование RPG. После чего мир заиграл новыми красками, а жизнь вновь стала прекрасна и удивительна.

                  Но, как я понимаю, было не совсем так. На этой платформе уже был инструмент, который заруливал и C++, и Java, и C#. Было бы странно отказываться от него. Но раз инструмент заточен и под платформу, и под задачу, то какой смысл все время противопоставлять его C++?

                  Вот кто-то бы начал сравнивать C++ с регулярными выражениями, уместно бы было такое сравнение?

                  Да, на C++ вы могли бы закатывать Солнце вручную выписывая что-то вроде:

                  auto exp = capture(repetition(2, 8, range('0', '9')));
                  

                  вместо ([0-9]{2,8})

                  Но если бы кто-то начал бы снова и снова приводить примеры того, как лаконично и просто выглядит регулярка, и как все сложно с ее повторением в C++, то нормальным бы это вряд ли выглядело.


                  1. SpiderEkb
                    28.04.2024 11:35

                    Ваше восхваление RPG можно было бы понять, если бы вы пришли на платформу IBM i и начали решать свои специфические задачи на C++, долго от этого страдали, пока кто-то не раскрыл вам глаза на существование RPG. После чего мир заиграл новыми красками, а жизнь вновь стала прекрасна и удивительна.

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

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

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

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

                    И да, я знаю людей, которые по прежнему пишут на С++ то, что мы делаем на RPG. И Честно скажу - результат не впечатляет. Ни по затраченным усилиям, ни по производительности конечного продукта.

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

                    На заре деятельности я точно также выбирал между Паскалем и С. Потом был момент выбора между Clipper и Clarion. В какой-то момент даже использовал dbVista (ныне RDM - Raima Data Manager) т.к. ее сетевая модель БД более подходила под конкретную задачу, нежели реляционная.


                    1. eao197
                      28.04.2024 11:35

                      И да. Мне привычнее было бы делать все на С/С++. И я пробовал это делать на С/С++.

                      Ну понятно. Именно C++ плох потому, что вы пришли из C++. Пришли бы из Java, то плохой по сравнению с RPG была бы Java. Но т.к. в вашем багаже Java (или C#) не было, то плох именно C++.


                      1. SpiderEkb
                        28.04.2024 11:35

                        Где я говорил что С++ плох? Вот прям чтобы вот так вот. Я говорил только то, что для задач, связанных с реализацией бизнес-логики на этой платформе есть более удобные инструменты.

                        Везде писал что RPG позволяет данные конкретные задачи решать проще и эффективнее. При этом, на нем можно сделать все, что можно сделать на С, но если нужно что-то системное, то это будет достаточно неудобно. Можно, но на С или С++ это будет проще и изящнее.

                        Скажем, я могу на RPG написать движок для распараллеливания обработки больших объемов данных или тот же USRQ API, но это займет больше времени и будет "неуклюже" по сравнению с С/С++.

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


                      1. eao197
                        28.04.2024 11:35
                        +1

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

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

                        Не подставляете же вы именно потому, что в бэкграунде у вас C++. Была бы Java, вы бы точно так же говорили про Java.

                        Если и сейчас до вас не дойдет...


                      1. SpiderEkb
                        28.04.2024 11:35

                        RPG "рвет всех" только в конкретном классе задач. Это узкозаточенный инструмент.

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

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

                        И, честно говоря, не совсем понимаю вашу позицию - вы что пытаетесь доказать? И кому?


                      1. eao197
                        28.04.2024 11:35

                        И, честно говоря, не совсем понимаю вашу позицию - вы что пытаетесь доказать?

                        Я пытаюсь доказать, что когда вы сравниваете RPG с C++ в своих задачах, то вы напрасно акцентируетесь на C++. Т.к. на место C++ в этом сравнении можно подставить хоть Java, хоть C#, хоть Kotlin, хоть Scala. Ничего не поменяется.

                        И кому?

                        Вам.


                      1. SpiderEkb
                        28.04.2024 11:35

                        Я сравниваю сравнимое. То, что можно взять и запустить параллельно в двух терминальных сессиях и посмотреть разницу (по времени выполнения, PEX статистикам). Сравнить время на реализацию одной и той же задачи.

                        И неоднократно говорил, что ряд задач я делаю на С/С++ потому что это быстрее и удобнее. Но это системные задачи. Обычно требующие плотной работы с системными объектами (системные указатели, MI), работы в TERASPACE модели памяти с 64бит указателями и т.п. Для всего этого на этой платформе нет альтернативы С/С++.


                      1. eao197
                        28.04.2024 11:35
                        +1

                        Я сравниваю сравнимое.

                        Да я тоже могу на C++ сделать DSL для описания регулярного выражения, а потом сравнить его результат с выхлопом, скажем, re2c. Или сделать на C++ вручную bottom up LR(1) парсер и сравню его с bison-ом.

                        Не, ну а чё? Сравнимое же.

                        А потом еще и буду говорить, но вот когда нужно делать обработку 2D изображений, тогда, конечно же, С++ круче, чем re2c и bison вместе взятых.

                        Получится как у вас с вашим любимым IBM i.

                        Я могу понять, когда люди сравнивают C++ и, скажем, Java для реализации проектов типа NetBeans или Eclipse. Когда компонентная архитектура, плагины от разных разработчиков. И хочется, чтобы все это работало и быстро, и надежно.


                      1. SpiderEkb
                        28.04.2024 11:35

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

                        Просто сейчас, так сложилось, у меня 80% задач требуют вполне определенного инструмента. Но это не значит что я буду натягивать сову на глобус и использовать тот же инструмент под оставшиеся 20%.


                      1. eao197
                        28.04.2024 11:35

                        Ну так о чем и речь.

                        Тогда повторю свой вопрос: а при чем здесь C++?

                        Зачем вы именно C++ противопоставляете "наиболее эффективному инструменту под задачу"?


                      1. Cerberuser
                        28.04.2024 11:35

                        В переводе на русский, я так понимаю, "зачем именно под статьёй про C++, где обсуждается только C++ и всё, что с ним связано, влезать со своим инструментом", верно? Просто, ну, это можно было бы и прямо сказать, со стороны совсем не очевидно, что эта статья только про C++.


                      1. eao197
                        28.04.2024 11:35
                        +1

                        Как бы да, и я стараюсь об этом прямо сказать.

                        Но, позволю себе немного растечься мыслею. Сама статья весьма специфична и в ней речь идет о (кмк) сравнительно узкой сфере задач, решаемых на C++. Тем не менее, она актуальна в том плане, что автор статьи рассказывает о вещах, которые появились a) недавно (так мне показалось) и b) как способ преодолеть ограничения, достигнутые при применении универсальных языков (C++ в частности). Т.е. сперва задачи из предметной области автора статьи решались на C++ (и конкурентов практически не было), но времена меняются и сейчас вне C++ можно достичь результатов лучше, чем на C++.

                        Здесь все OK. С поправкой на то, что не нужно специфику узкой области автора статьи экстраполировать на вообще весь C++ и сферы его применения (что, кмк, происходит в комментариях, автор статьи такой попытки и не делал).

                        Но вот появляется персонаж, который в очередной раз (ну вот реально он не впервые прибегает с RPG в комментарии к статьям про C++) говорит: "А вот RPG в моих задачах рвет C++ как тузик грелку". И это не имеет отношения к поднятой в статье теме просто потому, что RPG специально затачивался под такие задачи, и рвет он не только C++, но и вообще все за исключением COBOL-а (с той же самой платформы).

                        Мало того, что данный персонаж со своим RPG уже поднадоел, так еще и непонятно какие выводы из его историй стоит сделать (и это принципиально отличает обсуждаемую статью). Ближайшая аналогия: если вам нужны регулярки, то используйте готовые regex-а, а не костыльте их самостоятельно. Ну как бы да, спасибо, Кэп.


                      1. SpiderEkb
                        28.04.2024 11:35

                        Если внимательно почитать статью, то

                         Я по-прежнему писал в основном на C++, но то и дело ко мне обращались с вопросом: «А хочешь вот эту задачу, она не на C++?» И я отвечал: «Давай!» А затем брался за задачу, какова бы она ни была.

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

                        Дальше идет перечисление инструментов, которые к каких-то узких областях лучше С++

                        И вывод:

                        Если мне придётся променять  C++ на «не C++», то 80% моей работы никак не изменится. Язык C++ уже просто нерелевантен для большинства моих задач. Могу ли я в таком случае утверждать, что для меня C++ уже на 80% мёртв?

                        Вот об этом мои комментарии. И самый первый вроде как особых возражений не вызвал. Потому что на 100% соответствует последнему абзацу статьи.


        1. sdramare
          28.04.2024 11:35
          +1

          Да, но речь в посте же идет про задачи общего назначения, не привязанные к вендору и экосистеме(как RPG и IBM). К примеру, амазон может сделать специальную интеграцию Visual Basic для своего api, что позволит писать на нем под AWS быстрее и проще чем на любом другом языке, но это не сделает Visual Basic в общем случае удобнее и производительней чем С++


  1. Alexey2005
    28.04.2024 11:35
    +62

    C++ умирает именно потому, что современное программирование - оно вообще не про написание кода, а про его чтение и компоновку из готовых кусков.

    Самый лучший язык XXI века - тот, который позволит вам сделать git clone, тут же собрать склонированное и наскоро адаптировать к своей задаче парочкой воткнутых костылей.

    Соответственно, рулит не синтаксический сахар, синтаксис сейчас не значит вообще ничего.

    Непременные признаки хорошего современного языка общего назначения:

    • Удобный тулчейн, чтобы сборка проекта, стянутого по git clone, осуществлялась одной командой и не падала хотя бы в 95% случаев, а при падении выдавала внятное сообщение об ошибке.

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

    • Богатая стандартная библиотека.

    У C++ нет ни первого, ни второго, ни третьего. Какое будущее может быть у языка, в котором написать программу заново может быть проще, чем заставить скачанное с github'а просто собраться на вашей машине? Современный программист не будет читать пять страниц инструкции по сборке, которая подразумевает ручную установку зависимостей и ручную же синхронизацию пачки скриптов, использующих make, bash, perl, python и node.js одновременно.

    Что за язык, у которого в стандартной библиотеке до сих пор нет нормальных средств работы с сетью? И это когда WiFi уже даже в микроконтроллеры встроен, а весь софт давно ушёл в облака.


    1. nameless323
      28.04.2024 11:35
      +12

      C++ умирает именно потому, что современное программирование - оно вообще не про написание кода, а про его чтение и компоновку из готовых кусков.

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


      1. Lodinn
        28.04.2024 11:35
        +9

        На плюсах писал лет 8. Пришёл к ощущению, что что-то совсем низкоуровневое лучше на "голых" сях слабать, а если что посложнее, особенно с зависимостями - тулинг незаменим, так что какой-нибудь Rust или Python, да хоть Ruby.

        Кажется, что на C++ пишут приложения, которые с одной стороны будут живы и через 15-20-30 лет, с другой - поддерживать их будут плюс-минус те же команды. В современном климате таких не настолько много: или делаем на века и для всех (как ядро Linux), или цикл разработки занимает несколько лет: зашли на рынок, привлекли денег, выросли, выкинули старый код и переписали заново.


        1. nameless323
          28.04.2024 11:35
          +5

          Кажется, что на C++ пишут приложения, которые с одной стороны будут живы и через 15-20-30 лет, с другой - поддерживать их будут плюс-минус те же команды.

          Весь серьезный геймдев, всякие про приложения типа фотошопа, 3д макса и т.д.

          что-то совсем низкоуровневое лучше на "голых" сях слабать

          Я вроде и согласен, но бывает что людям хочется чтобы С, но были классы, или темплейты, или что еще. Проще в таком случае взять С++ и кастрировать (а давайте напишем на плюсах, но без RTTI/исключений/смартпоинтеров/у_кого_на_что_фантазии_хватит), что собственно часто и происходит.


          1. sdramare
            28.04.2024 11:35
            +1

            Весь серьезный геймдев

            Геймдев пишет на том, начем написан лучший движок по сочетаюнию цена/качество. Когда был бум мобильных игр, геймдев писал на Unity/C#.

             всякие про приложения типа фотошопа, 3д макса и т.д.

            Blender на чем написан?


            1. Serpentine
              28.04.2024 11:35

              Blender на чем написан?

              80% на C++. Тулзы и аддоны на Python. Его рендер (cycles) на C и С++.

              Если я ничего не путаю, в Maya расширения тоже пишутся на Python, когда она сама написана C++.


            1. cyber_dream
              28.04.2024 11:35

              И Godot на C++ со своей скрипт машиной. Теперь все будут писать на gdscript, спускаясь в плюсы по желанию


            1. nameless323
              28.04.2024 11:35
              +1

              Геймдев пишет на том, начем написан лучший движок по сочетаюнию цена/качество.

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


              1. Ken-Sei
                28.04.2024 11:35

                Сам юнити так же написан на С++.

                Нет, Unity написана C#, в отличии от 99% движков, которые, да, написаны на C++.


                1. Serpentine
                  28.04.2024 11:35

                  Нет, "Unity itself is written in C++".

                  Там же: "The code that has to run super-fast like the physics and animation, all that  is C++" (David Helgason, the CEO and co-founder of Unity Technologies).


                1. nameless323
                  28.04.2024 11:35

                  C# это скриптовый язык юнити и, если правильно помню, редактор написан на шарпе, сам кор движка/рендер написан на плюсах


          1. Lodinn
            28.04.2024 11:35

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

            Проще в таком случае взять С++ и кастрировать

            То так, мне кажется, что многие конторы так и работают с C++. Но управлять процессом сложно; в плюсах за годы появилось много всего хорошего, конечно, но нет какого-то... Ясного видения, что ли. Стандарты выглядят как свалка. "Фичи? Их есть у нас!". То же самое начинает проникать и в код - или у тимлида светлая голова и готовность следить за внутренними стандартами, или "мы пишем без темплейтов кроме вон тех трёх мест, где лучше не придумали". Иногда лучше меньше, да лучше.

            Мой аргумент, пожалуй, в том, что "пишем на кастрированном C++" - в некой мере следствие занятой плюсами доли рынка. То бишь уже есть люди, умеющие писать на C++, и дешевле занять их, чем переучивать или находить кого-то, умеющего в идеально подходящий с точки зрения технологии стек. От такого язык не умрёт быстро, но на спад, пожалуй, идёт. Так-то и фортран с коболом "живы".


      1. pooqpooq
        28.04.2024 11:35

        Если нужен перформанс, то делается свой собственный язык и пишется компилятор для него на чём угодно. C++ в таком случае не подходит, потому что стандарт оставляет слишком много вещей как undefined behavior (лайфтаймы, алиасинг, всё такое), которые надо обкостыливать для соответствия стандарту и надеяться, что оптимизатор поймёт, что вы имели в виду.


        1. Kelbon
          28.04.2024 11:35
          +18

          это какой то уже бред в активной стадии, под перфоманс написать свой язык и компилятор


          1. pooqpooq
            28.04.2024 11:35

            Не более бред, чем использовать для топового перформанса язык, который топовый перформанс не даёт (и это я ещё интринсики не вспоминал) и на котором невозможно писать надёжный код (ведь C in CVE stands for C/C++), даже если обложиться санитайзерами, линтерами и программистами старой школы, пишущими ядро.

            А если топовый перформанс вам не нужен, то и C++ можно не выбирать, других языков хватит.


          1. pooqpooq
            28.04.2024 11:35
            +5

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

            C++ во всех таких местах используется (если используется) исключительно из-за соображений легаси и доступного пула программистов. Ну, «программист», могущий выдать что-то криво-косо как-то работающее на плюсах, таки чуть более распространён, чем человек, способный написать DSL, а что там завтра будет или через 10 лет с поддержкой — да хоть трава не расти.


            1. vkni
              28.04.2024 11:35

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

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


              1. pooqpooq
                28.04.2024 11:35

                Но согласитесь, очень уж печальные у вас примеры: по-сути это просто высокообразованные высокотехнологичные спекулянты

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

                живущие в городах-помойках.

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


                1. vkni
                  28.04.2024 11:35
                  +1

                  Ну это вопрос философский, особенно считать ли спекулянтов чем-то плохим.

                  Всё упирается в философский вопрос - "нужны ли мы нам?" :-) Если вас интересует существование человечества, развитие индустриальной цивилизации, то спекулянты безусловно плохи.

                  Это особенно печально, но удалёнка нас спасёт.

                  Очень приятно осознавать, но удалёнки у спекулянтов нет. :-) Так что если хочется на них работать, придётся жить в помойке. Карма.

                  Да и можно заработать спекуляциями многоденег, переехать в город подешевле и поприятнее

                  Для этого надо быть спекулянтом, а не работать на них.


                  1. pooqpooq
                    28.04.2024 11:35
                    +1

                    Всё упирается в философский вопрос - "нужны ли мы нам?" :-) Если вас интересует существование человечества, развитие индустриальной цивилизации, то спекулянты безусловно плохи.

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

                    Более того, есть разные интересные эффекты следующего порядка малости. Например, тот же HFT — это вообще-то локально игра с нулевой суммой, поэтому туда идёт весьма определённый тип людей с определённой толерантностью к риску. Ты уверен, что хочешь, чтобы эти люди иначе пошли бы, я не знаю, писать софт для кардиостимуляторов или ракет, особенно учитывая, что писать софт для кардиостимуляторов или ракет бесконечно скучно на фоне инфры для трейдинга или язычка для описания правил выдачи кредитов с Хиндли-Милнером и row polymorphism'ом по всем последним папирам с POPL?

                    А так и волки сыты, и овцы целы, и ликвидность генерируется без попыток ответа на не имеющие ответа вопросы вроде «в какой момент инвестирование перестаёт быть инвестированием и начинает быть спекуляциями в плохом смысле?»

                    Для этого надо быть спекулянтом, а не работать на них.

                    Чувак, пишущий инфру для трейдеров (или те вот всякие DSL) — он уже спекулянт или ещё нет?

                    Очень приятно осознавать, но удалёнки у спекулянтов нет.

                    В зависимости ответа на предыдущий вопрос она может быть :] Мне не так давно предлагали удалёнку у весьма известных спекулянтов, и там в офисе надо быть что-то вроде около одного дня в неделю, а в некоторых командах — раз в месяц. Можно жить в паре часов езды от ближайшего города и выбираться в офис раз в неделю по какому-нибудь там 183-му шоссе ;]


                    1. vkni
                      28.04.2024 11:35

                      Чувак, пишущий инфру для трейдеров (или те вот всякие DSL) — он уже спекулянт или ещё нет?

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

                      и там в офисе надо быть что-то вроде около одного дня в неделю

                      Это и есть нет удалёнки.


                      1. netch80
                        28.04.2024 11:35
                        +1

                        Главное, что он бессмысленно прожигает свою жизнь.

                        Ну может быть исключение - если он разработает, например, новый lock-free wait-free gluten-free алгоритм или структуру данных :) чтобы сэкономить 5нс заказчику, и опубликует это. А иначе прогресс в таких областях не очень-то и движется.


                      1. vkni
                        28.04.2024 11:35

                        На ICFP последней реально было крайне печально смотреть на применения «высокого программирования»: либо пузырь AI, либо спекуляции, либо геймдев (кмк, мэтры очень стеснялись участия в этом геймдеве, но может быть и зря — затея хотя бы отчасти благородная: дать передний край детям). Одна радость — у Tarides был стенд про космос (они запустили Mirage на одном из индийских спутников в качестве нагрузки).


                      1. pooqpooq
                        28.04.2024 11:35
                        +2

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


          1. vkni
            28.04.2024 11:35

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


          1. DarthVictor
            28.04.2024 11:35

            Бред, но ещё это может быть HFT.


    1. LordCarCar
      28.04.2024 11:35

      Как это согласуется с

      В мире настолько много опытных программистов, как никогда ранее в истории.

      Не особо-то они, значит, и опытные, а так, натасканные на несколько шаблонов.


    1. Sly_tom_cat
      28.04.2024 11:35
      +1

      Я правильно понял что языком общего назначения вы назвали Golang? :)


    1. Starl1ght
      28.04.2024 11:35

      Не смотря на то, что эта критика абсолютно валидна - мы это решили при помощи vcpkg, где Cmake сначала скачивает все зависимости (залоченные), а потом собирает.

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


    1. aryk38
      28.04.2024 11:35

      " У C++ нет ни первого, ни второго, ни третьего " - ололо. поколение nuevo гордится тем что не слышало про make ?


      1. Starl1ght
        28.04.2024 11:35
        +17

        Make это отрыжка из 70ых.


      1. isadora-6th
        28.04.2024 11:35
        +10

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


        1. feelamee
          28.04.2024 11:35

          я бы воспользовался кросс компилированием)


      1. MountainGoat
        28.04.2024 11:35

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


    1. perfect_genius
      28.04.2024 11:35

      до сих пор нет нормальных средств работы с сетью

      Работу с файлами дождались - и сети дождёмся. И зачем-то 2D графику...


    1. releyshic
      28.04.2024 11:35

      " рулит не синтаксический сахар, синтаксис сейчас не значит вообще ничего " тот в C# каждый год новый сахар завозят - от ненужности )) там есть сомнительные вещи, но они больше для "когда очень нужно"


    1. feelamee
      28.04.2024 11:35
      +1

      думаю у c++ сообщество все это есть.

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

      1. Есть тот же conan, vcpkg. Но хорошие либы и без них собираются одной командой - make или cmake.

      2. conan, vcpkg

      3. boost

      Вообщем-то

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

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


  1. MountainGoat
    28.04.2024 11:35
    +22

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

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

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

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


    1. rukhi7
      28.04.2024 11:35
      +2

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

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


      1. SpiderEkb
        28.04.2024 11:35
        +1

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


        1. rukhi7
          28.04.2024 11:35

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

          В этом принципиальна разница.


          1. netch80
            28.04.2024 11:35
            +1

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

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


            1. rukhi7
              28.04.2024 11:35

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

              Давайте остановимся на общем выводе что краш-тесты тоже имеют право на жизнь :) .


              1. netch80
                28.04.2024 11:35
                +1

                Крэш-тесты устраивают и самолётам. Разумеется, какому-нибудь 737MAX - вряд ли, хотя, при минимуме набивки только на голой обшивке - возможно, и делали (ну, с макетами набивки). А средствам подешевле и помассовее - тем более.

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

                Давайте остановимся на общем выводе что краш-тесты тоже имеют право на жизнь :)

                Безусловно :)


              1. esaulenka
                28.04.2024 11:35

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

                От отсутствия системы предупреждения самолёт сам по себе не упадёт. Это именно предупреждение для уставшего/отвлёкшегося пилота, что "кажется что-то пошло не так", и надо срочно что-то делать.

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


    1. vkni
      28.04.2024 11:35
      +3

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

      Бизнес часто попадает в ловушку, когда первые 10% решения действительно пишутся в 4 раза быстрее, а вот последние 10% из-за динамического типизирования встают в такую копеечку! См. тесты, объём которых больше программы. :-)

      Так что "я сын С++ника, тут не всё так однозначно". :-)


      1. releyshic
        28.04.2024 11:35
        +1

        " См. тесты, объём которых больше программы " а как размер тестов зависит от типизации?


        1. orenty7
          28.04.2024 11:35
          +9

          Например, можно выразить часть гарантий в типах


        1. vkni
          28.04.2024 11:35
          +1

          Антипропорционально зависит.

          И то, и другое — это средство проверки гарантий, если брать аналогии с интегрированием, то тесты — это Монте-Карло, а статическая типизация — автоматизированные аналитические вычисления (Mathcad/Mapple/Mathematica/Maxima).

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


      1. RH215
        28.04.2024 11:35

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


        1. vkni
          28.04.2024 11:35
          +2

          причём во все типизацию тем или иным способом завезли.

          «Органично совмещая недостатки обоих подходов» ;-)


  1. rukhi7
    28.04.2024 11:35
    +7

    Зачем вообще программировать на C++, если достаточно дать высокоуровневое описание алгоритма — и инструмент сделает вам код, в два раза обгоняющий код на C++?

    тут есть один простой вопрос: на чем написан инструмент, который сделает вам код, в два раза обгоняющий код на C++, хотя бы частично?

    Я боюсь что тут вариантов никогда не будет: С++, С, Ассемблер.

    Они бессмертны, пока есть процессоры система команд которых это и есть ассемблер.


    1. HemulGM
      28.04.2024 11:35

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


      1. Apoheliy
        28.04.2024 11:35
        +6

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

        Если говорить про возможности чистого C:

        Чистый C позволяет писать программы без динамической памяти. И в ряде случаев это хорошо. Однако чистому C требуемся стэк (и, возможно, немного локальной памяти). Без стэка C не может. С другой стороны: много ли языков могут работать в таких условиях?

        И если пойти чуть дальше:

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

        К чему это? А, да: сомнительно, что таких подобных языков (заменителей C, Asm) много. Вот честно: очень сомнительно. Приведёте парочку примеров?


        1. HemulGM
          28.04.2024 11:35
          +3

          FPC, например


          1. Yuri0128
            28.04.2024 11:35

            Ну так Turbo Pascal, на систаксисе которого обосновался Free Pascal он весьма древний, как-бы не древнее ANSI C++....


        1. netch80
          28.04.2024 11:35
          +2

          С другой стороны: много ли языков могут работать в таких условиях?

          Про Паскаль уже сказали. Естественно, его продолжения в виде Modula и Ada. Fortran.

          Всем названным нужно отрубить стандартную библиотеку, ну так и для C это тоже надо (freestanding сборка с нужными свойствами).

          Среди этого комплекта мы в основном помним C только потому, что в нём соответствующие действия выражаются максимально прямо.

          правда, иногда подставляя кэш процессора как квази-память для создания ощущения стэка

          Это ортогонально использованию стека. Если говорить про старт x86 в до-ME реализациях, то там псевдо-RAM на стеке использовалась и для данных (типа результатов итерирования устройств на шинах), а до получения такой псевдопамяти стек эмулировался адресами возврата прямо в коде.

          А, да: сомнительно, что таких подобных языков (заменителей C, Asm) много.

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


    1. MountainGoat
      28.04.2024 11:35
      +26

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


      1. Alexey2005
        28.04.2024 11:35
        +3

        Это вы рассматриваете только легальные (белые) вакансии. Тогда как для ассемблерщиков самые доходные вакансии связаны не с написанием кода, а с реверсинженирингом всего подряд - от потрошения прошивок микроконтроллеров до слома коммерческого софта.

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


        1. perfect_genius
          28.04.2024 11:35
          +1

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

          Или вы про читоделов?

          Читал, что не хватает с обратной стороны - античитеров в команде разработчиков онлайновых игр, которые реверсили бы читы.


          1. esaulenka
            28.04.2024 11:35

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


      1. rukhi7
        28.04.2024 11:35

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


        1. perfect_genius
          28.04.2024 11:35

          А как же программирование микроконтроллеров?


          1. isadora-6th
            28.04.2024 11:35

            esp8266 гоняет с 80кб общей оперативки и 4мя мегабайтами места под программу (она вообще flash, можно барахло свое хранить). Ассемблер там не то чтобы нужен.

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


    1. MountainGoat
      28.04.2024 11:35
      +2

      Компиляторы теперь частенько состоят из двух частей. Первая переводит из языка в универсальный код, а вторая компилирует универсальный код под конкретную архитектуру. Это позволяет отделить оптимизацию под архитектуру от языка в разные проекты. Первая часть у любого приличного языка написана на нём самом. Даже у Python есть такой вариант. Вторая часть - это обычно LLVM, написанный на С. Но есть и Cranelift написанный на Rust.

      Так что как минимум Rust можно целиком компилировать на нём самом. Другое дело, что так никто не делает, потому что никто кроме вас и ещё пары человек, не видит в этом ценности. Когда Rust начнёт вытеснять C, просто LLVM перепишут на Rust и дальше двинутся. Да уверен, кто-нибудь уже начал.


      1. morijndael
        28.04.2024 11:35
        +3

        никто кроме вас и ещё пары человек, не видит в этом ценности

        Ну вообще конкретно в этом случае ценность есть. Cranelift оптимизируется под скорость компиляции, и позволит делать debug-сборки быстрее, и быстрее получать обратную связь

        </зануда>


        1. MountainGoat
          28.04.2024 11:35

          Не, вы не въехали :) Речь о том, чтобы компилятор/среда языка был целиком написан на нём самом, без вставок на С. Я не думаю, что отсутствие такого софта говорит, что язык плох, или жить не может без С. Припрёт - перепишут, а сейчас зачем, если есть и С и кодыри на нём?


      1. redfox0
        28.04.2024 11:35

        Про это как раз в статье написано:

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

        Появляются новые оптимизации в LLVM - и сразу появляются во всех языках, которые используют его.


        1. MountainGoat
          28.04.2024 11:35
          +1

          Не согласен. Оптимизации и решения до уровня LLVM гораздо более важны для производительности. Из банального: один язык при любом обращении к массиву проводит проверку выхода за пределы n < arr.length, второй тупо берёт *arr[0] + n*sizeof(arr[0]), а Rust вообще имеет интеллектуальную выбиралку, проверять или нет. LLVM такие штуки не решает, столь сложные вещи ему даны в ощущениях.


        1. netch80
          28.04.2024 11:35
          +1

          Проведите эксперимент - возьмите C++ код, скомпилируйте в LLVM IR опциями типа `-S -emit-llvm`, напустите ll-opt на результат и затем в бинарь. С разными уровнями оптимизации у clang и у ll-opt. Увидите, что если clang получил -O0, то результат исправить потом практически нереально, будет куча лишних операций.


    1. pooqpooq
      28.04.2024 11:35
      +4

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


      1. gev
        28.04.2024 11:35

        Тоже используем Haskell для этого, только генерим С99 =)


    1. Ken-Sei
      28.04.2024 11:35

      тут вариантов никогда не будет: С++, С, Ассемблер.

      А как же Forth?


      1. netch80
        28.04.2024 11:35

        А как же Forth?

        Среда Forth со старта уже требует RAM и двух стеков в нём (данных и возвратов) и стандартной библиотеки.


      1. rukhi7
        28.04.2024 11:35

        А как же Forth?

        не знаю такого, забыл наверно. У него есть бесплатная среда разработки, компилятор?


        1. netch80
          28.04.2024 11:35

          Начните с GNU Forth. Да, есть. (Но условиям он не соответствует)


    1. DarthVictor
      28.04.2024 11:35

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

      Компиляторы большинства компилируемых языков написаны на них самих.


  1. mvv-rus
    28.04.2024 11:35
    +18

    Мы же теперь живём в XXI веке. В мире настолько много опытных программистов, как никогда ранее в истории. И ещё нам сейчас более чем когда-либо ранее необходим эффективный софт.

    В XX веке всё было проще. У вас есть идея, вы обёртываете её в некий пользовательский интерфейс и продаёте как продукт для ПК. Программа тормозит? Какие проблемы? Всего через полтора года компьютеры станут вдвое быстрее. Главное войти на рынок, начать продажу фич, предпочтительно — чтобы они работали без багов.

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

    Но вот почитать про языки, предназначеные для повышения эффективности выполнения - это таки да, интересно. Так что если рассматривать статью исключительно с этой стороны, то она IMHO таки достойна перевода.


  1. JordanCpp
    28.04.2024 11:35
    +9

    Тем не менее, считаю, что писать на C++ — плохая привычка. Этот язык небезопасен, не так эффективен, как считается, при работе с ним программисту приходится жутко ломать голову над вещами, которые никак не связаны с созданием ПО. А вы знаете, что в MSVC uint16_t(50000) * uin16_t(50000) == -1794967296? А знаете, почему? Да, вот такие мысли меня занимали.

    Доктор, когда я так делаю. Мне больно.

    Так не делайте так.

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


    1. SpiderEkb
      28.04.2024 11:35
      +1

      uint16_t(50000) * uin16_t(50000)

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

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


      1. aryk38
        28.04.2024 11:35

        если ваш конпилятор Сам Без Спросу преобразует Unsigned в Signed то это уже диагноз к выбиральщику конпилятора.


        1. Gumanoid
          28.04.2024 11:35
          +3

          Но ведь он должен так делать по стандарту: https://en.cppreference.com/w/cpp/language/implicit_conversion#Integral_promotion


          1. voldemar_d
            28.04.2024 11:35

            Даже если делать так?

            uint64_t a = uint16_t(50000) * uin16_t(50000);


            1. sergio_nsk
              28.04.2024 11:35
              +2

              Да.


        1. SpiderEkb
          28.04.2024 11:35
          +1

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


    1. KanuTaH
      28.04.2024 11:35
      +9

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


      1. pooqpooq
        28.04.2024 11:35
        +5

        Безотносительно того, что пишут выше, современный программист может быть специалистом в своей предметной области (от бизнес-процессов в компании до какого-нибудь машинного обучения или финансовых стратегий в трейдинге), и требовать от него заодно знать все особенности integer promotion, лайфтаймов в юнионах, алиасинга для char* и работы со строками, и автоматически учитывать, что «вот тут мы пишем на C++17, поэтому mmap точно лайфтайм не начинает, и мне надо поприседать, а вот тут мы пишем на C++20, где некоторые функции лайфтайм начинают неявно, и… пажжи, а является ли это implementation-defined и что там моя implementation пишет про конкретный mmap?» — ну, вы, конечно, можете, но это сильно уменьшает пул доступных специалистов, либо требует иметь двоих разных человек на двух разных позициях. А это снова увеличивает стоимость разработки и увеличивает сроки (потому что между этими людьми должна быть коммуникация с её оверхедом, и если «совмещённому» специалисту что-то станет ясно на тему предметной области прямо в момент написания кода, то с двумя людьми нужно дожидаться точки их синхронизации).


        1. BugM
          28.04.2024 11:35

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

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


          1. pooqpooq
            28.04.2024 11:35
            +10

            Это разумно требовать от С++ сеньора.

            Это ровно случай двух узких специалистов, про который я писал в самом конце. И да, у него есть свои недостатки помимо упомянутых (например, хорошие C++-сеньоры дороги и редко интересуются предметной областью, им интереснее посмотреть свежий cppcon и почитать свежие пропозалы).

            Более того, это может быть хуже: спец по C++ может не заметить при отладке-экспериментировании, например, что у него там какая-нибудь матрица блочно-диагональная получается (потому что он не линейный алгебраист и у него глаз тупо не намётан), и не свести задачу её SVD-разложения (или разложения Холецкого) к разложению блоков, что снизило бы сложность с кубической по сумме всех блоков (Σnᵢ)³ к сумме кубов блоков Σ(nᵢ³), что может быть быстрее на порядки. Зато да, сэкономит проценты на перекладывании байтиков или GC или jit или ещё чём-нибудь таком.

            Или вот на хабре есть один любитель поговорить про мейнфреймы, который гордился, как можно мейнфреймно и низкоуровнево решить задачу пересечения двух здоровенных множеств чисел (или что-то подобное), упаковывая там байты куда надо и утилизируя кэши. В его формулировке он делал два вложенных линейных цикла, имея сложность O(mn), хотя в его же формулировке множества не менялись, и можно было бы хотя бы предварительно отсортировать одно множество (там числа, это дёшево) и потом свести задачу к бинарному поиску с итоговой сложностью O(n logn + m logn), что при подстановке его чисел показало бы на порядки улучшенную производительность. А можно было бы сделать ещё лучше, чем предсортировка (ну там, вспомнить про блум-фильтры, или потратить выходные не на вымучивание ещё одного пропозала, а на чтение папиров и узнавание про binary fuse-фильтры, например). Но не, зачем асимптотики, зачем думать про всё это? Лучше потратить фокус внимания на упаковку байтов и утилизацию кэшей.

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

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

            Ну и, к слову, интересно, сколько игр, где производительность важна (скажем, FPS с графонием), использует собственные движки, а сколько — используют какой-нибудь unreal/unity/etc?

            Почему игра 2024-го года на Unreal при всех крышесносных возможностях этого движка у меня выглядит паршивее и тормозит больше, чем Crysis 3 2013-го года? Куда делись все эти C++-разрабы с их супероптимизированным кодом?

            Почему, кстати, другая постоянно развиваемая игра с C++-движком с историей лет в 20-25 в 2024-м году никак не осилит многопоточность? Может, это как-то связано с тем, что писать многопоточку на C++ тяжело, и все эти замечательные хакерские оптимизации встают колом, когда вы живёте не в 2003-м году (хотя уже тогда был HT), а хотя бы в 2013-м?


            1. Dooez
              28.04.2024 11:35
              +1

              Игра 2024 года на Unreal выглядит лучше Crysis 2013 года. А ещё лучше посмотреть на то что последние версии Unreal могут не в играх, там явно видно производительность.

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


      1. RH215
        28.04.2024 11:35

        привык собирать все из готовых кусочков

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


    1. pooqpooq
      28.04.2024 11:35
      +13

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

      Да и вообще, пишите код без багов, а с багами не пишите. Оценивайте сроки правильно, а неправильно не оцениваете.

      Разработка ПО — это очень просто!


    1. netch80
      28.04.2024 11:35

      Если "новые стандарты" и "хорошие практики" это ckd_mul() из C23 и аналогичные интринсики GCC+Clang, то я согласен. Но для их прихода ко всем ещё несколько лет. Кстати, куда пропала ckd_shl?


  1. YurijLi
    28.04.2024 11:35
    +1

    Похоже, вы единственный, кто сможет ответить на ваш же вопрос. ☺


  1. KorovanKorov
    28.04.2024 11:35
    +1

    Описанное весьма похоже на ситуацию, когда в очередном проекте с классической классической клиент-серверной архитектурой я решил выбрать одну из ORM: не нужно строить многоэтажные SQL запросы, система всё разрулит сама, в крайнем случае железа помощнее добавим и т.д. Ну, всё хорошо, пока не появятся узкие места: большие объемы, необходимость явного управления транзакциями, оптимизация, миграция между версиями структр... Ага, я справился, так как раньше делал "руками" и понимал, что должно происходить на нижнем уровне.

    Имхо, здесь описанными преимуществами сможет воспользоваться лишь тот, кто понимает, во что и почему транслируется Python - код, т.е., специалист, имеющий опыт работы с С/С++...


  1. WASD1
    28.04.2024 11:35
    +11

    1. Многочленная модель в 3 раза быстрее стандартной синусной функции, если собрать её при помощи clang 11 и опциями -O2 -march=native, а затем выполнить на Intel Core i7-9700F. Но, если собрать её при помощи NVCC с опциями --use-fast-math и выполнить на GPU, а именно на GeForce GTX 1050 Ti Mobile, то стандартная синусная функция окажется в 10 раз быстрее многочленной модели.

    Это мне напомнило детские приколы. Кто быстрее: черепаха или Усэйн Болт?
    А если Усейну ноги в бетон залить?

    при "use fast math" - у вас будут не синусы, а функции, отдалённо их напоминающие (там ошибка уж очень большая). Что по-хорошему должно отдельно указываться в ТЗ (для нейросеток с пониженной точностью пойдут, только если learning и inference - использовать на одинаковых устройствах).


  1. Serpentine
    28.04.2024 11:35
    +5

    Киллер C++ № 1. SPIRAL

    Использует:

    Shell 60.7% (написана на С)

    GAP 21.0% (написана на C)

    C 16.6%

    Киллер C++ №2. Numba

    Библиотека Python, который вроде как на C реализован.

    Киллер C++ №3. ForwardCom

    Итак, ForwardCom — это ассемблер .... С любой практической точки зрения, это C будущего.

    Инструментарий написан на C и С++.

    Как я понял, у всех этих киллеров под капотом C и C++. Это уже самоубийство какое-то :)


    1. mobi
      28.04.2024 11:35
      +20

      Ну, если так рассуждать, то развитие именно так и происходит:

      1. Убийца машинного кода был написан на машинном коде.

      2. Убийца ассемблера был написан на ассемблере.


      1. Serpentine
        28.04.2024 11:35
        +2

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

        Тогда вопрос, а вот с этими убийцами убийц ассемблера такой фокус получится? Или для их существования/поддержки всё же требуется C/C++/третий язык?

        Как заметил автор статьи, сейчас ничего полностью не умирает, тот же ассемблер, например. Он просто перестал быть мейнстримом. И если говорить о мейнстриме, то ИМХО Python или JS несколько лет уже вроде как "убили" сами знаете кого. Только вот ОС, драйвера и прочее, где нужна перфа, на этих "убийцах" не пишется.


        1. Ritan
          28.04.2024 11:35
          +4

          Наблюдая за тем как всё больше "десткопных" приложений не могут запуститься без гигабайта памяти для текстового чата, я очень сильно надеюсь, что не будут писаться и дальше. Иначе linux.js, куда нужно установить gcc.js и просто подождать ещё 43 часа пока скачивается весь npm


  1. alysnix
    28.04.2024 11:35
    +8

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

    вот еще три новых имени появилось.


  1. WASD1
    28.04.2024 11:35
    +22

    Если говорить серьёзно по теме статьи, то три киллера С++ (в порядке увеличения их значимости):
    1. С++
    2. Go
    3. Rust.

    Го подпирает С++ "сверху", как язык более высокоуровневый, Rust снизу, как более низкоуровневый, а сам С++ - стимулирует программистов переходить на другие, более удобные \ безопасные whatsoever языки.


    1. ImagineTables
      28.04.2024 11:35
      +2

      Это смешно, потому что правда.


  1. SinsI
    28.04.2024 11:35
    +2

    если можно писать на Python и иметь производительность как на C++, то зачем вообще писать на C++?

    Из-за преимуществ Обязательной Статической Типизации.

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

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


    1. ImagineTables
      28.04.2024 11:35

      То, что статическая типизация повышает безопасность это, извините, миф. Конечно, если:

      1. Использовать ЯП/среду со строгой типизацией, которая кидает исключения при малейшем несовпадении, а не JS.

      2. Использовать эксплиситные конвертеры. (Грубо говоря, var a = 5.0; var s = String.print("%f", a);, для переменной a тип не выводится, типов нет, но если подсунуть %i, вылетит птичка).

      3. Покрывать код тестами и прогонять их перед тем, как пускать недельные циклы.

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

      И я бы сказал, вообще так ставить вопрос нельзя — про преимущества статической типизации. Есть сценарии, где без неё просто нельзя обойтись. Например, задачи, для которых надо явно задать i4 или i8. Да хотя бы вот, если надо написать динамическую типизацию (для юзеров), нужна статическая типизация для себя (или CaaS). А есть сценарии, когда обойтись можно и нужно.


      1. pooqpooq
        28.04.2024 11:35
        +5

        Статическая типизация очень сильно различается по силе в разных языках. Если говорить о типизации в стиле C++ или там, не знаю, Java — да, конечно, вы правы, без тестов всё равно никуда. Если же говорить о типизации в стиле полноценных зависимых типов [или хотя бы хаскеля], то она устраняет необходимость в тестах и является более мощным инструментом [или, соответственно, существенно их снижает], с точностью до предположений о внешнем мире (то есть, тестировать остаётся ваши предположения о том, как работает условная ФС/БД/тому подобное).

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

        Это следует из progress theorem и preservation theorem.


        1. ImagineTables
          28.04.2024 11:35

          Контекст был именно плюсы )


        1. insecto
          28.04.2024 11:35
          +4

          дедфуд ты что ли? а с тем акком что случилось?


          1. pooqpooq
            28.04.2024 11:35
            +1

            Стоит вспомнить про типы, и сразу вспоминают про этого! A great disservice to the community.


        1. Cerberuser
          28.04.2024 11:35

          тестировать остаётся ваши предположения о том, как работает условная ФС/БД/тому подобное

          Тот неловкий момент, когда прочитал "как работает условная ФСБ"...


          1. pooqpooq
            28.04.2024 11:35
            +4

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


      1. SinsI
        28.04.2024 11:35

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

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

        Если в C++ есть два модуля, каждый из которых работает правильно и при их взаимодействии нет никаких ошибок то они будут работать правильно и вместе в >90% случаев.

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


        1. DirectoriX
          28.04.2024 11:35
          +1

          Самое грустное, что на Python уже некоторое время можно накидывать типы, но в доке чёрным по серому написано

          Note: The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.


          1. netch80
            28.04.2024 11:35
            +1

            Я не найду с ходу ссылки, но делались расширения рантайма, который это проверял именно на основании описанных типов (вставлял assertʼы в рекомендованных местах). Встроиться в систему импорта в Python достаточно легко.

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


        1. ValeryIvanov
          28.04.2024 11:35
          +2

          Руки бы поотрывать тому, кто меняет сигнатуру публичной функции без сохранения обратной совместимости. И это безотносительно ЯП.


      1. netch80
        28.04.2024 11:35
        +3

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

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

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

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

        1. Покрывать код тестами и прогонять их перед тем, как пускать недельные циклы.

        Конкретный workflow вашего проекта имеет мало отношения к общей работе отрасли.

        И я бы сказал, вообще так ставить вопрос нельзя — про преимущества
        статической типизации. Есть сценарии, где без неё просто нельзя
        обойтись. Например, задачи, для которых надо явно задать i4 или i8.

        Или задачи, в которых должно быть именно целое число. Или задачи, в которых должно быть целое число от -90 до +90. Или ещё стопиццот вариантов, когда i4 или i8 это просто контейнер, слишком широкий для представимых значений, просто ближайший адекватный.


        1. ImagineTables
          28.04.2024 11:35
          +1

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

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

          А мои «полноценные тесты» это результат творческого процесса, потому что узкие места определяет программист, а не тупой автомат (компилятор). Именно поэтому они и лучше.

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


          1. eao197
            28.04.2024 11:35
            +1

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

            Э...

            В статически-типизированном языке C++ можно определить шаблон класса bounded_value, что-то типа:

            template<typename T, T Lower_Bound, T Upper_Bound>
            class bounded_value {...};
            

            конструктор которого будет проверять, что переданное ему значение попадает в [Lower_Bound, Upper_Bound]. Что легко протестировать один раз.

            После чего при тестировании функций вида

            void do_something(bounded_value<char, -90, 90> a, bounded_value<char, 0, 90> b) {...}
            

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

            В отличии от языков с динамической типизацией.


            1. ImagineTables
              28.04.2024 11:35
              +2

              Это называется: «Дай сиплюсплюснику гвоздь и микроскоп, и он оба эти предмета будет забивать шаблоном» ))))

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


              1. eao197
                28.04.2024 11:35
                +3

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

                Можно примеры?


                1. MountainGoat
                  28.04.2024 11:35

                  А чем руками плохо?

                  from typing import NewType
                  LimitedInt = NewType('LimitedInt', int)
                  
                  def check_limit(a: int, min_a: int, max_a: int) -> LimitedInt:
                    if a < min_a or a > max_a:
                      raise ValueError
                    return LimitedInt(a)
                  
                  def do_something( a: LimitedInt, b: LimitedInt ):
                    pass
                  
                  def main():
                    do_something(check_limit(argv[1], 0, 10), check_limit(argv[2], 100, 500))

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


                  1. eao197
                    28.04.2024 11:35

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

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


                    1. MountainGoat
                      28.04.2024 11:35

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


                      1. eao197
                        28.04.2024 11:35
                        +2

                        Почему же?

                        По факту.

                        Без декларации типов не заработает.


                    1. netch80
                      28.04.2024 11:35
                      +1

                      Может, это и хорошо, что закончилась. Потому что её ж тут намеренно ограничивают.

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


                  1. orenty7
                    28.04.2024 11:35
                    +1

                    По-моему упущен важный момент, что ограничения выражены в типе. У вас LimitedInt просто чем-то ограничен, у комментатора выше он ограничен конкретными значениями


                    1. MountainGoat
                      28.04.2024 11:35

                      А, так это же легко сделать, просто в check_limit не передавать пределы, а жёстко вкодить. Он выступает, по сути, конструктором для LimitedInt. Я просто показал пример семантического использования типов.

                      from typing import NewType
                      Hours = NewType('Hours', int)
                      Minutes = NewType('Minutes', int)
                      
                      def make_hours(a: int) -> Hours:
                        if a > 24:
                          raise ValueError
                        return Hours(a)
                      
                      def make_minutes(a: int) -> Minutes:
                        if a > 60:
                          raise ValueError
                        return Minutes(a)
                      
                      def timeout(hours: Hours, minutes: Minutes):
                        seconds: int = hours * 60 *24 + minutes * 60
                      


                1. lorc
                  28.04.2024 11:35
                  +2

                  Ну в вашем примере вы упомянули конструктор. Значит проверки будут происходить в рантайме. А значить даже в каком-нибудь пайтоне можно описать класс BoundedNumber который точно так же будет проверять в конструкторе что там ему пришло на вход.

                  А хотелось бы перенести проверки на этап компиляции. Правда, это возможно только на всяких экзотических языках типа Coq, где компиляция программы будет эквивалентна доказательству теоремы "Значения типов bounded_value никогда ни при каких обстоятельствах не выйдут за указанные пределы"


                  1. MountainGoat
                    28.04.2024 11:35

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

                    Можно вынести на этап прогона юнит-тестов, который во многом заменяет для Питона проверки при компиляции. Через assert-ы которые убираются в релизном запуске.


                  1. eao197
                    28.04.2024 11:35
                    +1

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

                    Посмотрите эту ветку обсуждения: https://habr.com/ru/articles/811151/comments/#comment_26776101
                    Я там основную претензию высказал -- когда начинается декларация типов в Python, исходная динамическая типизация прощается с нами.

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

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


          1. netch80
            28.04.2024 11:35
            +1

            при компиляции даже характер данных не проверяется!

            Ну пока вы будете думать только про типы i4 и тому подобные - да. Но если вы ни разу не видели enumʼы, ranged типы с проверкой при присвоении, и т.п. - это говорит только о вашем кругозоре. Даже в стандартном C++ тривиально сделать диапазонный тип (или взять готовый из boost). А с помощью __builtin_assume при чтении значения такого типа можно ещё и указать компилятору, какой диапазон значений, чтобы он использовал это в своих оптимизациях.

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

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

            А динамическая типизация в первую очередь даёт потерю этих самых гарантий контроля, ещё более "эффективно", чем undefined behavior и прочие неприятные особенности.

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

            Если вы не в курсе, то такой результат получается без ограничения размера и сложности систем. А если вы проверяете, например, функцию деления чисел в процессоре без команды деления (как Itanium) - то перебрать полное множество сочетаний делимого и делителя возможно, даже если это 2^128 вариантов (2^64 * 2^64). И искусство тестирования состоит в том, чтобы определить, какие тестовые входные значения нужны, а какие - излишни, зная алгоритм (или, наоборот, не зная).

            Что-то с вашим забегом в теорию тут откровенно "не то".

            А мои «полноценные тесты» это результат творческого процесса, потому что
            узкие места определяет программист, а не тупой автомат (компилятор).
            Именно поэтому они и лучше.

            А с чего вы решили, что у кого-то узкие места определяет компилятор? Что вы вообще тут назвали "узкими местами"?

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

            Передёргиваете именно вы, выставляя такие требования. Статическая и сильная типизации помогают отлавливать большое количество проблем, которые при динамической и при слабой типизациях можно было бы ловить только тестами. Я пишу на Python с 2004 года и постоянно имею дело с проблемами динамики - то None вместо строки, то строка вместо числа, то двумерный массив вместо объекта матрицы и наоборот (привет numpy)... и это ещё Python, где типизация в основном неплохо сильная. Раньше ещё был Perl, где простейший аналог d['x'] выдаёт undef, и такая ерунда плодится ещё сильнее. Простейшая разметка типов и напускание чекера на код сокращает время на тестирование раза в 2-3 при первом написании, и несчётно - при последующем возврате к коду.


          1. pooqpooq
            28.04.2024 11:35

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

            Почему? Я жду.

            И поэтому ушёл с C++, кстати.

            И мне было отвечено, что введением фундаментальных ограничений, которыми в ЯП общего назначения и не пахнет.

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

            Да и никто не мешает иметь два фрагмента — тотальный с ограничениями и гарантиями, и нетотальный с более классическим балансом.

            А мои «полноценные тесты» это результат творческого процесса, потому что
            узкие места определяет программист, а не тупой автомат (компилятор).
            Именно поэтому они и лучше.

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


        1. ImagineTables
          28.04.2024 11:35

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

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

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

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

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

          Теперь, главное: в чём польза. Когда компилятор делает свои наивные проверочки, ему доступна, ещё раз, только грамматика языка. Она, конечно, фильтрует много откровенного шлака, но очень примитивного. А когда проверки делает программист тестов, ему доступны помимо грамматики: 0) знания о типичных сценариях, 1) общие знания о предметной области, 2) характерные данные, 3) реальное поведение среды (со всеми багами и трактовкой ошибочно неоднозначных мест из документации). Естественно, проверки, написанные программистом, будут лучше и полнее, чем результат компиляции.

          Поэтому я зверею, когда слышу от нубов, которые не знают ничего кроме JS и TS или JS и C++ (никого из присутствующих в виду не имею), что статическая типизация + компиляция что-то добавляет к безопасности. Я бы не летал самолётами, не плавал пароходами, и вообще не маршировал с такими мальчишами, которые полагаются на проверки компилятора, вместо того, чтобы все их закладывать в тесты (вместе с кучей других проверок). Которые, разумеется, не потребуют никаких миллионов лет.

          При всём при том, я противник TDD, и как раз по той же самой причине: TDD слишком всерьёз принимает всякие «покрытия тестами», а это идёт вразрез с фундаментальными ограничениями природы.


          1. netch80
            28.04.2024 11:35
            +3

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

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

            Автор тестов делает то же самое.

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

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

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

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

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

            И я вынужден повториться, что вы крайне примитивно думаете про возможности компилятора, не говоря уже о том, что ошибаетесь в терминологии. Что выражает конкретный тип это уже не грамматика, это семантика, и эту семантику компилятор может проверить - и часто проверяет. Если у вас один тип имеет границы значений [0..100], а другой [200..300], то присвоение первого второму может вызвать ошибку компиляции. Если у вас должно быть число, а вы передали строку - типовая ошибка в JavaScript - лучше, если это будет отловлено на компиляции, а не в рантайме.

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

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

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

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


      1. rblaze
        28.04.2024 11:35
        +1

        "Посмотрите, как при помощи усердия и прилежания в языке X можно сделать то же самое, что в Y реализуется при помощи тайпчекера."

        И ведь у C++ система типов довольно слабая, но даже она лучше, чем ничего.


    1. Fedorkov
      28.04.2024 11:35
      +2

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

      ...а в середине недельного цикла компиляции. :)

      xkcd.com/303 — это именно про плюсы.
      xkcd.com/303 — это именно про плюсы.


      1. Alohahwi
        28.04.2024 11:35
        +3

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


        1. MountainGoat
          28.04.2024 11:35

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


  1. aryk38
    28.04.2024 11:35
    +5

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

    азязя. тебя с самого детсва подло обманывали. это Не С++.


    1. BugM
      28.04.2024 11:35

      Для 2005 года нормально. С++03 еще был свеж и не мог быть везде в кодовой базе. Значит С++98.


      1. Yuuri
        28.04.2024 11:35
        +1

        C++03 — это несколько заплаток на C++98, новых фич там нет.


  1. aryk38
    28.04.2024 11:35

    понимаете, все эти люди, которые хвалят прости госпади "Пытхон", они Реально Верят что 10тыщ строчек это БигДата. и что "создать зарплатную ведомость" это "очень трудоемкий процесс".


    1. releyshic
      28.04.2024 11:35

      Пытхон за счёт С библиотек часто быстрее С# работает, а иногда быстрее С++. тоже касается вычислений на видеокарте

      Пытхон очень быстрый


  1. yokotoka
    28.04.2024 11:35
    +4

    C++ в первую очередь угрожает сам C++

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


    1. emusic
      28.04.2024 11:35

      Не "сам C++", а комитет по его "развитию" и стандартизации.

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

      То есть, хочется использовать новые возможности - придется смириться с тем, что контроль за результатом практически утрачен. Да, программа читабельна, она компилируется и правильно работает, но на то, каким образом это достигается "под капотом", лучше не смотреть, чтобы не расстраиваться. Это ставит C++ в ряд остальных непрозрачных языков, напрочь лишая его уникальности.

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


      1. Ken-Sei
        28.04.2024 11:35
        +1

        По мне так шаблоны в C++ выглядят странно, и я надеялся, что комитет их починит...


        1. emusic
          28.04.2024 11:35
          +2

          "Странно" - это крайне мягкая характеристика. :)

          -- Сынок, что сказал папа, упав с лестницы?
          -- Маты пересказывать можно?
          -- Нет, конечно!
          -- Тогда он промолчал...

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


      1. eao197
        28.04.2024 11:35
        +1

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

        Такое впечатление, что уход тех, кто "просекал фишку" состоялся во времена добавления в C++ механизма исключений. А то и еще раньше, когда перегрузку операторов сделали.


        1. emusic
          28.04.2024 11:35
          +1

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

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

          А вот когда ввели шаблоны, то понеслась массовая истерия в стиле "долой макросы, до здравствуют шаблоны!", "если ты до сих пор используешь макросы, то ты не понимаешь C++". И все бы ничего, если б хватило ума и настойчивости приделать к тем шаблонам вменяемые средства управления. Но из всего, что хоть как-то похоже на средства управления шаблонами, почти ничто не заслуживает эпитета "вменяемое". Сплошь кривые костыли, работающие только в рамках "официально одобренных" способов применения.

          А уж когда обнаружилось, что шаблоны составляют пресловутую полноту, у народа вообще поехала крыша, и с тех пор всё, что технически возможно сделать на SFINAE, по определению считается кошерным и "в стиле C++". Типа, на хрена вводить в язык какие-то новые конструкции, если того же можно добиться с помощью трех, пяти или пятнадцати завязанных в уродливый клубок шаблонов, которые потом аккуратно замести под ковер вынести в заголовок?

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


          1. netch80
            28.04.2024 11:35
            +1

            А вот когда ввели шаблоны, то понеслась массовая истерия в стиле "долой
            макросы, до здравствуют шаблоны!", "если ты до сих пор используешь
            макросы, то ты не понимаешь C++". И все бы ничего, если б хватило ума и
            настойчивости приделать к тем шаблонам вменяемые средства управления.

            И всё равно в целом они вменяемее тех макросов, что были до того.

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


            1. emusic
              28.04.2024 11:35

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

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

              А большинство проблем с макросами растет от того, что в C/C++ не макропроцессор, а препроцессор, противоестественно отделенный от компилятора. В начале 70-х это еще можно было объяснить экономией ресурсов, но в конце 80-х такое объяснение уже не годилось. Могли бы в C++ объединить его с компилятором, превратив в нормальный макропроцессор, сделать минимально приличное управление и диагностику - и изрядная часть проблем ушла бы сразу.


              1. netch80
                28.04.2024 11:35

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

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

                А большинство проблем с макросами растет от того, что в C/C++ не макропроцессор, а препроцессор, противоестественно отделенный от компилятора.

                Да.


          1. eao197
            28.04.2024 11:35

            А вот когда ввели шаблоны, то понеслась массовая истерия в стиле "долой макросы, до здравствуют шаблоны!", "если ты до сих пор используешь макросы, то ты не понимаешь C++". И все бы ничего, если б хватило ума и настойчивости приделать к тем шаблонам вменяемые средства управления.

            Осталось определиться на что вы жалуетесь:

            а) на накладные расходы, которые связанны с шаблонами (какие, кстати говоря?);
            b) на отсутствие "средств управления" (какого, кстати говоря?)

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


            1. emusic
              28.04.2024 11:35
              +1

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

              Использование же шаблонов в том виде, как оно принято в "современном C++" - многократно вложенных, со множеством рекурсий, неочевидных зависимостей и костылей из побочных эффектов - порождает чудовищные накладные расходы прежде всего на их сочинение, анализ, компиляцию и поиск ошибок. Мало кто толком понимает, как работает эта кухня, поэтому большинство лишь применяет то, что сделано "продвинутыми программистами". Эти "продвинутые", не имея адекватных языковых средств, реализуют нужную функциональность преимущественно на побочках, но таким образом трудно сделать оптимально, чаще будет "так уж получилось, но ведь работает".

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

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

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


              1. pooqpooq
                28.04.2024 11:35
                +2

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

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


                1. emusic
                  28.04.2024 11:35

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

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


                1. rukhi7
                  28.04.2024 11:35

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


                  1. pooqpooq
                    28.04.2024 11:35

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

                    Но сложные шаблоны в итоге всем надоели, и мы начали делать полноценный DSL. Правда, не на плюсах, плюсы там вообще мешались.


                    1. rukhi7
                      28.04.2024 11:35

                      Но сложные шаблоны в итоге всем надоели

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

                      Я сам пытался найти смысл применения шаблонов, шаблонов со спецификациями еще в самом начале 2000-х и пришел к выводу что разные формы наследования и владения объектов работают гораздо эффективней шаблонов практически в любой задаче. Вы не найдете шаблонов в WPF например, в OpenGL, что еще привести для примера? Шаблоны хороши только для библиотек функций по коллекциям например.


                      1. pooqpooq
                        28.04.2024 11:35
                        +1

                        в пользу более традиционных (что ли) решений

                        стали писать DSL

                        Традиционность DSL можно посмотреть по оценке соответствующего утверждения общественностью выше.

                        разные формы наследования [...] работают гораздо эффективней шаблонов

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

                        владения объектов

                        Как без шаблонов в одном случае владеть объектом «линейное преобразование входных данных», а в другом — «квадратичное»?


                      1. rukhi7
                        28.04.2024 11:35

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

                        я вам с удовольствием поверю если вы продемонстрируете какой-то пример в котором это работает так как вы говорите.

                        Традиционность DSL можно посмотреть по оценке

                        под DSL вы имеете ввиду domain-specific language ?

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


                      1. pooqpooq
                        28.04.2024 11:35

                        я вам с удовольствием поверю если вы продемонстрируете какой-то пример в котором это работает так как вы говорите.

                        Что именно работает? Лишнее время на вызов виртуальной функции и последствия её неинлайнинга?

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

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

                        Ну вот под конкретную задачу он и придумывается, да.


                      1. rukhi7
                        28.04.2024 11:35

                        Что именно работает? Лишнее время на вызов виртуальной функции и последствия её неинлайнинга?

                        Лишнее время по сравнению с чем?

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

                        и последствия её неинлайнинга

                        это какое то новое слово в технике программирования, хотелось бы посмотреть пример функции которую вот прям нужно заинлайнить, опять же. Может и new тогда нужно заинлайнить?


                      1. pooqpooq
                        28.04.2024 11:35
                        +1

                        Лишнее время по сравнению с чем?

                        По сравнению с отсутствием виртуальной функции и использованию темплейтов.

                        Если вы посмотрите в ассемблер

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

                        Да и не только я — на том же cppcon люди относительно регулярно так или иначе стараются избегать виртуальных функций, пусть и в разных контекстах (см. доклад Луи Дионна на cppcon 17, или кучу докладов-вариаций на тему замены массива полиморфных объектов на набор массивов мономорфных объектов).

                        то вы не обнаружите сколько нибудь заметной разницы между вызовом виртуальной функции и обычной статической функции.

                        Серьёзно?

                        В одном случае у вас аллокации самих объектов, вызов виртуальной функции по указателю, создание и заполнение временного вектора внутри первой getVal, ну и вдобавок экзепшоны полезли. В другом случае у вас просто mov eax, edi; ret . Компилятор даже заинлайнил вырезал создание и заполнение вектора внутри второй, шаблонной getVal.

                        При этом я написал очень дружественный к оптимизатору код в случае наследования¹, тогда как в классических подходах к архитектуре на основе наследования точка создания полиморфного объекта и точка его использования разнесены куда больше, и компилятору ещё сложнее делать какие-либо оптимизации (вроде того, что он делает тут, заметив, что D1 и D2 имеют одинаковые размеры, выделяя память единожды, и делая сводя выбор объекта в рантайме к cmov'у нужного втбл). На практике всё будет сильно хуже.

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

                        Или вы считаете что дополнительное копирование из памяти в регистр занимает много времени?

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

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

                        Надеюсь, выше это тоже видно.

                        Может и new тогда нужно заинлайнить?

                        Когда компилятор осиливает убрать new (тоже как в примере выше с вектором) — это вообще праздник!


                      1. rukhi7
                        28.04.2024 11:35

                        Серьёзно?

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


                      1. pooqpooq
                        28.04.2024 11:35

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

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


                      1. rukhi7
                        28.04.2024 11:35

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

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


                      1. eao197
                        28.04.2024 11:35

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

                        Жаль, что здесь есть только две оценки для комментария: +1 и -1.
                        Очень, очень не хватает оценки facepalm. Хотя здесь бы более уместной была бы double-facepalm.


                      1. rukhi7
                        28.04.2024 11:35

                        и последствия её неинлайнинга?

                        еще: если у вас функция на три строчки вызывается 1000 000 раз надо подумать о структуре кода-алгоритма, а не шаблоны городить которые пытаются лечить неэффективно построенный алгоритм.

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


                      1. pooqpooq
                        28.04.2024 11:35

                        У меня функция на три строчки вызывается 10'000'000'000 раз потому, что у меня в день приходит 10'000'000'000 сообщений от биржи, и каждое из них надо, например, предварительно отфильтровать или иначе как-то обработать.

                        Помогите подумать о структуре кода-алгоритма, чтобы этого делать было не надо.


                      1. rukhi7
                        28.04.2024 11:35

                        Как без шаблонов в одном случае владеть объектом «линейное преобразование входных данных», а в другом — «квадратичное»?

                        владение объектом с интерфейсом преобразование входных данных нет? не подходит? Почему?

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

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


                      1. pooqpooq
                        28.04.2024 11:35

                        владение объектом с интерфейсом преобразование входных данных нет? не подходит? Почему?

                        Потому что это лишний indirection в рантайме, и это медленнее, чем могло бы быть.

                        Представляете какой там трафик генерируется-обрабатывается?

                        Там нет цели минимизировать задержку, топовая производительность там не нужна. Да и от немаксимального throughput'а, в общем, тоже никто особо не пострадает. В трейдинге же, для которого я это делал, такая цель есть, потому что минимизация задержки напрямую конвертируется в деньги. Топовая производительность там важна.


                      1. rukhi7
                        28.04.2024 11:35

                        Там нет цели минимизировать задержку, топовая производительность там не нужна.

                        я вам по секрету скажу что вы это сейчас рассказываете тому кто занимался, как раз, там, как раз, достижением Топовой Производительности со всеми MMX-ами, SSE-векторизациями, DirectX-ами, и фиг знает еще с чем. Ваш

                        лишний indirection в рантайме

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


                      1. pooqpooq
                        28.04.2024 11:35
                        +1

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

                        А вот HFT-клоунада вся эта — там и деньги потеряют, и готовы платить дофига специалистам за каждые следующие несколько наносекунд.

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

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

                        Не во всех задачах можно дожидаться накопления достаточного количества данных, чтобы обрабатывать их, например, применяя упомянутую вами «SSE-векторизацию». От опытного человека можно было бы ожидать понимания разницы между latency и throughput, и какие компромиссы есть в тех или иных случаях, но, видимо, не сегодня.


                      1. netch80
                        28.04.2024 11:35

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

                        Потому что на 1 единицу затраты времени на вызов виртуальной функции приходится 1000 или 100000 затрат на энкодинг-декодинг, даже с учётом всего ускорения через вылизанный векторный код поверх последнего SSE, BMI2 и прочих вкусняшек.

                        И поэтому же какие-нибудь считаемые на GPU нейросети работают под запускалкой на Python - затраты Python, в 30-100 раз больше даже самого неоптимального кода на C, не имеют значения.

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

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

                        А теперь загляните внутрь самого кодека...


              1. eao197
                28.04.2024 11:35

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

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

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

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

                Во-вторых, а на каком объеме исходного текста вы способны отслеживать качество результирующего машинного кода и для какой конкретно платформы?

                Допустим, у вас 10KLOC и вы в состоянии оценить качество кода для семейства x86 процессоров. А если добавить сюда еще и пару-тройку ARM-ов? А если еще и e2k? А если еще что-нибудь?

                А для проекта в 100KLOC вы в состоянии глобальное качество отслеживать?
                А для проекта в 500KLOC?
                А для проекта в 1MLOC?
                И т.д.?

                Может для проекта хотя бы в 100KLOC будет уже свой набор требований, в которых требования к производительности уже не будут приоритетом №1 (а если и будут, то не ко всему коду, а к отдельным его кускам)? Например, там будут иметь значения такие вещи, как обеспечение корректности, повторное использование и отсутствие копипасты с ее проблемами. Как раз те вещи, для обеспечения которых шаблоны (включая многоэтажные) хорошо себя зарекомендовали.


                1. netch80
                  28.04.2024 11:35

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

                  Я на двух последних проектах работал в команде (считаем, по сотне человек в каждом). Телеком, полу-свои железки. Команда - люди стараются, конечно, но простые вызовы ASIO и замыкание на пару параметров для них предел. Уровень выше дают ну может до 5 из всех, скорее 3. Это пойдёт как уже значимая выборка? 3-5%.

                  (Как пример, дважды рассылался "циркуляр" про правильное использование erase-remove в итераторах, после того, как один кусок крэшился из-за erase() на map без него. Там вообще хватало простого clear(), но все пропустили.)

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


                  1. eao197
                    28.04.2024 11:35

                    Это пойдёт как уже значимая выборка? 3-5%.

                    Да.

                    Но при крайне слабом контроле качества кода это всё превращается в невнятную кашу.

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

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


                    1. pooqpooq
                      28.04.2024 11:35

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

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


                      1. eao197
                        28.04.2024 11:35

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

                        Раз пошла такая пьянка, то по моим личным ощущениям (моим личным, это важно) C++ники вообще никому не нужны.


            1. emusic
              28.04.2024 11:35

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

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

              А вот если мы хотим сделать на C++ шаблонный алгоритм, в который можно подставить энное количество данных и функций, которые он будет комбинировать для реализации алгоритма, то оказываемся примерно в той же ситуации, как если бы в подпрограмме на ассемблере можно было лишь тупо копировать параметры, переданные в регистрах, в другие регистры или память, а вместо команд сравнения/тестирования и условных переходов у процессора были только весьма странные конструкции, неочевидным образом меняющие порядок этого копирования, и на которых можно было бы, при известной сноровке, сгородить нечто похожее на привычные условные операции. Те, кто привык городить такое, воспринимали бы идею реализации в процессоре простых условных операций с удивлением и недоверием. :)


              1. isadora-6th
                28.04.2024 11:35

                Шаблоны же просто подставляют на места своих аргументов выведенные типы - нет?

                >3ий абзац

                Очень интересно вы просто кучу сгенерированных функций, которые в целом не отличаются от простого написания всего этого барахла руками через ctrl+c/v, во что-то, что генерирует непонятное в процессоре.

                На примере std::less, опишите генерируемые оверхеды и почему std::less<int> может быть хуже int_less_comparer?

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

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

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

                Еще скажите, что auto, это вообще игрушка дьявола которая убивает перформанс


                1. pooqpooq
                  28.04.2024 11:35

                  Шаблоны же просто подставляют на места своих аргументов выведенные типы - нет?

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

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

                  Например, если вы в кишках вектора не учли, что int можно memcpy'ать при ресайзе, а std::string — нет, и поэтому поэлементно всё подряд копируете всегда несмотря ни на что.

                  А во всём остальном вы правы, конечно.


                  1. isadora-6th
                    28.04.2024 11:35

                    Диагональный гуглеж говорит, что вектора делают memcpy для тривиальных типов и потенциально тривиальных агрегатов (всякие стракты интов) [что логично], но красивых пруфов на руках у меня нет, пишут, что где-то в дебрях std::copy магия.

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

                    Каждый раз как в первый раз пишу std::hash с кучей гугла


                    1. pooqpooq
                      28.04.2024 11:35
                      +1

                      ЕМНИП это вопрос quality of implementation. Ну и тут я примешал необходимость написать свой контейнер (например, мне как-то надо было сделать циркулярный буфер, и там всё правильно, эффективно и по стандарту сделать тоже та ещё весёлая задача).

                      Каждый раз как в первый раз пишу std::hash с кучей гугла

                      Перестал писать std::hash после того, как начал писать deriving (Generic, Hashable) , а дальше компилятор всё сам. Рекомендую!


                      1. isadora-6th
                        28.04.2024 11:35

                        А можно пожалуйста пример/ссылку/детальку про deriving (Generic, Hashable), как-то ничего не нашел толкового в гугле для c++


                      1. pooqpooq
                        28.04.2024 11:35
                        +1

                        Это такая шутка-самосмейка на тему ухода от C++ в более хорошие языки, извините.


  1. syrus_the_virus
    28.04.2024 11:35
    +1

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


    1. voldemar_d
      28.04.2024 11:35
      +6

      Про хобби уже много на Хабре написано: пельмени, когтеточки, тюльпаны, магазины на севере...


    1. Alohahwi
      28.04.2024 11:35
      +1

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


      1. MountainGoat
        28.04.2024 11:35

        А это те, кому баб не хватило.


  1. cdriper
    28.04.2024 11:35
    +5

    автор сидит в очень узкой нише и натягивает ее на C++ в целом

    давайте возьмем кодовую базу Chrome

    перепишем ее на новом универсальном ассемблере? перепишем на Python, чтобы выполнять на GPU?


    1. SpiderEkb
      28.04.2024 11:35
      +1

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

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

      Речь не о том, что "что-то убийца чего-то", а исключительно о выборе наиболее эффективного инструмента для решения конкретной задачи.


  1. JordanCpp
    28.04.2024 11:35
    +3

    Через 10 лет выйдет статья на Хабре, что вот опять, снова С++ умирает. А С++ программисты, будут так же писать С++ код и заниматься, С++ проектами. Браузерами, высоконагруженныи сервисами, читать статьи о новых версиях стандарта и т. д Жизнь идёт, С++ уже лет 30 помирает, ещё чуть чуть осталось:)


    1. netch80
      28.04.2024 11:35

      С C++ постоянно мигрируют на другие средства, как только есть возможность и они чем-то лучше. Если считать в рыночной доле, то она показывает стойкую тенденцию к снижению - это и есть то умирание. Ну и молодёжь на него неохотно идёт.


      1. sdramare
        28.04.2024 11:35
        +1

        Она не снижается, а размывается. Те проекты, которые имеют кодовую базу на плюса, от игровых движоков, до кад систем или баз данных, на плюсах и остаются. Но при этом растет доля проектов(в основном веб-сервисов), написаных на более простых языках - typescript, C#/java, go. Но это не умирание, это просто занятие своей ниши - ΙΤ переходит, вернее уже почти перешло, от состояния "один язык для всего" к "для каждой задачи свой инструмент".


        1. netch80
          28.04.2024 11:35
          +2

          Но при этом растет доля проектов(в основном веб-сервисов), написаных на более простых языках - typescript, C#/java, go.

          Ну вот у вас или CAD с базами данных, или веб-сервисы. А промежуточного вы не видите. Когда-то такие задачи, как email сервер, VoIP-софтсвич, IDE (смотрим на пучок от JetBrains) - можно было писать только на полностью компилируемых языках с ручным управлением памятью, другое не работало. Сейчас IDE - на Java, C#. Свич - какие-нибудь кодекоконвертеры и передатчики RTP - ладно, C/C++, но сигнализация - нафига там C++ когда Java справляется не хуже (а я до сих пор парой пальцев ноги в проекте где Python). Email - аналогично, накой там C++, Java пробовалась уже с 2000 (если это не что-то масштаба Google), хотя Go дышит в затылок. И так далее.

          А завтра и CAD будут забирать у C++, потому что нафиг там такое не нужно, кроме, может, пары критичных алгоритмов какой-нибудь сложной разводки. И то, хорошо оптимизированный язык типа C# вполне может за-JITʼиться в сравнимый код. Игровые движки - сколько игр не требуют 144fps и вполне бегают на Unity, а то и на чистом JS?

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

          Я к этому думаю также, что если бы Go и Rust не старались паковать всё в один статический бинарник - то уже давно большинство содержимого всяких /usr/bin было бы на них. Сейчас они сами себе тормозят это продвижение (намеренно или нет - понять сложно).


          1. sdramare
            28.04.2024 11:35

            А чего плохого в одном бинарнике? Место на диске ничего не стоит, а проблем с версионностью куда меньше.


            1. netch80
              28.04.2024 11:35

              Место на диске ничего не стоит

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


              1. sdramare
                28.04.2024 11:35

                Жесткий диск в 1 терабайт стоит как два часа работы среднего программиста, о каких дорогих мегабайтах вы говорите?


                1. netch80
                  28.04.2024 11:35
                  +1

                  Жесткий диск в 1 терабайт стоит как два часа работы среднего программиста

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

                  ​1. Какой "жёсткий диск"? HDD? Замечательно. В типовом лаптопе сейчас SSD на M.2. За 1TB это будет 100$. "Средний программист" exUSSR получает где-то 2000$/месяц, то есть это уже день, а не два часа. Это пока что уровень разработки. Или вы про "среднего программиста" Кремниевой долины? Там, конечно, и час будет, но таких мало от общего числа по миру.

                  ​2. Если мы говорим про ответственный сервер, то там цена быстрого надёжного хранилища будет равна минимум 4 таким дискам плюс контроллер для RAID 5/6/Z. Ладно, контроллер софтовый (не всегда адекватно, но упростим) - всё равно умножили на 4. Не буду считать это в 4 днях работы разработчика, хотя сколько тех серверов нужно для реального сервиса - может, и 1000... это уже 11 лет зарплаты одного такого программиста. Ладно, 3 года, если из Фремонта или Санта-Анны. И 20-30 лет, если из Бангалора:))

                  ​3. Теперь, говорим про железяку на выносе (мой последний случай именно такой). Диск - флэшка на ~200GB, SD card. Уже занято ~70% на старте, ещё ~20% выделено на логи. И ещё требуется скорость обновления, каждые 10 секунд на счету, потому что это массовое падение сервиса - железяка обслуживает несколько тысяч клиентов. Лишний гиг несжимаемого (именно так) бинаря, по мегабайту на тысячу рабочих программ - реально, полминуты на скачивание (по совсем не свободным каналам, у юзеров звонки и трафик, а на сервере драка сотен скачивающих) плюс где-то столько же на заливку на флэшку.

                  Понимаете, почему расчёт на один HDD это, ничего личного, просто ламерство? И почему, например, убунтовцы в 24.04 говорили "очень-очень-очень извините, но наши стековые фреймы стоят пару процентов места и времени, но нам всем эта пара процентов много даёт непоправимой пользы, поэтому мы таки сделали"?


                  1. sdramare
                    28.04.2024 11:35

                    "Средний программист" exUSSR 

                    Ну вы бы еще с сомали сравнили. Я говорю про рейт тех стран, где находится большая часть IT компаний - европа, китай, индия и сша. Ставка 30 долларов в час это средний доход. А кремнивая долина это 60 в час и больше. Но даже при 2000 в месяц, диск обойдется вам в 4 часа работы.

                    Если мы говорим про ответственный сервер

                    AWS вам подойдет как ответственный сервер? S3 стоит $0.0125 per GB, стоимость мегабайта в рублях посчитайте сами.

                    Теперь, говорим про железяку на выносе

                    200GB диск, желязка, полминуты на скачивание - какие-то истории из 2004 года. Ваша "желязка" случайно не по модему в 28800 бод соединение держит? То же был бы неплохой аргумент. Но так да, понятно, вexUSSR своя атмосфера, согласен что каждый лиший килобайт может весь бюджет проекта опрокинуть.

                    убунтовцы в 24.04 говорили

                    Причему тут стековые фреймы, что за каша началась.


                    1. netch80
                      28.04.2024 11:35
                      +1

                      европа, китай, индия и сша. Ставка 30 долларов в час это средний доход.

                      Нет, вы слишком хорошо думаете о среднем доходе. Разделите минимум на два. Для Индии ещё больше.

                      AWS вам подойдет как ответственный сервер? S3 стоит $0.0125 per GB, стоимость мегабайта в рублях посчитайте сами.

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

                      Как активный диск у Амазона (пусть мы смотрим таки на Амазон) надо смотреть, например, на EBS, и вот тут уже видно - provisioned IOPS (мы ж хотим гарантированную полосу?) это 125$/TB/месяц (открыло Ohio, пусть будет оно). Это соответствует расходу одного такого диска в месяц, или комплекта RAID за 4 месяца. Деля ещё на 2 за on-demand вариант (а не предоплаченный на 3 года, как аналог персонального железа), соответствует 8 месяцам. Ну, износить SSD за год активным общением это как пить дать. Кстати, там в цене ещё сами IOPS надо добавить, а не только хранилище для них:) Так что Амазон тоже на активном дисковом хранилище использует какой-то RAID, как я и описывал.

                      Собственно, упоминания S3 достаточно, чтобы понять, что вы упорно сравниваете паровозы с апельсинами, и, или 1) не понимаете вообще, что сравниваете несравнимое, или 2) активно троллите. Выбирайте сами.

                      200GB диск, желязка, полминуты на скачивание - какие-то истории из 2004
                      года. Ваша "желязка" случайно не по модему в 28800 бод соединение
                      держит?

                      ​2024. Нет, это чистая реальность из этого года. Головная станция интернет-доступа. И тоже или даже не подумали калькулятор взять, или троллите. Полминуты на 28800 это ~86kB, а не гигабайт. Мне уже как-то надоедает вести беседу на таком уровне.

                      Причему тут стековые фреймы, что за каша началась.

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

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


            1. morijndael
              28.04.2024 11:35

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

              Это больше касается Go, правда. В Rust для релизных сборок обычно включают LTO, и в бинарнике остаётся только необходимое


              1. sdramare
                28.04.2024 11:35

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


    1. mzigan
      28.04.2024 11:35

      И в Линуксе он так и не появится


  1. JordanCpp
    28.04.2024 11:35

    Странно С++ сравнивать с dsl языками заточенными для работы с БД. Естественно, что у универсального языка будет менее удобные интерфейсы.


    1. SpiderEkb
      28.04.2024 11:35
      +1

      Так о том и говорим. Не лучше или хуже, а о более или менее подходящий для конкретной задачи.

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


      1. JordanCpp
        28.04.2024 11:35

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


        1. SpiderEkb
          28.04.2024 11:35

          Универсальность - это как автомобиль который "одинаково хорош" и в трофи-рейде и в кольцевой гонке.

          Заказчику неважно на чем оно там у вас написано. Ему важно чтобы это было сделано быстро и работало максимально эффективно. А где использовать sql, а где прямой доступ к БД - это вы сами должны решить. И не быть ограниченными в возможностях только потому что "универсальный язык" чего-то там не позволяет. Не позволяет - ищите тот инструмент, который позволяет. Или ищите другого заказчика и другие задачи.


          1. JordanCpp
            28.04.2024 11:35
            +1

            Вы меня не слышите. Зачем тянуть dsl в проект. Если в итоге все использование сведется к методу GetMyData()?

            Сравнение языков с машинами всегда некорректно.

            Сам sql это уже абстракция и язык запросов. Накатить dsl + orm + ещё кака какая-то.


            1. SpiderEkb
              28.04.2024 11:35

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

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

              И вот эта самая функция получения данных каждый раз будет новая. А потом эти данные надо обработать. С учетом того, что все числовые данные всегда только в формате с фиксированной точкой и никак иначе (скажем, типа float я тут вообще ни разу не видел, хотя в языке он есть, int используется для всяких индексов массивов и параметров циклов, ну счетчики иногда). Т.е. язык обязан уметь работать с фиксированной точкой, причем, с минимальными накладными расходами. Без создания лишних объектов на каждый чих.

              Тем и хорош DSL что он позволяет делать все это без лишних затрат.


              1. JordanCpp
                28.04.2024 11:35

                Поэтому к любой штуке есть свой интерфейс. К БД как пример libpxx, у внешнего движка свой интерфейс и т. д

                Зачем ещё прикручивать именно dsl? И к этому dsl интерфейсы доступа,?


            1. netch80
              28.04.2024 11:35
              +1

              Зачем тянуть dsl в проект. Если в итоге все использование сведется к методу GetMyData()?

              Зачем, что DSL это очень часто не столько специализация, сколько ограничение.

              Простейший пример: Java, C#, Go это DSLи рантайма с автоматическим управлением памятью. Ограничение возможностей программиста (полное или почти полное с ужатием до unsafe-кода) ограничивает его возможности, но за счёт избавления от проблем нарушения памяти резко увеличивает объём кода, разрабатываемого и сопровождаемого при той же квалификации коллектива и руководства.

              SQL ещё больше ограничен, в нём не сделаешь, например, приём бинаря из Интернета на исполнение:), или это будет очень явно видно по характерным вызовам.

              И это мы ещё не вспоминаем, что на нём банально проще писать, чем каждый раз рисовать самому код, который будет считать, по которому из индексов лучше итерироваться:)


  1. alpet_m
    28.04.2024 11:35
    +2

    Надо посмотреть, чего этот Spiral может в реальных условиях. Интересно, если это микс ИИ и брутфорс-компилятора, который вылизывает код до мопов. Я помнится в середине нулевых писал редактор памяти (WGC), ещё на Delphi, ибо был сильно недволен скоростью ArtMoney и прочих аналогов. В итоге на ассемблере код вылизал до мопов так, что до сих пор он в однопоточном режиме лучше моих-же многопоточных вариантов на любом высокоуровневом языке программирования. Особенно когда в гигабайтном объеме памяти надо найти все нулевые байты.


    1. HemulGM
      28.04.2024 11:35

      На ассемблере в виде выставок в Делфи или на чистом ассемблере?


      1. alpet_m
        28.04.2024 11:35
        +1

        Сначала были только вставки, потом появились ассемблерные файлы. Решил выложить ещё раз код https://github.com/alpet83/WGC - архив когда-то был на сайтах распространявших программу, и сгинул уже.


  1. releyshic
    28.04.2024 11:35
    +2

    для архитектуры "правильный С++" это С#.

    Для производительности числодробилки можн на С++ писать и дёргать их


    1. Fedorkov
      28.04.2024 11:35

      Тогда уж на Си.

      Чтобы использовать в дотнете плюсовые классы, нужно писать обёртку на языке C++/CLI, стиль и семантика которого напоминают творение доктора Франкенштейна.


      1. releyshic
        28.04.2024 11:35

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


    1. lorc
      28.04.2024 11:35
      +1

      Самое ужасное что числодробилка из C++ - такая себе. Мешают штуки типа pointer aliasing, динамическая аллокация памяти и т.д. Я видел вырожденные примеры, где даже Java обгоняла C++.


      1. SpiderEkb
        28.04.2024 11:35

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

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


        1. DirectoriX
          28.04.2024 11:35
          +1

          Даже без танцев с аллокаторами, простой вынос "числодробильности" в отдельные объекты, выделяющие память (да и просто выполняющие тяжёлые операции вроде создания вспомогательных файлов, соединений, потоков, каких-нибудь fftw_plan , чтения и обработки конфигов, и прочей вспомогательной ерунды) только при создании зачастую уже достаточно. А если не пересоздавать эти объекты-числодробилки с нуля, а переиспользовать старые (те самые worker pool) - так и вовсе красота.


          1. SpiderEkb
            28.04.2024 11:35

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

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