Как скомпилировать бинарный файл

Обновлено: 07.07.2024

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

make

Общество свободного программного обеспечения считает утилиту make излюбленным инструментом для компиляции исходных кодов. Это дает следующие преимущества:

Разработчик экономит время, потому что у него есть возможность эффективно управлять процессом компиляции своего проекта.

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

Действия, которые необходимо выполнить для получения откомпилированной версии исходных кодов, обычно хранятся в файле с названием Makefile или (реже) в файле GNUMakefile. На самом деле, при вызове команды make читается этот файл из текущего каталога. Возможно явное указание этого файла для команды make с помощью опции -f.

Правила

Этот текст является только маленьким введением, инструкцией по выживанию в сложном мире make. Для того, чтобы узнать больше, мы советуем вам отправится на сайт APRIL , где размещена более подробная документация о make. [31] Для получения исчерпывающей информации обратитесь ко второму изданию O'Reilly Managing Projects with Make Andrew Oram и Steve Talbott .

Поехали!

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

make без аргументов производит просто компиляцию и не выполняет установки программы.

make install компилирует программу (правда не всегда), и инсталлирует необходимые файлы в нужное место в файловой системе. Некоторые файлы не всегда корректно инсталлируются (man, info), но их можно скопировать вручную. Иногда команду make install необходимо запускать несколько раз в подкаталогах. Обычно это случается с модулями, разработанными третьими лицами.

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

На первой стадии необходимо скомпилировать программу, для этого нужно набрать: (нереальный пример):

Объяснения

Если вы достаточно любопытны, чтобы заглянуть в файл Makefile, то вы найдете там известные команды (rm, mv, cp, и т.д.) и, кроме того, странные строки вроде этой $(CFLAGS).

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

Обычно определены следующие переменные:

CC: Это компилятор. Обычно это cc, который присутствует в большинстве свободных систем, также это может быть его аналог gcc. Если у вас возникают сомнения, ставьте gcc.

CFLAGS: это дополнительные опции компилятору, которые используются компилятором на первой стадии компиляции. Среди них:

-I<path>: указывает компилятору где искать дополнительные заголовочные файлы (к примеру: -I/usr/X11R6/include разрешает компилятору использовать файлы header, расположенные в allows /usr/X11R6/include).

-D<symbol>: определяет дополнительные символы, которые могут быть необходимы для программ, компиляция которых зависит от определения таких символов (к примеру: использовать заголовочный файл string.h в случае, если определено HAVE_STRING_H).

Строка для компиляции обычно выглядит следующим образом:

LDFLAGS (или LFLAGS): Этот аргумент используется во время конечной стадии компиляции. Среди них:

-L<path>: Определяет дополнительные пути поиска библиотек (например: -L/usr/X11R6/lib).

-l<library>: Определяет дополнительные библиотеки, которые будут использоваться в конечном этапе компиляции.

А что если. все это не работает?

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

glloq.c:16: decl.h: No such file or directory

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

Проверьте, действительно ли существует данный файл в следующих каталогах: /usr/include, /usr/local/include, /usr/X11R6/include или в каком-нибудь из подкаталогов. Если там нет, поищите по всему диску (с помощью утилит find или locate) и, если вы все же не можете найти этот файл, проверьте, действительно ли вы установили библиотеку, в которую входит этот заголовочный файл. Примеры использования утилит find и locate вы можете найти в соответствующих страничках руководства (man find; man locate).

Проверьте, действительно ли этот файл доступен для чтения (для проверки этого введите less <path>/<file>.h )

Если это каталог типа /usr/local/include или /usr/X11R6/include, вам прийдется добавить новый аргумент компилятору. Откройте соответствующий Makefile (будьте внимательны: нужный файл находится в каталоге, в котором произошла ошибка компиляции [32] ) в вашем любимом текстовом редакторе (Emacs, Vi, и т.д.). Перейдите на строку, в которой содержится ошибка, и добавьте строку -I<path> - где <path> это путь к каталогу, в котором вы отыскали недостающий заголовочный файл. Эти опции нужно добавить в конце строки, в которой вызывается компилятор (gcc, или $(CC)). Если вы не знаете куда добавить эти опции, допишите их в конце строк CFLAGS=<something> или CC=<something>, которые расположены в начале файла.

Запустите снова make и, если это опять не работает, перепроверьте снова то, что эти опции (смотрите предыдущий пункт) действительно были добавлены и получены компилятором.

glloq.c:28: `struct foo' undeclared (first use this function)

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

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

Станьте root-ом и выполните следующую команду:

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

Определение структуры выглядит так:

Проверьте, соответствует ли это тому, что имеется у вас. Если да, то это значит, что заголовочный файл не включен в .c файл, содержащий ошибку. Для устранения этого дефекта есть два способа:

скопируйте определение этой структуры в начало .c файла (на самом деле это не очень правильно, зато обычно помогает).

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

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

Ошибки синтаксического анализа (parse error)

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

Замечание

еще может быть parse error в старых библиотеках curses, если мне не изменяет память.

no space left on device (на диске кончилось место)

Эту проблему легко решить: недостаточно места на диске для того, чтобы создать бинарник из исходника. Решение состоит в расчистке места на том разделе, на котором находится каталог инсталляции (удалите временные файлы или исходники, деинсталлируйте программы, которые вы не используете). Если вы развернули его в /tmp, а не в /usr/local/src, это зря, потому что он напрасно занимает место на разделе /tmp. Проверьте, нет ли на диске файлов core>. Если найдутся, удаляйте их или заставьте их удалиться, если они принадлежат другому пользователю.

/usr/bin/ld: cannot open -lglloq: No such file or directory

Это означает, что программа ld (используемая gcc во время последнего шага компиляции) не может найти библиотеку. Для того чтобы ее включить, ld ищет файл, чье имя является аргументом типа -l<library>. Это файл lib<library>.so. Если ld не находит его, получается ошибка. Для решения проблемы делайте следующее:

Проверьте, есть ли файл на диске с помощью команды locate. Графические библиотеки обычно находятся в /usr/X11R6/lib. Например:

Если поиск ничего не принес, вы можете попытаться поискать с помощью команды find command (то есть: find /usr -name libglloq.so*). Если и это ничего не дало, вам прийдется установить его.

Как только библиотека будет размещена, проверьте ее на доступность для ld: файл /etc/ld.so.conf определяет, где искать библиотеки. Добавьте каталог-виновник в его конец (возможно, вам прийдется перегрузить компьютер, чтобы изменения вступили в силу). Кроме того, вы можете добавить этот каталог путем изменения содержимого переменной окружения LD_LIBRARY_PATH. Например, если каталог такой /usr/X11R6/lib, напишите:

(если ваша shell это bash).

Если до сих пор не работает, убедитесь что формат библиотеки это выполняемый файл (или ELF) командой file. Если он является символической ссылкой, проверьте что ссылка правильная и не указывает на несуществующий файл (например, так nm libglloq.so). Права файла тоже могут быть неверными (если вы используете аккаунт, отличный от root, и если библиотека защищена от чтения, например).

glloq.c(.text+0x34): undefined reference to `glloq_init'

Это проблема с символом, которая не была решена во время последнего шага компиляции. Обычно это проблема библиотеки. Может возникать по нескольким причинам:

первое, что необходимо выяснить, это предполагалось ли наличие символа в библиотеке. Например, если символ в начале это gtk, он принадлежит библиотеке gtk. Если имя библиотеки легко определимо, (frobnicate_foobar), вы можете вывести список символов библиотеки командой nm. Например,

Добавив параметр -o к nm, вы получите вывод имени библиотеки в каждой строке, что облегчит поиск. Давайте предположим, что мы ищем символ bulgroz_max, сырое решение поиска выглядит так:

Замечательно! Символ bulgroz_max определен в библиотеке frobnicate (большая буква T стоит перед ее именем). Теперь вам только нужно добавить строку -lfrobnicate в строку компиляции, отредактировав файл Makefile : добавьте ее в конец строки, где определены LDFLAGS или LFGLAGS (или, на худой конец, CC) , или в строку, соответствующую созданию конечного бинарного файла.

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

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

Segmentation fault (core dumped)

no space on /tmp

Компиляции необходимо временное рабочее пространство во время ее различных шагов; если ей не хватит места на диске, она упадет. Поэтому вы можете почистить свои разделы диска, но будьте осторожны с некоторыми программами, которые выполняются (X, сервер, каналы и т.д.), так как они могут упасть, если некоторые файлы будут удалены. Вы должны знать что делаете! Если раздел /tmp является частью раздела, который содержит не только его (например, root), поищите и, по возможности, удалите несколько core файлов.

make/configure в бесконечном рекурсивном цикле

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

Проблемы с датой могут заставить make бесконечно пересоздавать саму себя (или формировать снова и снова поддерево в бесконечном цикле рекурсии). В таком случае проблема решается обычно использованием команды touch (которая здесь используется для установки файлам по запросу текущего времени).

или так (грубо, но эффективно):

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

Все действия будут производиться на Ubuntu версии 16.04.
Используя компилятор g++ версии:

Состав компилятора g++

Мы не будем вызывать данные компоненты напрямую, так как для того, чтобы работать с C++ кодом, требуются дополнительные библиотеки, позволив все необходимые подгрузки делать основному компоненту компилятора — g++.

Зачем нужно компилировать исходные файлы?

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

Этапы компиляции:

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

driver.cpp:

1) Препроцессинг

Самая первая стадия компиляции программы.

Получим препроцессированный код в выходной файл driver.ii (прошедшие через стадию препроцессинга C++ файлы имеют расширение .ii), используя флаг -E, который сообщает компилятору, что компилировать (об этом далее) файл не нужно, а только провести его препроцессинг:

Взглянув на тело функции main в новом сгенерированном файле, можно заметить, что макрос RETURN был заменен:

В новом сгенерированном файле также можно увидеть огромное количество новых строк, это различные библиотеки и хэдер iostream.

2) Компиляция

На данном шаге g++ выполняет свою главную задачу — компилирует, то есть преобразует полученный на прошлом шаге код без директив в ассемблерный код. Это промежуточный шаг между высокоуровневым языком и машинным (бинарным) кодом.

Ассемблерный код — это доступное для понимания человеком представление машинного кода.

Используя флаг -S, который сообщает компилятору остановиться после стадии компиляции, получим ассемблерный код в выходном файле driver.s:

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

3) Ассемблирование

Так как x86 процессоры исполняют команды на бинарном коде, необходимо перевести ассемблерный код в машинный с помощью ассемблера.

Ассемблер преобразовывает ассемблерный код в машинный код, сохраняя его в объектном файле.

Объектный файл — это созданный ассемблером промежуточный файл, хранящий кусок машинного кода. Этот кусок машинного кода, который еще не был связан вместе с другими кусками машинного кода в конечную выполняемую программу, называется объектным кодом.

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

Получим машинный код с помощью ассемблера (as) в выходной объектный файл driver.o:

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

4) Компоновка

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

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

Получим исполняемый файл driver:

5) Загрузка

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

Запустим нашу программу:

Заключение

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

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

Компилирование кода Assembler
cseg segment ;чтобы работало в реальном режиме assume cs:cseg, ds:cseg org 100h main.

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

1. откомпилировать в объектный файл, без линковки
2. объединить объкетники в один файл копированием в бинарном режиме
(например в Windows так copy /b 1.obj + 2.obj + 3.obj)

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

Riffi
пункт 2 заведомо делать нельзя, т.к. файл .obj - это не просто набор кода, это файл, описаный по некотроым правилам, который включает в себя помимо кода кучу дополнительной информации

