Pull to refresh

Comments 182

Не стоит забывать в таком ключе о objective-c эта реализация С c ООП гораздо ближе к оригиналу чем C++.
Это смотря, что считать «оригиналом» ООП: Симулу или Смолтолк.
В данном контексте под «Оригиналом» имелся ввиду язык Си, а не ООП.
Objective-C ближе к Си, чем C++.
Objective-C не просто ближе к С, а имеет с ним полную обратную совместимость (по крайней мере это декларируется) т.е. любой кусок кода на С можно просто вставить в Obj-C и он заведется (если конечно не брать нечто уж очень древнее типа void f(){return 1}, но такое и не все си компиляторы съедят), а вот с C++ так сделать получится не всегда
UFO just landed and posted this here
А почему это «кроме более жесткого контроля типов при «ручном» выделении памяти.»? Как-то странно получается, я говорю что код на С не всегда соберется C++ компилятором, но собирается при этом obj-c, а мне в ответ «ну покажи код с несовместимой фичей, только чур несовместимую фичу не используй», поэтому проигнорирую это условие и приведу пример кода как раз с ним (из книжки Thinking in C++, хотя искал я там другой пример — было еще что-то с массивами):
int i = 10;
void* vp = &i;
int* ip = vp;
Собирается в c и obj-c, но не в c++
Разная семантика inline. Программа из двух файлов. test1.c:
inline void f() {}
int main() { f(); return 0; }

test2.c:
void f() {}

Валидно в C, ошибка линковки в C++. Если второй файл убрать, то валидно в C++, ошибка линковки в C (точнее, программа не соответствует стандарту, и может быть ошибка линковки в зависимости от настроения компилятора — будет с gcc -O0, не будет с gcc -O2).
На Википедии есть пример с sizeof('x').
Молчаливое преобразование к/из void*, как выше уже привели пример, отнюдь не ограничивается «ручным» выделением памяти.
Ну а если смотреть на новые стандарты, то они вообще по-разному развиваются: _Bool вместо bool, float _Complex, всякие
#define mysqrt(X) _Generic((X), long double: sqrtl, default: sqrt, float: sqrtf)

UFO just landed and posted this here

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

UFO just landed and posted this here
По идее там сложного нет, если знать как оно внутри устроено
Но с указателями вроде проще: это адрес на кусок памяти.


На низком уровне это может быть очевидно, но код с указателями на указатели, с указателями на функции и т. п. нечитабельный и сложный для разбора и понимания человеком, потому что это не просто "адрес на адрес на кусок памяти" — это то, над чем построены структуры данных, то над чем работают алгоритмы, связанные между собой в единую систему, работу которой надо понимать в целом. Это то же самое, что сказать про сложный часовой механизм "это просто шестерёнки, они просто крутятся".
UFO just landed and posted this here
Короче, хватит натягивать «я ниасилил» на весь остальной мир.


Не знаю, как в вашем мире, но в остальном мире общепризнанно, что указатели являются опасным инструментом, поэтому в современных языках их использование сведено к минимуму и/или сделано более безопасным. Все эти умные люди, которые разработали современные языки тоже "ниасилили" указатели? Не знаю как вам, а мне смешно.
UFO just landed and posted this here
опасная бритва, спички — тоже опасные. Общепризнанно.

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

То же самое и с языками программирования. Подавляющее большинство задач с успехом решается «безопасными» языками без использования возможностей C. И использовать C для таких задач при наличии безопасных аналогов достаточно глупо. А вот в тех местах, где достойных аналогов нет надо, несомненно, пользоваться C, несмотря на проблемы с «отстреленными ногами».
Извините, но не общепризнано ни разу. Тем более что явно или неявно, но они используются везде, и при написании более-менее сложного приложения все равно с концепцией указателя нужно знакомиться и считаться.
Что сложного в понятии «указатель на указатель»? Адрес ячейки памяти, в которой лежит адрес какого-то другого объекта. Гиперссылка на страничку в интернете, на которой написана другая гиперссылка — уже на интересную статью на Хабре.
Я вот тут изучаю PHP (после 15 лет C/C++), это боже мой — как можно жить, не объявляя переменные? Ну ладно слабая типизация, фиг с ней. Но без объявлений… любая опечатка — и у тебя новая переменная, а ты даже не догадываешься. Вот где сложность! А Си — простой язык.
Что сложного в понятии «указатель на указатель»?


Да ничего сложного нет в низкоуровневом понятии указателя! Вы серьёзно сейчас пытаетесь мне рассказать, что такое указатель на указатель?

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

Легко понять, что такое указатель на указатель, да хоть указатель на указатель на… на указатель. Проблема в том, что чем больше указателей содержит код, особенно если есть арифметика указателей, то тем больше вероятность допустить где-то ошибку: обратиться по неправильному указателю, привести к неправильному типу, обратиться к NULL, к не обнуленному указателю.


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


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

UFO just landed and posted this here
Проблемы с указателями, как и с целостным кодированием, всегда по недомыслию. И в плохом порядке кодирования. Если писать код строго придерживаясь простых правил, вытекающих друг из друга, то проблем исчезающе мало. Каких правил? Разнообразных контрактов использования и владения структурами данных. Они как бы неявные, но если приложить мысль и абстрактное мышление, то выводятся на счёт раз и далее используются на-автомате. Только поняв эти особенности можно и следует писать код. И это так для любого языка программирования.

Если кратко — следует ПОЛНОЦЕННО соблюдать ОО т.е. область определения аргументов и собственных данных при кодировании любой функции (в т.ч. с учётом контракта) и блоков кода, а также постоянно учитывать ОДЗ т.е. область допустимых значений данных после вычислений и преобразований. Указатель лишь частный случай. Если «программист» не способен полноценно соблюдать ОО и учитывать ОДЗ, но какой язык ему не дай, будет лишь обезьяна с гранатой.
Rust как раз именно это проверяет сам. Другое дело что обезьяна просто плюнет и выберет другую гранату после 120-ой ошибки компиляции.
UFO just landed and posted this here
Я бы согласился, но тогда получится, что я 90% кода пишу на несуществующем языке.
UFO just landed and posted this here
Как язык он ещё не сформировался.


И в чём это проявляется?
UFO just landed and posted this here
Скорее всего что то ещё будет добавляться, что то уйдёт.


Дык, и в С++ кое-что депрекейтяд и новое добавляют. Обратную совместимость после 1.0 пообещали ведь.

Относительно либ: было бы странно, если у языка с сорокалетней историей их было бы меньше. Ну то есть, да, это "проблема", но очевидная. И тут уже надо смотреть есть ли нужные либы или нет и насколько затратно писать если что. Даже при таких условиях в дропбоксе кое-что на расте решили написать.
Rust делает больше, чем C/C+, позволяя автоматизировать соблюдение некоторых контрактов. Не идеально, конечно, но вполне удобно. Только вот чего такого сложного в самостоятельном понимании и соблюдении контрактов? По опыту работы с программистами на C++ считаю, что они либо забивают на качество и продуманность своего кода, либо недопонимают что должен делать или делает их код. «Ленятся лениться.» Пропускают состояния, путаются в вариантах, недообрабатывают частные случаи. И таких много, очень много. Корректный код на любом языке умеют писать, похоже, считанные единицы, которых действительно можно назвать программистами. Остальные постоянно фейлят. Почему так?
Ключевое слово опыт. Его надо где-то набрать. И если набирать его на том же С++, то 1) подопытный становится обезьяной с гранатой, 2) работу статического анализатора берет на себя reviewer(превращающийся в няньку), 3) учиться, наступая на грабли и отлавливая потом баги долго и выматывает(это если нет хорошего старшего). Может это конечно лучше запоминается в таком виде, но иметь альтернативу как минимум не плохо.

А после всего этого — да, это не сложно. И всё же глупые ошибки делают с той или иной периодичностью все.
Все три пункта зависят от включенности мозга в работу. Не просто памяти, логики и интуиции, но также и критического мышления. Есть такой подход — осторожное разумное исследование неизвестной территории с опорой на самые надёжные методы и постоянной перепроверкой их надёжности. Требует адаптировать ассоциативную память под как можно более быстрые и полные (с учётом деталей) выборки и синтез вариантов решения для каждого случая. Делается выборка/синтез из N разносторонних вариантов, далее мысленно проверяется насколько каждый подходит для решения. Отбраковываются неподходящие, адаптируются подходящие, выбирается лучший. Далее ищется M подтверждений что этот вариант действительно лучший из всех N. Замечу, что именно разносторонний N даёт качественный эффект. Постоянно вижу как программисты используют 1 вариант который привычен или первым пришёл в голову. Но ведь задача программиста не сделать «как-нибудь», а сделать в т.ч. качественно т.е. способ решения тоже имеет значение.
Только вот чего такого сложного в самостоятельном понимании и соблюдении контрактов?


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

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

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

Ну ещё очень хочется нормальной поддержки сериализации =)

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


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


Да, многие штуки в плюсы можно добавить в том или ином виде. Но тут у раста есть преимущество (которое является и недостатком, ага) в виде отсутствия легаси. Фичи проще прикручивать в нормальном виде, а не думать как их добавить так, чтобы ничего не поломать. Если в С++ завезут паттерн матчинг или нормальные макросы и модули, то это всё равно вынуждено будет жить параллельно с тем, что уже есть.


А с сериализацией что не так?

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

