Как работает компьютер программирование

Обновлено: 06.07.2024

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

"Прямо как в Терминаторе", - сказал бы Тота, если бы видел фильм Терминатор.

Объект быстро исчез, оставив на траве чёрный дымящийся ящик.

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

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

Тота обнаружил интересное свойство: если нажать Х и О последовательно, а потом опустить рычаг, то на короткое время вначале загорится кнопка О, а потом Х.

Я вам говорил, что Тота был невероятно умным? Самым умным в своей пещере.

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

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

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

Вскоре он открыл другие схемы: одна из них производила ненавистный Тоте звук, после другой выплевывался лист с какими-то пометками, а некоторые комбинации вообще ни к чему не приводили.

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

Он понял, что натренировал этого зверя, точно так же, как однажды натренировал волчонка.

Так что это за штука?

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

Давайте начнём с кнопок. Похоже машина "понимает" определённые комбинации и не понимает другие. Мы не знаем назначения кнопок и комбинации, поэтому я хочу назвать это "кодом", вроде "я понятия не имею что это, но, наверное, это что-то значит". Некоторые коды работают, некоторые — нет, так же как в речи некоторые звуки означают что-то, а другие — нет. "Язык", кажется, подходящее слово. Эта машина понимает определенный язык кодов.

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

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

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

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

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

Продолжая рассматривать эту странную машину, мы можем понять кое-что важное в компьютерах в целом:

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

И второе: компьютеры по-настоящему тупы.

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

как работает процессор и языки программирования

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

Все описанное ниже как всегда упрощено для лучшего понимания.

Процессор и оперативная память

процессор управляет всеми устройствами и процессами в компьютере

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

Процессор знает много команд и у каждой из них есть свой числовой код, например:


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


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

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

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

как работает процессор и оперативная память

Ассемблер

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

Для того, чтобы заставить процессор выполнить какую-то программу, например решить уравнение 2 + 2 * 2, нам нужно написать цепочку простых числовых команд.

процессор выполняет цепочка команд из числовых кодов команд

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

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

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

Помните примеры кодов команд, которые были указаны выше? Теперь они выглядят так:

Также к названию команд были добавлены операнды (один или более), которые дают дополнительную информацию для выполнения команды.

пример кода на языке assembler

Что-то слишком много непонятного кода для такой пустяковой задачи, не правда ли?

Языки программирования высшего уровня

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

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

Так стали появляться языки программирования высокого уровня.

Компилируемые языки

Первыми появились компилируемые языки программирования. К ним относится С, С++, Java и другие.

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

процессор не понимает код высшего порядка напрямую

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

как работает компилятор

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

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

компилятор языка C собрал исполняемый файл

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

Интерпретируемые языки

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

Вот тут в ход идут интерпретируемые языки программирования такие как: Python, PHP, Perl, Pascal и другие.

Это тоже языки высшего порядка, которые также упрощают написание кода. Но у них есть как минимум два преимущества перед компилируемыми языками:

как работает интерпретатор

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

Подытожим

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

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

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

set a 1
set b 2
add a b c
print c

Это простой язык, так что мы можем без опаски предположить, что этот код всего лишь выводит на экран 3. Оператор set берёт переменную и присваивает ей число (совсем как $a=1 в PHP). Оператор add берёт две переменные для добавления и сохраняет результат в третьей. Оператор print выводит её на экран.

Теперь давайте напишем программу, которая считывает каждое «выражение», находит оператор и операнды, а затем что-то с ними делает, в зависимости от конкретного оператора. Это довольно просто реализовать на PHP, как вы можете видеть на примере листинга 1.


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

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

  • Какой оператор нужно выполнить?
  • Это правильный оператор?
  • Есть ли у него нужное количество операндов?

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

Как можно заставить нашу программу «работать быстро»? Это можно сделать разными способами. Один из них, разработанный в Facebook, называется HipHop (я имею в виду «старую» систему HipHop, а не используемую сегодня HHVM). HipHop преобразовывал один язык (PHP) в другой (С++). Результат преобразования можно было с помощью компилятора С++ превратить в двоичный код. Его компьютер способен понять и выполнить без дополнительной нагрузки в виде интерпретатора. В результате экономится ОГРОМНОЕ количество вычислительных ресурсов и приложение работает гораздо быстрее.

Этот метод называется source-to-source компилированием, или транскомпилированием, или даже транспилированием (transpiling). На самом деле происходит не компилирование в двоичный код, а преобразование в то, что может быть скомпилировано в машинный код существующими компиляторами.

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

