Почему мы должны обменять абстракцию на скорость?

11

Почему языки высокого уровня, по-видимому, никогда не достигают языков низкого уровня с точки зрения скорости? Примерами языков высокого уровня могут служить Python, Haskell и Java. Низкоуровневые языки было бы сложнее определить, но, скажем, C. Сравнения можно найти по всему Интернету и все они согласны с тем, что C намного быстрее, иногда в 10 и более раз.1

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

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


1 Примеры:

гоблин
источник
5
«и они все согласны с тем, что С намного быстрее» - это, безусловно, неправильно.
Рафаэль
2
В любом случае, я думаю, что на этот пост лучше всего ответить мой ответ на столь же непродуманный вопрос ; дублировать?
Рафаэль
2
Смотрите также здесь и здесь . Остерегайтесь ненужных ответов.
Рафаэль
Релевантно: stackoverflow.com/questions/6964392/… Миф о том, что все «языки высокого уровня» медленны, довольно печален.
xji
Я согласен с другими, что абстракция! = Медлительность, но я боюсь, что есть гораздо более серьезная проблема, о которой учителя информатики (я был один) не знают. Это означает, что для реальных программ (1000 строк кода и более) есть много способов сделать это, и они могут различаться по производительности на порядки. Просто думать о биг-о совершенно не хватает смысла. Проверьте здесь .
Майк Данлавей

Ответы:

19

Развенчать некоторые мифы

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

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

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

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

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

Простота, скорость, абстракция: выбирайте два.

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

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

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

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

Аннотация Не значит медленный

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

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

jmite
источник
2
«Код на C, как правило, быстрее, потому что люди, которым нужен каждый сантиметр производительности, используют C» - точно. Я хочу увидеть тесты формы «среднее время выполнения кода, написанного студентами / профессионалами X-го года с Y-летним опытом работы в Z». Я надеюсь , что ответ на C, как правило , «код не является правильным» для малых X и Y. Было бы очень интересно посмотреть , насколько больше опыта / знания вы должны эффективно использовать потенциал для обещаний производительности C.
Рафаэль
Haskell действительно исключение, которое подтверждает правило. ;) Я думаю, что C ++ нашел довольно разумный подход к GC по его умным указателям, если вы не вкладываете общие указатели и будете работать так же быстро, как C.
Kaveh
0

Вот несколько ключевых идей по этому вопросу.

  • Простое сравнение / изучение ситуации, чтобы сделать абстракцию против скорости - это Java против C ++. Java был разработан, чтобы абстрагироваться от некоторых низкоуровневых аспектов C ++, таких как управление памятью. в первые дни (около изобретения языка в середине 1990-х годов) обнаружение Java-мусора было не очень быстрым, но после нескольких десятилетий исследований сборщики мусора были чрезвычайно точно настроены / быстрыми / оптимизированными, поэтому сборщики мусора в значительной степени исключаются как утечка производительности на Java. например, смотрите даже этот заголовок 1998 года: тесты производительности показывают Java так же быстро, как C ++ / javaworld

  • Языки программирования и их длительная эволюция имеют внутреннюю «пирамидальную / иерархическую структуру» как своего рода трансцендентный шаблон проектирования. на вершине пирамиды находится то, что контролирует другие нижние участки пирамиды. другими словами, строительные блоки состоят из строительных блоков. это можно увидеть и в структуре API. в этом смысле большая абстракция всегда приводит к некоторому новому компоненту наверху пирамиды, управляющему другими компонентами. так что в некотором смысле это не так, что все языки находятся на одном уровне друг с другом, но что языки требуют рутины на других языках. Например, многие языки сценариев (python / ruby) часто вызывают библиотеки C или C ++, числовые или матричные процедуры являются типичным примером этого. поэтому существуют языки более высокого уровня и более низкого уровня, а языки высокого уровня, так сказать, называют языками более низкого уровня. в этом смысле измерение относительной скорости на самом деле не сопоставимо.

  • Можно сказать, что все время изобретаются новые языки, чтобы попытаться оптимизировать соотношение абстракции и скорости, то есть его ключевую цель проектирования. может быть, не столько, что большая абстракция всегда жертвует скоростью, а в том, что с новыми дизайнами всегда ищут лучший баланс. Например, Google Go был специально оптимизирован с учетом компромисса, чтобы быть одновременно и высокоуровневым, и производительным. см., например, Google Go: почему язык программирования Google может конкурировать с Java на предприятии / techworld

ВЗН
источник
0

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

Я хочу видеть следующее: каждая ли выполняемая инструкция «зарабатывает на себе», внося существенный вклад в конечный результат? В качестве слишком простого примера рассмотрим поиск записи в таблице из 1024 записей. Это 10-битная проблема, потому что программа должна «выучить» 10 бит, прежде чем она узнает ответ. Если алгоритм представляет собой двоичный поиск, каждая итерация вносит 1 бит информации, поскольку она уменьшает неопределенность в 2 раза. Таким образом, требуется 10 итераций, по одной на каждый бит.

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

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

Майк Данлавей
источник
0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Пол А. Клейтон
источник