Сравнение парадигм программирования - Comparison of programming paradigms
В этой статье делается попытка изложить различные сходства и различия между различными парадигмами программирования в виде резюме как в графическом, так и в табличном формате со ссылками на отдельные обсуждения этих сходств и различий в существующих статьях Википедии.
Основные парадигмальные подходы
Есть два основных подхода к программированию:
- Императивное программирование - фокусы о том , как выполнить, определяет управление потоком , как заявления , которые изменяют программу состояния .
- Декларативное программирование - фокусируется на том, что выполнять, определяет логику программы, но не подробный поток управления .
Следующие примеры широко считаются основными парадигмами программирования, как это видно при измерении популярности языков программирования :
- Процедурное программирование , структурированное программирование - определяет шаги, которые программа должна предпринять, чтобы достичь желаемого состояния.
- Функциональное программирование - рассматривает программы как оценивающие математические функции и избегает состояния и изменяемых данных.
- Объектно-ориентированное программирование (ООП) - организует программы как объекты : структуры данных, состоящие из полей данных и методов вместе с их взаимодействиями.
Ниже приведены распространенные типы программирования, которые могут быть реализованы с использованием различных парадигм:
- Программирование, управляемое событиями - поток управления программой определяется событиями , такими как входные данные датчиков или действия пользователя ( щелчки мыши, нажатия клавиш) или сообщения от других программ или потоков .
- Программирование на основе автоматов - программа или часть рассматривается как модель конечного автомата или любого другого формального автомата.
- Реактивное программирование - это парадигма декларативного программирования, связанная с потоками данных и распространением изменений.
Подпрограммы, реализующие методы ООП, могут быть в конечном итоге закодированы в императивном, функциональном или процедурном стиле, который может или не может напрямую изменять состояние от имени вызывающей программы. Между парадигмами неизбежно есть некоторое совпадение, но основные особенности или идентифицируемые различия суммированы в этой таблице:
Различия в терминологии
Несмотря на то, что несколько (типов) парадигм программирования существуют параллельно (с иногда явно противоречивыми определениями), многие из лежащих в основе фундаментальных компонентов остаются более или менее одинаковыми ( константы , переменные , поля данных , подпрограммы , вызовы и т. Д.) И, таким образом, неизбежно должны быть включены в каждую отдельную парадигму с одинаково похожими атрибутами или функциями. Приведенная выше таблица не предназначена для определения точных сходств, а скорее как указатель того, где искать дополнительную информацию, основанную на различных именах этих сущностей в каждой парадигме. Еще более усложняют ситуацию нестандартизированные реализации каждой парадигмы на многих языках программирования , особенно на языках, поддерживающих несколько парадигм , каждая со своим собственным жаргоном .
Языковая поддержка
Синтаксический сахар - это улучшение функциональности программы за счет введения языковых функций, облегчающих конкретное использование, даже если конечный результат может быть достигнут без них. Одним из примеров синтаксического сахара могут быть классы, используемые в объектно-ориентированных языках программирования . Императивный язык C может поддерживать объектно-ориентированное программирование с помощью своих средств указателей на функции , приведения типов и структур. Однако такие языки, как C ++, стремятся сделать объектно-ориентированное программирование более удобным путем введения синтаксиса, специфичного для этого стиля кодирования. Более того, специализированный синтаксис подчеркивает объектно-ориентированный подход. Точно так же функции и синтаксис циклов в C (и других процедурных и структурированных языках программирования) можно рассматривать как синтаксический сахар. Язык ассемблера может поддерживать процедурное или структурированное программирование с помощью своих средств изменения значений регистров и выполнения ветвления в зависимости от состояния программы. Однако в таких языках, как C, введен синтаксис, специфичный для этих стилей кодирования, чтобы сделать процедурное и структурированное программирование более удобным. Возможности языка C # (C Sharp), такие как свойства и интерфейсы, аналогичным образом не позволяют использовать новые функции, но предназначены для того, чтобы сделать хорошие методы программирования более заметными и естественными.
Некоторые программисты считают эти возможности несущественными или даже несерьезными. Например, Алан Перлис однажды пошутил, ссылаясь на языки , разделенные скобками , что «синтаксический сахар вызывает рак точки с запятой » (см. « Эпиграммы по программированию» ).
Расширением этого является синтаксический сахарин или бесплатный синтаксис, который не упрощает программирование.
Сравнение производительности
Только при общей длине пути инструкций программа, написанная в императивном стиле, не использующая подпрограмм, будет иметь наименьшее количество. Однако двоичный размер такой программы может быть больше, чем у той же программы, закодированной с использованием подпрограмм (как в функциональном и процедурном программировании), и будет ссылаться на большее количество нелокальных физических инструкций, которые могут увеличивать количество промахов в кэше и накладные расходы на выборку инструкций в современных процессорах .
Парадигмы, которые широко используют подпрограммы (включая функциональные, процедурные и объектно-ориентированные) и не используют также значительного встроенного расширения (встраивание через оптимизацию компилятора ), следовательно, будут использовать большую часть общих ресурсов на связях подпрограмм. Объектно-ориентированные программы, которые намеренно не изменяют состояние программы напрямую, а вместо этого используют методы мутатора (или сеттеры ) для инкапсуляции этих изменений состояния, как прямое следствие, будут иметь больше накладных расходов. Это связано с тем, что передача сообщений - это, по сути, вызов подпрограммы, но с тремя дополнительными накладными расходами: динамическое выделение памяти , копирование параметров и динамическая отправка . Получение памяти из кучи и копирование параметров для передачи сообщений может потребовать значительных ресурсов, которые намного превышают ресурсы, необходимые для изменения состояния. Аксессоры (или геттеры ), которые просто возвращают значения частных переменных-членов, также зависят от аналогичных подпрограмм передачи сообщений вместо использования более прямого присваивания (или сравнения), увеличивающего общую длину пути.
Управляемый код
Для программ, выполняемых в среде управляемого кода , такой как .NET Framework , многие проблемы влияют на производительность, на которые существенно влияет парадигма языка программирования и различные используемые языковые функции.
Примеры псевдокода, сравнивающие различные парадигмы
Псевдокод сравнение императива, процедурный и объектно - ориентированных подходы , используемые для вычисления площади круга (πr²), предполагая отсутствие подпрограммы встраивания , нет макро препроцессоров , зарегистрировать арифметические и взвешивание каждой «шага» команд , как только одна инструкция - как грубая мера длины пути команд - представлена ниже. Шаг инструкции, который концептуально выполняет изменение состояния, в каждом случае выделяется жирным шрифтом. Арифметические операции, используемые для вычисления площади круга, одинаковы во всех трех парадигмах, с той разницей, что процедурная и объектно-ориентированная парадигмы заключают эти операции в вызов подпрограммы, что делает вычисление универсальным и многократно используемым. Тот же эффект может быть достигнут в чисто императивной программе с использованием препроцессора макросов только за счет увеличения размера программы (только на каждом сайте вызова макроса) без соответствующей пропорциональной стоимости времени выполнения (пропорциональной n вызовам, которые могут быть расположены в пределах внутренний цикл, например). И наоборот, встраивание подпрограмм компилятором может уменьшить процедурные программы до чего-то похожего по размеру на чисто императивный код. Однако для объектно-ориентированных программ, даже со встраиванием, сообщения все равно должны быть построены (из копий аргументов) для обработки объектно-ориентированными методами. Накладные расходы на вызовы, виртуальные или другие, не зависят от изменения потока управления, а зависят от затрат на сопутствующие соглашения о вызовах , таких как код пролога и эпилога , настройка стека и передача аргументов (см. Здесь более реалистичную длину пути инструкций, стек и другие расходы, связанные со звонками на платформе x86 ). См. Также здесь слайд-презентацию Эрика С. Робертса («Распределение памяти по переменным», глава 7), иллюстрирующую использование стека и кучи памяти при суммировании трех рациональных чисел в объектно-ориентированном языке Java .
Императив | Процедурный | Объектно-ориентированный |
---|---|---|
load r; 1 r2 = r * r; 2 result = r2 * "3.142"; 3 . . . . . . . . . . . . . . . . . . .... storage ............. result variable constant "3.142" |
area proc(r2,res): push stack 5 load r2; 6 r3 = r2 * r2; 7 res = r3 * "3.142"; 8 pop stack 9 return; 10 ............................................... main proc: load r; 1 call area(r,result); +load p = address of parameter list; 2 +load v = address of subroutine 'area'; 3 +goto v with return; 4 . . . . .... storage ............. result variable constant "3.142" parameter list variable function pointer (==>area) stack storage |
circle.area method(r2): push stack 7 load r2; 8 r3 = r2 * r2; 9 res = r3 * "3.142"; 10 pop stack 11 return(res); 12,13 ............................................... main proc: load r; 1 result = circle.area(r); +allocate heap storage; 2 +copy r to message; 3 +load p = address of message; 4 +load v = addr. of method 'circle.area' 5 +goto v with return; 6 . . .... storage ............. result variable (assumed pre-allocated) immutable variable "3.142" (final) (heap) message variable for circle method call vtable(==>area) stack storage |
Преимущества процедурной абстракции и объектно-ориентированного полиморфизма плохо иллюстрируются небольшим примером, подобным приведенному выше. Этот пример предназначен в основном для иллюстрации некоторых внутренних различий в производительности, а не для абстракции или повторного использования кода.
Подпрограмма, накладные расходы на вызов метода
Присутствие (вызываемой) подпрограммы в программе не вносит ничего лишнего в функциональность программы независимо от парадигмы, но может в значительной степени способствовать структурированию и универсальности программы, что значительно упрощает ее написание, изменение и расширение. Степень, в которой различные парадигмы используют подпрограммы (и связанные с ними требования к памяти), влияет на общую производительность всего алгоритма, хотя, как указал Гай Стил в статье 1977 года, хорошо спроектированная реализация языка программирования может иметь очень низкие накладные расходы на процедурную абстракцию. (но сожалеет, что в большинстве реализаций они редко достигают этого на практике - будучи «довольно легкомысленными или небрежными в этом отношении»). В той же статье Стил также рассматривает аргументы в пользу программирования на основе автоматов (с использованием вызовов процедур с хвостовой рекурсией ) и приходит к выводу, что «мы должны проявлять здоровое уважение к вызовам процедур» (потому что они мощные), но предлагал «использовать их экономно. "
По частоте вызовов подпрограмм:
- Для процедурного программирования степень детализации кода в значительной степени определяется количеством дискретных процедур или модулей .
- Для функционального программирования распространены частые вызовы библиотечных подпрограмм, но они часто могут быть встроены оптимизирующим компилятором.
- Для объектно-ориентированного программирования количество вызываемых вызовов методов также частично определяется степенью детализации структур данных и, таким образом, может включать в себя множество доступов только для чтения к объектам низкого уровня, которые инкапсулированы и, следовательно, недоступны ни в каком другом, более прямом виде, способ. Поскольку повышенная степень детализации является предпосылкой для большего повторного использования кода , наблюдается тенденция к детализации структур данных и соответствующему увеличению количества дискретных объектов (и их методов) и, следовательно, вызовов подпрограмм. Активно не поощряется создание божественных объектов . Конструкторы также добавляют к счетчику, поскольку они также являются вызовами подпрограмм (если они не встроены). Проблемы с производительностью, вызванные чрезмерной детализацией, могут не проявиться до тех пор, пока масштабируемость не станет проблемой.
- Для других парадигм, где может использоваться сочетание вышеперечисленных парадигм, использование подпрограмм менее предсказуемо.
Выделение динамической памяти для хранения сообщений и объектов
Уникально, что объектно-ориентированная парадигма включает динамическое выделение памяти из кучи как для создания объектов, так и для передачи сообщений. Тест 1994 года - «Затраты на выделение памяти в больших программах на C и C ++», проведенный Digital Equipment Corporation для разнообразного программного обеспечения с использованием инструмента профилирования на уровне инструкций, позволил измерить, сколько инструкций требовалось для распределения динамической памяти. Результаты показали, что наименьшее абсолютное количество выполненных инструкций составляло в среднем около 50, но другие достигали 611. См. Также «Куча: Удовольствия и боли» Мурали Р. Кришнана, в котором говорится: «Реализации кучи имеют тенденцию оставаться общими для всех платформ, и следовательно, имеют большие накладные расходы ". В статье IBM 1996 года «Масштабируемость алгоритмов динамического распределения памяти» Аруна Айенгара из IBM демонстрируются различные алгоритмы динамической памяти и их соответствующее количество инструкций. Даже рекомендуемый алгоритм MFLF I (HS Stone, RC 9674) показывает количество инструкций в диапазоне от 200 до 400. Приведенный выше пример псевдокода не включает реалистичную оценку этой длины пути выделения памяти или задействованных накладных расходов префикса памяти и связанного с этим мусора. накладные расходы по сбору. Настоятельно заявляя, что выделение кучи является нетривиальной задачей, один микрокаллокатор программного обеспечения с открытым исходным кодом , разработанный разработчиком игр Джоном В. Рэтклиффом , состоит из почти 1000 строк кода.
Динамически отправляемые вызовы сообщений v. Накладные расходы на прямые вызовы процедур
В своей аннотации « Оптимизация объектно-ориентированных программ с использованием статического анализа иерархии классов » Джеффри Дин, Дэвид Гроув и Крейг Чемберс с факультета компьютерных наук и инженерии Вашингтонского университета утверждают, что «интенсивное использование наследования и динамическое -связанные сообщения, вероятно, сделают код более расширяемым и многоразовым, но это также накладывает значительные накладные расходы на производительность по сравнению с эквивалентной, но нерасширяемой программой, написанной не объектно-ориентированным способом. В некоторых областях, таких как пакеты структурированной графики стоимость производительности за счет дополнительной гибкости, обеспечиваемой использованием сильно объектно-ориентированного стиля, является приемлемой. Однако в других областях, таких как библиотеки базовых структур данных, пакеты численных вычислений, библиотеки рендеринга и фреймворки моделирования на основе трассировки, стоимость передача сообщений может быть слишком большой, что вынуждает программиста избегать объектно-ориентированного программирования в «горячих точках» своего приложения ».
Сериализация объектов
Сериализация накладывает большие накладные расходы при передаче объектов из одной системы в другую, особенно когда передача осуществляется в удобочитаемых форматах, таких как Extensible Markup Language ( XML ) и JavaScript Object Notation ( JSON ). Это контрастирует с компактными двоичными форматами для не объектно-ориентированных данных. Как кодирование, так и декодирование значения данных объекта и его атрибутов участвуют в процессе сериализации, который также включает понимание сложных проблем, таких как наследование, инкапсуляция и скрытие данных.
Параллельные вычисления
Профессор Университета Карнеги-Меллона Роберт Харпер в марте 2011 года написал: «В этом семестре мы с Дэном Ликатой совместно преподаем новый курс функционального программирования для будущих студентов первого года обучения CS ... Объектно-ориентированное программирование полностью исключено из вводной программы. , потому что он одновременно антимодульный и антипараллельный по самой своей природе и, следовательно, не подходит для современной учебной программы CS. Предлагаемый новый курс по методологии объектно-ориентированного проектирования будет предложен на втором курсе для тех студентов, которые хотят изучать Эта тема."
Смотрите также
- Сравнение языков программирования
- Сравнение языков программирования (базовые инструкции)
- Гранулярность # вычисления
- Передача сообщений
- Подпрограмма
использованная литература
дальнейшее чтение
- "Распределитель памяти" Дуга Ли
- «Распределение динамической памяти и связанные структуры данных» ( Шотландский квалификационный орган )
- "Внутри распределителя памяти" доктор-новичок, доктор философии.
внешние ссылки
- Сравнение парадигм программирования от д - ра Рейчел Харрисон и г - н Линси Samaraweera
- Сравнение программирования парадигм: оценка функционального и объектно-ориентированных программ по Харрисон, Р. , Samaraweera, LG, Доби, MR и Льюис, PH (1996) С. 247-254.. ISSN 0268-6961
- «Основные парадигмы программирования» Питер Ван Рой
- «Концепции, методы и модели компьютерного программирования» (2004) Питера Ван Роя и Сейфа Хариди, ISBN 0-262-22069-5
- Истинная стоимость звонков - из блога "Жестче, лучше, быстрее, сильнее" компьютерного ученого Стивена Пиджа.