Транскомпилирование также используется для того, чтобы сделать «жёсткие» языки более простыми и динамичными. Например, браузеры не понимают код, написанный на LESS, SASS и SCSS. Но зато его можно транспилировать в CSS, который браузеры понимают. Поддерживать CSS проще, но приходится дополнительно транскомпилировать.

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

К сожалению, написание компилятора — одна из труднейших задач в информатике. Например, при компилировании в двоичный код нужно учитывать, на каком компьютере он будет выполняться: на 32-битной Linux, или 64-битной Windows, или вообще на OS X. Зато интерпретируемый скрипт может легко выполняться где угодно. Как и в PHP, нам не нужно переживать о том, где выполняется наш скрипт. Хотя может встречаться и код, предназначенный для конкретной ОС, что сделает невозможным выполнение скрипта на других системах, но это не вина интерпретатора.

Но даже если мы избавимся от стадии транскомпилирования, нам никуда не деться от компилирования. Например, большие программы, написанные на С (компилируемый язык), могут компилироваться чуть ли не час. Представьте, что вы написали приложение на PHP и вам нужно ждать ещё десять минут, прежде чем увидеть, работают ли внесённые изменения.

Если интерпретирование подразумевает медленное выполнение, а компилирование сложно в реализации и требует больше времени при разработке, то как работают языки вроде PHP, Python или Ruby? Они довольно быстрые!

Это потому, что они используют и интерпретирование, и компилирование. Давайте посмотрим, как это получается.

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

Выглядит очень заманчиво. По сути, подобным образом работают многие языки — PHP, Ruby, Python и даже Java. Вместо считывания и поочерёдного интерпретирования строк исходного кода, в этих языках используется другой подход:

  • Шаг 1. Считать скрипт (PHP) целиком в память.
  • Шаг 2. Целиком преобразовать/компилировать скрипт в байт-код.
  • Шаг 3. Выполнить байт-код посредством интерпретатора (PHP).

Процесс можно легко оптимизировать: предположим, что мы запустили веб-сервер и каждый запрос выполняет скрипт index.php . Зачем каждый раз грузить его в память? Лучше закешировать файл, чтобы можно было быстро преобразовывать его при каждом запросе.

Ещё одна оптимизация: после генерирования байт-кода мы можем использовать его при всех последующих запросах. Так что можно закешировать и его (главное, убедитесь, что при изменении исходного файла байт-код будет перекомпилироваться). Именно это делают кеши кода операций (opcode caches), вроде расширения OPCache в PHP: кешируют скомпилированные скрипты, чтобы их можно было быстро выполнить при последующих запросах без избыточных загрузок и компилирования в байт-код.

Наконец, последний шаг к высокой скорости — выполнение байт-кода нашим PHP-интерпретатором. В следующей части мы сравним это с обычными интерпретаторами. Во избежание путаницы: подобный интерпретатор байт-кода часто называется «виртуальной машиной», потому что в определённой степени он копирует работу машины (компьютера). Не надо путать это с виртуальными машинами, запускаемыми на компьютерах, вроде VirtualBox или VMware. Речь идёт о таких вещах, как JVM (Java Virtual Machine) в мире Java и HHVM (HipHop Virtual Machine) в мире PHP. Свои виртуальные машины есть у Python и Ruby. В некотором роде все они являются высокоспециализированными и производительными интерпретаторами байт-кода.

Каждая ВМ выполняет собственный байт-код, генерируемый конкретным языком, и они несовместимы между собой. Вы не можете выполнять байт-код PHP на ВМ Python, и наоборот. Однако теоретически возможно создать программу, компилирующую PHP-скрипты в байт-код, который будет понятен ВМ Python. Так что в теории вы можете запускать PHP-скрипты в Python (серьёзный вызов!).

Как выглядит и работает байт-код? Рассмотрим два примера. Возьмём PHP-код:


Теперь возьмём аналогичный пример на Python:


Python может напрямую сгенерировать коды операций ©python:


У нас есть два простых скрипта и их байт-коды. Обратите внимание, что байт-коды похожи на язык, который мы «создали» в начале статьи: каждая строка представляет собой оператор с любым количеством операндов. В байт-коде PHP к переменным добавляется префикс !, поэтому !0 означает переменную 0. Байт-коду не важно, что вы используете переменную $a: в ходе компилирования имена переменных теряют значение и преобразуются в числа. Это облегчает и ускоряет их обработку виртуальной машиной. Большинство необходимых «проверок» выполняются на стадии компилирования, что также снимает нагрузку с виртуальной машины и увеличивает скорость её работы.

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

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

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

Рассмотрим следующие PHP-выражения:


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

