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

А ещё можно было ба написать версию для тензор ядер… Было бы вообще огонь по скорости.

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

Думаю что в будущем, когда остановится развитие ПО backend и desktop приложения будут писать на языках типа c++ и rust. Ведь тогда производительность будет единственным местом где можно будет победить конкурентов

Конкурентов зачастую побеждает удобство использования и маркетинг.
Если сейчас (да и раньше) программисты писали на медленных яваскрипт и python, то почему через несколько лет, когда производительность еще вырастет, и только потом остановится, все сразу убегут на C++?
Потому-что невозможно добавлять фичи бесконечно JS и Python позволяют писать код быстрее в ущерб производительности. В какой-то момент ПО перестанет развиваться активно и станет вопрос производительности.
Хм, почему сейчас ПО по-вашему «развивается», а вскоре вдруг перестанет? Нет, ну может быть лет через 50 такое и наступит, но мне к тому моменту будет уже глубоко на это пофиг
Процессоры стали работать в 2 раза быстрее -> появляется новый фреймворк, который работает в 2 раза медленнее, но ускоряет разработку на 30%. Оптимизировать некогда, нужно осваивать новый фреймворк. А внутри этого фреймворка всё примерно также — некогда было оптимизировать, срочно осваивали новую библиотеку/стандарт/подход. И вот так оно растёт, аки снежный ком — вот такое сейчас «развитие».
Когда процессоры будут удваивать скорость лет за 10~15, варианты «работает в 2 раза медленнее, зато разрабатывать на этом в 1.2 раза быстрее» больше не проканают, будет спрос на более быструю реализацию устоявшихся парадигм — и вот тут начнут оптимизировать.
За последние 10 лет производительность одного ядра и выросла примерно в 2 раза. Дальше производительность растёт только если программист осилил многопоточность.
Это на PC и конкретно у Интел.
В то время как на мобилках более чем на порядок.
А до предела однопоточной производительности ещё далеко.
В то время как на мобилках более чем на порядок.

Так мобилки и стартовали с мегагерц. Ровно так же, как на порядок росли скорости десктопов в нулевых.
Казалось бы, причём тут нулевые, когда речь шла про
За последние 10 лет

Кривая роста производительности у мобильных чипов сильно другая. Можно объяснить это эффектом «низкой базы», но A4 @ 800MHz десятого года это всё же процессор, который исправно выполнял все «смартфонные» функции.
Прогресс быстро шёл из-за того, что на мобильном рынке новая микроархитектура и новый техпроцесс стабильно появляются каждый год, в отличие от :)
Казалось бы, причём тут нулевые

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

Как и соответствующий P3 ))
Ну с многопоточностью сейчас гораздо легче, чем 10 лет назад. Даже в древней Дельфе появилось TParallel.For(, вместо ручного создания потоков.
Как будто проблема параллельных вычислений в самостоятельном создании потоков… Попробуйка распараллелить bsearch или aes-cbc. А если эти потоки ещё и приходится синхронизировать, то вообще начинается ад с дидлоками и рандом-багами.

Для bsearch если у Вас и 1е8 элементов, то это лишь ~26 операций будет в худшем случае, такое не надо параллелить. Если вы часто ищете — надо параллелить чтение(bsearch) и блокировать запись (вставка нового элемента). Если блокировки частые и мешают работе, можно как-то пул вставок делать и время от вермени сливать массивы.
С aes-cbc таже песня, алгоритм использует данные на предыдущем шаге, если у вас данные огромной длинны — то тут я не знаю как можно распараллелить, если у вас много поменьше — то это норм распределить на потоки.

Потому что парадигма «быстрый старт, потом оптимизация» — работает. Пока оно молодое, важны только возможности, если не совсем тормозное — делать упор на возможности это занять/отжать часть рынка. Выстрелило — есть популярность, начинаем оптимизировать. Тот же php постоянно заявлял «теперь мы быстрее от 10 до 100%» почти в каждой версии, и мельком видел тесты — 7 прилично быстрее чем 5. Ещё плюс — просто обновили версию, оттестировали и «на шару» получили экономию ресурсов.
Не выстрелило — не потратили много времени на работу, которая никуда не нужна. Плюс есть даже такой термин «преждевременная оптимизация». «преждевременная оптимизация — корень всех зол» — Дональд Кнут»
Другое дело спец вычисления, но их обычно и пишут сразу правильно и на всяких фортранах, лиспах… А если надо ещё быстрее — есть ASIC, например сети — те же циско часто раньше роутинг на асиках делали, получая очень быструю работу.
есть популярность, начинаем оптимизировать
Чего по факту не происходит. Исходят из того, что раз оно популярно то и оптимизировать незачем, лучше потратить деньги на новые фичи из за которых будет лагать ещё больше.
Эта цитата выдрана из контекста.
Полная цитата
Another important aspect of program quality is the efficiency with which the computer's resources are actually being used. I am sorry to say that many people nowadays are condemning program efficiency, telling us that it is in bad taste. The reason for this is that we are now experiencing a reaction from the time when efficiency was the only reputable criterion of goodness, and programmers in the past have tended to be so preoccupied with efficiency that they have produced needlessly complicated code; the result of this unnecessary complexity has been that net efficiency has gone down, due to difficulties of debugging and maintenance.

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.

We shouldn't be penny wise and pound foolish, nor should we always think of efficiency in terms of so many percent gained or lost in total running time or space. When we buy a car, many of us are almost oblivious to a difference of $50 or $100 in its price, while we might make a special trip to a particular store in order to buy a 50 cent item for only 25 cents. My point is that there is a time and place for efficiency; I have discussed its proper role in my paper on structured programming, which appears in the current issue of Computing Surveys
Не стоит оптимизировать код вообще, если это в конечно счёте даст процент разницы. Но когда речь про изменение производительности в разы, то не оптимизировать крайне глупо!

Конечно нет, зачем это делать, если труд разрабов дороже железа до определенного момента. Как только это иначе и заметили — сразу начинают оптимизировать. Не знаю что у Вас по факту не происходит. Да, было бы приятно, если бы пользователи какой-то ОС прямо сейчас получили прирост скорости в 200%. Но если в случае майрософт это будет стоить пару сотен жизней человеко работы — то нет, спасибо. Все это люди которые чет вперед двигают, и фичи, и безопасность и тд. Если бизнес у которого полно денег будет и на эти задачи забирать людей которые чет умеет, то на остальные задачи ресурсов не хватит, и энтузиазма отказаться от денег. А ведь там есть область к медицине близкая, строительство и тд.
И что Вам даст это ускорение, вот один продукт взять — Винда ускорила, все классно, но через время опять фреймворки и пайтоны всякие/js уперлись в потолок по железу, и все. Если все будут оптимизацию в главный угол ставить — то это опять же куча человеко-часов непонятно на что.

По закону Мура каждые 18-24 месяцев количество транзисторов удваивается. До сих пор это происходило за счёт уменьшения их размеров.
Сейчас вводится техпроцесс на 5 нм для производства процессоров. В 2022 году будет 2 или 3 нм. В 2024 — 1. В 2026 — .5, в 2028 — .25 — и это предел, это размер атома кремния. Не получится сделать транзистор меньше атома.
Ну через многоядерность и параллелизацию что-то получится. Ну ещё площадь кристалла можно увеличить… но всё. В обозримое время мы упрёмся в ограничение производительности железа.
Будут ли к тому времени распространены квантовые компьютеры и как они будут программироваться — вопрос.
Давно уже упёрлись и сейчас все эти нанометры — банальный маркетинг.

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

Проектные нормы в микроэлектронике: где на самом деле 7 нанометров в технологии 7 нм?
Понятно, что статически типизированные языки имеют фундаментальный бонус в скорости.
Но тот же python с развитием jit-компиляции (pypy, numbaJIT) и добавлением опциональной типизации — возможно, ещё долгое время можно будет выжимать из ускорения интерпритатора и JIT-компиляции. ( даже если не появится террагерцевых процессоров, квантовых компьютеров оптических вычислений и классический закон Мура встанет )
Понятно, что статически типизированные языки имеют фундаментальный бонус в скорости.


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

Как я себе вижу: компилятор может оптимизировать некоторые вещи, зная тип данных (
т.е. если функции на вход всегда приходит массив int'ов — оптимизировать проще, чем если это может быть int, float, строка, или вообще разные кастомные классы, для которых определено сложение друг с другом и деление на int. ( понятно, что такое можно написать и для статически типизированного языка, но если мы ищем среднее арифметическое массива интов [например, средняя зарплата], то у компилятора/интерпретатоора вообще не будет шансов из логики понять, что, например, строк там не будет. ( и и я не знаю динамически типизированных языков, где программист сможет это явно указать [python type hints — не влияют на оптимизацию, numba — статически типизирована, pypy — будет оптимизировать только при многократном вызове с интами])
)
Динамическая типизация всего лишь означает, что тип аргумента функции становится известен во время выполнения (например, прямо перед её запуском), а не заранее при обработке программы целиком. Соответственно, в этот момент можно компилировать функцию под конкретные типы. В случае, если типы аргументов действительно не меняются, оверхед по сравнению с компиляцией «заранее» будет нулевой.
Из языков ровно так работает, например, julia — причём явным образом типы указывать не требуется (но можно).
Numba в питоне тоже, конечно, никак не статически типизирована — до выполнения программы типы аргументов могут быть неизвестны.
def div(a,b):
    return a/b

В каком месте интерпретатор питона поймет что я передал ему строки, я так понимаю прям перед делением он выполнит эту проверку. В то время как статически типизированный язык просто интерпретирует куски памяти как int float или как мы их объявляли, ничего не будет проверять и поделит. Разве это не выигрышь времени выполения?
Нигде не поймёт — ну так я и не писал, что питон так делает (но см. в конце комментария про нумбу).
Julia, например, компилирует в такое:
> f(a, b) = a / b
> @code_native f(1.0, 2.0)  # float
# выдаёт ассемблерный код:
# vdivsd	%xmm1, %xmm0, %xmm0
# retq
> @code_native f(1, 2)  # int
# vcvtsi2sd	%rdi, %xmm0, %xmm0
# vcvtsi2sd	%rsi, %xmm1, %xmm1
# vdivsd	%xmm1, %xmm0, %xmm0
# retq
> @code_native f(big"1.0", 2.0)  # bigfloat - вызов соответствующего метода
# pushq	%rax
# movabsq	$"/", %rax
# callq	*%rax
# popq	%rcx
# retq
> f("abc", 1)  # строки делить нельзя
# MethodError: no method matching /(::String, ::Int64)

Эту функцию, конечно, можно использовать и внутри других функций
Заголовок спойлера
function g(x, b)
    res = 0.0
    for a in x
        res += f(a, b)
    end
    return res
end
> a = rand(100)
> @code_native g(a, 2.0)
#     movq    8(%rdi), %rax
#     testq   %rax, %rax
#     jle L74
#     movq    (%rdi), %rcx
#     vmovsd  (%rcx), %xmm1           # xmm1 = mem[0],zero
#     vdivsd  %xmm0, %xmm1, %xmm1
#     vxorpd  %xmm2, %xmm2, %xmm2
#     vaddsd  %xmm2, %xmm1, %xmm1
#     cmpq    $1, %rax
#     je  L69
#     movl    $1, %edx
#     nopw    (%rax,%rax)
# L48:
#     vmovsd  (%rcx,%rdx,8), %xmm2    # xmm2 = mem[0],zero
#     vdivsd  %xmm0, %xmm2, %xmm2
#     vaddsd  %xmm2, %xmm1, %xmm1
#     incq    %rdx
#     cmpq    %rax, %rdx
#     jb  L48
# L69:
#     vmovapd %xmm1, %xmm0
#     retq
# L74:
#     vxorps  %xmm1, %xmm1, %xmm1
#     vmovaps %xmm1, %xmm0
#     retq


Как видно, единственный оверхед здесь после запуска — один раз скомпилировать функцию для каждого требуемого типа аргументов. Затем выполнение идёт без замедлений относительно нативного кода.
При этом типизация динамическая, и можно составить такую функцию:
h(a) = f(rand(Bool) ? big"1.0" : 1.0, a)

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

В питоне нумба, как я писал, тоже по сути динамически типизированная — ведь статических типов в нём в принципе нет.
@numba.njit
def g(x, b):
    res = 0.
    for a in x:
        res += a / b
    return res
a1 = np.random.rand(100)
a2 = np.random.rand(100).astype(int)
g(a1 if np.random.rand() > 0.5 else a2, 2.0)

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

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

Затем выполнение идёт без замедлений относительно нативного кода.

Доказательства?
@code_native f(1, 2) # int
vcvtsi2sd %rdi, %xmm0, %xmm0
# vcvtsi2sd %rsi, %xmm1, %xmm1
# vdivsd %xmm1, %xmm0, %xmm0
# retq

Ага, «инт». (╯°□°)╯︵ ┻━┻
По вашим примерам ничего не видно, т.к. не показан код, который вызывает эти функции

Весь код показан — просто открываете REPL джулии и вводите ровно то, что в моём комментарии, те строки которые не с # начинаются. То, что с # в начале — это будет вывод. Можно и в скрипт запихать конечно, только явным образом print'ы проставить.
Больше кода никакого и нет, если конечно вы не имеете в виду реализацию самого компилятора или что-то подобное.
не показан код, который вызывает эти функции

Итак, вызывающий код — это просто f(1, 2), g(a, 2.0) и т.п.

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

А какой требуемый тип?

Может я не очень вас понял, но требуемый тип — это просто тип, с которым нужно скомпилировать функцию. Есть вызов f(1, 1) — компилируем f(::Int, ::Int); есть f(1.0, 1.0) — это f(::Float, ::Float), и т.д. Если с такими типами уже компилировали — ничего не делаем, просто вызываем (или инлайним) имеющуюся.

Ага, «инт»

Да, возможно я не уточнил, т.к. считал очевидным — какой тип имеют литералы. 1 и 2 — типа Int64, поэтому f(1, 2) вызывает f(::Int, ::Int). 1.0, 2.0 — это Float64.

Затем выполнение идёт без замедлений относительно нативного кода.

Доказательства?

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

Например, предложенный в моём предыдущем комментарии вызов g(a, 2.0) у меня на лаптопе выполняется за ~70 ns. Сравнивать с C/C++ лениво, но для последовательных делений и сложений по 100 раз время вполне соответствует ожидаемому от нативного кода.
При выполнении этого кода уже не важно, когда он появился — заранее или во время выполнения — скорость будет такая же. С чего вообще по-другому может быть?
Даже ели допустить, что Julia в runtime'е выполняет настолько качественную оптимизацию насколько возможно (что вряд ли, так как это занимает время) — инлайнинг, к примеру, в такой модели совершенно неприменим. Да и само переключение компиляция/выполнение бесплатно не проходит.
инлайнинг, к примеру, в такой модели совершенно неприменим

Не знаю, откуда такое мнение. Ведь прямо в комментарии выше, где я привожу пример выдаваемого ассемблерного кода, показан инлайнинг! А именно, функция f вставлена в g — никакого дополнительного вызова не происходит.

Даже ели допустить, что Julia в runtime'е выполняет настолько качественную оптимизацию насколько возможно (что вряд ли, так как это занимает время)

Да и само переключение компиляция/выполнение бесплатно не проходит.

Не вижу никакой проблемы проводить качественную оптимизацию во время выполнения программы, перед первым запуском каждой функции с новыми типами аргументов. Как я и писал, эта компиляция — единственный оверхед во время работы, но он случается только один раз на каждую комбинацию <функция, типы аргументов>.
Не знаю, откуда такое мнение.
Ну допустим Вы наткнулись на вызов функции и захотели его заинлайнить — как Вы это сделаете, если код функции, в которую нужно инлайнить, уже готов (это ведь он выполняется сейчас)? Единственный способ — вставить код с помощью memcpy, но уж лучше тогда не-inline вызов использовать.
Ага, нашел Ваш пример. ОК, есть инлайнинг, но видимо тогда все дерево вызовов скомпилировано за раз, а не по одной функции, как Вы говорите. Ассемблер, кстати, странновато выглядит.
Не вижу никакой проблемы проводить качественную оптимизацию во время выполнения программы
Дело в том, что качественная оптимизация использует данные обо всей программе, а не об одной конкретной функции. Например, если бы в Вашем примере было целочисленное деление — хороший оптимизатор бы заметил, что второй параметр всегда константный, и заменил бы его на умножение на константу. А компиляция по одной функции такого не сделает, откуда ей об этом знать?
Как я и писал, эта компиляция — единственный оверхед во время работы
Ага, а как на счет кэша скомпилированных функций? Перед каждым вызовом будьте добры сделать лукап, ну и апдейт кэша после компиляции, если вхождение не было найдено.
Ну допустим Вы наткнулись на вызов функции и захотели его заинлайнить — как Вы это сделаете, если код функции, в которую нужно инлайнить, уже готов

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

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

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

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

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

Не понял, зачем лукап перед каждым вызовом? Когда типы выводятся компилятором, этого точно не происходит; если типы невозможно вывести — ставится «лукап», либо из нескольких вариантов, либо если вообще ничего неизвестно — полноценный динамический.
Например, когда есть два варианта:
> f(a, b) = a * b
> h(a) = f(rand(Bool) ? 1 : 1.0, a)
> h(1)  # возвращает либо 1::Int, либо 1.0::Float
> @benchmark h(1)  # показывает, что h(1) занимает 5-8 ns
> @code_native debuginfo=:none h(1)
# под спойлером

Заголовок спойлера
Видно и целочисленное imulq, и дробное vmulsd. Более детально я в ассемблере не шарю :)