for ( auto field : AnyType:::Fields ) // AnyType:::Fields.size(), etc.
{
// field.offset, field.size, field.type, field.ToString(obj), field.FromString(obj)
}

for ( auto method : AnyType:::Methods ) // ...
{
// method.Call(obj), method.ToString(), method.FromString()
}

DynType:::BuildMetaData;

void foo()
{
BaseType:::DynamicDerivedType derivedType( "DynType" );
// derivedType.Fields(), derivedType.Methods()
}


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

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


Вы преувеличиваете, существует множество вещей, на порядок более сложных чем какие-то указатели. Я уверен, ни у кого, кто пишет свои программы на Си, мозги в кашу от указателей не превращаются, но это не значит, что те люди не делают ошибки в своих программах. Мозги, при написании программы, должны думать не об указателях, а о предметной области. А, во-вторых, тот, кто, скажем, начинает писать на том же C++, с удовольствием заменяет все свои сырые указатели на умные и прекрасно себя чувствует. Минимальный оверхед при многих плюсах. И ведь гораздо приятнее просто держать свои мозги в тонусе, не давая им превратиться в кашу, думая об сложных и интересных проблемах и новых подходах к решению этих проблем, чем о том, что снова надо вылавливать эти странные баги, которые появились, возможно, из-за тех самых звёздочек. :)
«Вы преувеличиваете, существует множество вещей, на порядок более сложных чем какие-то указатели.»

Именно об этом и речь, «какие-то указатели» читаются/пишутся на автомате, и только там где они уместны, поэтому проблем из-за звездочек не бывает — это всего-лишь синтаксис, который нужно знать. Звездочки никак не отвлекают от понимания кода, и проблем предметной области. Основная причина ошибки на любом языке — это невнимательность. И самонаводящиеся ракеты эту невнимательность развивают, превращая элементарные ** для некоторых в непонятную/опасную кашу.
Тут, наверное, вопрос больше в перспективности такого подхода, чем в споре «хорошо или плохо применять сырые указатели».
Начинающий на С++, использующий умные указатели, несомненно, освободит мозг для лучшего обдумывания функциональности, но с набором квалификации всё равно возникнет вопрос: изучать ли сырые указатели и повышать производительность своего софта (например, когда 95% операций в нагруженной программе проводятся с динамическими объектами, даже минимальный оверхед на интеллектуальные обёртки даёт себя знать) или же остаться в уютном мире относительно медленного ПО и не думать о том, как программа работает на низком уровне.

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


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

П.С.: Кстати, если уж тут пошёл батл Java vs C (достаточно странный батл, на мой взгляд) никто не мешает делать обвязки API через JNI и получить с одной стороны красоты Java в высокоуровневой организации архитектуры, и, с другой стороны, высокую эффективность программы в реализациях подключаемых С-шных библиотек.
UFO just landed and posted this here
В случае Rust на оружие навешано кучу проверок типа есть ли патрон, а куда ты собрался стрелять, а как правильно ты прицеливаешься, а как ты держишь оружие, а одел ли ты шапку, а покушал ли перед выстрелом, и все такое. Вы все еще думаете, что указатели это страшно и сложно? ))
И если ты не покушал, то он (Rust) не кормит тебя, а дает тебе возможность самому решить как это лучше сделать. В итоге и сыт, и мозг в тонусе, и ноги целы!.. Это ли не счастье? ))
А не дает.
На каждую подзадачу у Раста есть ровно один способ решения, соответствующий его парадигме разработки. Это не плохо и дисциплинирует, с одной стороны, но с другой — резко повышает порог вхождения в язык.

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


Ну и про "ровно один способ" относительно раста тоже можно поспорить. (:

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

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


Насчёт каста к трейту не совсем понял в чём проблема:


trait T {}
struct S {}
impl T for S {}

let s = S{};
let t: &T = &s;

Ну и то, что новый язык предлагает другие абстракции и старые не ложатся напрямую — это нормально, как по мне. Вон в недавней рассылке проскакивала статья с мыслью, что если "условный джавист" возьмётся за раст, то будет ожидать от языка сложностей и они будут. А если возьмётся "условный плюсовик", то будет думать, что всё должно быть просто: ведь со всякими низкоуровневыми нюансами он знаком. Но нет, сложности тоже будут — ведь язык другой.
Не уверен, что полностью согласен, но что-то в этом есть. Лично мне раст не показался особо сложным, если не брать всякие навороты из растономикона. На "базовом уровне" всё нормально. "Проблемы" были разве что с "принятием" — в том смысле, что "непривычно".

Непривычно — да, совершенно согласен. Даже не скажу, что плохо или не очевидно, просто неожиданно.
Про касты я имел в виду вот такое
https://stackoverflow.com/questions/26126683/how-to-match-trait-implementors

Ну это каст из трейта. (:


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


Кстати, если мы точно знаем, что в трейте лежит конкретный объект, то можно извернуться через raw::TraitObject, но надо понимать, что это не dynamic_cast и если мы в своём предположении ошиблись, то будет плохо.

Если реализация трейта скрыта, то не знаем (это мой случай). Есть еще transmute, но он небезопасный и тоже работает только с объектами.

Дык, если реализация скрыта, то тем более странно приводить к ней, разве нет?

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

С другой стороны, для некоторых задач такой язык, как Си — более удобный,
потому что позволяет создавать быстрые, короткие и эффективные программы.
UFO just landed and posted this here
Насколько я понимаю (сам плюсовик, не имевший опыта с embded, могу ошибаться), Java такая популярная за счёт простоты языка и широты спектра устройств, на которых умеет запускаться — от детской машинки до шагающих экскаваторов. Тут можно сказать, что С так же умеет компилироваться под те же миллионы разных устройств… Но в С есть указатели на указатели (с), которые для простой задачи типа «написать гую для банкомата» (на эффективность кода пофигу — мы на банкомате не будем Навье-Стокса решать) действительно могут сделать написание кода необоснованно сложным.
Java такая популярная за счёт PR. В своё время её продвигал Sun, #1 на рынке UNIX-систем. Соответственно весь enterprise, который не остался на мэйнфреймах подсел на Java. На Windows, правда, Java «не пошла», там её брат-близнец C# рулит.

Качество самого языка в таких вопросах — дело десятое. Всё решают дяди, которые сами, в общем, ничего не пишут — но дальше начинает раскручиваться спираль: раз у нас упор на Enterprise, то это значит что у нас работают 100500 «индусов», которые ни черта не понимают в программировании (так как у нас ограничен не общий фонд заработной платы, а размер «ставки», то нанять 100 крутых профессионалов мы не можем, а 10000 индусов — как раз можем), дальше под это начинают затачиваться все инструменты и так — пока язык не окажется малопригоден для использования кем-либо, кроме вот этой вот самой толпы «индусов».

Ещё есть, немного сбоку, Android: там, с одной стороны, есть Java (чтобы, опять-таки, привлечь «индусов»), а с другой — все популярные программы написаны не на Java (в них нативные библиотеки, где, как раз и происходит всё самое интересное). Так и живём.
нанять 100 крутых профессионалов мы не можем, а 10000 индусов — как раз можем

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


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

Дисклеймер об индусах
Дабы не кормить стереотип, подчеркну: тут речь идёт не о всяких индусах, а о низкоквалифицированных работниках из Индии. Частенько индусы — весьма башковитые прогеры. Однажды, например, я проходил course era по openGL… Курс вёл индус. И это не единственный пример, когда обитатели полуострова индостан показывали класс.
Дело вовсе не в PR, а именно в том, что на Java могут писать эти самые десять тысяч индусов.
Дело именно в PR. Вначале Java была «продана» как что-то, на чём можно писать большие проекты — но из этого не вышло ровным счётом ни-че-го: почитайте на досуге о судьбе HotJava, Corel Office for Java, Network Computer… Про Java-applet'ы, в конце-концов, вспомните…

Однако признаться в том, что деньги были вбуханы в чушь собачью было страшно и потихоньку-полегоньку Java превратилась-таки «в суп из топора». Лет примерно через 10 начали появляться большие непровальные проекты типа Eclipse (переписанный на Java IBM Visual Age), IntelliJ и прочих, со временем для «индусов» всё и приспособили.
Ну, любой язык переживает период становления, когда проекты на нём не взлетают. Приведённые примеры неудач отображают, скорее, не проблемы языка как такового, а проблемы его экосистемы, которая рождалась в муках.

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

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

Перефразируя известную фразу — если бы Java не было, её стоило бы придумать. Поэтому-то я и считаю, что (в меру моего понимания истории языка) PR послужил не главным фактором триумфа Java и приведённые примеры ничего не доказывают.
Если я правильно понимаю (если нет — интересно было бы глянуть на тогдашних конкурентов), Java оказался языком, создатели которого успели первыми реализовать описанные полезные и востребованные рынком особенности.
Ага, конечно. А как же P-код, Smalltalk, Lisp-машины и прочее?
И именно за счёт этого Java должна была «выстрелить» рано или поздно.
В том-то и дело, что в Java не было никаких «прорывов». Java была первым языком, создателям которого которым удалось убедить крупную компанию вбухать деньги в технологию, которая, в конечном итоге, эту самую фирму и похоронила — это да. А чисто технологически — всё это перепевки вещей, которые были придуманы задолго до того, как Sun решил покончить жизнь самоубийством.
В самом крайнем случае появился бы другой язык, который реализовывал бы описанные две киллер-фичи Java, но категорически лучше чем она.
Этих языков — как грязи. И до Java и после. Lisp — это даже не 60е, а 50е годы (правда самый конец), CLOS и Smalltalk80е. Но только одному из конкурентов посчастливилось получить сравнимую поддержку — C# (кстати забавно что результат сравним: Microsoft оказался покрепче, чем Sun, к банкротству увлечения архитектурной астронавтикой не привели, но мобильный рынок в результате был успешно профукан).
Перефразируя известную фразу — если бы Java не было, её стоило бы придумать.
Зачем? Чтобы превратить VisualAge в Eclipse?
Поэтому-то я и считаю, что (в меру моего понимания истории языка) PR послужил не главным фактором триумфа Java и приведённые примеры ничего не доказывают.
Они бы «ничего не доказывали бы», если был бы хоть один «выстреливший» проект, рождённый в первые 2-3 года после появления Java, когда все ВУЗы переходили на неё (кто с Lisp'а, а кто и со Smalltalk'а). Но нет ни-че-го. Совсем ничего. Все успешные проекты на Java — это либо проекты переведённые на Java «декретом» (с сегодняшнего дня мы вместо Cobol'а/Smalltalk'а используем Java), либо разработки середины нулевых годов (когда Java было уже много лет и в её развитие вбухали много миллионов долларов). Много из этих проектов вы знаете? А ведь это — 1999й год, когда Java уже преподавалась в ВУЗах и когда уже несколько лет объяснялось, что «Java — это будущее»!

Согласитесь — несколько странная ситуация для языка, который якобы «выехал за счёт своих качеств», а не за счёт PR?
Ух, спасибо за столь подробный ответ! И если Lisp ещё не совсем проходит как «С для быдлокода» (функциональное программирование мне всегда казалось изотериков), то Smalltalk… Виртуальная машина, сборка мусора… Не знал всего этого. Спасибо. Теперь убедили.

П.С.: Кстати, материал выглядит как синопсис остросюжетной статьи о становлении императора Java в мире программирования. Если бы не опасность породить холливар — с удовольствием почитал бы «полный метр» по мотивам.

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

Ну, я имел в виду Lisp тех бурных времён творения современного прогерского мира, когда он мог выступать конкурентом Java на её поле — безопасный по работе с памятью, кроссплатформенный и простой для понимания язык… Или в Lisp с самого начала был задуман как мультипарадигменный язык, поддерживающий и функциональную и объектноориентированную парадигмы?

Могу что-то упускать, но Java появилась в 1995 году. Common Lisp стандартизирован в 1994, появился ещё раньше. CLOS (грубо говоря, ООП-часть языка) в стандарт вошла.


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


А ещё у Common Lisp с тех пор новых стандартов не было. Да, реализации вносят что-то новое, но в основном, в виде функций, библиотек и т.д. То есть можно сейчас взять книгу "Practical Common Lisp" (кажется) 2003 года и посмотреть как было на тот момент.

В языке есть циклы, мутабельные данные, побочные эффекты и т.д.
Главное что в нём есть — это рефлексия. По большому счёту развитие Java и C# медленно и со скрипом движется в направлении реализации фич, которые существуют в Lisp'е уже не одно десятилетие.
Например задумайтесь над вопросом: что должно произойти с уже существующими объектами, если программа изменит стуктуру класса, который их описывает? Ни Java, ни C# до этой стадии ещё не дошли (может в какой-нибудь верии 20.0 доберутся) — а книжка которая обсуждает и решает эту проблему для Lisp'а появилась в 1991м году.
А ещё у Common Lisp с тех пор новых стандартов не было.
Да — это большая проблема. Есть много реализаций, но они довольно заметно «разъехались».
что должно произойти с уже существующими объектами, если программа изменит стуктуру класса, который их описывает?

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


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


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

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

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

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


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

Справедливости ради: линукс, вроде, умеет обновляться и без перезагрузки и это без использования лиспа.
Не умеет. Новые библиотеки оказываются на диске, но запущенные процессы продолжают использовать старые. Есть, правда, Ksplice который пытается «что-то такое» сделать, но у него куча ограничений.
Да и в целом меня это не особо напрягает, особенно в рамках отдельных программ — они как правило хотят всего-лишь перезапуска, а не рестарта системы целиком.
Ну да, это не конец света — просто показывает, что проблема, на самом деле, есть и она довольно-таки неприятна (и с увеличением сложности программ и частотой выхода новых версий становится всё острее: одно дело 90е, когда «заплатки» выходили два-три раза в год, другое — сейчас, когда они уже чуть не каждый день выходят).
Искать что-то прорывное в мейнстриме и правда не стоит, но обкатанные решения туда постепенно попадают.
Одно дело, когда туда попадают решения, которые только-только обкатали где-нибудь в академиях, другое — когда туда возвращаются вещи, уже бывшие мейнстримом лет 30-40 назад…
С другой стороны, лисп мог бы оставаться лиспом и со статической типизацией.
Только работать с ним стало бы гораздо сложнее. Вы метапрограммы на C++ писали? Вот это — почти что «Lisp со статической типизацией»… сложный он. И часто нелогичный…
просто показывает, что проблема, на самом деле, есть и она довольно-таки неприятна

Дык, но это всегда результат компромиссов. Взяв Common Lisp таким каким он сейчас является мы получим больший размер рантайма. Относительно С/С++, скорее всего, проиграем в скорости. То есть, как универсальное решение это тоже не всегда подойдёт. Если утрировать, то я предпочту перезапускать браузер раз в неделю, но чтобы он работал хоть немного быстрее. Тем более, что у меня он со временем и так начинает "необъяснимо тормозить" и не думаю, что другой язык тут избавит от всех проблем. (:


Вы метапрограммы на C++ писали?

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


Можно посмотреть на D или Rust. Первый просто немного улучшает/развивает имеющееся в плюсах и сразу многие вещи становятся значительно понятнее и легче в написании. Во втором, есть два вида макросов и хотя одни смотрятся в языке чужеродно, а вторые не особо просты, но это тоже большой шаг вперёд (от уровня С++).


Ещё лучше посмотреть на Racket — там система типов весьма навороченная и в типизированном диалекте (жаль только он, насколько я могу судить, развивается по остаточному принципу) они присутствуют явно. Макросы и метапрограммирование, разумеется, в наличии.


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

Или в Lisp с самого начала был задуман как мультипарадигменный язык, поддерживающий и функциональную и объектноориентированную парадигмы?
Во времена, когда Lisp был создан ещё не было ни той, ни другой парадигмы. Lisp использовался для того, чтобы их изобрести :-) А также для того, чтобы придумать современный GUI, мышь и многое другое. Вот только тяжёл он был для тех лет — машинки стоили самолёт. А насчёт «мультипарадигменности»… когда в языках появляются всякие «суперфичи» типа expression trees и реализованной «поверх них» LINQ — то в среде лисперов обычно возникают только дискуссии по типа «мы это имели уже в 60х годах или всё-таки только в 70х?».
А основная беда Lisp'а (особенно современных диалектов типа Scheme) — в том что он очень «математический», что ли. Почему-то у людей подход с кучей явно выраженных парадигм вызывает меньше проблем, чем когда одно решение используется для всего (начиная с той же префиксной записи математических выражений).
Если программировал на ASM для DEC PDP-11 то ничего сложного :-)
Впрочем С и вырос из системы команд DEC PDP-11
Так и есть. Оттуда и указатель на указатель (косвенная адресация), и автоинкремент (декремент).
да, довольно сложно. Говорю как преподаватель, когда начинаешь изучать программирование, 2 самые сложные темы — рекурсия и указатели.
А, вот оно оказывается как… Не моноиды в категории эндофункторов, не эндоморфизмы под композицией, не замыкания/продолжения/корутины, не комбинаторы неподвижной точки при нормальном и аппликативном порядке редукции и не море всего другого — а вызовы функций и адреса ячеек памяти — самые сложные темы!..
Вы совершенно зря доводите до абсурда аргумент о сложности указателей, сводя его к «неспособности понять концепцию адреса ячейки памяти». Это не говоря о том, что в ВУЗе (моем, например) сначала изучается Си, во 2 или 3 семестре, если я правильно помню, а на 4м курсе — ассемблер, организация ЭВМ — то, что дает ту самую концепцию «адреса ячейки». Но бог с ним, проблема не в самой концепции, а в сложночитаемом синтаксисе, на мой взгляд. И я не о простом «указатель на инт», а об указателе на массив или массив указателей и т.п.
У многих ли языков синтаксис неудобоварим настолько, что пишутся статьи вида «как читать type declaration» или сайт-переводчик
http://cdecl.org/
Можно говорить о том, что такие конструкции есть только потому, что язык позволяет их создать, и тут уж вопрос рук программиста. Но тому, кто будет поддерживать чужой код, от этого не легче.
Понимается — элементарно, особенно для тех, кто писал на Ассемблере. Втч и более сложные структуры вида указателей на функцию, возвращающей указатель на указатель на что-то.

Но читается ровно никак. Это субъективно, да.
Не знаю, но неужели проблема именно в указателях? Мне кажется когда говорят про указатели в половине случаев имеются в виду проблемы динамического выделения памяти.
А это правда, что операторные скобки { и } вместо английских слов были введены в язык только для экономии памяти?
Тоже слышал, что раньше были текстовые редакторы, которые не могли редактировать файлы с большим количеством символов. И эти редакторы повлияли на синтаксис языка и стандартной библиотеки
Ну, на PDP (во времена) были в ходу строчные редакторы, а не оконные.
Вряд ли дело в редакторе, даже оконный работает с кусками файла. Другое дело перфокарты, особенно с поколоночной набивкой.
Не памяти, а времени набора программ.
Экономия времени, надо полагать, тоже была актуальна в 70-х? Вроде программисты не машинистки.
Если исходить из тезиса об экономии времени, то надо вспомнить, что в те далекие годы еще были в ходу пакетные системы.
Я не знаю, была ли эта проблема актуальна вообще, но она по какой-то причине было точно актуальна для Кена Томпсона: уже при создании языка B он стремился улучшить компактность программ за счёт уменьшения числа символов. В самом начале Users' Reference to B читаем «Because of the unusual freedom of expression and a rich set of operators, B programs are often quite compact.» Была ли у этого устремления какая-то реальная физическая причина (например, большие задержки при вводе) или это был просто «бзик» утверждать не берусь.

P.S. Хотя конкретно операторные скобки {...} были уже и в предшественнике B, языке BCPL, просто часто вместо них писали $(...$) по причине частого отсутствия кнопочек для ввода фигурных скобок на клавиатурах тех дней.
Переносимость все-таки ограничена. На машинах, где указатели принципиально отличаются от данных и отсутствует адресная арифметика (Intel iMAX 432), реализация C затруднена.
UFO just landed and posted this here
UFO just landed and posted this here
Перевод очень корявый. Оригинал (немного отличный от статьи) я лично нашел по одной из ссылок в статье: http://givi.olnd.ru/chist/chist.html
K&R заложили бомбу в типобезопасность С (одну из бомб!), когда написали в стандарте 1978 года, что все операции с плавающей точкой должны проводиться с операндами двойной точности (double). Поэтому, например, следующий фрагмент кода:

float value=1.0/3.0;
if (value==1.0/3.0) printf(«equal»);
else printf(«not equal»);

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

Константу для проверки нужно дать правильного размера:
if (value==1.0f/3.0f) printf(«equal»);
И будет «equal».

А вообще, лучше сравнивать числа с плавающей запятой с учетом погрешности, независимо от языка.
Да кто спорит-то? Или, еще лучше, написать double value = 1.0/3.0. Проблема-то не в том, что написать, а в стандарте языка. Зная его, безусловно, можно обойти все подводные камни. Я могу себе представить программиста 1978 года, работающего за каким-нибудь PDP-11, долго смотрящего на экран, а потом плюющего и начинающего переписывать все на старом добром ассемблере, где, можно применить, условно говоря, команду FDIV, а потом забрать значение из регистра, и спать всю ночь сном праведника.
Я предложил решение, просто на какую-то бомбу это не тянет, в любом языке, который поддерживает несколько типов с плавающей запятой будет тоже самое, стандарт тут не при чем, так работает сопроцессор. Вот пожалуйста пример для Java, абсолютно то же самое:

float xxx=1.0f/3.0f;
if(xxx==1.0/3.0)xLog(«Equal»);
else xLog(«Not Equal»);

Стандарт мне тоже местами не нравится, особенно моменты известные как «неопределенное поведение». Но сам язык лично мне нравится тем, что я могу случайно отстрелить себе ногу — это да, но зато я не обязан отстреливать себе яйца. Иначе говоря, я могу выбирать и меня никто не ограничивает в средствах достижения цели.
Всегда сравниваю вещественные числа в Си с использованием макроса с константой DBL_EPSILON (или FLT_EPSILON) из float.h и ни один линтер не ругается :)
Именно так.
По уму вообще бы запретить использование операторов == и != для значений с плавающей точкой.
Иногда нужно именно точное сравнение. Например чтобы убедиться, что данная точка — это именно именно _та самая точка_. Это и быстрее, и допустимую погрешность оценить не всегда просто.
Вовсе не потому:
root [1] float value = 1./3.;
root [2] value == 1./3.
(bool) false
root [3] value == 1.f/3.f
(bool) true
root [4] value == float(1./3.)
(bool) true