Токенизация (лексинг) заключается в преобразовании исходного PHP-кода — без понимания его значения — в длинный список токенов. Это сложный процесс, но в PHP вы можете довольно легко сделать нечто подобное. Представленный в листинге 2 код выдаёт следующий результат:


Строковое значение преобразуется в токены:

  • <?php преобразован в токен T_OPEN_TAG,
  • $a преобразован в токен T_VARIABLE, который содержит значение $a.

При парсинге токенов мы должны следовать некоторым «правилам», составляющим наш язык. Например, может быть правило: первый обнаруженный токен в программе должен быть T_OPEN_TAG (соответствует <?php).

Ещё одно возможное правило: присваивание может состоять из любого T_VARIABLE, после которого идёт символ =, а затем T_LNUMBER, T_VARIABLE или T_CONSTANT_ENCAPSED_STRING. Иными словами, мы разрешаем $a = 1, или $a = $b, или $a = 'foobar', но не 1 = $a. Если парсер обнаруживает серию токенов, не удовлетворяющих какому-то из правил, автоматически будет выдана ошибка синтаксиса. В общем, парсинг — это процесс, определяющий язык и позволяющий нам создавать синтаксические правила.

Посмотреть список правил, используемых в PHP, можно по адресу. Если ваш PHP-скрипт удовлетворяет синтаксическим правилам, то проводятся дополнительные проверки, чтобы подтвердить, что синтаксис не только правильный, но и осмысленный: определение public abstract final final private class foo() <> может быть корректным, но не имеет смысла с точки зрения PHP. Токенизация и парсинг — хитрые процессы, и зачастую для их выполнения берут сторонние приложения. Нередко используются инструменты вроде flex и bison (в PHP тоже). Их можно рассматривать и в качестве транскомпиляторов: они преобразуют ваши правила в С-код, который будет автоматически компилироваться, когда вы компилируете PHP.

Парсеры и токенизаторы полезны и в других сферах. Например, они используются для парсинга SQL-выражений в базах данных, и на PHP также написано немало парсеров и токенизаторов. У объектно-реляционного маппера Doctrine есть свой парсер для DQL-выражений, а также «транскомпилятор» для преобразования DQL в SQL. Многие движки шаблонов, в том числе Twig, используют собственные токенизаторы и парсеры для «компилирования» файлов шаблонов обратно в PHP-скрипты. По сути, эти движки тоже транскомпиляторы!

После токенизации и парсинга нашего языка мы можем генерировать байт-код. Вплоть до PHP 5.6 он генерировался во время парсинга. Но привычнее было бы добавить в процесс отдельную стадию: пусть парсер генерирует не байт-код, а так называемое абстрактное синтаксическое дерево (Abstract Syntax Tree, AST). Это древовидная структура, в которой абстрактно представлена вся программа. AST не только упрощает генерирование байт-кода, но и позволяет нам вносить изменения в дерево, прежде чем оно будет преобразовано. Дерево всегда генерируется особым образом. Узел дерева, представляющий собой выражение if, обязательно имеет под собой три элемента:

  • первый содержит условие (вроде $a == true );
  • второй содержит выражения, которые должны быть выполнены, если соблюдается условие true ;
  • третий содержит выражения, которые должны быть выполнены, если соблюдается условие false (выражение else ).

В результате мы можем «переписать» программу до того, как она будет преобразована в байт-код. Иногда это используется для оптимизации кода. Если мы обнаружим, что разработчик раз за разом перевычислял переменную внутри цикла, и мы знаем, что переменная всегда имеет одно и то же значение, то оптимизатор может переписать AST так, чтобы создать временную переменную, которую не нужно каждый раз вычислять заново. Дерево можно использовать для небольшой реорганизации кода, чтобы он работал быстрее: удалить ненужные переменные и т. п. Это не всегда возможно, но когда у нас есть дерево всей программы, то такие проверки и оптимизации выполнять куда легче. Внутри AST можно посмотреть, объявляются ли переменные до их использования или используется ли присваивание в условном блоке ( if ($a = 1) <> ). И при обнаружении потенциально ошибочных структур выдать предупреждение. С помощью дерева можно даже анализировать код с точки зрения информационной безопасности и предупреждать пользователей во время выполнения скрипта.

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

В PHP 7.0 появился новый движок парсинга (Zend 3.0), который тоже генерирует AST во время парсинга. Поскольку он достаточно свежий, с его помощью можно сделать не так много. Но сам факт его наличия означает, что мы можем ожидать появления в ближайшем будущем самых разных возможностей. Функция token_get_all() уже принимает новую, недокументированную константу TOKEN_PARSE, которая в будущем может использоваться для возвращения не только токенов, но и отпарсенного AST. Сторонние расширения вроде php-ast позволяют просматривать и редактировать дерево прямо в PHP. Полная переработка движка Zend и реализации AST откроет PHP для самых разных новых задач.

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

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