pushq %r14
pushq %rbx
pushq %rax
movq %rsi, %rbx
movq %rdi, %r14
movabsq $rand, %rax
callq *%rax
movabsq $4607182418800017408, %rdx # imm = 0x3FF0000000000000
movl $1, %ecx
testb $1, %al
cmoveq %rdx, %rcx
jne L70
vcvtsi2sd %rbx, %xmm0, %xmm0
vmovq %rcx, %xmm1
vmulsd %xmm1, %xmm0, %xmm0
vmovsd %xmm0, (%r14)
movb $1, %dl
xorl %eax, %eax
jmp L81
L70:
imulq %rbx, %rcx
movq %rcx, (%r14)
movb $2, %dl
xorl %eax, %eax
L81:
addq $8, %rsp
popq %rbx
popq %r14



Более полезный пример:
# если дали nothing - вернуть ноль, иначе умножить на 2
> f(x) = 2*x
> f(::Nothing) = 0

# хотим суммировать f(x[1]) + f(x[2]) + ...
> g(x) = sum(f, x)

# массив int'ов:
> a = rand(Int, 1000)
> @benchmark g(a)
# 80-90 ns

# массив int'ов и nothing'ов вперемешку:
> a = [rand(Bool) ? rand(Int) : nothing for _ in 1:1000]
> @benchmark g(a)
# 200-220 ns

В общем, даже в этой ситуации, где типы принципиально нельзя вывести статически (и есть лукап во время выполнения), получился весьма эффективный код. Даже simd используется, судя по времени.
Ладно, кажется я понял в чем проблема — в терминологии. Правильно ли я понял, Вы под вызовом функции подразумеваете команду вроде "> @benchmark g(a)"? Ну вот я это воспринимаю как запуск программы. А вот внутри себя она уже вызывает функции — это инструкции call или заинлайненный код других функций. Так вот я утверждаю, что каждая такая программа перед запуском полностью компилируется (включая все то, что она вызывает). Согласны с таким образом сформулированной позицией?
Этот подход эффективен — если Вам нужно запустить одну такую команду-программу. Но, опять же, есть аналогичная проблема — компилятор вынужден анализировать команды по одной, не видя общей картины. Например:
> f(x) = x
> g(x) = sum(f, x)
> a = [1 for _ in 1:1000]
> @benchmark g(a)
Если этот код компилируется по одной команде — будет цикл с суммированием. А если весь сразу — будет просто «return 1000».
Так вот я утверждаю, что каждая такая программа перед запуском полностью компилируется (включая все то, что она вызывает). Согласны с таким образом сформулированной позицией?

По-моему это принципиально невозможно сделать. Например:
> g() = eval(Meta.parse(readline()))
# вызываем g(), набираем в консоли код - он исполняется

# или

> g(x) = some_function(deserialize(x))
# вызываем g("file_name"), из этого файла десериализуется объект, на нём вызывается some_function

# или даже так

> g() = eval(Meta.parse(randstring(Char.(30:128), 123)))
# вызывать g()
# обычно будет ошибка,
# но возможно иногда случайно получится валидный код

Во всех трёх случаях при вызове функции g() нельзя определить, какие ещё функции будут вызваны. Это можно сделать только потом, когда часть кода g() будет выполнена.
Если этот код компилируется по одной команде — будет цикл с суммированием. А если весь сразу — будет просто «return 1000».

А для какого языка компилятор сможет сделать "return 1000", если a имеет ссылочный тип?


Если a будет иммутабельного типа и помечена как константа, то всё свернётся (но это не точно). Например, с пакетом StaticArray, добавляющим иммутабельные массивы фиксированного размера:


julia> f(x) = x
julia> g(x) = sum(f, x)
julia> const a = @SVector [1 for _ in 1:1000]
julia> @code_native (function () g(a) end)()
    .text
; ┌ @ REPL[8]:1 within `#10'
    movl    $1000, %eax             # imm = 0x3E8
    retq
    nopw    %cs:(%rax,%rax)
; └

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

Весь код показан — просто открываете REPL и вводите ровно то, что в моём комментарии
Ещё раз. У вас не показан код который вызывает оверхед. И так понятно, что leaf-функции будут откомпилированы в нативный код. Смотреть на них не интересно.

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

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

Т.е. рантайм затраты на лукап и компиляцию функции вы не считаете оверхедом?
*facepalm*

Вызвать функцию в 5 строк из REPL и выполнять некий софт со 100 мегабайт кода это несколько разные вещи.

Да, возможно я не уточнил, т.к. считал очевидным — какой тип имеют литералы. 1 и 2 — типа Int64, поэтому f(1, 2) вызывает f(::Int, ::Int).


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

Например, предложенный в моём предыдущем комментарии вызов g(a, 2.0) у меня на лаптопе выполняется за ~70 ns.


Так же из repl дёрнули? Так он компильнулся / проинлайнился, оверхед вызова скрыт. Это не показывает ничего, кроме наличия jit — компилятора.
К тому же про 70нс я очень сильно сомневаюсь.
70 нс это латентность одного обращения к памяти.

Давайте посмотрим на инструкцию VDIVSD (на 4GHz Skylake)
:VDIVSD xmm, xmm, xmm L: 3.49ns= 14.0c T: 1.00ns= 4.00c
В цикле 100 итераций, но div не в цепочке зависимости, поэтому имеем время их исполнения не 14*100, а 4*100 тактов == 100 нс, если компилятор не векторизировал цикл (буду приятно удивлён, если векторизировал).
Оверхед будет в программе, которая вызывает вашу функцию, если функция не проинлайнена.

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

Т.е. рантайм затраты на лукап и компиляцию функции вы не считаете оверхедом?

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

Языки такого плана обычно не умеют нормально работать с int.

Какого «такого» плана? Как «нормально»? Делить нацело можно, делить по-обычному тоже можно — просто это разные операторы.
Ваша функция с int не выполняет целочисленное деление.
Она переводит int в double, а потом делит даблы.

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

Так же из repl дёрнули? Так он компильнулся / проинлайнился, оверхед вызова скрыт. Это не показывает ничего, кроме наличия jit — компилятора.

Поведение в repl и в коде из файлов в аспекте скорости и подхода к компиляции абсолютно никак не отличается.

К тому же про 70нс я очень сильно сомневаюсь.
70 нс это латентность одного обращения к памяти.

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

В цикле 100 итераций, но div не в цепочке зависимости, поэтому имеем время их исполнения не 14*100, а 4*100 тактов == 100 нс,

Вполне возможно, что я действительно куда-то не туда посмотрел в прошлый раз. Сейчас перепроверил — выходит 100-110 ns.

если компилятор не векторизировал цикл (буду приятно удивлён, если векторизировал).

Вроде бы, судя по командам, цикл не векторизирован. Для векторизации нужно перед словом for добавить simd — тогда время работы сокращается раза в 2 у меня.
На питоне это всё тоже можно было-бы написать довольно быстро (хотя-бы попробовать), с помощью пакетов Numpy + Numba
Это скользкая дорожка
В таком случае можно говорить что и print это не питон

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

Чтобы писать на Python «правильно», надо на нём писать общую логику, а всё, что хоть как-то требовательно к производительности, засовывать в модули. Которые на Си, который уже совсем не Python. И да, NumPy именно так и сделан.
Думаю что в будущем, когда остановится развитие ПО backend и desktop приложения будут писать на языках типа c++ и rust.

Не уверен. Оптимизации из статьи начиная с четвертой не очень то и зависят от языка программирования. Сборщик мусора тут тоже мимо — на весь тест три объекта.
Я бы поставил на языки с нормальный типизацией, которые будут давать возможность нормально задавать на уровне типов подобные оптимизации. Ну или для начала удобные библиотеки для работы со стандартными оптимизациями.
Я бы поставил на языки с нормальный типизацией, которые будут давать возможность нормально задавать на уровне типов подобные оптимизации.

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


Будущее за языками с зависимыми типами даже с точки зрения производительности, ура!

И по прежнему 80% будет уходить на ожидание, диска ли, сети ли… Но можно ждать быстрее!

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

Не уверен. Все приложения всё ещё используют БД и там как упирались в диск, та и упираемся. С latency пока не сильно хорошо.

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

Попытка использовать samsung 970 под бд привела к его смерти за 2 месяца. Сначала тоже казалось, что отличное решение и под ноут подходит. Сейчас у меня хорошего решения нет. Да и по цене я не назвал бы это массовым.

2 месяца это что-то странное, похоже на брак.
У меня 2 штуки SSD Samsung работают почти непрерывно 6 лет — один система, другой рабочие файлы. Так вот смарт показывает оставшийся ресурс 91 и 99% соответственно.

Я был немилосерден. У меня циклы разработки такие, что записываются по 300-700gb и стирается и по-новой раз в пару-тройку дней.

Ну грубо можно принять 200 ГБ/день. За два месяца без выходных получится 12 ТБ — это на порядок меньше даже заявленного ресурса. А реальный, согласно тестам, должен быть больше ещё примерно на порядок. То есть ваш диск отработал ~1% того, что должен был.

Странно, потому-что у меня с хардами прямо противоположная ситуация. Если использовать под систему и на нём-же проводить всякие сборочки: начинал дохнуть где-то через полгода-год. С ssd ситуация противоположная: 2 года, 500 гб обьём (CT500P1SSD8), используется в качестве системного диска и под проекты тоже (брался из-за хорошего показателя 4k random write). 24.6 терабайт записаных за этот период, полёт нормальный (drive remaining life рапортует 90%), нареканий ноль.

Странно, потому-что у меня с хардами прямо противоположная ситуация. Если использовать под систему и на нём-же проводить всякие сборочки: начинал дохнуть где-то через полгода-год. С ssd ситуация противоположная: 2 года, 500 гб обьём (CT500P1SSD8), используется в качестве системного диска и под проекты тоже (брался из-за хорошего показателя 4k random write). 24.6 терабайт записаных за этот период, полёт нормальный (drive remaining life рапортует 90%), нареканий ноль.


Ну а у меня за последние 10 лет сдохло 20% SSD и 5% HDD.
Что за ssd-хи хоть?

Они померли и кто их уже помнит.
Хорошие модели. Не дешёвые. Я хреновых не покупаю.
Там где важна скорость — все базы данных давно крутятся на SSD, in_memory для кеширования тоже в помощь. Понятно, что это дороже — но варианты есть. У меня сейчас на проекте в стате по нгинксу — 99-ый квантиль укладывается в 40ms на апи(а на части эндпойнтов и в 20ms) — при том что там идёт работа с тремя разными базами данных, две in_memory, одна обычная. А главная страница — ~2.4 секунды до полной загрузки(меньше секунды до отображения интерфейса), 2mb данных, под сотню http запросов — стили, скрипты, картинки, и основное время в таймингах — ttfb.

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

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

Все приложения всё ещё используют БД и там как упирались в диск, та и упираемся

Что, прямо все-все приложения на свете? :)
В кровавом энтепрайзе работают не только лишь все.(с)
У меня вот ни одна задача не упирается в диск.
Когда проект в десятки мегабайт кода собирается на 16 ядрах, SSD загружен на 0-1%.
А вообщем то решают эту задачу.
Просто обычно в варианте «у нас спутниковый канал а значит пинг в полсекунды и полоса узкая а нам надо работать».
Как в тупом варианте для обычных пользователей (которым http нужен(тогда еще https был редкий) — Globax/TelliNet (спецпрокся сразу запрашивает кроме страницы еще и картинки) так и серьезные оптимизации протоколов вроде SMB чтобы снизить количество запросов + кэшировать информацию локально и передавать дельты. Это например продукты от Riverbed делают.

В HTTP/2 есть попытки оптимизировать по минимуму (например — то что не надо каждый раз соединение устанавливать и гонять TCP и TLS рукопожатия)

В HTTP/2 есть попытки оптимизировать по минимуму (например — то что не надо каждый раз соединение устанавливать и гонять TCP и TLS рукопожатия)

Как ни странно, этого не приходится делать даже в HTTP/1.1, если использовать заголовок «Connection: keep-alive». Причём, это используется везде, даже в библиотеках вроде requests для Python и cURL для PHP.
Вторая версия мультиплексирует все соединения к серверу в одно. Да и keep-alive регулярно обрывают для избежания всяких там проблем, что браузеры, что вебсервера.
Как в тупом варианте для обычных пользователей (которым http нужен(тогда еще https был редкий) — Globax/TelliNet (спецпрокся сразу запрашивает кроме страницы еще и картинки)


Я правильно понимаю, что это те системы, которые передают все данные всех пользователей через 1 спутниковый канал на землю, а исходящий траффик через другой канал?
И именно там была популярна «рыбалка», когда более подкованный пользователь перехватывал входящие спутниковые пакеты, адресованные не ему, и мог прочитать чужой http-траффик. (привет угон кукисов и прочие радости)

Ох… Прямо в сердце… Трудно сделать базу шустрой на стороне клиента. А надо.

Полно задач, где на производительность вообще наплевать. Отчёт посчитается сегодня — отлично, послезавтра — тоже сойдёт.

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

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

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

Это как? Если вместо одной большой библиотеки будет много маленьких, каким образом я уменьшу количество зависимостей?


Разве это не тот путь, которым пошли с npm.js, и который в итоге приводит к 13000 зависимостей?

Это как? Если вместо одной большой библиотеки будет много маленьких, каким образом я уменьшу количество зависимостей?

Большая библиотека А может иметь компоненты b, c, d с зависимостями E,F,G соответственно. То есть библиотека A зависит от библиотек E, F, G. Разделяя библиотеку на библиотеки Ab, Ac, Ad (с зависимостями E,F,G соответственно), мы даём возможность пользователю подключить только библиотеку Ab c зависимостью E (если ему не нужны компоненты c, d), и не тянуть зависимости F, G ненужных компонентов.


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

Это именно то, к чему пришел npm. В котором «меньшими порциями» подключить себе 13000 зависимостей — сейчас довольно просто.

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

число библиотек будет такого же порядка

Как по мне, это проблема. В общем случае, я бы предпочел одну большую библиотеку + tree shaking, чем 10 маленьких:


  • больше вероятность, что она написана в одном стиле;
  • не возникнет конфликтов между версиями, когда библиотека A требует библиотеку C v1, а библиотека B требует библиотеку C v2;
  • меньше вероятность дублирования кода, поскольку компоненты одной библиотеки знают о существовании друг друга;
  • проще обновить одну библиотеку на новую версию, чем 10;
  • компоненты одной библиотеки будут совместимы между собой;
  • и т.д.

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

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

Это распространённое заблуждение:


  • Этот десяток строк нужно протестировать ровно так же, как и любой другой десяток строк нашего кода — никакой особой разницы тут нет.
  • Насколько протестирован аналог этих строк в библиотеке — обычно никто не знает, и при выборе библиотеки на это не смотрит.
  • Поиск подходящих библиотек и выбор из нескольких альтернатив занимает время, нередко превышающее время на написание и тестирование этих 10 строк.
  • Добавление дополнительной зависимости в проект создаёт дополнительные риски и увеличивает стоимость поддержки проекта само по себе (которые принято игнорировать, но если прятаться под одеялом то монстр всё-равно никуда не исчезнет) — лицензии, зависимость от стороннего вендора, куча лишнего кода с потенциальными уязвимостями и т.д. (подробнее в https://habr.com/ru/post/443620/). Но если их не игнорировать, то стоимость поддержки и тестирования своих 10 строк почти всегда окажется значительно ниже стоимости поддержки дополнительной зависимости.

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

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

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

Прямые зависимости — они явные. Транзитивные зависимости — неявны, этим они и страшны.

Транзитивные зависимости вызывают большее беспокойство, чем прямые (прямые подключаются явно, а не ВНЕЗАПНО, они все видны). Описанный способ борется с лишними транзитивными зависимостями.

Описанный способ приведет (и приводит) к аду с управлением зависимостями, когда библиотека A зависит от библиотеки C v1, а библиотека B зависит от библиотеки C v2. А я про эту библиотеку C вообще ничего не знаю, и она мне нафиг не сдалась бы. И чем больше таких микро-библиотек, тем больше вероятность конфликтов.


Но это все лирика, повторюсь, к моему изначальному вопросу это не имеет никакого отношения.

к моему изначальному вопросу это не имеет никакого отношения.

По-моему, я описал механизм, как эта идея работает. Это отвечает на вопрос "как?"

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


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


А вы мне в этой ветке говорите про то, что транзитивные зависимости вызывают большее беспокойство. Отсюда и вопрос: "И что?"

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

Это не так.

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

В случае с примером, описанным Akon32, библиотека A разбитая на библиотеки Ab, Ac и Ad — это одна библиотека. Все зависимости в данном случае должны быть едиными и иметь общую версию. Кроме того, версия библиотек Ab, Ac и Ad тоже должна быть общей, даже при выпуске новой версии где изменения коснулись только одной части. Если придерживаться этих правил проблем с зависимостями точно не прибавится.

При этом транзитивных зависимостей будет меньше только в теории. На практике же, если библиотека A тоже пошла этим путем, то Ab будет зависеть не от E, а от Ex, Ey, Ez. Каждая из которых, в свою очередь, будет зависеть еще от 5 микро-библиотек.


Получается, что вместо зависимости A –> E, F, G, у меня будет Ab, Ac –> Ex, Ey, Ez, Fp, Fq -> *

На практике же, если библиотека A тоже пошла этим путем, то Ab будет зависеть не от E, а от Ex, Ey, Ez.

Если E пошла тем же путём, возможно, что в зависимости Ab можно прописать только Ex, а не все Ex, Ey, Ez, поэтому ни Ey и Ez, ни их зависимости грузиться не будут; и т.д.
На практике может быть и так, что нужны все Ex, Ey, Ez, и тогда преимущества такого способа нет.

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

Теоретически да, практически — далеко не всегда.


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

С другой стороны проблемы не в размере самих библиотек, а в том, что современные технологии неприспособлены для автоматического dead code elimination. В сях все было просто: неважно сколько библиотек ты подключил — линкер урежет все, что статически недосягаемо из main(), делая очень компактный бандл. В других языках как Java, Python, JS пришлось искусственно городить модульную систему и требовать от пользователя каждый раз въявную указывать компоненты, которые он будет использовать. Так что это по большей части шаг назад.

В сях все было просто: неважно сколько библиотек ты подключил — линкер урежет все, что статически недосягаемо из main(), делая очень компактный бандл. В других языках как Java, Python, JS пришлось искусственно городить модульную систему и требовать от пользователя каждый раз въявную указывать компоненты, которые он будет использовать. Так что это по большей части шаг назад.

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

  • Во-первых, стек библиотек, поставляемый с платформой (версией ОС, SDK, etc), как правило всегда унифицирован транзитивно.
  • Во-вторых, есть требование, чтобы библиотеки с одной и той же major version были обратно совместимы. http://www.sourceware.org/autobook/autobook/autobook_61.html В этом случае можно просто слинковать более свежую библиотеку.
  • И в-третьих, в одном бандле можно использовать транзитивные библиотеки разных версий. Для этого линкер должен включить номер версии в таблицу символов.
    https://blog.habets.se/2012/05/Shared-libraries-diamond-problem.html
  • Во-первых, стек библиотек, поставляемый с платформой (версией ОС, SDK, etc), как правило всегда унифицирован транзитивно.

Но не все зависимости в этот стек входят.


  • Во-вторых, есть требование, чтобы библиотеки с одной и той же major version были обратно совместимы. http://www.sourceware.org/autobook/autobook/autobook_61.html В этом случае можно просто слинковать более свежую библиотеку.

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


  • И в-третьих, в одном бандле можно использовать транзитивные библиотеки разных версий. Для этого линкер должен включить номер версии в таблицу символов.
    https://blog.habets.se/2012/05/Shared-libraries-diamond-problem.html

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

это просто работает

Просто это работать не может. Если есть цепочка A-B-D1 и А-С-D2 и нужно объект из библиотеки D передавать между функциями из B и С. Как это сделать?


Если же библиотеки D1 и D2 не торчат наружу, то возможно конкретная реализация в Си немного тупая. Но в целом решение очевидное.

Просто это работать не может. Если есть цепочка A-B-D1 и А-С-D2 и нужно объект из библиотеки D передавать между функциями из B и С. Как это сделать?

Если вы не указали, чтобы B и C использовали одну и ту же версию — никак, у вас объект из библиотеки D разных версий, т. е. фактически разных библиотек.

Окей, это интересно (нет, серьёзно), но что делать с зависимостями, которые линкуются статически?

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


Или просто в новой версии транзитивной зависимости добавили новую функцию, которая имеет то же имя, что и в библиотеке.

Когда говорят о транзитивных зависимостях, почему-то нигде не указывают тип транзитивности. Вне зависимости от языка программирования любая библиотека всегда состоит из двух частей: интерфейса (структуры, типы, сигнатуры функций) и имплементации (непосредственно код). Интерфейс библиотека экспортирует наружу, тогда как имплепентация — это внутреннее поведение библиотеки. Если транзитивная зависимость используется полностью в имплементативной части библиотеки, то нет никаких проблем иметь разные версии в разных зависимостях: различные платформы предоставляют свои средства "изоляции" (namespaces, dynamic linkers, classloaders, etc...).


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


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

Если транзитивная зависимость используется полностью в имплементативной части библиотеки, то нет никаких проблем иметь разные версии в разных зависимостях: различные платформы предоставляют свои средства "изоляции" (namespaces, dynamic linkers, classloaders, etc...).

Только не в C, там из средств изоляции только static для функций.

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

Тут таки сложнее. В Windows, например, статические библиотеки линкуются как внутрь основного бинарника, так и внутрь DLL (это долго относилось, а может, и сейчас, к CRT — C runtime library); если статическая библиотека это не импорт DLL, то каждая DLL получит свою копию этой статической, со своей версией и своими глобальными переменными. Поэтому лучше, например, malloc/free/new/delete делать переходниками на соответствующие функции user32, если нет жёстких причин делать иначе; если же рантаймы у двух DLL различаются, то возникает жёсткое правило что освобождать память должна та же DLL, что её аллоцировала.

В Unix, соответствующие уровни обычно сделаны в общей libc, а в общем случае подключается только одна библиотека (самая свежая) каждого типа, но и там можно при желании натворить хаков — статическая линковка внутрь SO, прямой dlopen на желаемую библиотеку, и прочая и прочая.

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

Теоретически — да. Практически же этот механизм приводит к тому, что ссылка двух DLL друг на друга по кольцу без явного резолвинга в коде уже ломает всё, и аналогично для .a (.lib).
Деление на интерфейс и реализацию в этой схеме совершенно нетипично, это не классовая иерархия.
Это в теории. В практике 99% разработчиков будут писать что-нибудь типа «include *», а то мучаться, выбирать нужные библиотеки… Сразу все включить и не мучаться ((

include * не значит что используется всё. компилятор может знать что используется а что нет. А вот какой то __import__(random_string()) в интерпретируемых языках это уже хуже.

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

"Нужно оптимизировать ПО" — весьма банальная идея.
Программы, в которых действительно нужна скорость, и так тщательно оптимизируются.
Но есть и такие программы (и их много), где не важно, 100 или 200 миллисекунд будет обрабатываться нажатие кнопки. А процесс оптимизации — это некоторые затраты.


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

В ряде случаев это, конечно, хорошая идея, но постоянная память сегодня практически ничего не стоит. Какая-нибудь SD карта размером с ноготь может хранить сотни гигабайт. Нет смысла беспокоиться, пока система не начнёт работать настолько плохо, что за неё станет стыдно (или пока конкуренты не прижмут). Если 100500МБ библиотек перестали влезать в SSD — определённо, надо что-то делать.
Ну и собственно разделение библиотек может быть хорошей идеей по ряду других причин, не связанных с объёмом исполняемого кода, например уменьшение вероятности некоторых конфликтов зависимостей.

Проблема в том, что у разработчиков ПО обычно топовые компьютеры с кучей памяти, быстрыми многоядерными процессорами и быстрыми дисками. И быстрый интернет. Особенно быстрый канал связи до своих серверов. У них всё работает быстро и без тормозов. А у пользователей разные компьютеры, в том числе старые и тормозные.
Если предложить разработчику поработать на старом нетбуке с процессором Atom и двумя гигами оперативки и подключить его через ADSL, то наверняка он сразу задумается об оптимизации своего софта. И не только своего.

Проще: оптимизация начнется, как только стоимость разработки станет меньше стоимости железа.

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

Проблемы пользователей разработчиков не волнуют. По крайней мере проблемы 10-20% пользователей с наиболее слабым железом. Они кассу не делают.
Это не правда. Если разработчики поставляют ПО, которое у пользователей (проблемы которых, якобы, программистов не волнуют) тормозит, пользователи уходят к другим поставщикам софта. Тогда к программистам приходит менеджмент и их начинают волновать другие проблемы.
К какому поставщику ушли пользователи винды, ютуба, или гугломейла? (вопрос риторический)

Благо, ютуб пока что ещё смотрится через mpv, а в гугломейле не отрезали классический HTML-вид, но это пока :-)

А другого софта нет? Я работаю в ЕРП и у нас такое случается — клиент может громко хлопнуть дверью. Просто тут каждый клиент стоит как сотня клиентов в виндовзе. А ютюб и гугломейл вообще бесплатные.
пользователи уходят к другим поставщикам софта


… с новым глюкодромом и тормозами. А от «другого» поставщика переходит кто-то к первому, лелея мрию что тут ему сделают красиво :)

Повторюсь: оптимизация начнется, когда стоимость разработки станет меньше стоимости железа.


Объясню: большему числу пользователей примерно наплевать на производительность — они всё равно будут пользоваться.


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


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


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


Любой серьёзный софт с хорошей оптимизацией — это, зачастую, десятки человеколет, либо bus factor == 1 (Умирает человек — умирает проект, как будет когда-нибудь с foobar2000, к примеру, потому что с его кодом практически никто не знаком. Максимум — растаскают по разным плеерам).


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

Повторюсь: оптимизация начнется, когда стоимость разработки станет меньше стоимости железа.

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

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

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


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

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

Поковырялся в памяти и вспомнил: не у линкера не хватало памяти, а у самого получающегося бинаря — ОС не могла его загрузить, потому что слишком много туда было влинковано, и оно тоже выходило за какие-то пределы. Плюс, это был то ли AIX, то ли SunOS, про особенности каждого из них я знаю очень мало, так что какие именно лимиты там ломались, я уж не подскажу.


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


Так что npm с его 13 тыщами зависимостей для тудушки и гигабайтами на диске — это ерунда и детский лепет по сравнению с Ъ тырпрайзным сиплюсплюс.

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

Банальный виндовый exe (PE) не может быть больше 2ГБ. Лимиты ближе, чем кажутся. Я видел dll от nvidia размером в несколько сотен МБ.

xul.dll из Mozilla Firefox весит 107 метров. Надо будет дома посмотреть, думаю, у игрушек можно найти бинари и побольше.
Самые большие бинари — это самораспаковывающиеся архивы. А у игрушек обычно компактный движок в виде exe или dll и гигабайты ресурсов лежат рядом.
У игрушек — возможно. Потому что там этих ресурсов слишком много и они регулярно обновляются. А вот у «обычных» подвиндовых программ как раз исполняемые файлы, включая DLL, используются в качестве контейнеров для хранения ресурсов.
Если я правильно помню, как происходит работа с динамическими библиотеками, то в память загружается только инициализирующий код, а остальное подтягивается по мере необходимости. Ну и у виндовых исполняемых файлов, в том числе у DLL, зачастую, основной объём — ресурсы, которые в *nix-ах в исполняемых файлах не хранятся, а лежат «рядом» отдельными файлами картинок и прочего подобного.

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

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

Хорошо, как давно Вы ставили последний раз ПО исходя из этого?


А если, к примеру, я предложу Discord с показателем потребления /10, но за подписку 5$/месяц? Подскажу: скорее всего, большинство соберется купить себе ноут подороже и с большей батареей или проц пошустрее, чем отдать 3-5$ за ПО, если у него есть тормозные бесплатные альтернативы. Ну просто потому, что "незачем отдавать этому производителю ПО бабло просто потому, что соседнее ПО один хрен цпу/рам выжрет, а если покупать каждое — проще новый проц поставить".


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


Могу даже упростить: можете показать выписку с донатами Mozilla (если Вы ей пользуетесь), Canonical (если пользуетесь Ubuntu), Eclipse Foundation (если пользуетесь Eclipse IDE)?


Если нет — то какие вопросы?

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


А как вы решите деньгами разряжающийся за 2 часа ноутбук на core i7 8 поколения?


можете показать выписку с донатами Mozilla (если Вы ей пользуетесь), Canonical (если пользуетесь Ubuntu), Eclipse Foundation (если пользуетесь Eclipse IDE)?

Давайте без выписки. Если вам интересно, поддерживал и поддерживаю свободное ПО как Ubuntu, phpmyadmin и прочие линуксы. Буквально несколько дней назад отправил 10$ Википедии.
Вы правы, я буду платить за быстрое ПО.

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

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

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


Так же что Скайп, к сожалению, хороший пример. Пока 80% готовы мириться с плохой скоростью ПО, остальные 20% тоже будут. Скорость — не определяющий фактор.

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

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

Если файлы сложные — не можете. У ваших клиентов просто поедет всё, придётся вам потерпеть. Если файлы простые, то повезло.


браузер

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

деградация производительности скайпа ерунда по сравнению с отвратительной работой на мобильных. Показать что мне пришло сообщение с опозданием на несколько часов или не показать вовсе — вот что для меня полностью убивает его как мессенджер.
А вот это, как я понимаю, косяк служб Google (если Вы про него, а не про iOS) или чего-то на этом уровне, потому что уведомления не приходят не только в Скайп. Причём, на старом смартфоне с Android 5.1 всё работает на ура. Но да, на смартфоне он тормозной. По крайней мере, это сильно заметно, если смартфон не тягается по производительности с дэсктопом.
Там закрутили гайки, да, но пуши вполне себе работают, если их уметь правильно готовить.
Ну вот из всего, что стоит у меня, адекватно пуши работают только у WhatsApp, который используется вынужденно, и у GMail, у которого до недавнего времени так же были с этим проблемы.

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

А если, к примеру, я предложу Discord с показателем потребления /10, но за подписку 5$/месяц?
На самом деле, цены на freemium-софт сильно завышены, потому что каждый покупатель подписки платит за себя и того парня взвод других парней. Помню, несколько лет назад писали, что у Evenote платят только 4% пользователей. Может быть, сейчас иначе, но тем не менее.
Так вот если заставить платить всех без исключения, то 5 баксов в месяц могут легко и непринужденно превратиться в 5 баксов в год :)

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


Можете для эксперимента предложить пенсионерам заплатить 5$ в год за подписку на видеозвонилку в дополнение к интернету, когда у них уже есть скайп. Заодно словарный запас можно пополнить, у пенсионеров опыта бывает много.


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

Да нет, идея стара как мир. За колбасу, ботинки и такси платят все, а не некоторые.

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

Никакое «заставить платить» тут быть не может.

Винда же (если без пиратства). Да, есть линуксы, но сидя на работе под линуксом, дома поставил винду, потому что даже в steam из всех игр что у меня куплены — в линь умеют процентов 10 от силы. Плюс под винду написана просто масса по, которого нет под линь, а аналоги… Вот чем заменить мс офис именно при серьёзном документообороте? openoffice, libreoffice — простые документы обычно норм, но сложные, со сложным форматированием, с режимом комментирования — ломаются в 90+% случаев. То нумерация уплывает, то страницы рвутся криво, то вёрстка уезжает и иногда это важно… Мы пытались использовать и линухи и *офисы, но по моему субъективному опыту — самым стабильным был ooo 2.2 (выпущенное году этак 2010, последний раз сравнивали году в 2015), а либра била доки даже те, которые в ооо 2.2 показывались как надо.
И это при том что режим правок например терялся полностью. Итог — внутри фирмы — ООо, всем кто общался с клиентами и вообще с миром — винда и мс офис.
Возможно, мак был бы решением, там вроде как есть и мс офис и ещё много софта, но он сам по себе дороже и это отдельная экосистема.
простые документы обычно норм, но сложные, со сложным форматированием, с режимом комментирования
Вот никогда не понимал, а зачем нужны «сложные» доковские документы. Либра рецензирование, формулы и ссылки вполне себе вывозит в 5 версии.
Вот никогда не понимал, а зачем нужны «сложные» доковские документы. Либра рецензирование, формулы и ссылки вполне себе вывозит в 5 версии.


Внутри себя вывозит.
При импорте-экспорте — съезжает форматирование.
У кого как :)

Не у кого, а у чего.
Зависит от самого документа.

Причем съезжает форматирование и у элементарных вещей типа «выставить счет в OpenOffice и открыть в Excel или наоборот».

А счет это такая простейшая форма:

image

Вы можете предположить, что съезжает картинка с печатью или подписью?

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

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


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

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

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

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

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

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

Какая "интересная" идея. Ресурсоёмкое ПО сразу вылетает из реестра. Остальные пишут костыли, чтобы удовлетворить формальным требованиям.
А когда железо подешевеет, ПО его не может использовать, потому что законодательно будет ограничено лимитом, аналогичным "640кБ". Развитие приостановится.


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

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

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

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

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

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

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

Да, а это плохо?


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


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

Ну Ops и SRE не могут позволить себе такой роскоши как выключенный телефон )

Ops и SRE не получают звонки на скайп (я надеюсь) и могут себе позволить условный PagerDuty.


Впрочем, on-call duty с минутным SLA 8 часов в день, 7 дней в неделю, при работе программистом всякой HFT-дряни на прошлой работе — это была основная причина, почему я оттуда хотел уходить.

Тоже так думаю. Идеальный пример Whatsapp и и Telegram. Первый вышел на рынке раньше и сейчас является монополистом практически, на фоне телеграмма, который уделывает его по всем параметрам, но большинство, как пользовалось whatapp, так и пользуется. Кроме тех людей, которые попробовали telegram конечно...

Телега очень долго не имела собственно связи, это был мессенджер.
Вацап — это вполне себе интернет-телефон и видеофон с самого начала.
Вацап — это вполне себе интернет-телефон и видеофон с самого начала.

Это не так. Изначально WhatsApp был заменой SMS и ничего кроме сообщений не умел. И причём был платным.

Первый выпуск приложения относится к январю 2009 года. К апрелю 2012 года ежедневно при помощи WhatsApp пересылалось 2 миллиарда сообщений, более 10 миллиардов в августе 2012 года, более 27 млрд в июне 2013 и более 50 млрд сообщений на март 2015. В феврале 2018 около 1 млрд человек отправляли через WhatsApp 55 млрд сообщений в день.
По мнению, опубликованному в газете Financial Times, приложение WhatsApp сделало с SMS то же, что сделала программа Skype с международными телефонными звонками.

В феврале 2014 года CEO WhatsApp намекнул, что к лету планируется добавить функциональность VoIP-звонков между пользователями, и в конце марта 2015 года эта функция была реализована на платформе Android. В апреле 2015 года звонки появились на платформе iOS. В июне 2015 года функция стала доступна владельцам Windows Phone.

19 февраля 2014 года компания Facebook Inc. объявила о приобретении WhatsApp за 19 млрд долларов.
WhatsApp стал бесплатным с 18 января 2016 года. Ранее за подписку на использование сервиса взималась плата в размере около 1 доллара США каждый год, начиная со второго года использования, либо (для платформ Apple), однократный платёж при покупке приложения

К 2015 году WhatsApp стал самым популярным приложением для обмена сообщениями в мире, и по состоянию на февраль 2020 года в нем насчитывалось более 2 миллиардов пользователей. WhatsApp стал основным средством электронной связи во многих странах и регионах, включая Латинскую Америку, Индийский субконтинент и большую часть Европы и Африки.


Пользовался и телеграмом и вотсапом на смартфоне, не вижу, чем именно телеграм лучше.

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


  1. Максимальный размер файлы в WhatsApp — 16Mb (с ограничениями по типу файлов, старые файлы удаляются с сервера), в Telegram — 2Gb (без ограничений на тип файла, старые файлы не удаляются и их всегда можно скачать)
  2. Максимальное число участников в группе WhatsApp — 256, в Telegram — 200000
  3. Хорошие боты: в Telegram они есть
  4. В Telegram можно можно сохранять сообщения для самого себя, никому их не отправляя

Ага т.е. он стал бесплатен после покупки фейсбук. Что они от этого выиграли? Куда идет телеметрия и какая, является ли проект дотационным?) С телегой тот же вопрос. Или телега ещё на стадии пилим деньги инвесторов?

Или телега ещё на стадии пилим деньги инвесторов?

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

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

Волнуют. В указанных в ТЗ рамках.
А если там например указано (или НЕ указано но сказано неявно) что телефоны с дисплеем меньше N" не поддерживаем — пользователю с таким — не повезло. Если по ТЗ надо при старте приложения показать пользователю продукты которые ну очень будут ему полезны — это и будет сделано.
Если надо сделать приложение «красивее» (пусть даже это значит на определенный период в приложении будет и новый и старый код ) — ну вы поняли.
Если надо «по соображениям безопасности» дергать 3rd party либу которая не только собирает данные все какие может но еще и спамит в logcat при этом (серьезно тормозя при этом работу) — ну значит максимум разработчики сделают возможность собирать версию где эта либа просто не инициализируется. Для себя сделают потому что в прод это выкатывать — «это ж безопасность».
Кэширование? Ну может быть сделаем, но нам же актуальность данных важнее.

Не помню точно, в какой корпорации это было, но кажется в Facebook. В общем, там в какой-то момент ввели «день 2G»: раз в неделю разработчикам настоятельно рекомендовали переключать свои убер-мега-топовые смартфоны на 2G, чтобы почувствовать себя «в Африке» и перестать пытаться загрузить видео прямо в шапку главной странице сайта. Последнее я кое-где встречал.

И да, у меня есть старый нетбук с 2 гигами ОЗУ, которого мне долго хватало для разработки, но вот просмотр сайтов он уже давно не тянул, хотя как бы НЕТбук. Последнее время стало получше — Firefox-у всё-таки навели рефакторинг, но всё-таки.
переключать свои убер-мега-топовые смартфоны на 2G

Мухаха, мне нужен день 4G, когда я свой убер топовый смартфон наконец-то подключу через 4G вместо на жёсткую выставленного 2G ))
Ну ой. У кого щи жидки, у кого жемчуг мелок. Вроде, в областном центре живу, а знаю места в городе, причём, не в жопе мира, где связь сильно так провисает.
Да ловит у меня везде отлично, это я так, не пользуюсь мобильным интернетом за ненадобностью и сижу на тарифе по 1,5 рубля за мегабайт и плачу 100 рублей в месяц.
Проблема в том, что у разработчиков ПО обычно топовые компьютеры с кучей памяти, быстрыми многоядерными процессорами и быстрыми дисками. И быстрый интернет.

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

Постоянная память, хоть и дешевая, но слишком медленная, чтобы код исполнялся непосредственно из нее. Оперативная динамическая память стоит гораздо больше, а быстрая статическая память, используемая для кэша, еще больше.
В конечном итоге — в чем проблема в огромном количестве зависимостей (в плане производительности конечного ПО)? Да, при разработке выкачивается вся цепочка. Но уже сборщик потом решает, что брать и нет. И роль будет играть реализация тех или иных алгоритмов, а не объем кода разработки. Или я не прав?
Он может их не брать только если на 100% уверен, что условие их вызывающее не выполнится никогда. Беда в том, что программы так разжирели, что компилятор не может вычислить все возможные состояния программы. Соответственно тянет всё, что указано в коде. А алгоритмы позволяют оптимизировать только узкие места, но у современной программы, весь её код работает одинаково медленно. А ещё благодаря ООП подходу объекты инициализируют другие объекты, которые могут лезть к диску или сети. А результат окажется абсолютно ненужен!
Беда в том, что программы так разжирели, что компилятор не может вычислить все возможные состояния программы.Соответственно тянет всё, что указано в коде.

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

Ну почему, -fvisibility=hidden -Wl,--as-needed вполне себе помогает и в случае С++.

Нет, это связано с чем-то другим
Пример заставляющий VC++ генерировать большой бинарник без необходимости
//ни один другой объектный файл не увидит эту функцию
static int func() {
        // и этот массив тоже
        // собственно он будет вырезан компилятором, если не вызвать эту процедуру
	const static char arr[30 * 1024 * 1024] = { 50 };

	int retval = 0;
        // используем только пять первых байт, вероятность доступа к остальным 0%
	for (int i = 0; i < 5; i++)
		retval += arr[i];

        // очевидно, что процедура вернёт 250
	return retval;
}

int main()
{
	printf("%d\n", func());
}

По идее, всё работает примерно так: линковщик «заходит» в функцию main(), собирает список функций, которые из неё вызываются, потом заходит уже в них и проделывает аналогичную работу и так до тех пор, пока в списке не перестанут появляться новые функции. И вот это всё он линкует, а остальное выкидывает, потому что оно никогда и ни при каких условиях не могут быть вызвано просто потому, что не упоминаются в коде. А вот оптимизация на предмет «это не может быть вызвано, потому что этот код вообще не может быть выполнен» делается компилятором и именно на этом этапе лишние вызовы просто выкидываются из объектного файла.
Виртуальные методы не выкидываются никогда, даже если нет ни единого их вызова. Так же естественно подключаются все методы которые они используют и все виртуальные методы созданных объектов и т.д. Если в функции будет условный блок, которой не будет вызван никогда, он как правило всё равно будет засчитан, потому, что компилятор не сможет оптимизировать это.
Понятно, почему линковщик подтягивает виртуальные методы — на них есть ссылка. И, собственно, это как раз дело компилятора убрать ссылки, которые никто не использует. А однозначно отследить «мёртвый» код сейчас может только человек. IDE умеют его «заподозрить» и маякнуть человеку, что вот здесь код подозрительно похож на недоступный и его, возможно, стоит убрать нафиг. Но это именно что подозрение, а оконочательное решение принимает человек. Линковщик — довольно тупая программа, если смотреть на общую логику, а не на способы её реализации, включая многопоточность и прочую радость.
Человек не может и не будет менять код сторонних библиотек, подключенных как исходники. Хотя бы потому, что после такого изменения он не сможет обновлять их версию. Желательно, чтобы это делал именно линковщик при сборке версий.

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

Компилятор (без LTO) не видит, используются ли эти ссылки в других TU или нет.

Тренд на параллелизацию виден уже сейчас.

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

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

Вы наверное говорите про закон Амдала? Но он про другое.

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

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

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

А ведь распараллелить можно только независимые задачи

Мой коммент был как раз про это.
Вот, например, скалярное произведение вектора.
dot = a.x*b.x+a.y*b.y+a.z*b.z
a.x*b.x, a.y*b.y, a.z*b.z < — параллельная часть
x' + y' + z' < — последовательная часть
Да, и вы уже не сможете, например, a.x*b.x разбить на ещё более мелкие части. А так же не сможете на простаивающих ядрах запустить операции, которые зависят от результата вашего скалярного произведения.

Я это писал к тому, что распараллеливание это очень полезно, но не панацея — в зависимости от задачи вы сможете использовать от одного потока(например, вычисляя последовательность Фибоначчи) до N потоков(обрабатывая N атомарных задач) но увеличение до N*2 вам всё равно не поможет во втором случае, а в первом — вообще все дополнительные ядра будут простаивать.
в зависимости от задачи вы сможете использовать от одного потока(например, вычисляя последовательность Фибоначчи),… вообще все дополнительные ядра будут простаивать

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

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

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

Быть может, это подходит для высокопроизводительных вычислений, числодробилок итп. Ну, или для крайних случаев, когда Windows читает файлы десятки тысяч раз при нажатии ПКМ. Но что делать, если софт "подлагивает" немного, но в куче мест? Вот, какое-нибудь модное приложение, а оно написано на React + Electron. Сначала запускается отдельный инстанс Chrome, потом немного парсится JS, JIT и все такое. Пользователь нажал кнопочку — создался какой-то объект (аллокация памяти), потом он прошел через десяток всяких редьюсеров, строки сравниваюстя, объекты создаются, пересоздаются, аллоцируются, удаляются, сборщик мусора пыхтит… Вроде, каждое отдельное действие быстрое и оптимизированное (напр., сравние строк внутри сишных библиотек на SSE), но все вместе приводит к тому, что итоговый результат чуть-чуть лагает. И из тысяч таких кусочков складывается картина, что железо становится мощнее, а обычный "юзерский" софт, в тех моментах, которые не упираются в числодробилки, все лагает и лагает. Быть может, я заблуждаюсь, но мне кажется, что тут профилированием не решить.

Вот, какое-нибудь модное приложение, а оно написано на React + Electron.

Может быть, просто не надо лепить такие поделия? Когда мессенджер жрет гигабайт оперативной памяти — это не серьезно.

Skype сейчас — это именно такое приложение. Там даже интерфейс сейчас лагает.

Я вам больше скажу — VS Code тоже сделана на электроне, и… не тормозит и не лагает. Не в «модности» технологий дело.
Я вам больше скажу — VS Code тоже сделана на электроне, и… не тормозит и не лагает.

После того, как там переписали ядро на C++ — да, не лагает. До этого лагала.

Интерфейс (то самое что на электроне, ага) в VS Code не лагал примерно никогда.

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

Не тормозит, но какой-нибудь Sublime показывает более лучший пользовательский опыт.
Отзывчивость вестимо. Банально символы быстрее появляются, сам он быстрее запускается. Если что, сам на VSCode.
У меня символы появляются мгновенно (т.е. в пределах 50-100ms, доступных для восприятия) и запускается VSCode меньше секунды — в чём для меня будет «более лучшим» опыт, где символы появляются так же мгновенно, а среда запускается так же за пренебрежимо малое время?
Ну ХЗ, я вполне себе чувствовал разницу между отображением введённых символов, хотя тайминги сказать не смогу. А секунда на запуск это из кеша ОС, попробуйте после перезагрузки, желательно достаточно быстро, а то ОС по типу Windows сама всё в кеш утащит.
А секунда на запуск это из кеша ОС, попробуйте после перезагрузки, желательно достаточно быстро, а то ОС по типу Windows сама всё в кеш утащит.

Так а с точки зрения пользовательского опыта разница какая — закеширует ОС или просто быстро приложение откроется?

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

Ну это опять теоретические рассуждения. Для пользователя, на практике — разница какая?

Набросал наивную реализацию на JS.
Нода v12 на обычном среднем десктопе показала 1145 секунд.
Ну что тут сказать, питон огонь!

Последняя версия питона: 3.8.5.
Тестировалось на второй, поддержка которой в этом году заканчивается.

В оригинале статьи


The code takes about 7 hours on a modern computer to compute the matrix product, as shown by the first row (version 1) in Table 1, achieving only 0.0006% of the peak performance of the machine. (Incidentally, Python 3 requires about 9 hours for the same computation.)

Только 10е правило Гринспена (Любая достаточно сложная программа на Си или Фортране содержит плохо продуманную, плохо документированную, забагованную и медленную реализацию половины языка Common Lisp.) от этого никуда не девается — как только появляется ресурс для освобождения головы программиста от несущественных деталей, он на это используется.

Второй быстрее запускается, а вот насчёт скорости работы уже запущенной программы не уверен.
Если использовать jit — компиляцию (numba) то питон справляется за 808 секунд на колабе, а на стационарном компе (с распаралеливанием) — 232 с. Если использовать умножения матриц в numpy — 0.8 с.
NumPy — это всё-таки не Python, а очень даже C, прикидывающийся модулем Python. Хотя, конечно, именно так и надо делать подобные вещи на Python.

Так нельзя сравнивать. Может, у вас комп в 3 раза быстрее.
Надо на одном и том же железе запускать и вашу реализацию и питоновскую.


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

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

JS-код для тестирования
const N = 4096;
const N2 = N * N;

const A = new Float64Array(N2);
const B = new Float64Array(N2);
const C = new Float64Array(N2);

function fillRandom (M) {
	for (let i = 0, len = M.length; i < len; ++i) {
		M[i] = Math.random();
	}
}

fillRandom(A);
fillRandom(B);

function index (i, j) {
	return i * N + j;
}

console.time('mul');

for (let i = 0; i < N; ++i) {
	for (let j = 0; j < N; ++j) {
		for (let k = 0; k < N; ++k) {
			C[index(i, j)] += A[index(i, k)] * B[index(k, j)];
		}
	}
}

console.timeEnd('mul');

// печатаем несколько произвольных элементов,
// чтобы оптимизирующий компилятор не счёл код мертвым
console.log(C[N]);
console.log(C[Math.round(N2/5)]);
console.log(C[Math.round(N2/2)]);
Думаю это проблема решится, когда в сферу разработки ПО войдет ИИ. Только он способен проанализировать гигабайты мусора и выбросить все ненужное. А в будущем, думаю, люди будут писать код на каком-то абстрактном метаязыке, а умный компилятор будет генерить оптимальный код под нужную архитектуру с учетом всех ее особенностей.
Нынешний ИИ, насколько он мне видится, это тоже распиленная часть чего-то, что должно быть целым. Мы учим машину зрению, хотя понимаем, что сами полагаемся не только на него и не только на собственный опыт, когда опознаём картинку или другое окружение. Хотим такое же восприятие как у нас, только быстрее и надёжнее, сами при этом состоим из категоричных минусов, которые являются частью каких-либо плюсов.
ИИ может хорошо проверить и построить по указке, но лично я бы не стал ему доверять именно создание чего-либо, особенно сложных систем.
А в будущем, думаю, люди будут писать код на каком-то абстрактном метаязыке, а умный компилятор будет генерить оптимальный код под нужную архитектуру с учетом всех ее особенностей.

Ирония в том, что под ваше описание подходят Java, Python… Да и С++. Возможно, что и Fortran.

Только этим ИИ никто не будет пользоваться, как сейчас не пользуются лысой лошадью от PVS-Studio, которая за час найдёт почти в любом современном проекте больше багов, чем отдел тестирования за месяц.
Пробовал лысую лошадь, сильного преимущества перед clang-tidy (встроенного, к слову, в CLion) не нашел.

Аналогичные впечатления.


А coverity (который бесплатно доступен для опенсорса на scan.coverity.com) заруливает вообще их обоих вместе взятых.

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

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


Проблема тут в том что библиотека делается для пользователей, а не для разработчиков библиотеки, но 90% её пользователей (а то и все 99%) никогда не посмотрят внутрь, надеясь на то что разработчики сделали всё максимально эффективно, а не наглядно.

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


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


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


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

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


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


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

Либы конечно отдельная тема

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


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


А так да, если известно что какой-то кусок кода требует аж 10ms на выполнение (вместо возможных 10ns), но при этом ждёт ответа от сервиса который требует минимум 500ms на обработку — то это имеет мало смысла оптимизировать.

Я говорил именно о них. Простой пример — функцию подсчёта числа установленных бит в слове (32 бита) можно написать перебором битов (96 операций), а можно с помощью bit twiddling (12 операций

Либо даже в managed языке дёрнуть какой-нибудь BitOperations.PopCount, который под капотом вызывает нативную инструкцию для x86 / ARM и только в крайнем случае делает фоллбэк на софт-реализацию.

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

Более того, оптимальный код может быть даже проще и читабельнее. Несколько раз было, что наивный перебор занимает 100 строк навороченного рекурсивного кода, когда как решение через динамическое программирование кроме того, что быстрее, еще и занимает 20 строк кода+комментарии, где просто 2 вложенных цикла и простые вычисления.


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

В качестве примера исследователи приводят перемножение двух матриц 4096×4096. Они начали с реализации на Python как одного из самых популярных языков высокого уровня. Например, вот реализация в четыре строки на Python 2:

Какой-то наркоманский пример.

Если надо перемножать большие матрицы на Python — есть numpy. ( который для этого и придуман)
С тем же успехом можно было сравнивать трудоёмкость написания асинхронного веб сервера на nodeJS и ассемблере.

Я не к тому, что любое мобильное приложение надо писать на electron (привет Slack), просто инструмент — под задачу. ( хотя, очевидно, если все мобильные приложения будут писать на Rust люди с нормальной алгоритмической подготовкой — они будут сильно быстрее)

Если мне надо умножить две матрицы 1 — раз, чтобы получить результаты статистических расчетов по клиентам банка, возможно, самое простое —
import numpy
. Если надо их перемножать, чтобы в реальном времени управлять космическим кораблём — наверное — C\C++\Rust на процессоре с realtime-архитектурой.

А numpy не на C реализован? Получаетя нечестно, говорим про питон, а привлекаем C ему в помощь.
И сколько numpy займет гигабайт на SSD диске, а он ведь не резиновый. И если нужно в нескольких средах работать, и каждая, скажем, по 5 гигабайт. А еще — Keras и Tensorflow новых версий видите ли, не работают с некоторыми проектами, и нужно еще и прошлые версии тоже установить. И т.д.
Как раз речь и идет об ожирении программ...

1. На С, (как и питон)) ). Нечестно — сравнивать по производительности питон и С. (как нечестно сравнивать ассемблер и nodeJS по скорости написания асинхронного веб сервера)
2. Numpy в 5 ГБ — Вы слегка загнули. Поставил — 67 МБ ( python3.8 ).

Много ли какой-нибудь TODO list перемножает матриц? ( если не брать видеосистему ОС, где всё оптимизированно, кстати, это ещё и к честности сравнения, т.к. большинство приложений используют ещё и api ОС, которую написали на C)
Некоторым программам — нормально жиреть, некоторым нет. Как я сказал выше — инструмент под задачу.
Согласен, по 5 Гигабайт у меня среды занимают. Но их много. (Но, чтобы перемножить матрицы я не стал бы и 67 Мб тратить). Матрицы — это же для примера. Автору статьи нужно было нестандартную задачу привести, такую, для которой нет реализации в готовых библиотеках.
Это я о своем, пробовал машинное обучение на Windows, macOS, Linux. Работает нормально в Linux, в других системах модели получаются, но нерабочие.
А еще известно, что результаты научных расчетов на питоне могут различаться в зависимости от того, под какой ОС они получены. Из-за этого дискредитировано около сотни работ.
Вообще, float в стандартной компиляции — не гарантирует одинаковый результат ( он зависит от процессора), если нет специального флага, тем более — ML на GPU. ( Сомневаюсь, что если написать и обучить нейросеть на tensorflow-gpu + C++ — результат будет детерминированее, чем tensorflow-gpu + python.

stackoverflow.com/questions/7295861/enabling-strict-floating-point-mode-in-gcc
Сомневаюсь, что если написать и обучить нейросеть на tensorflow-gpu + C++ — результат будет детерминированее, чем tensorflow-gpu + python.

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

Можно фиксировать seed (и весьма желательно для пресловутой воспроизводимости).

Скорее тут не ОС виноваты, а авторы которые допустили неустойчивости в алгоритмах, проглядели деления на маленькие числа, и т.п…
NumPy занимает чуть меньше 23 МЕГАбайт. И это действительно серьёзная библиотека. А главное — универсальная. Потому и такая толстая. Остальное Python, который всё равно есть в системе, если речь о Linux.
Мечтать нужно не о мифической скорости (типа крайне редкой задачи перемножения матриц), а об эффективности.

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

Сколько читаю статьи на этом сайте — одни разговоры про моду, и почти ничего про реально нужное. Количество примитивно мыслящих убивает всё и вся, включая самих себя. Переходят на мобилки, на минимум движений, на минимум мыслей. И статьи вроде этой им кажутся «важными», «открывающими горизонты». Как же легко обманывать простодушных любителей мобилок…
А как этот разумный баланс измерить?
Если бы я был заказчиком и мне бы сказали, что скорость загрузки моего сайта можно уменьшить до 120 мс, в то время как у моих конкурентов скорость загрузки 240 мс, поэтому при прочих равных я буду привлекательнее для потребителя, но за такую оптимизацию я должен заплатить 100 тысяч у.е., то я сразу пойму, что за эффективность тут имеется в виду.
А если мне скажут «За X тысяч у.е. мы улучшим взятый на потолке показатель эффективности на X%», то я, конечно, понятливо покиваю, а потом обращусь к тем людям, которые предлагали мне до этого скорость.

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

Как правило, подобные вещи достигаются серверным рендерингом

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

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

Или оптимизировать скорость вывода синего экрана смерти и ждать 15 секунд ввода от пользователя, вместо…
крайне редкой задачи перемножения матриц

Разработчики игр и искусственного интеллекта с вами не согласятся.
Решил проверить пример на Java 10 и .NET Core 3.1.
Результаты:
Java 10: 68 sec.
.NET Core 3.1: 79 sec.

Даже не близко.

C#
            int[][] C = new int[4096][];
            int[][] A = new int[4096][];
            int[][] B = new int[4096][];

            Array.Fill(A, new int[4096]);
            Array.Fill(B, new int[4096]);
            Array.Fill(C, new int[4096]);

            var watch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < 4096; i++)
            for (int j = 0; j < 4096; j++)
            for (int k = 0; k < 4096; k++)
            {
                C[i][j] += A[i][k] * B[k][j];
            }

            watch.Stop();
            var elapsedMs = watch.ElapsedMilliseconds;
            Console.WriteLine($"Time: {elapsedMs / 1000} sec.");


Java
        int[][] C = new int[4096][];
        int[][] A = new int[4096][];
        int[][] B = new int[4096][];

        Arrays.fill(A, new int[4096]);
        Arrays.fill(B, new int[4096]);
        Arrays.fill(C, new int[4096]);

        long startTime = System.nanoTime();
        for (int i = 0; i < 4096; i++)
            for (int j = 0; j < 4096; j++)
                for (int k = 0; k < 4096; k++)
                {
                    C[i][j] += A[i][k] * B[k][j];
                }

        long endTime = System.nanoTime();
        long timeElapsed = endTime - startTime;
        System.out.println("Execution time in sec : " +
                timeElapsed / 1000000 / 1000);

А как работает fill в Java и C#? А то в питоне такой код единожды создаст массив из 4096 элементов и скопирует ссылку на него 4096 раз, но это не то, что надо.

Создается новый объект.

IL код
IL_0000: ldc.i4 4096 // 0x00001000
IL_0005: newarr int32[]
IL_000a: stloc.0 // C


IL_0000: Pushes a supplied value of type int32 onto the evaluation stack as an int32
IL_0005: Pushes an object reference to a new zero-based, one-dimensional array whose elements are of a specific type onto the evaluation stack.
IL_000a: Pops the current value from the top of the evaluation stack and stores it in a the local variable list at a specified index.

Вы уверены?
Из вашего листинга:


// [13 13 — 13 42]
IL_0021: ldloc.1 // A
IL_0022: ldc.i4 4096 // 0x00001000
IL_0027: newarr [System.Runtime]System.Int32
IL_002c: call void [System.Runtime]System.Array::Fill<int32[]>(!!0/int32[]/[], !!0/int32[]/)


Вот здесь вызывается Fill. Новый массив точно копируется целиком, а не лишь ссылка на него? В java переменные с типом "массив" — это ссылки на объекты "массив". В C# точно по-другому?


Посмотрите, не одинаковы ли строки в этих массивах.

В C# массивы это ссылочный тип. Однако тут ссылки на разные объекты. Метод Fill проходится по массиву и присваивает значения.
Вообще, присваивание ссылки выглядит так:
// B = A;
IL_0033: ldloc.1 // A
IL_0034: stloc.2 // B


Сам код Array.Fill из библиотеки
        public static void Fill<T>(T[] array, T value)
        {
            if (array == null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
            }

            for (int i = 0; i < array.Length; i++)
            {
                array[i] = value;
            }
        }

        public static void Fill<T>(T[] array, T value, int startIndex, int count)
        {
            if (array == null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
            }

            if (startIndex < 0 || startIndex > array.Length)
            {
                ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index();
            }

            if (count < 0 || startIndex > array.Length - count)
            {
                ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
            }

            for (int i = startIndex; i < startIndex + count; i++)
            {
                array[i] = value;
            }
        }


github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Array.cs


Вам не кажется некорректным вот это поведение?
https://ideone.com/DDV9EC


Код и вывод
using System;

public class Test
{

    public static void Main()
    {
        // your code goes here
        int[][] a = new int[5][];
        Array.Fill(a,new int[5]);
        printMatrix(a);
        a[1][2]=3;
        printMatrix(a);
    }

    private static void printMatrix(int[][]arr)
    {
        for (int i = 0; i < arr.GetLength(0); i++)
        {
            for (int j = 0; j < arr[i].GetLength(0); j++)
            {
                Console.Write(arr[i][j]+" ");
            }
            Console.WriteLine();
        }
        Console.WriteLine();
    }
}

Вывод:


0 0 0 0 0 
0 0 0 0 0 
0 0 0 0 0 
0 0 0 0 0 
0 0 0 0 0 

0 0 3 0 0 
0 0 3 0 0 
0 0 3 0 0 
0 0 3 0 0 
0 0 3 0 0 

Это очень странное создание матриц.

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

Вообще-то многомерный массив на java выделяется как


double[][] a = new double[4096][4096];

а не


int[][] A = new int[4096][];
Arrays.fill(A, new int[4096]);

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

На double всё драматичнее. С массивами предварительно заполненными случайными числами время:
Java: 1358 sec.
C#: 325 sec.

а вы уверены, что компилятор не заоптимизировал циклы? А все указанное время он занимался выделением памяти и ее очисткой?

Не ленитесь выводить результат…
А все указанное время он занимался выделением памяти и ее очисткой?

Ну, уж выделение-то памяти для каких-то 12к объектов всяко быстрее 90 секунд работает. Не говоря уже о том, что там выделяются всего 6 объектов по 16-32кБ.
Но всё равно заведомо нулевые числа перемножать и суммировать — идея так себе.

не ну если все честно компилятор делает — то он выделяет примерно 400 МБ памяти. для int_16t

Порядка 384МБ для double (по 8 байт). 3*(4096*4096*8+ 4096*8) "нетто". "Всего" 12тыс. объектов. У меня на это уходит примерно секунда (если замерять визуально).


Это немного. Тормоза, как я помню, начинаются от нескольких млн. объектов.

Здесь основная проблема не в этом — на весь код всего 6 операторов new() — т.е. всего 6 объектов. Это неправильная матрица.

Не большой знаток IL, но вроде нет.

IL
.method private hidebysig static void
Main(
string[] args
) cil managed
{
.entrypoint
.maxstack 5
.locals init (
[0] int32[][] C,
[1] int32[][] A,
[2] int32[][] B,
[3] class [System.Runtime.Extensions]System.Diagnostics.Stopwatch watch,
[4] int64 elapsedMs,
[5] int32 i,
[6] int32 j,
[7] int32 k
)

// [9 13 - 9 41]
IL_0000: ldc.i4 4096 // 0x00001000
IL_0005: newarr int32[]
IL_000a: stloc.0 // C

// [10 13 - 10 41]
IL_000b: ldc.i4 4096 // 0x00001000
IL_0010: newarr int32[]
IL_0015: stloc.1 // A

// [11 13 - 11 41]
IL_0016: ldc.i4 4096 // 0x00001000
IL_001b: newarr int32[]
IL_0020: stloc.2 // B

// [13 13 - 13 42]
IL_0021: ldloc.1 // A
IL_0022: ldc.i4 4096 // 0x00001000
IL_0027: newarr [System.Runtime]System.Int32
IL_002c: call void [System.Runtime]System.Array::Fill<int32[]>(!!0/*int32[]*/[], !!0/*int32[]*/)

// [14 13 - 14 42]
IL_0031: ldloc.2 // B
IL_0032: ldc.i4 4096 // 0x00001000
IL_0037: newarr [System.Runtime]System.Int32
IL_003c: call void [System.Runtime]System.Array::Fill<int32[]>(!!0/*int32[]*/[], !!0/*int32[]*/)

// [15 13 - 15 42]
IL_0041: ldloc.0 // C
IL_0042: ldc.i4 4096 // 0x00001000
IL_0047: newarr [System.Runtime]System.Int32
IL_004c: call void [System.Runtime]System.Array::Fill<int32[]>(!!0/*int32[]*/[], !!0/*int32[]*/)

// [17 13 - 17 65]
IL_0051: call class [System.Runtime.Extensions]System.Diagnostics.Stopwatch [System.Runtime.Extensions]System.Diagnostics.Stopwatch::StartNew()
IL_0056: stloc.3 // watch

// [18 18 - 18 27]
IL_0057: ldc.i4.0
IL_0058: stloc.s i

IL_005a: br.s IL_00a8
// start of loop, entry point: IL_00a8

// [19 18 - 19 27]
IL_005c: ldc.i4.0
IL_005d: stloc.s j

IL_005f: br.s IL_0099
// start of loop, entry point: IL_0099

// [20 18 - 20 27]
IL_0061: ldc.i4.0
IL_0062: stloc.s k

IL_0064: br.s IL_008a
// start of loop, entry point: IL_008a

// [22 17 - 22 46]
IL_0066: ldloc.0 // C
IL_0067: ldloc.s i
IL_0069: ldelem.ref
IL_006a: ldloc.s j
IL_006c: ldelema [System.Runtime]System.Int32
IL_0071: dup
IL_0072: ldind.i4
IL_0073: ldloc.1 // A
IL_0074: ldloc.s i
IL_0076: ldelem.ref
IL_0077: ldloc.s k
IL_0079: ldelem.i4
IL_007a: ldloc.2 // B
IL_007b: ldloc.s k
IL_007d: ldelem.ref
IL_007e: ldloc.s j
IL_0080: ldelem.i4
IL_0081: mul
IL_0082: add
IL_0083: stind.i4

// [20 39 - 20 42]
IL_0084: ldloc.s k
IL_0086: ldc.i4.1
IL_0087: add
IL_0088: stloc.s k

// [20 29 - 20 37]
IL_008a: ldloc.s k
IL_008c: ldc.i4 4096 // 0x00001000
IL_0091: blt.s IL_0066
// end of loop

// [19 39 - 19 42]
IL_0093: ldloc.s j
IL_0095: ldc.i4.1
IL_0096: add
IL_0097: stloc.s j

// [19 29 - 19 37]
IL_0099: ldloc.s j
IL_009b: ldc.i4 4096 // 0x00001000
IL_00a0: blt.s IL_0061
// end of loop

// [18 39 - 18 42]
IL_00a2: ldloc.s i
IL_00a4: ldc.i4.1
IL_00a5: add
IL_00a6: stloc.s i

// [18 29 - 18 37]
IL_00a8: ldloc.s i
IL_00aa: ldc.i4 4096 // 0x00001000
IL_00af: blt.s IL_005c
// end of loop

// [25 13 - 25 26]
IL_00b1: ldloc.3 // watch
IL_00b2: callvirt instance void [System.Runtime.Extensions]System.Diagnostics.Stopwatch::Stop()

// [26 13 - 26 55]
IL_00b7: ldloc.3 // watch
IL_00b8: callvirt instance int64 [System.Runtime.Extensions]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_00bd: stloc.s elapsedMs

// [27 13 - 27 65]
IL_00bf: ldstr "Time: {0} sec."
IL_00c4: ldloc.s elapsedMs
IL_00c6: ldc.i4 1000 // 0x000003e8
IL_00cb: conv.i8
IL_00cc: div
IL_00cd: box [System.Runtime]System.Int64
IL_00d2: call string [System.Runtime]System.String::Format(string, object)
IL_00d7: call void [System.Console]System.Console::WriteLine(string)

// [28 9 - 28 10]
IL_00dc: ret

} // end of method Program::Main


C какими аргументами запускалась компилация для dotnet и java?
Эх, нравятся мне эти апелляции к высказыванию Вирта. В предыдущий раз встретил это в выступлении Джо Армстронга, который упоминал эту самую статью (второй абзац). Но блин, это же просто вступление для рекламы написанного ими Оберона (кстати, а где он?), ничего более. Звучит красиво, не спорю, конечно, многим бы хотелось чтобы и сейчас софт требовал считанные килобайты, но требования пользователей то тоже растут и применения для автоматизации находятся самые разные, также требующие ещё большей функциональности и, соответственно, ресурсов на реализацию этой самой функциональности, не считая чуть более косвенные вещи вроде стоимости и времени разработки.
А почему никто не говорит, что надо улучшать компиляторы? Если работа высокоуровневого программиста — перемножать матрицы в 4 строки и двигаться дальше, то почему он должен каждый раз нырять в низкоуровневые дебри? Описанный код вполне может быть ускорен, распараллелен и векторизован умным анализатором, JIT, AI компилятором и чем-либо еще. Если мы хотим сохранить разделение на уровни абстракции, а не смешивать все слои в попытках оптимизировать все перед релизом, то сами инструменты должны эволюционировать каждый год. Но никто из разработчиков ОС/языков/компиляторов не хвастается, что с новым апдейтом Windows или MacOS стала быстрее запускать программы, а код для Python 3.8 с апдейтом до 3.9 станет автоматом подключать numpy, прогонять нужный кусок на автоматических тестах и в итоге его заменять на быструю версию. Даже в случае явной эволюции .Net Framework -> .Net Core нельзя запустить старый код и получить прирост производительности за счет RyuJIT и других вкусностей.
Наверное, потому, что компиляторы и так улучшаются по мере возможности. А вот про оптимизацию софта такое сказать нельзя.
Даже в случае явной эволюции .Net Framework -> .Net Core нельзя запустить старый код и получить прирост производительности за счет RyuJIT и других вкусностей.

Вообще-то можно и получается: 2.0, 2.1, 3.0, 5.

Скажем так, приличную часть кода можно из .Net Framework «портировать» в .Net Standard и спокойно запускатъ как в .Net Framework, так и в .Net Core. Причём опять же в большинстве случаев даже особо напрягаться не надо будет.

Но именно .Net Framework библиотеки в .Net Core работать не будут.
Описанный код вполне может быть ускорен, распараллелен и векторизован умным анализатором, JIT, AI компилятором и чем-либо еще.

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


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


Максимум возможностей оптимизатора — это эффективно реализовать то что вы написали на C/C++/Rust/etc в машинном коде, с учётом особенной процессора и архитектуры, по дороге выкинув лишнее (CSE, упрощение выражений, etc).


Основная же проблема — это чрезмерное обобщение и абстрагирование, которым буквально пронизаны большинство современных фреймворков и библиотек — классический случай "Jack of all trades, master of none".


Да, разработчикам проще и быстрее, но — ценой ресурсов, причём не их ресурсов.

Никакой оптимизатор не знает с какой целью создан код, а без знания цели можно только оптимизировать очевидные вещи, которые хоть и помогают иногда, но всё же не далеко не всегда.
Сейчас DLSS умеет достраивать изображение в игре в реалтайме, повышая разрешение почти в два раза. Неужели рано или поздно оптимизатор не сможет понять, что конкретно в этом примере — умножение матриц, которое можно сделать с помощью AVX?
Аналогично с распараллеливанием — OpenMP и GCC уже два десятка лет обрабатывают #pragma omp parallel for, но аж в трех языках программирования и только с прямым указанием, что этот цикл можно параллелить.
Неужели рано или поздно оптимизатор не сможет понять, что конкретно в этом примере — умножение матриц, которое можно сделать с помощью AVX?

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


Разумней подходить с другой стороны — на входе должна быть спецификация (что ожидаем на входе и выходе, ограничения по ресурсам etc), а жутко вумный компилятор подберет самые эффективные алгоритмы для реализации задачи на конкретной платформе, что-то типа "Ok Google, сделай-ка для меня приложение которое...".


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

разве что он (AI) может проверить идентичность всех возможных входов всем возможным выходам исходного алгоритма (привет квантовым компьютерам).

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

Разве нельзя в компиляторе сделать прогон хотя бы циклов с попыткой их оптимизации?
В цикле на миллион проходов выполняется n++. Может ли компилятор пропустить его в пользу n=1000000?

Это как раз делается (иногда), но опять-таки, компилятор может лишь "догадываться" что важно именно значение "n" на выходе, а не побочные эффекты (время выполнения, нагрузка на кэш/память etc).


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

а не побочные эффекты (время выполнения, нагрузка на кэш/память etc).

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

Зря недооцениваете ИИ… К примеру, написал кто-то js либу, и заюзали ее 100500 проектов на гитхабе. Плюс, если спарсить код топ 1000000 сайтов мира, то там тоже на 10% она встретится.
То есть, ии может
1) проанализировать код этих сайтов и гитхаба
2) запустить код с гх и сайтов и отпрофайлить его со всех сторон
3) запустить ещё несколько миллиардов синтетических прогонов
4) создать оптимальный код для самых частых ветвей исполнения кода.
5) вывести правила, при которых оптимизацию запускать можно (чтобы не запустить оптимизацию на коде, который появится потом и будет использовать какой-то нестандартный грязный хак).


Может ли человек сделать ч