Evg,
пункт 2 заведомо делать нельзя
я в курсе. но поскольку ему нужен мусор, по пожалуйста

Добавлено через 1 минуту 31 секунду
ps. У тебя со зрением плохо, или не знаешь как тыкнуть в "обратиться по нику"?

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

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

Если твой изначальный исходник типа

То с ним такой мухлёж действительно можно сделать, ибо тут нет никиах обращений за пределы функции. Единственное, надо быть уверенным, что программные соглашения по передаче параметров на всех ОС совпадают. Обычно соглашения пишутся на архитектуру, но есть подозрения (особенно в части intel'а), что на разных ОС их могут немного менять. Т.е. в случае с данной функцией нужно быть уверенным, что при вызове значения x и y передаются на всех ОС на одних и тех же регистрах (или под одному и тому же смещению в стеке), аналогично с результатом

такими свойствами уже не обладает. Потому как в дополнение к самому байтовому представлению кода нужно знать, что в каком-то месте этого кода должен присуствовать адрес функции fabs. В объектном файле такая информация есть, а в чистом коде ей взяться неоткуда Вот для примера (IAR Embedded Workbench).
На выходе получается бинарик работающий без ОС. Поддерживает кучу платформ, в том числе различные микроконтроллеры и процессоры. К сожалению х86 не поддерживается. А мне нужно собрать не маленький проект на с++ под х86.

Можешь на словах объяснить, что за хрень и что делает? А то всё это читать и вникать желания нет.

Правда это всё для embedded систем, т.е. для систем, где ОС как таковой и нету. Я ничего не читал, но подозреваю, что получается некий статический бинарник, в котором нет никаких обращений к ОС. Либо они есть, но через специальные механизмы (на них должна быть спецификация и рализация на каждую платформу). Т.е. если ты хочешь, чтобы это хозяйство работаело под intel, то тебе нужно иметь некую среду, внутри которой запустится этот бинарник. Это всё лишь мой предположение

Для embedded точно. Но такие системы строятся в том числе и на основе любого процессора (есть они на Intel, AMD, и т.д.) и под каждую такую систему как правило есть ОС: Windows, Linux и т.д. (на всех платформах с процессорами на ARM ядрах есть точно). Соответственно если проект под одну из осей, то вопросов нет - под Windows есть Visual Studio, под Linux есть gcc. Мой проект в силу специфики должен работать без использования стороннего кода и соответственно без ОС. Платформа представляет собой по сути обычный PC: та же архитектура процессора, память ddr, шина pci и т.д. Только она намного менее расширяема и более компактна.
IAR на выходе дает бинарный код который запускается на заданном процессоре и содержит обращения только к самому коду и включенным в него статическим библиотекам. Код запускается по вектору RESET процессора. В принципе иаровский линковщик и весь проект можно настроить так, чтобы код запускался и под Windows, но не в этом суть. Мой код будет начинать работу с того места где начинает работу загрузчик ОС (если рассматривать стандартный РС) и который никакую ОС не пользует по определению. Я конечно могу написать все на асме, но проект не маленький и в качестве языка однозначно выбран с++. Вот и встал вопрос: чем такое сделать.

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

А можешь написать какую-нибудь конкретную короткую программу. Например, сложить числа 2 и 3 и выдать результат на экран (или что там эта штука умеет делать).

Т.е. в итоге тебе нужно на Си++ написать типа консольное приложение и слинковать его статически. При этом твоё приложение не должно содержать никаких обращений к библиотекам, не должно содержать высокоуровневых конструкций Си++ типа new (т.к. в конечном итоге оно работает через библиотечный malloc). Если работаешь под линухом, то можно сделать нормальный код, запустив ручками линкер, подстраховавшись при этом от того, что подключатся какие-либо библиотеки. Но вот точки взаимодействия с средой, в которой будет работать твоя программа, мне пока непонятны. А точки эти - точка входа (грубо говоря, каким образом мы попадаем в main, как предеаём тут argc, argv, envp) и обращения к "ОС"


Предположим, вы разработали приложение или библиотеку на Python и уже готовитесь передать его / её заказчику. И в этот момент появляются самые неудобные вопросы.

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

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

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

И вот тут настало время скомпилировать Python-код. Меня зовут Руслан, я старший разработчик компании «Цифровое проектирование». Сегодня я расскажу, как выбрать тот самый компилятор из множества доступных.

AOT/JIT

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

AOT-компиляция (ahead-of-time) – компиляция перед исполнением программы. Т.е. программа компилируется один раз, в результате компиляции получается исполняемый файл.

JIT-компиляция (just-in-time) – компиляция во время исполнения программы. Т.е. программа (а точнее, блоки программы) компилируется много раз - при каждом запуске.

Бенчмарк

Так как одной из целей является ускорение, необходимо оценить, насколько быстро работает скомпилированный код. В качестве бенчмарка будем использовать pyperfomance. К сожалению, pyperfomance не подошел для Cython и Pythran, потому что не позволяет визуализировать все возможности языка. Ускорения для Cython без модификации кода получить не удалось, а Pythran не умеет в пользовательские классы. Для них воспользуемся вычислением числа пи:

Эксперименты будем проводить на процессоре Intel Core i7 10510U. На CPython 3.9.7 время вычисления числа пи до 100.000.000 знака заняло 5.82 секунды.

AOT-компиляция Python

PyInstaller

PyInstaller упаковывает приложения Python в автономные исполняемые файлы в Windows, GNU / Linux, Mac OS X, FreeBSD, Solaris и AIX.

Устанавливается через pip:

pip install pyinstaller

После установки для создания исполняемого файла достаточно выполнить команду:

В результате будет создано:

*.spec – файл спецификации (используется для ускорения будущих сборок приложения, связи файлов данных с приложением, для включения .dll и .so файлов, добавление в исполняемый файл параметров runtime-а Python);

build/ – директория с метаданными для сборки исполняемого файла;

dist/ – директория, содержащая все зависимости и исполняемый файл.

Сборку приложения можно настроить с помощью параметров командной строки:

--name – изменение имени исполняемого файла (по умолчанию, такое же, как у сценария);

--onefile – создание только исполняемого файла (по умолчанию, папка с зависимостями и исполняемый файл);

--hidden-import – перечисление импортов, которые PyInstaller не смог обнаружить автоматически;

--add-data – добавление в сборку файлов данных;

--add-binary – добавление в сборку бинарных файлов;

--exclude-module – исключение модулей из исполняемого файла;

--key – ключ шифрования AES256. Да, приложение не будет содержать исходного кода, но его можно декомпилировать, например, так: Pyinstaller Extractor (.exe → .pyc) и uncompile6 (.pyc → .py). Для скрытия исходного кода можно обфусцировать байт-код Python с помощью шифрования.

У PyInstaller есть ограничения. Он работает с Python 3.5–3.9. Поддерживает создание исполняемых файлов для разных операционных систем, но не умеет выполнять кросскомпиляцию, т. е. необходимо генерировать исполняемый файл для каждой ОС отдельно. Более того, исполняемый файл зависит от пользовательского glibc, т. е. необходимо генерировать исполняемый файл для самой старой версии каждой ОС.

PyInstaller знает о многих Python-пакетах и умеет их учитывать при сборке исполняемого файла. Но не о всех. Например, фреймворк uvicorn практически весь нужно явно импортировать в файл, к которому будет применена команда pyinstaller. Полный список поддерживаемых из коробки пакетов можно посмотреть здесь.

Cython

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

Ставится Cython через pip:

pip install Cython

Рассмотрим его работу на примере с вычислением числа пи:

Немного модифицируем нашу функцию:

Cython → C:

cython -2 pi_approximater.pyx -o pi_approximater.c

Компилируем С-шный код:

gcc -g -O2 -shared -o pi_approximater.so pi_approximater.c python-config --includes --ldflags -fPIC

И замеряем время на бенчмарке: 3,66 секунды.

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

Создадим файл build.py:

Запустим: python build.py build_ext –-inplace

В результате будет сгенерирован .so/.dll файл.

Nuitka

Nuitka способна упаковывать приложения Python в автономные исполняемые файлы, а также транслировать Python-код в С для его последующей компиляции. Работает с разными версиями Python (2.6, 2.7, 3.3 - 3.9).

Ставится через pip:

pip install nuitka

Для генерации исполняемого файла достаточно выполнить команду:

python -m nuitka --follow-import some_program.py

Для компиляции модуля:

python -m nuitka --module some_module.py

Для компиляции пакета:

python -m nuitka --module some_package --include-package = some_package

Pythran

Pythran – статический компилятор Python, позиционирующий себя как ориентированный на научные вычисления и использующий преимущества многоядерных процессоров и блоков инструкций SIMD. Он транслирует Python-код, аннотированный описаниями интерфейса, в C++. До версии 0.9.5 (включительно) Pythran поддерживал Python 3 и Python 2.7. Последние версии поддерживают только Python 3.

pip install pythran

Генерируем бинарный файл <имя файла>.so:

pythran <имя файла>.py

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

Top level statements can only be assignments, strings,functions, comments, or imports

Добавим комментарий аннотации функции:

Скомпилируем и бенчмарк выдает 0,00007 секунды.

cx-Freeze

cx-Freeze – это набор скриптов и модулей преобразования скриптов Python в исполняемые файлы. cx_Freeze - кроссплатформенный. Он поддерживает Python 3.5.2 и выше.

Ставится с помощью pip:

pip install cx_Freeze

Для генерации исполняемого файла достаточно выполнить команду:

cxfreeze <имя файла>.py

Сборку можно настроить с помощью параметров командной строки:

-h, --help - справка;

-O - оптимизировать сгенерированный байт-код согласно PYTHONOPTIMIZE;

-c, --compress - сжать байт-код в zip-файлах;

-s, --silent - выводить только предупреждения и ошибки;

--target-dir=DIR, --install-dir=DIR - каталог, в который следует поместить целевой файл и все зависимые файлы.

Также возможно использование сценария сборки, например, так:

Сборка исполняемого файла:

python setup.py build

JIT-компиляция Python

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

PyPy

PyPy - интерпретатор языка программирования Python 2.7 и Python 3.7. Он написан на RPython и содержит:

компилятор байт-кода, отвечающий за создание объектов кода Python из исходного кода пользовательского приложения;

оценщик байт-кода, ответственный за интерпретацию объектов кода Python;

стандартное объектное пространство, отвечающее за создание и управление объектами Python, видимыми приложением.

PyPy поддерживает сотни библиотек Python, включая NumPy.

Основные особенности (сравнение с CPython):

Скорость. При выполнении длительно выполняющихся программ, когда значительная часть времени тратится на выполнение кода Python, PyPy может значительно ускорить ваш код.

Использование памяти. Программы Python, требующие много памяти (несколько сотен Мб или более), могут занимать меньше места, чем в CPython. Однако это не всегда так, поскольку зависит от множества деталей. Также базовый уровень потребления оперативной памяти выше, чем у CPython.

PyPy работает на Mac, Linux (не все дистрибутивы) или Windows.

Для запуска кода с помощью PyPy вместо команды python3 (как c помощью CPython) достаточно воспользоваться командой pypy3:

Pyston

Pyston - это форк CPython 3.8.8 с дополнительной оптимизацией производительности. В настоящее время он поддерживает установку только из исходников. Или с помощью pyenv.

В Pyston поддерживаются все возможности CPython, в том числе C API для разработки расширений на языке Си. Среди основных отличий Pyston от CPython помимо общих оптимизаций выделяется использование DynASM JIT и inline-кэширования.

Заключение

Итак, мы рассмотрели 5 фреймворков AOT-компиляции Python. Для любителей аналитики, ниже приведена таблица со сравнительным анализом.

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