Мы получаем скорость компилируемого кода и наслаждаемся преимуществами кода интерпретируемого. Подобные системы могут работать быстрее обычного интерпретируемого байт-кода, иногда гораздо быстрее. Речь идёт о JIT-компиляторах (just-in-time, точно в срок). Название подходит как нельзя лучше. Система обнаруживает, какие части байт-кода могут быть хорошими кандидатами на компилирование в двоичный код, и делает это в тот момент, когда нужно выполнять эти самые части. То есть — точно в срок. Программа может стартовать немедленно, не нужно ждать завершения компилирования. В двоичный код преобразуются только самые эффективные части кода, так что процесс компилирования автоматизируется и ускоряется.

Хотя не все JIT-компиляторы работают таким образом. Некоторые компилируют все методы на лету; другие пытаются только определить, какие функции нужно скомпилировать на ранней стадии; третьи будут компилировать функции, если они вызываются два и больше раза. Но все JIT’ы используют один принцип: компилировать маленькие куски кода, когда они действительно нужны.

Ещё одно преимущество JIT’ов по сравнению с обычным компилированием заключается в том, что они способны лучше прогнозировать и оптимизировать на основании текущего состояния приложения. JIT’ы могут динамически анализировать код во время runtime и делать предположения, на которые неспособны обычные компиляторы. Ведь во время компиляции у нас нет информации о текущем состоянии программы, а JIT’ы компилируют на стадии выполнения.

Если вам доводилось работать с HHVM, то вы уже использовали JIT-компилятор: PHP-код (и надмножественный язык Hack) преобразуется в байт-код, запускаемый на виртуальной машине HHVM. Машина обнаруживает блоки, которые могут быть безопасно преобразованы в двоичный код; если это ещё не было сделано, она это делает и запускает их. По окончании запуска ВМ переходит к следующим байт-кодам, которые могут быть преобразованы в двоичный код.

PHP 7 не выполняется на JIT-компиляторе, но зато его новая система превосходит все предыдущие релизы. Сейчас во всех его компонентах проводятся эксперименты со статическим анализом, динамической оптимизацией, и даже есть простые JIT-системы. Так что не исключено, что однажды даже PHP 7 окажется позади!

Большинство новичков в программировании, при написании очередной программы на уровне "Hello world", просто нажимают кнопку Run и даже не задумывается о том, что происходит с их кодом в момент компиляции. А зря.

Подписывайтесь на канал, ставьте лайк и мы начинаем!

Для чего мне это нужно?

Если у вас сейчас появился такой вопрос, то вот ответ на него:

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

Надеюсь, вы меня понимаете. Если всё Ok, давайте наконец начнём!

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

Шаг первый - Препроцессор

В момент нажатия кнопки Run , вы отправляете свой код в компилятор. Всё начинается с препроцессора:

На всякий случай, этот символ выглядит так:

Итак, препроцессор ищет в вашем коде директивы, затем выполняет их.

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

Один из самых распространённых примеров :

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

Шаг второй. Анализ.

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

Лексический анализ

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

Синтаксический анализ

После лексического анализа парсер (синтаксический анализатор), на основе грамматики языка, распознает построенные из лексем выражения и операторы, выявляет синтаксические ошибки.

Семантический анализ

Целью этого вида анализа является выявление разного рода смысловых ошибок. Например, повторное описание переменной.

Шаг три. Почти финал.

Вам было тяжело? Надеюсь, что нет. Мы скоро закончим.

Итак, если ошибок после всех предыдущих этапов нет - > начинается генерация кода. При этом, конкретный вид генерируемого кода зависит от того, приложение какого типа создаётся.

Для обычного Windows приложения строится объектник (объектный модуль) - заготовка исполняемой программы в машинном коде.

Финал?

Далее судьба этого приложения тоже зависит от типа приложения.

Для Windows приложения компоновщик (линкер) формирует исполняемый .exe файл, подключая к объектному модулю другие такие же модули, в том числе, содержащие элементы стандартных библиотек, которые вы используете в своём проекте (например, stdio).

Если программа состоит из нескольких файлов, они компилируются по-отдельности и объединяются на этапе компоновки. После всего этого мы имеем готовый .exe файл, который можно запускать.

Заключение

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

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

Спасибо за внимание, с вами был Дад.

Пишите в комментариях, что вы думаете о новом "логотипе" и названии канала, нравится ли вам?

Также пишите ваше мнение о данной статье, считаете ли вы её полезной. Любые ваши отклики улучшают качество контента на этом канале!

Читайте также: