Языки низкого уровня программирования список: Языки программирования высокого и низкого уровня: какие из них вам следует изучать
Языки программирования высокого и низкого уровня: какие из них вам следует изучать
- Подробности
-
ноября 10, 2017 -
Просмотров: 46402
Когда вы пытаетесь выяснить, какой язык программирования начать изучать, вы, вероятно, столкнетесь с терминами «высокий уровень» и «низкий уровень». Люди постоянно говорят о языках программирования высокого и низкого уровня. Но что именно это означает? И что значит научиться писать код? Начнем с определений каждого.
Языки программирования «Высокого» и «Низкого уровня»
В этой статье я расскажу о языках «высокого» и «низкого уровня». Но особых критериев для определения этого нет. Просто имейте в виду, что это во многом зависит от вашей перспективы. Если вы программист C, Java может показаться довольно высокоуровневым. Если вы привыкли к Ruby, Java может показаться языком низкого уровня.
Машинный код и языки низкого уровня
Независимо от того, считается ли язык высокоуровневым или низкоуровневым (или где-то посередине), речь идет об абстракции. Машинный код не имеет абстракции — он содержит отдельные инструкции, передаваемые на компьютер. И поскольку машины имеют дело только с числами, они представлены в двоичном виде (хотя они иногда записываются в десятичной или шестнадцатеричной нотации).
Вот пример машинного кода:
В машинном коде операции должны быть указаны точно. Например, если часть информации должна быть извлечена из памяти, машинный код должен будет сообщить компьютеру, где в памяти его найти.
Писать непосредственно в машинный код возможно, но очень сложно.
Низкоуровневые языки программирования добавляют немного абстракции к машинным кодам. Эта абстракция скрывает конкретные инструкции машинного кода за декларациями, которые более читабельны для человека. Языки ассемблера являются языками самого низкого уровня рядом с машинным кодом.
В машинный код вы можете написать что-то вроде «10110000 01100001», но язык ассемблера может упростить это как «MOV AL, 61h». Между тем, что написано на языке ассемблера, и инструкциями, переданными машине, по-прежнему существует почти одно-однозначное соответствие.
Перейдя на более популярные языки программирования, вы придете к чему-то вроде C. Хотя этот язык не такого низкого уровня, как язык ассемблера, все еще существует сильное соответствие между тем, что написано на C и машинным кодом. Большинство операций, написанных на C, могут быть заполнены небольшим количеством инструкций машинного кода.
Языки программирования высокого уровня
Как и языки более низкого уровня, более высокие уровни охватывают широкий спектр абстракций. Некоторые языки, такие как Java (многие относят его к языкам программирования среднего уровня), все же дают вам большой контроль над тем, как компьютер управляет памятью и данными.
Другие, такие как Ruby и Python, очень абстрактны. Они дают вам меньше доступа к функциям нижнего уровня, но синтаксис гораздо легче читать и писать. Вы можете группировать вещи в классах, которые наследуют характеристики, поэтому вам нужно только объявить их один раз.
Переменные, объекты, подпрограммы и циклы являются важными частями языков высокого уровня. Эти и другие концепции помогут вам рассказать машине о множестве вещей с короткими, краткими заявлениями.
Если язык ассемблера имеет почти единообразное соответствие между его командами и командами машинного кода, язык более высокого уровня может отправлять десятки команд с помощью одной строки кода.
Важно отметить, что «языки программирования высокого уровня» могут включать в себя все, что более абстрактно, чем язык ассемблера.
Какой язык изучать: низкого или высокого уровня?
Это, безусловно, общий вопрос среди новых и начинающих программистов. Какие языки программирования лучше изучать: высокого или низкого уровня? Как и в случае со многими вопросами программирования, вопрос о языках программирования высокого и низкого уровня не так прост.
Оба типа языков имеют важные преимущества. Низкоуровневые языки, так как они требуют небольшой интерпретации компьютером, обычно работают очень быстро. И они дают программистам большой контроль над хранением, памятью и извлечением данных.
Однако языки высокого уровня интуитивно понятны и позволяют программистам писать код намного эффективнее. Эти языки также считаются «более безопасными», так как есть больше гарантий, которые препятствуют кодеру издавать плохо написанные команды, которые могут нанести ущерб. Но они не дают программистам такого же контроля над процессами низкого уровня.
Помня об этом, вот список популярных языков по шкале от низкого до высокого:
- C
- C++
- Java
- C#
- Perl
- Lisp
- JavaScript
- Python
- Ruby
- SQL
Конечно, это отчасти субъективно. И включает только крошечную часть доступных языков.
Но это должно дать вам некоторое представление о том, на каком уровне находятся интересующие вас языки.
Что Вы хотите делать?
При принятии решения о том, какой язык вы будете изучать, ваш первый вопрос должен быть следующим: что вы хотите запрограммировать?
Если вы хотите программировать операционные системы, ядра или что-то, что необходимо для работы на максимальной скорости, язык более низкого уровня может быть хорошим выбором. Большая часть Windows, OS X и Linux написана на языках C и C-производных языках, таких как C ++ и Objective-C.
Многие современные приложения пишутся на языках более высокого уровня или даже на предметно-ориентированных языках. Python и Ruby особенно популярны для веб-приложений, хотя HTML5 становится все более мощным. Языки, такие как Swift, C #, JavaScript и SQL, имеют свои сильные и слабые стороны.
Рассмотрите возможность обучения языкам обоих уровней
Недавно читал тему на форуме по программированию и наткнулся на интересное предложение: изучите сразу оба уровня. Вы получите более глубокое понимание типов абстракций, которые делают язык более высокого уровня более эффективным.
Конечно, изучение двух языков одновременно непросто, так что вы можете немного растянуть их изучение. И выбор двух языков, которые наиболее похожи, может быть полезным.
Опять же, мы вернемся к тому, о чем я говорил раньше: выберите язык, основанный на том, что вы хотите сделать. Проведите некоторое исследование, чтобы узнать, какие языки люди используют в своей области. Затем используйте эту информацию, чтобы выбрать язык высокого и низкого уровня, и начните изучать их.
Вы скоро увидите параллели, и вы получите гораздо более глубокое понимание того, как работает программирование.
Сосредоточьтесь на цели, а не на средстве
Существует множество критериев, которые вы можете использовать для выбора языка программирования. Одним из критериев является высокий и низкий уровень. Но почти в каждом случае критерии, которые вы должны использовать, — это то, что вы хотите запрограммировать.
Вашему проекту может быть полезен язык низкого уровня. Или это может быть намного более эффективно на высоком уровне. Вы должны сами выбрать правильный инструмент для работы. Сосредоточьтесь на своей цели, и каждый раз выбирайте правильный язык.
У вас есть опыт работы с языками высокого и низкого уровня? Вы предпочитаете одни другим? Поделитесь своими мыслями в комментариях ниже!
Читайте также
Языки программирования низкого уровня: низкоуровневые языки
Языки программирования низкого уровня или низкоуровневые традиционно появились первыми и в последующем стали базисом для развития всей ИТ индустрии. Они так называются потому, что в своих командах обращаются фактически напрямую к железу компьютера, его микропроцессору. Каждый процессор способен воспринимать определенный набор понятных только ему команд, поэтому к каждой модели немногочисленных устройств в то далекое время создавались свои языки. Изначально такие языки имели минимальный набор команд, и в отличие от высокоуровневых не имели такого большого количества абстрактных классов, разнообразного синтаксиса.
Все существующие языки программирования обычно делят на две большие группы: низкоуровневые и высокоуровневые. Сейчас большинство используемых языков относятся ко второй категории, но так было не всегда. Если смотреть историю развития программирования как можно ближе к ее истокам, то там картина была прямо противоположной.
Языки программирование низкого уровня, либо имеющие их возможности, активно применяются также и сейчас. Ведь только благодаря им возможно писать драйвера на компьютерное железо, подключаемые периферийные устройства, создавать операционные системы и ядра прошивок, а также многие другие важнейшие задачи. В военной сфере, инженерии, медицине программы должны управлять непосредственно определенными устройствами и их физическими параметрами, именно поэтому здесь также языки очень востребовательны. Список используемых сейчас низкоуровневых языков не такой большой, но, тем не менее, они все еще актуальны и способны решать важные задачи.
Некоторые представители низкоуровневых языков
Изначально первым языком программирования низкого уровня считается так называемый машинный код. Он имел вид набора последовательных команд, которые передавались на процессор в виде нулей и единиц. За нуль отвечало отсутствие электрического сигнала на устройстве, а за единицу – подача на него определенного импульса. Таким образом, череда сигналов заставляла процессор решать поставленные перед ним задачи.
Первые такие коды были способны заставить ЭВМ выполнять элементарные простые операции, куда относятся арифметические операции, передача между регистрами простейшей информации, сравнение двух или больше разных кодов и тому подобные действия. Позже машинные языки научились решать сложные задачи, то есть такие, которые состоят из набора элементарных команд. В зависимости от архитектуры процессора он может выполнять разное количество команд и с разной скоростью. Чтобы машинные коды работали нормально на нескольких представителях одного семейства процессоров, их начали разбивать на микропрограммы. Список языков машинного кода вряд ли возможно составить, ведь на каждый процессор и ЭВМ своего времени создавался свой язык.
Ассемблер
Следующими после машинных кодов появились так называемые языки ассемблера. Их главная особенность состоит в том, что набор возможных команд здесь уже существенно более широкий и он не обязан строго соответствовать командам данной ЭВМ. Благодаря этому открылись многие новые возможности. Главными преимуществами ассемблера по сравнению с машинным кодом называют:
- Возможность создания наиболее компактного кода, что увеличивает быстродействие машины;
- Возможность хранить в оперативной памяти часть выполнения задачи и использовать ее по желанию;
- Программы получили больший функционал, при этом их ресурсоемкость стала существенно ниже.
И это только некоторые сильные стороны, которые предлагает ассемблер. Примеры кода этого семейства языков все еще часто используются в образовательных целях и дают возможность лучше понять суть процесса взаимодействия с микропроцессорами.
Forth
Низкоуровневый язык, появившийся приблизительно в 70-х. Имел свои существенные преимущества, которые сделали его довольно популярным в определенных кругах специалистов. Машинные языки программирования к этому времени уже начали уходить в прошлое, поэтому функционал Форт многим пришелся по вкусу. С его помощью программисты, знающие архитектуру процессора, могли написать устройству ядро за считанные дни. Сложно сказать, какая парадигма программирования здесь поддерживается. При использовании языка опытным программистом здесь можно реализовать самые оригинальные задумки.
С
Один из самых известных и используемых языков программирования, который начал свое существование в 70-х и не сходит с арены до сих пор. Он по своей структуре оказался довольно близок к машинным языкам и ассемблеру, благодаря чему начал активно использоваться для создания операционных систем, драйверов, системного ПО. Часто Си причисляют к высокоуровневым языкам, но здесь все не так однозначно, ведь он также полностью совпадает с определением низкоуровневых языков, поэтому вполне может быть занесен и в эту категорию. Вопрос о том, какой язык программирования относится к низкоуровневым, не насколько простой и здесь нужно смотреть скорее функционал и назначение языка, нежели его официальные определения.
Если принимать, что языком программирования низкого уровня является тот язык, который может обращаться непосредственно к набору поддерживаемых процессором или другими устройствами команд, то Си вполне подходит под это определение.
В целом, история языков программирования низкого уровня довольно объемна, но известность получили далеко не многие представители этого класса. Все дело в том, что каждый язык такого типа создавался под определенные «железки», поэтому проследить все такие попытки очень и очень сложно.
Низкоуровневые языки программирования
Понятие о низкоуровневых языках программирования
Программирование всегда является компромиссом между пониманием принципов работы компьютера и формулированием задачи, которую предполагается выполнить с помощью вычислительной техники. Если уделять основное внимание выполняемой задаче, то можно, отвлекшись от знания компьютерной архитектуры, заложить в вычислительное устройство алгоритм, который оно не в состоянии будет выполнить. И, напротив, если принципы управления компьютером всецело учитываются, но слишком сложны, то формулирование задач для него становится чрезмерно трудоемким.
Управление процессором компьютера производится с помощью бинарных последовательностей, представляющих собой с точки зрения человеческого мышления числа, которыми пронумерованы команды процессора и ячейки памяти, в которых хранятся обрабатываемые данные. Команды процессора и способы обращения к ним хорошо документированы производителями чипов, но, поскольку команд довольно много, их запоминание и составление из них алгоритмов может оказаться затруднительным для специалистов, не прошедших длительную подготовку. Поэтому уже на ранних этапах развития компьютерной техники возникла необходимость в оптимизации процесса программирования.
Готовые работы на аналогичную тему
Определение 1
Низкоуровневые языки программирования (ЯП) представляют собой формализованные синтаксисы, призванные облегчить запоминание машинных кодов, применяемых для управления компьютерными процессорами.
Характеристики и назначение низкоуровневых языков
Программист, создающий алгоритм для компьютера на языке низкого уровня, обращаются непосредственно к ресурсам компьютера: процессору, памяти, периферийным устройствам. Это обеспечивает высокую скорость работы программ, поскольку, в отличие от высокоуровневых, в низкоуровневых языках отсутствуют скрытые фрагменты кода, добавляемые автоматически компилятором во время преобразования исходного текста в бинарный код.
Высокий уровень абстракции, позволяющий писать программы на высокоуровневых языках, не задумываясь об архитектуре компьютера, достигается за счет дополнительных накладных расходов: скрытых от программиста проверок и «обвязок», помогающих не задумываться, например, о выделении памяти под переменные и возвращении ее в доступное пространство после использования.
Низкоуровневые языки, напротив, предполагают, что ответственность за все внутрикомпьютерные ресурсы (время загрузки процессора, объемы выделяемой памяти и т.п.) программист берет на себя. По этой причине языки низкого уровня относятся к «небезопасным»: при написании с их помощью более-менее обширных программ, в коде появляется больше ошибок, чем при написании на языках высокого уровня. Низкоуровневое программирование применяется в основном для создания компактного ПО, например, для программирования систем реального времени (когда недопустимы задержки в работе), встраиваемых систем (микроконтроллеров), а также драйверов, управляющих внешними устройствами:
- принтерами,
- сканерами,
- камерами и т.п.
Рисунок 1. Прошивка микроконтроллера программой на ассемблере. Автор24 — интернет-биржа студенческих работ
Язык ассемблера
За понятием «ассемблер» кроется не язык программирования с четко формализованным синтаксисом, а скорее принципы создания синтаксисов для управления процессорами разных архитектур. Процессоры, выпускаемые современной промышленностью, обладают различными архитектурами, различными наборами команд, поэтому и языки ассемблера для них различаются. Более того, для одного и того же процессора может быть разработано несколько видов языка ассемблера (например, для работы в различных операционных системах). Однако создаются ассемблеры на основе одних и тех же принципов: громоздкие машинные коды заменяются более удобными для запоминания синтаксическими конструкциями, удобными для запоминания благодаря тому, что они напоминают слова обычного языка. Поскольку большая часть вычислительной техники разрабатывается в США и Европе, слова в ассемблерах обычно используются английские. Например, команда ADD означает сложение регистров процессора и соответствует английскому глаголу to add — складывать.
Программирование на ассемблере сводится к занесению в регистры компьютера данных из памяти (к которой с точки зрения процессора относятся и подключенные к компьютеру устройства), выполнению операций над ними и записи результата в память. За регистрами процессора закреплены буквенные обозначения, номенклатура которых зависит от типа процессора (Intel x86, ARM и др).
Рисунок 2. Регистры процессора. Автор24 — интернет-биржа студенческих работ
Особый регистр-указатель команд (EIP) отвечает за последовательность выполнения команд. Его изменение недоступно программисту. Управление регистром-указателем производятся командами условных и безусловных переходов, циклов, вызова процедур и возврата из них.
Несмотря на сложный синтаксис, на ассемблере можно писать довольно сложные приложения, в том числе приложения с графическим интерфейсом. Для облегчения работы используются макросы, внешние подключаемые библиотеки, а также возможности операционной системы, предоставляемые разработчикам в задокументированном виде (API).
Рисунок 3. Программа на ассемблере. Автор24 — интернет-биржа студенческих работ
Замечание 1
Язык программирования Си обычно относят к высокоуровневым, но он располагает возможностями и для низкоуровневого программирования: написанные на нем программы способны непосредственно обращаться к набору процессорных команд, выделять и освобождать память в произвольном порядке и т.п. Программы на Си также могут содержать в себе ассемблерные вставки.
Чем отличаются низко- средне- и высокоуровневые языки программирования
Перевод статьи Клеофаса Мулонго «What Are Low, Middle, And High Level Programming Languages?».
Языки программирования можно разбить на три большие категории. Это высокоуровневые, среднеуровневые и низкоуровневые языки. Эти три вида языков отличаются друг от друга по многим характеристикам.
К высокоуровневым относят языки программирования, которые созданы с расчетом на то, что их должны понимать люди. Они независимы: программистам не нужно знать, на каком оборудовании будет запускаться программа. Примеры таких языков – C++ и Python.
Среднеуровневые языки служат как бы связующим звеном между аппаратной и программной частью компьютера. Они действуют на уровне абстракции.
Низкоуровневые языки, в свою очередь, созданы для удовлетворения нужд конкретной компьютерной архитектуры и учитывают требования «железа».
В этой статье мы рассмотрим некоторые ключевые отличия между вышеуказанными видами языков программирования.
1. Скорость
Что касается скорости, программы, написанные на низкоуровневых языках, являются более быстрыми, чем написанные на средне- или высокоуровневых языках. Причина этого в том, что эти программы не нуждаются в интерпретации или компиляции. Они взаимодействуют непосредственно с регистрами и памятью.
Программы, написанные на высокоуровневых языках, относительно медленные. Главая причина этого в том, что они пишутся на «человеческом» языке. Компьютеру приходится переводить и интерпретировать их, прежде чем выполнить. Все эти процессы занимают время.
Скорость среднеуровневых языков занимает промежуточное положение. Ее не назовешь ни слишком высокой, ни слишком низкой.
2. Требования к памяти
Это еще один параметр, с помощью которого можно разграничить три вида языков программирования. Низкоуровневые языки очень эффективны в этом плане, они потребляют мало памяти. Это их очень отличает от высокоуровневых языков, которые являются очень ёмкими в этом плане. А вот программы на среднеуровневых языках уже не требуют столько памяти.
3. Легкость использования
Низкоуровневые языки дружественны к машинам, но недружественны к программистам. Человеку довольно сложно иметь дело с бинарным кодом и мнемоникой. То, что каждая инструкция создается для конкретной архитектуры компьютера, делает низкоуровневые языки более техническими. Короче говоря, учить их сложно.
Высокоуровневые языки, напротив, дружественны к людям. Они состоят из фраз на английском языке, которые легко понять и запомнить. Это поясняет, почему именно языки высокого уровня являются наиболее популярными.
4. Портируемость
В данном случае портируемость означает возможность использовать язык на разных компьютерах. Низкоуровневые языки являются менее портируемыми, поскольку их инструкции «машинозависимы». То есть, каждая инструкция написана для конкретной машины. Код, написанный для конкретной машины, не запустится на на компьютере с другой архитектурой.
Высокоуровневые языки не зависят от аппаратной части. Один и тот же код может без проблем использоваться на разных машинах (и даже на машинах с разной архитектурой). Это означает, что высокоуровневые языки являются хорошо портируемыми. Вы можете перенести программу, написанную на таком языке, из одной среды в другую – и она все равно будет работать.
5. Абстракция
В этом контексте абстракция это отношения между языком и аппаратной частью компьютера. В случае с низкоуровневыми языками абстракция минимальная или вообще нулевая. Эти языки легко взаимодействуют с памятью компьютера и регистрами.
Расхождение между среднеуровневыми языками и аппаратной частью довольно существенное. Оно больше, чем у низкоуровневых языков, но меньше, чем у высокоуровневых.
Логично предположить, что высокоуровневые языки имеют максимальный уровень абстракции. Это потому, что они работают на самом верхнем уровне компьютера, где взаимодействие с аппаратной частью минимальное.
Как видите, низко-, средне- и высокоуровневые языки заметно отличаются. Каждый вид языков служит для определенных целей, а потому, конечно же, ни один не является лучше другого.
И всё же C — низкоуровневый язык / Блог компании Badoo / Хабр
За прошедшие с момента появления языка C десятилетия было создано множество интереснейших языков программирования. Какие-то из них используются до сих пор, другие — повлияли на следующие поколения языков, популярность третьих тихо сошла на нет. Между тем архаичный, противоречивый, примитивный, сделанный в худших традициях своего поколения языков C (и его наследники) живее всех живых.
Критика C — классический для нашей индустрии эпистолярный жанр. Она звучит то громче, то тише, но в последнее время буквально оглушает. Пример — перевод статьи Дэвида Чизнэлла «C — не низкоуровневый язык», опубликованный в нашем блоге некоторое время назад. Про C можно говорить разное, в дизайне языка действительно много неприятных ошибок, но отказывать C в «низкоуровневости» — это уже слишком!
Чтобы не терпеть такую несправедливость, я собрался с духом и постарался определиться с тем, что есть язык программирования низкого уровня и чего хотят от него практики, после чего перебрал аргументы критиков C. Так получилась эта статья.
Вот некоторые аргументы критиков С, перечисленные в том числе и в статье Дэвида Чизнэлла:
- Абстрактная машина языка C слишком похожа на устаревшую архитектуру PDP-11, которая давно уже не соответствует устройству популярных современных процессоров.
- Несоответствие абстрактной машины C устройству реальных машин усложняет разработку оптимизирующих компиляторов языка.
- Неполнота и сложность стандарта языка ведут к разночтениям в реализациях стандарта.
- Доминирование C-подобных языков не позволяет исследовать альтернативные архитектуры процессоров.
Давайте для начала определимся с требованиями к низкоуровневому языку, после чего вернёмся к приведённым аргументам.
Общепризнанного определения языка низкого уровня не существует. Но перед обсуждением спорных моментов желательно иметь хоть какие-то исходные требования к предмету спора.
Никто не станет спорить с тем, что язык ассемблера находится на самом низком уровне. Но на каждой платформе он уникален, поэтому код на таком языке не может быть переносимым. Даже на обратно совместимой платформе может потребоваться задействовать какие-то новые инструкции.
Отсюда следует первое требование к языку низкого уровня: он должен сохранять общие для популярных платформ черты. Проще говоря, компилятор должен быть портируемым. Портируемость компилятора упрощает разработку компиляторов языка для новых платформ, а многообразие поддерживаемых компиляторами платформ избавляет разработчиков от необходимости переписывать прикладные программы для каждой новой машины.
С первым требованием конфликтуют пожелания разработчиков специальных программ: языков программирования, драйверов, операционных систем и высокопроизводительных баз данных. Программисты, пишущие эти программы, хотят иметь возможности ручной оптимизации, прямой работы с памятью и так далее. Словом, низкоуровневый язык должен позволять работать с деталями реализации платформы.
Поиск баланса между этими двумя требованиями — выделение общих для платформ аспектов и доступ к по возможности большему числу деталей — фундаментальная причина сложности разработки языка низкого уровня.
Заметьте, что для такого языка не так важны высокоуровневые абстракции, — для него важнее служить контрактом между платформой, компилятором и разработчиком. А если есть контракт, то возникает необходимость в независимом от конкретной реализации стандарте языке.
Наше первое требование — общие для целевых платформ черты — выражается в абстрактной машине языка, поэтому с неё мы и начнём обсуждение C.
Платформа, на которой появился язык C, — PDP-11. В её основе лежит традиционная архитектура фон Неймана, в которой программы выполняются последовательно центральным процессором, а память представляет собой плоскую ленту, где хранятся и данные, и сами программы. Такая архитектура легко реализуется в железе, и со временем все компьютеры общего назначения стали использовать именно её.
Современные усовершенствования архитектуры фон Неймана направлены на устранение её главного узкого места — задержек при обмене данными между процессором и памятью (англ. von Neuman bottleneck). Разница в производительности памяти и центрального процессора привела к появлению кеширующих подсистем процессоров (одноуровневых и позже — многоуровневых).
Но даже кешей в наши дни уже недостаточно. Современные процессоры стали суперскалярными (англ. superscalar). Задержки при получении инструкциями данных из памяти частично компенсируются внеочередным выполнением (англ. instruction-level parallelism) инструкций вкупе с предсказателем ветвлений (англ. branch predictor).
Последовательная абстрактная машина C (и многих других языков) имитирует работу не столько конкретно PDP-11, сколько любых компьютеров, устроенных по принципу архитектуры фон Неймана. К нему относятся архитектуры, построенные вокруг процессоров с единственным ядром: настольные и серверные x86, мобильные ARM, сходящие со сцены Sun/Oracle SPARC и IBM POWER.
Со временем в один процессор стали интегрировать несколько вычислительных ядер, в результате чего появилась необходимость поддерживать когерентность кешей каждого ядра и потребовались протоколы межъядерного взаимодействия. Архитектура фон Неймана, таким образом, была масштабирована на несколько ядер.
Изначальный вариант абстрактной машины С был последовательным, никак не отражая наличие взаимодействующих через память потоков исполнения программ. Появление в стандарте модели памяти расширило возможности абстрактной машины до параллельной.
Таким образом, утверждение о том, что абстрактная машина C давно не соответствует устройству современных процессоров, касается не столько конкретного языка, сколько компьютеров, использующих архитектуру фон Неймана, в том числе и в параллельном исполнении.
Но как практик хочу отметить следующее: можно считать, что фоннеймановский подход устарел, можно считать, что он актуален, но это никак не отменяет того факта, что популярные сегодня архитектуры общего назначения используют производные от традиционного подходы.
Стандартизированное и переносимое воплощение архитектуры фон Неймана — абстрактная машина C — удобно реализуется на всех основных платформах и поэтому пользуется своей популярностью как портативного ассемблера вполне заслуженно.
Наше второе требование к языку низкого уровня — доступ к низкоуровневым деталям реализации каждой из популярной платформ. В случае C это непосредственная работа с памятью и объектами в ней как с массивом байтов, возможность напрямую работать с адресами байтов и развитая арифметика указателей.
Критики C указывают на то, что в стандарте языка даётся слишком много гарантий касательно, например, расположения отдельных полей в структурах и объединениях. Вместе с указателями и примитивными механизмами циклов это усложняет работу оптимизатора.
Действительно, более декларативный подход позволил бы компилятору самостоятельно решать проблемы выравнивания данных в памяти или оптимального порядка полей в структурах; а высокоуровневые циклы дают свободу, необходимую при векторизации.
Позиция разработчиков C в данном случае такова: низкоуровневый язык должен позволять работать на уровне, достаточно низком для самостоятельного решения программистом задач оптимизации. В рамках C возможно поработать компилятором, выбрав, к примеру, инструкции SIMD и правильно разместив данные в памяти.
Другими словами, наше требование доступа к деталям реализации каждой из платформ вступает в конфликт с пожеланиями разработчиков оптимизирующих компиляторов именно в силу наличия низкоуровневых инструментов.
Интересно, что Чизнэлл в статье под названием «C — не низкоуровневый язык» парадоксально утверждает, что C — слишком низкоуровневый, указывая на отсутствие в нём высокоуровневых инструментов. Но практикам бывают нужны именно низкоуровневые инструменты, иначе язык не получится использовать для разработки операционных систем и других низкоуровневых программ, то есть он не будет удовлетворять второму из наших требований.
Отвлекаясь от описания проблем оптимизации именно C, хочу заметить, что в настоящий момент в оптимизирующие компиляторы высокоуровневых языков (тех же C# и Java) вложено не меньше усилий, чем в GCC или Clang. У функциональных языков тоже хватает эффективных компиляторов: MLTon, OCaml и другие. Но разработчики того же OCaml пока могут похвастаться производительностью в лучшем случае в половину скорости кода на C…
Чизнэлл приводит в своей статье ссылку на результаты опроса, проведённого в 2015 году: многие программисты допускали ошибки в решении задач на понимание стандартов C.
Полагаю, что кто-то из читателей имел дело со стандартом C. У меня версия C99 есть в бумажном виде, страниц эдак на 900. Это не лаконичная спецификация Scheme объёмом меньше 100 страниц и не вылизанный Standard ML, состоящий из 300. Удовольствие от работы со стандартом C не получает никто: ни разработчики компиляторов, ни разработчики документа, ни программисты.
Но надо понимать, что стандарт C разрабатывался постфактум, уже после появления множества «почти-еле-только местами» совместимых диалектов. Авторы ANSI C проделали огромную работу, обобщив существующие реализации и прикрыв бесчисленными «костылями» неортогональности в дизайне языка.
Может показаться странным, что такой документ вообще кто-то взялся реализовывать. Но C был реализован множеством компиляторов. Я не буду пересказывать чужие байки о зоопарке мира UNIX конца 80-х, тем более что сам в то время считал не слишком уверенно и только до пяти. Но, очевидно, стандарт был всем в индустрии действительно нужен.
Прекрасно то, что он есть и реализован по меньшей мере тремя крупными компиляторами и множеством компиляторов поменьше, в совокупности поддерживающими сотни платформ. Ни один из языков — конкурентов C, претендующих на корону короля языков низкого уровня, не может похвастаться таким многообразием и универсальностью.
На самом деле современный стандарт C не так уж и плох. Более-менее опытный программист способен разработать неоптимизирующий компилятор C в разумные сроки, что подтверждается существованием множества полулюбительских реализаций (тех же TCC, LCC и 8cc).
Наличие общепринятого стандарта означает, что C удовлетворяет последнему из наших требований к языку низкого уровня: этот язык строится от спецификации, а не конкретной реализации.
Но Чизнэлл приводит ещё один аргумент, возвращаясь к устройству современных процессоров общего назначения, реализующих варианты архитектуры фон Неймана. Он утверждает, что имеет смысл изменить принципы работы центрального процессора. Повторюсь, что эта критика касается не конкретно C, а самой базовой модели императивного программирования.
Действительно, существует множество альтернатив традиционному подходу с последовательным исполнением программ: модели SIMD в стиле GPU, модели в стиле абстрактной машины Erlang и другие. Но каждый из этих подходов имеет ограниченную применимость при использовании в центральном процессоре.
GPU, например, замечательно перемножают матрицы в играх и машинном обучении, но их сложно использовать для трассировки лучей. Другими словами, эта модель подходит для специализированных ускорителей, но не работает для процессоров общего назначения.
Erlang прекрасно работает в кластере, но эффективную quick sort или быструю хеш-таблицу на нём сделать трудно. Модель независимых акторов лучше использовать на более высоком уровне, в большом кластере, где каждый узел — всё та же высокопроизводительная машина с традиционным процессором.
Между тем, современные x86-совместимые процессоры давно уже включают в себя наборы векторных инструкций, схожие с GPU по назначению и принципам работы, но сохраняющие общую схему процессора в стиле фон Неймана в целом. Не сомневаюсь, что любые достаточно общие подходы к вычислениям будут включены в популярные процессоры.
Есть такое авторитетное мнение: будущее за специализированными программируемыми ускорителями. Под такие неординарные железки действительно имеет смысл разрабатывать языки с особой семантикой. Но компьютер общего назначения был и остаётся похожим на ту самую PDP-11, для которой так хорошо подходят C-подобные императивные языки.
В статье Чизнэлла есть фундаментальное противоречие. Он пишет, что для обеспечения скорости программ на C процессоры имитируют абстрактную машину C (и давно забытую PDP-11), после чего указывает на ограниченность такой машины. Но я не понимаю, почему это означает, что «C — не низкоуровневый язык».
Вообще же речь идёт не о недостатках C как языка, а о критике распространённых архитектур в стиле фон Неймана и вытекающей из них модели программирования. Но пока не похоже, что индустрия готова отказаться от привычной архитектуры (по крайней мере, не в процессорах общего назначения).
Несмотря на доступность множества специализированных процессоров вроде GPU и TPU, в настоящий момент архитектура фон Неймана правит бал и индустрии нужен язык, позволяющий в рамках популярнейшей архитектуры работать на как можно более низком уровне. Достаточно простой, портированный на десятки платформ и стандартизированный язык программирования — это C (и его ближайшие родственники).
При всём при этом у C хватает недостатков: архаичная библиотека функций, запутанный и противоречивый стандарт, грубые ошибки в дизайне. Но, судя по всему, кое-что создатели языка всё же сделали правильно.
Так или иначе, нам по-прежнему нужен язык низкого уровня, причём построенный именно для популярных фоннеймановских компьютеров. И пускай C устарел, но, видимо, любому его преемнику всё равно придётся отталкиваться от тех же самых принципов.
Язык программирования низкого уровня — это… Что такое Язык программирования низкого уровня?
- Язык программирования низкого уровня
Низкоуровневый язык программирования (язык программирования низкого уровня) — язык программирования, близкий к программированию непосредственно в машинных кодах используемого реального или виртуального (например, Java, Microsoft .NET) процессора. Для обозначения машинных команд обычно применяется мнемоническое обозначение. Это позволяет запоминать команды не в виде последовательности двоичных нулей и единиц, а в виде осмысленных сокращений слов человеческого языка (обычно английских).
Иногда одно мнемоническое обозначение соответствует целой группе машинных команд, выполняющих одинаковое действие над разными ячейками памяти процессора. Кроме машинных команд языки программирования низкого уровня могут предоставлять дополнительные возможности, такие как макроопределения (макросы). При помощи директив есть возможность управлять процессом трансляции машинных кодов, предоставляя возможность заносить константы и литеральные строки, резервировать память под переменные и размещать исполняемый код по определенным адресам. Часто эти языки позволяют работать вместо конкретных ячеек памяти с переменными.
Как правило, использует особенности конкретного семейства процессоров. Общеизвестный пример низкоуровнего языка — язык ассемблера, хотя правильнее говорить о группе языков ассемблера. Более того! Для одного и того же процессора существует несколько видов языка ассемблера! Они совпадают в машинных командах, но различаются набором дополнительных функций (директив и макросов).
Также к языкам низкого уровня условно можно причислить MSIL, применяемый в платформе Microsoft .NET, Форт, .
См.также
Ссылки
Wikimedia Foundation.
2010.
- Язык подонков
- Язык программирования сверхвысокого уровня
Смотреть что такое «Язык программирования низкого уровня» в других словарях:
Язык программирования высокого уровня — Высокоуровневый язык программирования язык программирования, разработанный для быстроты и удобства использования программистом. Основная черта высокоуровневых языков это абстракция, то есть введение смысловых конструкций, кратко описывающих такие … Википедия
Язык программирования Модула — алгоритмический язык, предназначенный для составления программ, работающих в реальном времени. В языке Модула используются: понятия модуля и процесса; средства программирования низкого уровня. Программа на языке Модула формируется из независимых… … Финансовый словарь
ЯЗЫК ПРОГРАММИРОВАНИЯ — это совокупность набора символов (алфавита) системы, правил образования (синтаксис) и истолкования конструкции из символов (семантика) для задания алгоритмов с использованием символов естественного языка. В самом общем виде формальный язык… … Большая политехническая энциклопедия
ЯЗЫК ПРОГРАММИРОВАНИЯ — (programming language) Язык, используемый для выдачи задания (программы) (program) компьютеру (computer). Существует два основных вида языков программирования: языки низкого уровня (low level languages) и языки высокого уровня (high level… … Словарь бизнес-терминов
Язык программирования Си — Си Семантика: процедурный Тип исполнения: компилируемый Появился в: 1969 73 г. Автор(ы): Кен Томпсон, Денис Ритчи Типизация данных: статическая Основные реализации … Википедия
Язык программирования C — Си Семантика: процедурный Тип исполнения: компилируемый Появился в: 1969 73 г. Автор(ы): Кен Томпсон, Денис Ритчи Типизация данных: статическая Основные реализации … Википедия
Язык программирования — Язык программирования формальная знаковая система, предназначенная для записи компьютерных программ. Язык программирования определяет набор лексических, синтаксических и семантических правил, задающих внешний вид программы и действия,… … Википедия
ЯЗЫК ПРОГРАММИРОВАНИЯ — формальный язык для описания данных (информации) и алгоритма (программы) их обработки на ЭВМ. Основу Я. п. составляют алгоритмические языки. Первыми Я. п. были внутренние машинные языки, представляющие собой системы команд конкретной ЭВМ,… … Большой энциклопедический политехнический словарь
Низкоуровневый язык программирования — (язык программирования низкого уровня) язык программирования, близкий к программированию непосредственно в машинных кодах используемого реального или виртуального (например, Java, Microsoft .NET) процессора. Для обозначения машинных команд обычно … Википедия
Cyclone (язык программирования) — У этого термина существуют и другие значения, см. Cyclone. Cyclone Семантика: процедурный … Википедия
С — не низкоуровневый язык / Блог компании Badoo / Хабр
Ваш компьютер не является быстрой версией PDP-11
Привет, Хабр!
Меня зовут Антон Довгаль, я С (и не только) разработчик в Badoo.
Мне попалась на глаза статья Дэвида Чизнэлла, исследователя Кембриджского университета, в которой он оспаривает общепринятое суждение о том, что С — язык низкого уровня, и его аргументы мне показались достаточно интересными.
В свете недавно обнаруженных уязвимостей Meltdown и Spectre стоит потратить время на выяснение причин их появления. Обе эти уязвимости эксплуатировали спекулятивное выполнение инструкций процессорами и позволяли атакующему получать результаты по сторонним каналам. Вызвавшие уязвимости особенности процессоров наряду с некоторыми другими были добавлены для того, чтобы программисты на C продолжали верить, что они программируют на языке низкого уровня, хотя это не так уже десятки лет.
Производители процессоров не одиноки в этом. Разработчики компиляторов C/C++ тоже внесли свою лепту.
Что такое язык низкого уровня?
Американский учёный в области компьютерных технологий и первый лауреат премии Тьюринга Алан Перлис дал такое определение:
«Язык программирования является низкоуровневым, если написанные на нём программы требуют внимания к несущественному».
Хотя это определение относится к С, оно не даёт понимания того, что люди желают видеть в языке низкого уровня. Различные свойства заставляют людей считать язык низкоуровневым. Представьте некую шкалу языков программирования с ассемблером на одном конце и интерфейсом к компьютеру Enterprise — на другом. Низкоуровневые языки «ближе к железу», тогда как высокоуровневые ближе к тому, как думают люди.
Чтобы быть «ближе к железу», язык должен предоставлять абстракции, которые соответствуют абстракциям целевой платформы. Легко доказать, что С был низкоуровневым языком в PDP-11. Последовательное выполнение программ, плоское адресное пространство, даже операторы пре- и постинкремента отлично ложились на режимы адресации PDP-11.
Быстрые эмуляторы PDP-11
Ключевая причина появления уязвимостей Spectre и Meltdown в том, что создатели процессоров не просто делали быстрые процессоры, а делали быстрые процессоры с интерфейсом PDP-11. Это важно, потому что позволяет программистам на С и дальше верить в то, что их язык близок к аппаратной части.
Код С предоставляет в основном последовательный абстрактный автомат (до C11 — полностью последовательный, если исключить нестандартные расширения). Создание нового потока — это вызов функции библиотеки, операция, которая довольно дорога. Поэтому процессоры, желая продолжать выполнять код C, полагаются на параллелизм на уровне команд (instruction-level parallelism, ILP). Они анализируют соседние операции и выполняют независимые параллельно. Это значительно усложняет процессоры и приводит к увеличению потребления энергии, но позволяет программистам писать по большей части последовательный код. В противоположность этому графические процессоры (GPU) достигают высокой производительности другим путём: они требуют написания параллельных программ.
Высокий параллелизм на уровне команд является прямой причиной появления Spectre и Meltdown. Современный процессор Intel выполняет до 180 инструкций одновременно (в отличие от последовательной абстрактной машины C, которая ожидает, что предыдущая инструкция выполнится перед тем, как начнётся выполнение следующей). Типичная эвристика кода на С показывает, что есть одно ветвление в среднем на каждые семь инструкций. Если вы хотите держать конвейер инструкций полным, то вам нужно угадать следующие 25 ветвей. Это, в свою очередь, добавляет сложности — неправильно угаданную ветвь процессор сначала просчитает, а потом результаты расчётов выбросит, что негативно влияет на энергопотребление. Эти выброшенные данные имеют видимые косвенные результаты, что и было использовано в атаках Spectre и Meltdown.
Переименование регистров потребляет много энергии и площади кристалла в современных процессорах. Его нельзя отключить или уменьшить его потребление энергии, что делает его неудобным в эпоху «тёмного кремния», когда транзисторы стоят мало, но задействованные транзисторы являются ценным ресурсом. Это устройство отсутствует в GPU, где параллелизм достигается путём использования потоков вместо попыток параллельного выполнения изначально последовательного кода. Если инструкции не имеют зависимостей, которые нужно перестраивать, то и в переименовании регистров нет необходимости.
Рассмотрим ещё одну фундаментальную часть дизайна С: плоскую память. Её не существует уже пару десятилетий. Современный процессор зачастую имеет три уровня кеширования между регистрами и основной памятью, чтобы таким образом уменьшить время на обращение к последней.
Кеш скрыт от программиста и потому недоступен из C. Эффективное использование кеша — это один из способов ускорить выполнение кода на современном процессоре, однако он полностью скрыт от абстрактной машины и программисты вынуждены полагаться на знание деталей имплементации кеша (например, что два выровненных 64-битных значения могут оказаться в одной строке кеша) для написания эффективного кода.
Оптимизация С
Одна из общих характеристик, приписываемых языкам низкого уровня, — скорость. В частности, их должно быть легко транслировать в быстрый код без сложного компилятора. Тот аргумент, что достаточно умный компилятор может сделать язык быстрым, зачастую игнорируется сторонниками С, когда они говорят о других языках.
К сожалению, с помощью простой трансляции нельзя получить быстрый код из С.
Архитекторы процессоров прикладывают героические усилия для создания чипов, которые могут выполнять код на С быстро. Но уровни быстродействия, которые ожидают увидеть программисты, достигаются только с помощью невероятно сложных оптимизаций, выполняемых компилятором.
Компилятор Clang (включая соответствующие части LLVM) — это около 2 млн строк кода. Для анализа и трансформации кода, которые необходимы для ускорения С, нужны около 200 000 строк кода (без учёта комментариев и пустых строк).
Например, для обработки большого количества данных в C нужно написать цикл, который обрабатывает каждый элемент последовательно. Для оптимального выполнения этого цикла на современном процессоре компилятор должен определить, что итерации цикла не зависят друг от друга. Ключевое слово restrict может помочь в этом случае — оно гарантирует, что записи в один указатель не будут мешать чтению из другого указателя. Эта информация в C гораздо более ограниченна, чем в таком языке, как Fortran, что является основной причиной того, что C не сумел вытеснить его из сферы высокопроизводительных вычислений.
После того как компилятор определил, что итерации независимы друг от друга, следующий шаг — попытка векторизировать результат, потому что пропускная способность современных процессоров в четыре—восемь раз выше для векторизированного кода, чем для скалярного. Язык низкого уровня для таких процессоров имел бы собственные векторные типы произвольной длины. В промежуточном представлении LLVM присутствуют как раз такие типы, потому что всегда проще разбить большие операции с векторами на несколько маленьких, чем конструировать бОльшие векторные операции.
На этом этапе оптимизаторам приходится бороться с правилами работы памяти C. С гарантирует, что структуры с одинаковым префиксом могут использоваться взаимозаменяемо, и предоставляет доступ к смещению полей структур в языке. Это означает, что компилятор не может изменить очерёдность полей в структуре или добавить выравнивание для улучшения векторизации (например, трансформировав структуру из массивов в массив структур или наоборот). Это обычно не является проблемой в языках низкого уровня, где есть возможность контроля над расположением полей в структуре, но это делает сложнее задачу ускорения C.
C также требует выравнивания в конце структуры, поскольку он гарантирует отсутствие выравнивания в массивах. Выравнивание — довольно сложная часть спецификации C, которая плохо взаимодействует с другими частями языка. Например, у вас должна быть возможность сравнить две структуры, используя метод сравнения без учёта типов (то есть функцию memcmp()), поэтому копия структуры тоже должна быть выровнена. В некоторых случаях копирование выравнивания занимает значительное время.
Рассмотрим две базовые оптимизации, которые производит компилятор C: SROA (scalar replacement of aggregates, скалярная замена агрегатов) и размыкание цикла.
SROA пытается заменить структуры и массивы фиксированного размера на отдельные переменные. Это позволяет компилятору обрабатывать доступ к ним независимо друг от друга и игнорировать операцию, если очевидно, что её результат не используется. В некоторых случаях косвенным эффектом этой оптимизации является удаление выравнивания.
Вторая оптимизация, размыкание цикла, преобразует цикл с условием в условие с разными циклами в обеих ветвях. Это меняет порядок выполнения в противовес утверждению о том, что программист знает, что будет выполняться на языке низкого уровня. И ещё это создаёт серьёзные проблемы с тем, как в C обрабатываются неопределённые переменные и неопределённое поведение.
В С неинициализированная переменная имеет неопределённое значение, которое может быть разным при каждом обращении. Это важно, поскольку позволяет реализовать ленивую переработку (lazy recycling) страниц памяти. Например, во FreeBSD реализация malloc() сообщает системе, что страницы более не задействованы, а система использует первую запись в страницу как доказательство того, что это не так. Обращение к только что выделенной памяти может получить старое значение, тогда операционная система может повторно использовать страницу памяти, после чего заменить её на заполненную нулями страницу при следующей записи в другое место страницы. Второе обращение к тому же месту страницы получит нулевое значение.
Если в условии используется неопределённое значение, то результат тоже не определён — может произойти что угодно. Представьте оптимизацию по размыканию цикла, где цикл выполняется ноль раз. В оригинале весь цикл является мёртвым кодом. В разомкнутой версии теперь есть условие с переменной, которая может быть не инициализирована.
В результате мёртвый код может быть преобразован в неопределённое поведение. Это только одна из многих оптимизаций, которые при более тщательном исследовании семантики C оказываются ненадёжными.
В итоге можно заставить код на C работать быстро, но только затратив тысячи человеко-лет на создание достаточно умного компилятора. Но и это возможно только при условии нарушения некоторых правил языка. Создатели компиляторов позволяют программистам на C представлять, что они пишут код, который «близок к железу», но им приходится генерировать машинный код, который ведёт себя по-другому, чтобы программисты продолжали верить в то, что они пишут на быстром языке.
Понимая C
Одним из базовых атрибутов языка низкого уровня является то, что программисты могут легко понять, как абстрактная машина языка переносится на физическую машину. Это определённо было так на PDP-11, где выражения на C транслировались в одну или две инструкции. Аналогично компилятор клал переменные в слоты стека и преобразовывал простые типы в понятные для PDP-11.
С тех пор реализации C стали гораздо сложнее — для поддержания иллюзии того, что C легко переносится на аппаратную платформу и работает быстро. В 2015 году результаты опроса среди программистов на C, авторов компиляторов и членов комитета по стандартизации показали, что существуют проблемы с пониманием C. Например, этот язык позволяет реализации добавлять выравнивание в структуры (но не в массивы), чтобы гарантировать, что все поля правильно выровнены для целевой платформы. Если вы заполните эту структуру нулями и потом укажете значение некоторым полям, будут ли в битах выравнивания нули? Согласно результатам опроса, 36% были уверены, что будут, а 29% опрошенных не знали ответа. В зависимости от компилятора и уровня оптимизации это может быть правдой (или нет).
Это довольно тривиальный пример, однако многие программисты или дают неправильный ответ, или не могут ответить вовсе.
Если добавить указатели, семантика C становится ещё более запутанной. Модель BCPL была довольно проста: все значения являются словами. Каждое слово — это или данные, или адрес в памяти. Память — это плоский массив ячеек с индексацией по адресу.
Модель С позволяет реализацию для разных платформ, включая сегментированные архитектуры, где указатель может состоять из ID сегмента и смещения, а также виртуальные машины со сборщиком мусора. Спецификация C ограничивает разрешённые операции с указателями, чтобы избежать проблем с такими системами. Ответ на Defect Report 260 содержит упоминание происхождения указателя:
«Реализации могут следить за происхождением набора битов и обращаться с содержащими неопределённое значение иначе, чем с теми, которые содержат определённое. Они могут обращаться с указателями по-разному в зависимости от их происхождения, даже если они одинаковы с точки зрения их битового значения».
К сожалению, слово «происхождение» отсутствует в спецификации C11, поэтому компиляторы сами решают, что оно означает. GCC и Clang, например, отличаются в том, сохраняет ли своё происхождение указатель, который конвертировали в целое и назад. Компиляторы могут решить, что два указателя на результаты malloc() при сравнении всегда дают отрицательный результат, даже если они указывают на один и тот же адрес.
Эти недопонимания не являются сугубо академическими. Например, уже наблюдались уязвимости, которые стали результатом переполнения знакового целого (неопределённое поведение в C) или разыменования указателя до его проверки на NULL, притом что компилятору было указано, что указатель не может быть NULL.
При наличии подобных проблем сложно ожидать от программиста полного понимания того, как программа на C транслируется на соответствующую архитектуру.
Представляя процессор не для C
Предлагаемые исправления для защиты от Spectre и Meltdown вызывают серьёзное ухудшение производительности, сводя на нет все достижения микроархитектуры за последнее десятилетие. Возможно, пора перестать думать о том, как сделать код на C быстрее, и вместо этого задуматься о новых моделях программирования на процессорах, которые созданы для скорости.
Есть множество примеров архитектур, которые не были сфокусированы на традиционном коде C и из которых можно черпать вдохновение. Например, такие ориентированные на многопоточность процессоры, как Sun/Oracle UltraSPARC Tx, не требуют столько кеша, чтобы держать занятыми свои исполнительные устройства. Исследовательские процессоры расширили этот концепт до очень большого количества аппаратно-планируемых потоков. Ключевая идея состоит в том, что с достаточным количеством потоков процессор может приостановить те потоки, которые ожидают данных, и наполнить исполнительные устройства инструкциями из других потоков. Проблема же состоит в том, что программы на C обычно имеют очень мало потоков.
ARM’s SVE (Scalar Vector Extensions, скалярные векторные расширения) — ещё одна аналогичная работа из Беркли, которая предлагает взглянуть на улучшенный интерфейс между программой и аппаратным обеспечением. Обычные блоки векторизации реализуют операции с векторами фиксированного размера и ожидают, что компилятор адаптирует алгоритм к указанному размеру. В противоположность этому интерфейс SVE предлагает программисту самостоятельно описать уровень параллелизма и ожидает, что аппаратная часть самостоятельно адаптирует его к имеющимся в наличии исполнительным устройствам. Использовать это в C сложно, потому что автовекторизатор должен рассчитать параллелизм на основании циклов в коде.
Кеши имеют большой размер, но это не единственная причина их сложности. Протокол поддержки когерентности кеша — одна из сложнейших составляющих современного процессора. Большая часть сложности появляется из-за того, что нужно поддерживать язык, в котором данные могут быть одновременно разделяемыми и изменяемыми. В качестве обратного примера можно привести абстрактную машину в стиле Erlang, где каждый объект или локальный, или неизменяемый. Протокол когерентности кеша для такой системы имел бы только два случая: изменяемые данные и разделяемые данные. Кеш программного потока, который перенесли на другой процессор, нужно явно инвалидировать, но это относительно редкая операция.
Неизменяемые объекты могут упростить кеши ещё больше, а также сделать некоторые операции дешевле. В проекте Maxwell от Sun Labs было отмечено, что объекты в кеше и недавно созданные объекты — почти всегда одни и те же. Если объекты умирают до того, как их исключают из кеша, то можно не записывать их в основную память и таким образом сэкономить потребление энергии. Проект Maxwell предложил сборщик мусора, который работал в кеше и позволял быстро перерабатывать память. С неизменяемыми объектами в куче и изменяемым стеком сборщик мусора становится очень простым конечным автоматом, который легко реализуется в аппаратной части и позволяет эффективно использовать относительно небольшой кеш.
Процессор, который создан исключительно для скорости, а не для компромисса между скоростью и поддержкой C, вероятно, должен поддерживать большое количество потоков, иметь большие блоки векторизации и более простую модель памяти. Выполнять код на C на таком процессоре будет сложно, поэтому, учитывая объём старого кода на C в мире, он вряд ли будет иметь коммерческий успех.
В сфере разработки программного обеспечения есть миф о том, что параллельное программирование — это сложно. Алан Кэй был бы очень удивлён, услышав это: он научил детей использовать модель акторов, с помощью которой они писали программы на более чем 200 потоков. Это неизвестно и программистам на Erlang, которые часто пишут программы с тысячами параллельных компонентов. Более правильно говорить, что параллельно программирование сложно на языке с абстрактной машиной, подобной C. И если обратить внимание на преобладание параллельного аппаратного обеспечения (от многоядерных процессоров до многоядерных GPU), то это просто ещё один способ сказать, что C не подходит для современного аппаратного обеспечения.
Что такое язык низкого уровня?
Мы уже изучили особенности низкоуровневого языка программирования c и его приложений. В этом уроке мы узнаем, что именно означает язык низкого уровня.
Что такое язык низкого уровня в компьютерных науках?
- Машинно-понятный язык.
- Ассемблер — общий язык высокого уровня
- Зависит от внутреннего машинного кода
- Быстро работает, но медленно пишет и понимает
Что такое язык машинного уровня?
- Машинный код — это единственный язык, который микропроцессор может обрабатывать напрямую без предварительного преобразования.
- В настоящее время программисты никогда не пишут программы непосредственно в машинном коде, потому что это требует внимания к многочисленным деталям, которые язык высокого уровня обрабатывает автоматически.
- Low Level Language требует запоминания или поиска числовых кодов для каждой используемой инструкции.
- По этой причине языки программирования второго поколения предоставляют один уровень абстракции поверх машинного кода.
Рекомендуемая статья: Код языка ассемблера в программе C
Пример
Ниже приведен пример функции в 32-битном машинном коде x86 для вычисления n-го числа Фибоначчи
.
8B542408 83FA0077 06B80000 0000C383 FA027706 B8010000 00C353BB 01000000 B9010000 008D0419 83FA0376 078BD98B C84AEBF1 5BC3
Языки высокого и среднего уровня во многих аспектах отличаются от языков низкого уровня.
Краткое изложение языков нижнего уровня
Низкоуровневые языки машинно понятны, сложны для написания, требуют больше усилий для кодирования и отладки.
.
Определение языка низкого уровня
Язык низкого уровня — это тип языка программирования, который содержит базовые инструкции, распознаваемые компьютером. В отличие от языков высокого уровня, используемых разработчиками программного обеспечения, код низкого уровня часто является загадочным и нечитаемым человеком. Двумя распространенными типами языков программирования низкого уровня являются язык ассемблера и машинный язык.
Программные программы и сценарии написаны на языках высокого уровня, таких как C #, Swift и PHP. Разработчик программного обеспечения может создавать и редактировать исходный код на языке высокого уровня с помощью среды разработки программирования или даже простого текстового редактора.Однако код не распознается напрямую ЦП. Вместо этого он должен быть скомпилирован на языке низкого уровня.
Ассемблер на шаг ближе к языку высокого уровня, чем машинный язык. Он включает в себя такие команды, как MOV (перемещение), ADD (добавить) и SUB (вычитание). Эти команды выполняют базовые операции, такие как перемещение значений в регистры памяти и выполнение вычислений. Язык ассемблера можно преобразовать в машинный язык с помощью ассемблера.
Машинный язык или машинный код — это самый низкий уровень компьютерных языков.Он содержит двоичный код, часто генерируемый путем компиляции исходного кода высокого уровня для определенного процессора. Большинству разработчиков никогда не нужно редактировать машинный код или даже смотреть на него. Только программисты, которые создают программные компиляторы и операционные системы, должны просматривать машинный язык.
Обновлено: 14 февраля 2019 г.
TechTerms — Компьютерный словарь технических терминов
Эта страница содержит техническое определение языка низкого уровня. Он объясняет в компьютерной терминологии, что означает язык низкого уровня, и является одним из многих программных терминов в словаре TechTerms.
Все определения на веб-сайте TechTerms составлены так, чтобы быть технически точными, но также простыми для понимания. Если вы найдете это определение низкоуровневого языка полезным, вы можете сослаться на него, используя приведенные выше ссылки для цитирования. Если вы считаете, что термин следует обновить или добавить в словарь TechTerms, напишите в TechTerms!
.