Дело в том к какому типу приводится левый и правый операнды оператора ==. В вашем случае к double (для избежания потери точности).
Типичный FPU, кстати, не умеет делать операции над float. Регистры x87, например, 80-битные.

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

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

20 лет писал на С еше с PDP-11 и никогда грепом непользовался, какое он имеет отношение к языку C?
Ну, например, используете вы какую-нибудь функцию, хотите посмотреть её аргументы, а она фиг знает где объявлена. Если не пишешь в IDE, то нужно грепать. Но тут действительно какой-то странный юз кейс. «Прототип объявлен не на своем месте», это действительно фигня какая-то.
Забавно, что не только «С- жив», но и мамонт по кличке Кобол (последняя строчка 1ой страницы TIOBE). Видимо, не все спецы так любят новации, как пытаются убедить инноваторы :) По сравнению языков есть интересный сборник корифеев: Языки программирования: Ада, Си, Паскаль. Сравнение и оценка. М.: Радио и связь, 1989. Столько лет прошло, а до сих пор актуально :)
Мне не совсем понятно, почему лиспа нет в этом рейтинге, а кобол есть. Даже если по вакансиям смотреть, то у нас в городе есть несколько мест на CL, а про кобол уже никто давным-давно не слышал)
Вы не поверите, но компьютеризация началась не с Воронежа, так что всё закономерно :-)

Кобол — это окомпьютеризированные в 60е годы банки, с которыми, в общем, за пределами США мало кто сталкивается (Европа докатилась до компьютеризации банков в 70е, а в других странах это произошло ещё позже), зато как раз там — сотни тысяч программистов, миллионы клерков… достаточно, чтобы попасть на 1ю страницу, пусть и на последнюю строчку.

А LISP — это для «высоколобых умников», их, в общем, никогда особо много не было.
См. TIOBE : Lisp 28 место, далее Ада, но не самые плохие места: Пролог, нпр., на который возлагали столько надежд — 33 место, даже детский язык Лого его обогнал. А Go — вообще 48-ое. Интересно, что в начало второй страницы на 21 место Fortran переехал, который так поддерживали многие для научных вычислений (в квантовой механике, нпр.) и Интел поддерживал ;) Однако и у С/С++ тенденции к спаду.
UFO just landed and posted this here
Я на Perl не пишу, но когда участвовал в обсуждении рукописи книги John Levine, Linkers and Loaders, спросил автора: почему он выбрал перл для примеров? Он ответил, что эти же примеры на другом языке увеличили бы в 2-3 раза объем книги и она стала бы трудно читаемой.
UFO just landed and posted this here
BTW А чем D хорош? Механизмом вывода типов? contract-based programming from Eiffel?
UFO just landed and posted this here
Спасибо. Нужно будет присмотреться внимательнее к этому языку. Хотя про исправление косяков у меня очень пессимистичные впечатления. Если в целом взглянуть на последние десятилетия — то прогресс в языках тормознул: случилась в прошлом веке так называемая ОО-революция (ей предшествовали структурная и модульная революции) и больше никаких революций. Хоть и японцы 5-ое поколение обещали, другие высказывали мнения, что макросы электронных таблиц — качественно новый уровень языков программирования. Тот же Eiffel контракты предложил. Были еще всякие манифесты новых парадигм, только пока никаких революционных сдвигов, сравнимых с ООП, они не произвели.
UFO just landed and posted this here
Может революций и не будет. Тогда софт еще больше отстанет от железа :(

Ceylon не знаю. Вики утверждает, что он со строгой статической типизацией. Это не попытка наступить второй раз на грабли Виртовского Паскаля, где была невозможна универсальная функция умножения двух матриц? ;)
Может и не надо запихивать в язык максимум известного? Нпр., стрелку Пирса — достаточно других Булевых операций, чтобы реализовать эту ;)
UFO just landed and posted this here
Чем сложнее язык — тем больше багов окажется в его реализации, тем сложнее организовать достаточно полное тестирование. Но дело не только в разработчиках/кодерах языка и в его тестерах, но и в переносимости на другие платформы, в том числе, появившиеся уже после реализации языка. Кроме того, возрастает сложность стандартизации. Сложность изучения будет тормозить распространение языка. Это студентам хорошо, когда, помимо других предметов, на изучение какого-нибудь языка отводится целый семестр с лекциями и семинарами. Если студент не тупой лентяй, то может не торопясь вникать и разбираться. А работающему в коммерческой компании программисту обычно отводят очень сжатые сроки на изучение. И изучать он постарается не весь язык, а только то, что нужно для конкретной работы. Для больших проектов, чем сложнее и гибче язык, тем сложнее руководителю проекта установить правила написания и документирования кода. Чаще будут возникать ситуации, когда один кодер жалуется на другого, что тот пишет слишком трудно читаемый и трудно модифицируемый код. BTW C/C++ за это критиковали. В.Ш.Кауфман в одной из статей утверждает, что как бы ни старались разработчики языка и его стандартизаторы, любой язык будут содержать неоднозначности («темные места»). Количество этих неоднозначностей — один из основных параметров оценки качества языка. Исходя из общих соображений: чем сложнее язык и чем он гибче, тем больше будет неоднозначностей — ниже качество.

Что касается умножения матриц — они нужны не только в числодробилках. Например, в теории графов куча нечисленных задач с очень широким кругом приложений: от химии до интернет-технологий. Многие из этих задач решаются в том числе и матричным подходом.
UFO just landed and posted this here
Вики утверждает, что сейчас более восьми тысяч языков программирования и их число растет. Т.о. вероятность новому языку стать популярным уменьшается. Если не произойдет никаких революций, то языки скоро перестанут изобретать. М.б. это и к лучшему ;)
UFO just landed and posted this here
Зачем делают языки — хороший вопрос. Могу предположить несколько не очевидных вариантов.В Вики есть статья «Эзотерический язык программирования» — «в качестве шутки». В большинстве универов мира препод должен заниматься научной работой. Создать язык для отчетов подойдет :) BTW были утверждения, что Си был создан в качестве шутки :)
эволюционировавших раз-два и обчелся
Pascal -> Delphi? ;)
А работающему в коммерческой компании программисту обычно отводят очень сжатые сроки на изучение.


Работаю "в коммерческой компании" и как-то умудряюсь новое изучать, пусть и на энтузиазме. Собственно, если настанет день когда мне будет лень этим заниматься, значит как программист я перегорел. (:
Ok. Бывает 2 случая: 1) изучать для расширения кругозора, когда никто не требует и не торопит; 2) изучать, когда срочно требуется для основной работы.
Значит, ничего принципиально нового не изучаете. Скажем, родственные языки в рамках одной парадигмы — это не новое, это просто «перепевы». А надо, как там это говориться, «покинуть зону комфорта»! :D Вот попробуйте тот же Haskell, скажем, изучить в «сжатые сроки», или даже, чтоб не меняя парадигму, ту же Ada.
Почему не изучаю? Недавно CUDA стало нужно в «сжатые сроки»… :)
Про Аду много читал одну из ссылок привел выше:
Языки программирования: Ада, Си, Паскаль. Сравнение и оценка. М.: Радио и связь, 1989.
:)
Значит, ничего принципиально нового не изучаете


Из чего это следует? Основной мой инструмент — С++. В своё время осилил "Practical Common Lisp", кое-что писать пробовал. Предпринимал несколько попыток за хаскель взяться и хочется думать, что в итоге более-менее в этом преуспел, хотя без постоянной практики, конечно, всё забывается. Про Racket периодически почитываю. Хотя в последнее время "делал упор" на Rust. Достаточно чтобы покинуть "зону комфорта"?

А сжатые сроки зачем? Изучаю ведь ради самообразования и расширения кругозора.

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

В любом случае, мне "учиться" нравится. Так что если даже придётся решать задачу непривычным инструментом, то (при условии, что он заинтересует) присмотрюсь получше уже в спокойной обстановке.
Это не изучение, это «ковыряние». Полистал книжку, что-то в Интернете глазами пробежал, что-то простенькое накидал «на коленках». Это всё «ради самообразования и расширения кругозора», как Вы и сказали, но не более. Речь о том, что на изучение монструозных языков (С++) до уровня «решать задачу», а не «поковырять», нужно потратить слишком много времени, что, как и утверждал third112, «будет тормозить распространение языка». Вы, собственно, именно этот его тезис пытаетесь оспорить. Поэтому я и говорю: либо изучаете, но не новое, либо новое, но не изучаете. Иначе бы у Вас физически времени не хватило. Вот С++ Вы сколько изучали? До уровня «решать задачу» имеется ввиду.
Вот С++ Вы сколько изучали?


До сих пор изучаю, благо язык продолжает развиваться. (:

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

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

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

И ещё я не верю в "простые языки": если они развиваются, то продолжают вбирать фичи. Джава многими считается простой и консервативной, но лямбды и до неё добрались и дальше фичи пихать будут. C# от плюсов недалеко ушёл, хотя правильно будет сказать "стремительно догоняет": версии языка выходят чаще, фичи и "сахар" добавлять не стесняются. Возьмём Racket казалось бы — казалось бы, наследник минималистичной Scheme. Да, как всякий лисп, язык позволяет расширять себя и многие (все?) фичи и сделаны средствами языка, но толку? Если заглянуть в гайд, то глаза разбегаются от обилия "прикольных штук".
Разве что Go идёт по "другому пути" и то судить ещё рано, посмотрим через десяток лет и N версий языка.
Простые языки подходят для решения очень многих задач. Не для рекламы, а только в качестве примера упомяну наш проект игрового бота. Там, на мой взгляд, совершенно обосновано в качестве языка скриптов взят стандартный Паскаль. А взяли бы мы что-то посложней (или что-то попроще — совсем примитивное), до сих пор бы на старте буксовали :)
Простые языки подходят для решения очень многих задач


Подходят. Вот только для разных задач, скорее всего, будут разные языки. Ну и скриптовые языки и всякие ДСЛ — это несколько отдельная ниша.
И идея ДСЛ мне вполне импонирует, но это не "язык общего назначения".

Кстати, Паскаль ведь тоже развивается — в виде Делфи. Что-то там добавляют и наворачивают. А то ведь можно взять джаву 1.0 и говорить, что это простой язык.
Согласен, что скриптовые языки зачастую узко специализированы, но у нас другой случай: мы использовали язык общего назначения (Паскаль) в качестве скриптового :)

Согласен, что Паскаль развивается. В том числе и в виде Дельфи (есть еще и Free Pascal и др.). До Delphi-7 (включительно) развитие шло органично: делались пристройки, но сохранялось основное очень простое ядро. Отсюда была хорошая совместимость со старыми версиями. А потом стало несовместимым, и многие из тех, у кого много старого кода, продолжают использовать Delphi-7. Пример: игра КР (для которой упомянутый бот).
PS В боте язык не изменяли, только добавили предопределенные функции. Для многих других случаев можно использовать такой путь. Язык останется тем же.

Статью про бота читал некоторое время назад, но в первую очередь из-за любви к рейнджерам. (:


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

Первоначально одной из основных целей Паскаля была «учебный язык», т.е. язык для обучения программированию. Поэтому даже неискушенному новичку на Паскале написать очень плохой код гораздо труднее, чем на других языках :) Си, например, тоже довольно простой язык (если сравнивать с С++), но он очень гибкий. Опытные кодеры считают эту гибкость достоинством, но новичкам она вредит: такого понаписать можно… Описание Паскаля занимает всего 30 страниц — по силам прочитать и понять любому фанату КР, даже если никогда до этого не программировал и в школе информатику не учил :) И на форумах фрагменты кода на Паскале обсуждать удобно — никто не говорит, что не понимает столь простого языка :))
Про очень плохой код на паскале…
Помню будучи студентом, стукнуло мне в голову сделать «очень плохой код», а именно реализовать в функции массив указателей на метки (по которым goto работает) и прыгать на них вот так: goto labels[index];
СИ разумеется мне этого не позволил, как бы я не пытался, а вот Паскаль к моему глубоком удивлению позволил! Дело было вроде в Turbo Pascal 7.1, если память не изменяет…
Turbo Pascal с первых версий ради удобств сильно отходил от стандартов. Поэтому, например, в США многие преподаватели считали его не учебным. Интерпретатор нашего бота не позволяет написать «goto labels[index]^», выдавая две синтаксических ошибки: «integer expected» и «illegal symbol».
Стандарты ограничивают, лучше иметь рекомендации и возможность им следовать или не следовать, по крайней мере для учебного языка, ведь экспериментировать — это хорошо. В свое время я разрабатывал 2д рендер для простенькой системы на древнем ARM7 (40МГц), мне очень не хватало производительности, и тогда я написал серию самомодифицирующихся функций на ассемблере для отрисовки графики, выиграв при этом прилично скорости. Мне до сих пор интересно какими бы были язык высокого уровня и архитектура процессора в которых самодификация была бы стандартным явлением.
Кстати, по словам знакомых инженеров из Эльбруса — в DOOM для ПК тоже этим страдали, он из-за этого много ресурсов отбирает на их бинарном рекомпиляторе.
Да. Стандарты ограничивают и дисциплинируют. Прежде всего, производителей компиляторов и других инструментов программирования. Благодаря стандартам к «болту» одного производителя всегда можно подобрать «гайку» другого. От любого из существующих стандартов можно отказаться и оформить тот же текст в виде рекомендаций, но если среднего школьника, которому положено посещать уроки информатики и который не собирается стать профессиональным программистом, заставить вызубрить эти рекомендации – такая задача будет ему не по силам. А стандарты школьникам зубрить не нужно: компилятор обругает в том месте, где ученик отошел от стандарта. Думаю, что в школе и на первых курсах института самомодифицирующийся код изучать не надо: пусть сначала плавать научатся. И я любил работать на ассемблере: вплоть до Intel 80486 включительно удавалось творить чудеса (особенно для NP задач), а потом возросшие ресурсы позволили реализовать мощные оптимизаторы – я (и мои знакомые и сослуживцы по ассемблерным делам) быстро обнаружили, что из написанного на языке высокого уровня кода получается гораздо более быстрый «экзешник», чем нам удается сделать на ассемблере.
Против технических стандартов ничего не имею. Я имел ввиду немного другое — неписанные стандарты, кто-то вот решил, что указатели это зло и сказал — в школе будем изучать язык без указателей — это будет стандарт обучения. Я считаю здесь должен быть как минимум выбор. Человек очень быстро учится пока молодой, потом тяжелее и тяжелее. Поэтому, если когда-то и надо изучать что-то новое и сложное, то именно тогда когда ты учишься. Зубрить ничего не надо, надо понимать суть, для остального есть справочники, а школьные задачи можно решить на любом языке — пусть ученик сам решает — на сколько он готов выпендриваться. У нас в школе так и было, кто-то писал на Бейсике, кто-то на Паскале, я писал на Си.

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

Весь код на ассемблере после появления языков высокого уровня писали наверное только те, кому время девать некуда. А критические участки реализуют на ассемблере по сей день, и если что — я и мои знакомые можем делать на ассемблере код более быстрый, чем на языке высокого уровня :)
Я имел ввиду немного другое — неписанные стандарты, кто-то вот решил, что указатели это зло и сказал — в школе будем изучать язык без указателей — это будет стандарт обучения.
Бывают не только глупые/бездарные ученики, но и учителя :)
Зубрить ничего не надо, надо понимать суть, для остального есть справочники
Согласен. Но традиционно, даже в лучших универах, математику и естественные науки изучают путем зубрежки формул, теорем, доказательств и т.д. (А студенты-историки даты зубрят :))
а школьные задачи можно решить на любом языке — пусть ученик сам решает — на сколько он готов выпендриваться. У нас в школе так и было, кто-то писал на Бейсике, кто-то на Паскале, я писал на Си.
А если ученик возьмет совсем экзотический язык, который мало кто знает? Нпр., в моей коллекции есть описания и трансляторы языков T и Dee. Слышали о таких? ;)
А критические участки реализуют на ассемблере по сей день, и если что — я и мои знакомые можем делать на ассемблере код более быстрый, чем на языке высокого уровня
Сможете доказать? ;) Вот Вам и тема для очередной Хабр-статьи: возьмите какую ни будь задачку из общеизвестных (игра Жизнь, задача коммивояжера и т.д.), решите на языке высокого уровня, а потом ускорьте критические участки, переписав их на ассемблере. В статье сравните время выполнения и поделитесь секретами успеха, приложите исходный код. Уверен, что такая статья многих здесь заинтересует.
«А если ученик возьмет совсем экзотический язык, который мало кто знает? Нпр., в моей коллекции есть описания и трансляторы языков T и Dee. Слышали о таких? ;)»

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

«Сможете доказать? ;) Вот Вам и тема для очередной Хабр-статьи: возьмите какую ни будь задачку из общеизвестных (игра Жизнь, задача коммивояжера и т.д.)»

Есть задачи, которые компилятор на языке высокого уровня решить лучше человека не сможет, по крайней мере пока, поэтому да смогу, но пример выбирать не вам. Доказывать очевидное не буду, лучше возьмите открытые кодеки и системы распознавания образов — думаете люди от нечего делать там целые функции на ассемблере или интрисиками пишут?
Пусть берет, главное, чтобы результат был правильный. Необязательно преподавателю знать все языки.
Разве учителя не должно волновать, что в коде «goto labels[index]^» и подобное? ;) Если преподаватель будет формально смотреть только на результаты работы программы, то такого преподавателя легко обмануть: скачать решение из инета или попросить приятеля решить задачку и идти сдавать, совершенно не понимая, как работает это решение. А вот если преподаватель будет спрашивать по деталям исходного кода, будут видны действительные знания ученика и стиль его кода. Правильно оценить код на совершенно незнакомом языке — проблематично.

Есть задачи, которые компилятор на языке высокого уровня решить лучше человека не сможет, по крайней мере пока
Наверное есть. Это можно предположить, исходя из общих соображений. Вопрос в том, велика ли доля таких задач? Может, это частные случаи, какие-то редкие мелочи, неучтенные при создании компилятора? Если вспомнить компиляторы для IBM 360/370, то там почти со 100%-ой гарантией перенос на ассемблер критического участка кода давал заметный выигрыш. Сейчас, благодаря прогрессу, не всегда очевидно, стоит ли игра свеч, т.е. время на перенос можно потратить много, а выигрыш может оказаться очень небольшим.

думаете люди от нечего делать там целые функции на ассемблере или интрисиками пишут?
На столь общее утверждение существует столь же общее возражение: некоторые люди иногда совершают странные и непонятные стороннему наблюдателю поступки ;) М.б., со времен IBM 360 сложилась традиция какие-то специальные функции на ассемблере писать? ;) Переносили старый код и, недолго думая, все, что на ассемблере, написали на ассемблере. (Я, конечно, несколько утрирую, но в столь общих рассуждениях это неизбежно :)
PS
Необязательно преподавателю знать все языки.
Часто, глядя на код, особенно простой ученической задачи, легко придумать контрпример. Нпр., нет обработки исключения деления на 0, не учтен ввод пустой строки и т.д.
Но традиционно, даже в лучших универах, математику и естественные науки изучают путем зубрежки формул, теорем, доказательств и т.д. (А студенты-историки даты зубрят :))
Ничего не знаю про студентов-историков, но на мой вопрос «у меня плохая память на имена потому слова „теорема Такого-то Сякого-то“ мне ни о чём не говорят — не могли бы вы сказать о чём она?» преподаватели реагировали всегда вполне благосклонно — при условии, конечно, что я на самом деле знал как доказать эту самую теорему.

А вот к людям путающим значки ∃ и ∀ или не знающих что ∄x… расшифровывается не как ∃x¬…, а как ∀x¬… — отношение было совсем другое.

Вот Вам и тема для очередной Хабр-статьи: возьмите какую ни будь задачку из общеизвестных (игра Жизнь, задача коммивояжера и т.д.), решите на языке высокого уровня, а потом ускорьте критические участки, переписав их на ассемблере.
А вот тут — как бы хороший пример на путаницу ∃ и ∀. Для многих задач компилятор может сгенерировать весьма неплохой год (хотя как раз Жизнь ускорить с использованием ассемблера можно изрядно), но в любой программа размера чуть выше среднего находятся куски, которые на ассемблер ложатся плохо.

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

P.S. Почему я говорю о кусках кода с интринзиками как об «ассемблере»? Потому что фактически — это оно и есть. Каждая команда типа …sad_epu… — транслируется ровно в одну инструкцию, потому когда вы пишите «на интризиках» вы получаете почти оптимальный код. Который легко может обойти переносимую реализацию по скорости в 2-3 раза, иногда и больше. А вот ассемблер уже выигрывает по сравнению с этим какие-то слёзы — несколько процентов, если повезёт.
на мой вопрос «у меня плохая память на имена потому слова „теорема Такого-то Сякого-то“ мне ни о чём не говорят — не могли бы вы сказать о чём она?» преподаватели реагировали всегда вполне благосклонно — при условии, конечно, что я на самом деле знал как доказать эту самую теорему.
Помнится, в коллоидной химии есть формула (не помню чьего имени),
полученная эмпирически. Эта формула — 4х этажная дробь, где много всяких буковок и коэффициентиков. Студенты должны были ее знать, чтобы сдать экзамен на ХФ МГУ :) А в физмат школе, где я учился, мы должны были помнить формулу синуса тройного угла и еще кучу подобного мусора, который можно посмотреть в любом справочнике.
хотя как раз Жизнь ускорить с использованием ассемблера можно изрядно

Который легко может обойти переносимую реализацию по скорости в 2-3 раза, иногда и больше.
А Жизнь можно таким образом в 2-3 раза ускорить? На мой взгляд, это неочевидно. М.б. на 10-20% — более реалистичное ожидание?

Почему я говорю о кусках кода с интринзиками как об «ассемблере»?
Ok.
А Жизнь можно таким образом в 2-3 раза ускорить? На мой взгляд, это неочевидно. М.б. на 10-20% — более реалистичное ожидание?
Всё зависит от алгоритма, но если мы говорим о самом простом варианте (где живая/мёртвая клетка — это один бит), то там и версия на C и версия на асме будут непростыми. Но думаю что 2-3 — это реалистично.

Сами подумайте.

Понятно, что если обрабатывать по 1 биту за раз, то всё будет очень медленно, нужно пользоваться тем, что даже без ассемблера у нас есть битовые операции, которые «за раз» обрабатывают по 64 бита (то есть, скорее всего, можно «приловчиться» и разбить одно 64-битное число на 16 4-битовых полей), но там сравнивать с 2 и 3 будет не очень удобно (думаю что всё же возможно — но неудобно).

С другой стороны ассемблер и AVX2 дают нам «сходу» возможность использовать не 64 бита, а 32 байта (AVX512 даст аж прямо 64 байта — он он пока мало где поддерживается) и их можно «штатными средствами» сравнивать. Так что скорее всего грамотно написанная программа на ассемблере таки обгонит грамотно написанную программу на C в 2-3 раза.

Выигрыш у «тупой» программы будет ещё больше, но в любом случае — не в 32 раза, конечно.

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

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

Это утверждение требует отдельного доказательства, т. к. в «жизни» есть ветвления для определение выживаемости конкретной клетки.
Несколько лет назад на конкурсе Интела была задача распараллелить Жизнь. Не просто, но получилось у многих участников.
Нет там никакого «ветвления». Там простая функция
xn+1 = (sum_neibhoursn == 3) | (xn + sum_neibhoursn) == 3
На GPU (или AVX) сравнение серии чисел с выдаче результата — это операция, предусмотренная архитектурой, если вы будете руками обрабатывать 16 точек «за раз» — придётся малость с битовыми операциями повозиться. Но никаких ветвлений там нету и в помине.
Всё зависит от алгоритма, но если мы говорим о самом простом варианте (где живая/мёртвая клетка — это один бит)
Мы говорим о скорости, а память мы не экономим. На многих языках для скорости на клетку выгоднее отводить байт. И для ряда языков самым простым вариантом будет байт на клетку, а не бит на клетку.
С другой стороны ассемблер и AVX2 дают нам «сходу» возможность использовать не 64 бита, а 32 байта (AVX512 даст аж прямо 64 байта
Тут возникает «философское» возражение: что это уже другой алгоритм и что мы сравниваем — эффективность реализации или эффективность алгоритмов? ;))
и в любом случае GPU выиграет, так что практического смысла переписывать на ассемблер может и не быть.
Я совсем недавно занялся CUDA — до сих пор задач не было. М.б. поэтому не в курсе. CUDA Си есть, а вот не видел, что кто-то пишет на CUDA асме ;)
Мы говорим о скорости, а память мы не экономим.
Тут как бы есть некоторое противоречие. Если мы работаем с большими полями, то нам очень важно потребление памяти именно в плане скорости. Не забывайте что чтение из памяти — тоже времени требует.
И для ряда языков самым простым вариантом будет байт на клетку, а не бит на клетку.
Если уж мы начинаем задумываться о «вытягивании всех соков» с помощью ассемблерных вставок, то, уж наверное, все остальные оптимизации нужно сделать до этого, а не после. А то мы так начнём сравнивать программу на python'е и программу на ассмеблере — там выигрыш в скорости в 100500 раз будет!

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

Тут та же история: алгоритм тот же — обработка идёт побитно, вычисления по той же схеме, результат кладётся туда же. А что на C такое написать невозможно — ну так об этом и речь! Потому и всякие кодеки и Skia c V8 имеют вставки на ассемблере что на C алгоритм ложится «коряво» и компилятор оказывается бессилен! Для того и нужен ассемблер в современном мире!

Я совсем недавно занялся CUDA — до сих пор задач не было. М.б. поэтому не в курсе. CUDA Си есть, а вот не видел, что кто-то пишет на CUDA асме ;)
Потому что писать на CUDA и без того тяжело. Но с учётом того, что при использовании CUDA мы будем обрабатывать не 32-64 клетки за раз, а несколько тысяч — думаю и CUDA C обгонит даже хорошо оптимизированный AVX512-ассемблер. И даже четырёхратная разница в тактовой частоте не спасёт :-)
С переводом с C на ассемблер современные компиляторы справляются лучше человека
И я выше это утверждал, а мне в ответ:
и если что — я и мои знакомые можем делать на ассемблере код более быстрый, чем на языке высокого уровня
:))

Далее:
Не забывайте что чтение из памяти — тоже времени требует.
Звучит разумно, но сомнения остаются, как и про:
затык возникает в другом месте: при переводе алгоритма с «математического псевдокода» на C возникают дополнительные ограничения, которые часто мешают компилятору сделать оптимальный код, а когда вы пишите код на ассемблере — вы этих ограничений лишены.
думаю и CUDA C обгонит даже хорошо оптимизированный AVX512-ассемблер. И даже четырёхратная разница в тактовой частоте не спасёт
Это только предположения, пусть и разумные. Но статью бы об этом написали — тогда обсуждение станет конкретным.
PS
затык возникает в другом месте: при переводе алгоритма с «математического псевдокода» на C возникают дополнительные ограничения, которые часто мешают компилятору сделать оптимальный код
В общем, это не проблема кодера, а проблема автора алгоритма, т.к. к настоящему времени сложилась довольно устойчивая традиция публиковать не только мат. псевдокод алгоритма, но и выкладывать его реализацию.
Это проблема языков высокого уровня, к сожалению. Простейший пример: любой современный процессор в качество побочного действия от операции сложения порождает информацию о том произошёл перенос или нет — и может сложить два числа с учётом переноса. Однако ни один распространённый переносимый язык высокого уровня не имеет возможности это использовать — и, соответсвенно, алгоритмы, которым это нужно на C записать попросту нельзя. И это — вещь, которая появилась в процессорах 50 лет назад.

А появившиеся в 80386м 30 лет назад инструкции, работающие с битами? Да их std::vector ни в одном известном мне компиляторе до сих пор не умеет использовать — хотя, казалось бы, они именно для него «заточены»!

Чего уж говорить о новых наборах инструкций? Эффективно их использовать, я боюсь, компиляторы научатся ещё лет через 50. А до тех пор — интринзики или ассемблер (хитрые инструкции интринзики покрывают, хитровычисленные флаги, увы, нет).
любой современный процессор в качество побочного действия от операции сложения порождает информацию о том произошёл перенос или нет — и может сложить два числа с учётом переноса.
Сходу затрудняюсь представить, где это может быть полезно. Разве только арифметика многократной точности. Ну так это очень специальная область, нужная не часто. Подобное можно предположить и про другие возможности, не используемые в языках высокого уровня. Бывают особые случаи, когда функцию стоит написать на ассемблере. С этим никто не спорит. Но это не значит, что любой критический участок можно ускорить в 2-3 раза, перейдя на ассемблер. Такой переход оправдан только в редких случаях.
Сходу затрудняюсь представить, где это может быть полезно.
Какое-то у вас очень бедное воображение. Практически всегда, когда у вас данные приходят «извне» и вы начинаете с ними манипулировать вам нужно проверять что при этих вычислениях не просиходит переполнения.

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

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

Вопрос в другом: да, вы можете ускорить в 2-3 раза почти любой кусок кода. Однако на каждые 100 строк кода вам придётся потратить, примерно, день. Даст ли это вам адекватный выигрыш в деньгах или может на что-то другое время потратить? При размерах современных систем, измеряемых миллионами строк и новых архитектурах, выходящих каждые 2-3 года — вопрос далеко не очевиден.
Какое-то у вас очень бедное воображение.
Выдавать желаемое за действительное — это богатое воображение? ;)
Практически всегда, когда у вас данные приходят «извне» и вы начинаете с ними манипулировать вам нужно проверять что при этих вычислениях не просиходит переполнения.
Далеко не всегда. Допустим, у меня есть какой то список и мне нужно его отсортировать. Очень частая стандартная задача и решается стандартными путями. Зачастую список заведомо небольшой и никакого переполнения вызвать не может.
Вопрос в другом: да, вы можете ускорить в 2-3 раза почти любой кусок кода. Однако на каждые 100 строк кода вам придётся потратить, примерно, день. Даст ли это вам адекватный выигрыш в деньгах или может на что-то другое время потратить? При размерах современных систем, измеряемых миллионами строк и новых архитектурах, выходящих каждые 2-3 года — вопрос далеко не очевиден.
Не будем говорить о системах в миллионы строк, а посмотрим на множество ежедневно публикуемых новых алгоритмов, реализация которых занимает в среднем несколько десятков строк. Большую долю составляют эвристические алгоритмы. В этом случае автору приходится демонстрировать, что его алгоритм на практике работает быстрее, чем ранее предложенные. Но почему-то такие демонстрации обычно проводятся на языках высокого уровня. Неужели все эти авторы не подозревают, что на ассемблере они получат сногосшибательные результаты? ;)
Очень частая стандартная задача и решается стандартными путями. Зачастую список заведомо небольшой и никакого переполнения вызвать не может.
Да-да, конечно. А потом программы глючить начинают.

Неужели все эти авторы не подозревают, что на ассемблере они получат сногосшибательные результаты? ;)
Подозревают, но их это не волнует. Их ведь, в конечном итоге, нужна статья, а не результаты. И если вы внимательно посмотрите на эти статьи, то обнаружите что они всячески избегают влияния «реального мира» на свои статьи: обычно считаются не секунды, а какие-нибудь другие характеристики, на которые переписывание на ассемблере не влияет никак.

Секунды, впрочем, тоже считают — но уже другие люди. Те, кто занимается расширением ISA — и вот у них ассемблер в статьях вполне себе встречается…
А потом программы глючить начинают.
Я же сказал, что «список заведомо небольшой». Нпр., в упомянутой выше игре КР2HD 32 артефакта, если сортировать их список, то явно не заглючит :)
Подозревают, но их это не волнует. Их ведь, в конечном итоге, нужна статья, а не результаты. И если вы внимательно посмотрите на эти статьи, то обнаружите что они всячески избегают влияния «реального мира» на свои статьи: обычно считаются не секунды, а какие-нибудь другие характеристики, на которые переписывание на ассемблере не влияет никак.
Не знаю, о каких статьях Вы говорите. М.б. о статьях в провинциальных сборниках, печатающих аспирантов, которые торопятся защититься, пока аспирантура не кончилась? ;) Или о писанине хакеров в популярных изданиях? ;) Я говорил о серьезных работах в солидных научных журналах, сборниках, монографиях.
С переводом с C на ассемблер современные компиляторы справляются лучше человека
И я выше это утверждал
Ну уж нет. Вы-то утверждали совсем другое:
я (и мои знакомые и сослуживцы по ассемблерным делам) быстро обнаружили, что из написанного на языке высокого уровня кода получается гораздо более быстрый «экзешник», чем нам удается сделать на ассемблере
Это — заметно разные утверждения. Компилятор — очень сильно ограничен в том, что он может делать. Даже если забыть про разные заковыристые инструкции, то есть, например, проблема aliasingа, соглашения о вызовах и много чего другого, что ограничивает его возможности по оптимизации программы. Да в конце-концов чудовищная нехватка регистров — тоже очень мешает! Слава богу x86 с его несчастными 8 регистрами стал достоянием истории, но даже на x86-64 проблемы бывают.

Человек же не переводит программу с C на ассемблер — а потому гораздо более свободен в том, что он может делать!
А в чем проблема соглашения о вызовах? По указанной ссылке никакой проблемы не нашел.
Да в конце-концов чудовищная нехватка регистров — тоже очень мешает!
Переход на ассемблер регистров не прибавит :) Можно какой иначе использовать и только.
Человек же не переводит программу с C на ассемблер — а потому гораздо более свободен в том, что он может делать!
Свобода не гарантирует успеха :) Выше говорили, что турбо-Паскаль позволяет писать «goto labels[index]^». И что дает эта дополнительная степень свободы? — Возможность написать запутанный код, трудный для человеческого восприятия. И больше ничего.
А в чем проблема соглашения о вызовах?
В том, что они есть, однако. Когда вы пишите код на ассемблере — вы можете сказать: «Ok, я знаю где вызывающая процедура хранит те или иные данные и передавать их никуда не нужно». Или сказать: «Ok, я знаю что эта процедура портит только регистры R3 и R5 — вот только их и будем сохранять». А компилятор такой возможности зачастую лишён. LTO иногда может помочь, но, опять-таки, во многих случаях в программе не C (про С++ уже и не говорю) просто нет достаточно информации, чтобы подобное проделать — особенно при использовании указателей на функции и тому подобного.

Переход на ассемблер регистров не прибавит :)
Это так кажется. Задача распределения регистров — NP-полная, а компиляторы, которые «колдуют над программой» часами почему-то никому не нравятся — потому в реальности все алгоритмы распределения регистров очень плохо работают при их нехватке.

Выше говорили, что турбо-Паскаль позволяет писать «goto labels[index]^». И что дает эта дополнительная степень свободы? — Возможность написать запутанный код, трудный для человеческого восприятия. И больше ничего.
А вот и нифига. За счёт этого можно легко ускорить некоторые алгоритмы, работающие с DFA разика так в два. Или вы думаете зря эта возможность добавлена в некоторые компиляторы C?
Когда вы пишите код на ассемблере — вы можете сказать: «Ok, я знаю где вызывающая процедура хранит те или иные данные и передавать их никуда не нужно».
И на языках высокого уровня обычно функция имеет доступ к глобальным переменным и может их менять. Т.е. можно и не передавать данные, хотя это справедливо считается плохим стилем.ООП узаконило некоторые виды общих данных для методов класса. Но в общем случае надо делать такие функции, чтобы они вызывались не слишком часто — нпр., не всегда в тело цикла стоит вставлять вызов функции :)
Задача распределения регистров — NP-полная, а компиляторы, которые «колдуют над программой» часами почему-то никому не нравятся — потому в реальности все алгоритмы распределения регистров очень плохо работают при их нехватке.
Люди решают NP-полные задачи еще медленнее, чем машины :)
Или вы думаете зря эта возможность добавлена в некоторые компиляторы C?
Может, и не зря. Нужно сравнивать, насколько эта возможность делает быстрее реализации конкретных алгоритмов. И в стандартах Паскаля есть goto, но все учебники рекомендуют использовать этот оператор только в крайних случаях. Про goto много спорили и пришли к выводу, что исключительные ситуации, нпр., нужно обрабатывать иначе. И в Дельфи есть goto, но без него можно обойтись. Обычно и обходятся.
И на языках высокого уровня обычно функция имеет доступ к глобальным переменным и может их менять. Т.е. можно и не передавать данные, хотя это справедливо считается плохим стилем.ООП узаконило некоторые виды общих данных для методов класса. Но в общем случае надо делать такие функции, чтобы они вызывались не слишком часто — нпр., не всегда в тело цикла стоит вставлять вызов функции :)

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

Не исчезнут, но станут меньше. У функции с 10 аргументами пролог больше, чем у функции без аргументов.

Зависит от calling convention и количества используемых регистров, естественно. Например, могут использоваться SSE или AVX регистры как в cilkplus вместо стека, что может уменьшить размер пролога.

Ok. Следует ли из этого, что приняв свой особо экономный calling convention и написав согласно этому соглашению функцию на ассемблере, мы всегда сможем ускорить участок с ее вызовом в 2-3 раза?

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


Обычно это полезно для уменьшения latency. Ощутимое ускорение можно получить не используя стек вообще (например, на ARM).

Не следует. Как всегда, надо измерять для конкретного случая.
И я так думаю. Но чтобы измерять и сравнивать, надо написать вариант на ассемблере, что затратно. Как оценить риск, что затраты не окупятся?
Но об этом вы спорили не со мной.
Верно. Но, т.к., на мой взгляд, вопрос не простой, то 3-е, 4-е и т.д. мнения интересны.
Ощутимое ускорение можно получить не используя стек вообще (например, на ARM).
А насколько ощутимое? Неужели в 2-3 раза?
А насколько ощутимое? Неужели в 2-3 раза?

При использовании стека в DRAM (а не SRAM) по производительности можно выиграть и полпорядка-порядок, когда обработчик простой (например, меняет адрес и разрешает DMA). Это актуально для hard realtime или если задача упирается в потолок ресурсов железа.


Если же говорить за микрооптимизацию на x86_64 в контексте нейросеток, то часто осмыслены intrinsics для AVX/AVX2 (и использование icc, который хорошо умеет в cilkplus с его calling conventions и его стандартной библиотекой imsl; но intel хочет много денег), которые дают выигрыш в разы по сравнению с переносимым кодом на Си и до порядка-полутора по сравнению с использованием numpy+openblas. Но даже использование mkl вместо openblas в моих кейсах давало ускорение в 1.5-2 раза.


Разница же между SSE4 и AVX на float'ах у меня на ноуте в пределах 5-10% при использовании gcc 6.1 (который уже умеет в cilkplus). Есть ощущение, что та же программа, собранная icc работала побыстрее, но у меня нет icc под рукой, чтобы проверить. Будет время — попробую сравнить на своём кейсе icc vs gcc.


О сравнении asm vs C+intrinsics vs portable C лучше распрашивать тех, кто с этим уже повоевал.

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

Не знаю, поэкспериментируйте. Вообще для разных packed int есть огромное количество intrinsics в SSE2/SSE3/SSSE3/SSE4. Подробно не разглядывал, но арифметика, биторые операции, различные перестановки кусков, арифметика с насыщением там должна быть.

Я много экспериментировал в этом направлении в эпоху Pentium-4. Использовал разные руководства. Нпр., Бурдаев и др., Ассемблер в задачах защиты информации, М.: 2004. Там было много трюков, казавшихся парадоксальными, нпр., вставка NOP. Но для целочисленных задач на Pentium-4 ничего особо хорошего я не получил. М.б. сейчас для новых CPU ситуация изменилась?
Кто знает хорошее руководство по ускорению таких задач на ассемблере?
Про goto много спорили и пришли к выводу, что исключительные ситуации, нпр., нужно обрабатывать иначе. И в Дельфи есть goto, но без него можно обойтись. Обычно и обходятся.

Это сильно зависит от языка. При отсутствии RAII или defer часто именно goto спасает.

Да. В Фортране-4 только goto и спасало ;)
И на языках высокого уровня обычно функция имеет доступ к глобальным переменным и может их менять.
Я не говорю о глобальных переменных. Я говорю о переменных, которые использует вызывающая функция.

Но в общем случае надо делать такие функции, чтобы они вызывались не слишком часто — нпр., не всегда в тело цикла стоит вставлять вызов функции :)
Угу. А ещё иногда компилятор может функцию просто подставить, да. Но если вы вспомите что даже у самого распоследнего Skylake кеш инструкций — 32KiB, то поймёте, что не всё так просто.

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

И в Дельфи есть goto, но без него можно обойтись. Обычно и обходятся.
И? Программы для web-сервисов вообще редко на C или Delphi пишут, хотя какой-нибудь python может отставать от аналогичной программы на C даже не в 2-3 раза, а в 100 раз по скорости. Приоритеты просто другие.
Я не говорю о глобальных переменных. Я говорю о переменных, которые использует вызывающая функция.
Можете передавать данные в функцию через глобальные переменные.
поймёте, что не всё так просто
Конечно, все не просто. А на ассемблере гораздо сложнее и гораздо проще баги плодить :)
Но и требования к ним другие
Требования те же самые — распределить регистры наиболее оптимальным образом.
Программы для web-сервисов
А причем здесь программы для web-сервисов? Там действительно другие приоритеты. Мы здесь обсуждаем возможность ускорения кода с помощью ассемблера.
А на ассемблере гораздо сложнее

Проще.
Труднее, но проще, а не сложнее. Вы видите свою программу целиком, можете использовать нестандартное размещение данных в регистрах. Компилятор же обязан придерживаться конвенции вызова.
У этой борьбы было продолжение. В начале 2000-х ко мне подошел один человек и сказал: спасибо вам, как вы были правы, заставляя нас учить Юникс и Си. Это те моменты, ради которых стоит жить и бороться. А во что выльется импортозамещение по Минкосвязи я думаю всем понятно.
Sign up to leave a comment.

Articles

Change theme settings