LINUX.ORG.RU
ФорумTalks

Фронтенд к компиляторам vs компиляция в С

 


0

3

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

А собственно, за счёт чего он (прирост производительности) при этом получается?

★★★★★

А собственно, за счёт чего он (прирост производительности) при этом получается?

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

P.S. «Преждевременная оптимизация — корень большинства проблем в программировании.» // Дональд Кнут.

quickquest ★★★★★
()

Исторически так сложилось, что компиляторы каких либо ЯП часто сперва компилируют в С, затем полученный код уже на живую платформу.

Нет. Так конечно делают, но это не является очень уж распространенной практикой.

А собственно, за счёт чего он (прирост производительности) при этом получается?

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

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

И вообще, язык Си не является чем-то таким фундаментальным, процессоры специально затачивают под Си, чтобы он там эффективно исполнялся (см. https://habr.com/ru/company/badoo/blog/420407/). Есть архитектуры, на которые язык Си плохо ложится с его последовательным исполнением, циклами и рекурсией. Dataflow-архитектуры по Си вообще не заточены. Под FPGA код на Си без кривых костылей не скомпилируешь.

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от SZT

И вообще, язык Си не является чем-то таким фундаментальным

согласен, C можно было бы тем с же успехом заменить паскалем, адой, фортраном и много чем ещё, но большинство языков, тем не менее, близки к нему организационно - как минимум, все императивные тривиально на него «ложатся»

Dataflow-архитектуры по Си вообще не заточены. Под FPGA код на Си без кривых костылей не скомпилируешь.

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

Например, язык Си навязывает определенный способ организации стековых фреймов для функций, определенные соглашения вызова

вообще-то, это вполне управляется прагмами

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

это да

очень уж распространенной практикой

такая практика распространена, очень или нет - дело десятое

за счет того, что семантика Си не всегда позволяет выразить некоторые вещи, которые можно использовать для оптимизации

но ведь она и для целевого языка тогда потеряется? много ли языков вообще содержат в своей семантике хоть какие-то вещи, которые можно использовать для оптимизации? C++, rust, D, fortran. А ещё?

собственно, вопрос, как раз, об этом: хотелось бы увидеть примеры таких вещей, безусловно, они существуют

next_time ★★★★★
() автор топика
Ответ на: комментарий от quickquest

ибо критерии оптимальности по скорости и по памяти у трансляции и компилляции разные

это почему вдруг?

Преждевременная оптимизация — корень большинства проблем в программировании

вообще враньё, корень большинства проблем в программировании - отсутствие вменяемой документации

next_time ★★★★★
() автор топика
Ответ на: комментарий от next_time

вообще-то, это вполне управляется прагмами

Нет. Во-первых эти прагмы мимо стандарта, во-вторых этими прагмами не все настраивается. Например, нет такой прагмы, которой я мог бы глобально во всей программе заставить стек расти в другом направлении. В том же LLVM прошиты разные соглашения вызовов: https://llvm.org/docs/LangRef.html#calling-conventions - там есть специальное соглашение вызова для GHC, High Performance Erlang (HiPE), WebKit JavaScript и так далее. Никаких прагм в Си для этого нет, и если кому-то надо будет придумать принципиально новое соглашение вызовов, которое в том списке отсутствует - надо будет патчить сам LLVM и добавлять туда его

SZT ★★★★★
()
Ответ на: комментарий от next_time

это почему вдруг?

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

quickquest ★★★★★
()
Ответ на: комментарий от next_time

а на что они заточены?

Для всяких нестандартных архитектур придумывают свои языки и пишут компиляторы-оптимизаторы этих языков. И конечно же никто их в Си не транслирует т.к там нет обычной адресуемой памяти как в Си. Посмотри например на reconfigurable array architecture http://xputers.informatik.uni-kl.de/papers/paper055-1.html

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

Функциональщина на FPGA как раз ложиться сильно лучше си, крестов, фортрана и тому подобного.

SZT ★★★★★
()
Ответ на: комментарий от next_time

много ли языков вообще содержат в своей семантике хоть какие-то вещи, которые можно использовать для оптимизации? C++, rust, D, fortran. А ещё?

Например, Мигель де Икаса жаловался, что ему пришлось отказаться от своего специально запатченного LLVM для компиляции C# https://tirania.org/blog/archive/2015/Sep-02.html

Also, Mono uses hand-written assembly language code in various places (lots of small optimizations involving generics code sharing, method dispatch and other things like that). This poses a problem for WatchOS.

Secondly, Mono uses a modified version of LLVM that adds support for many .NET idioms. In particular, our changes to LLVM produce the necessary information to support .NET-style exception handling [1].

We spent the summer adapting Mono to produce Vanilla LLVM bitcode support. This includes the removal of our hand-tuned machine code, as well as devising a new system for exception handling that works in this context. Sadly, the exception handling is not as efficient as the one that we got with our modified LLVM.

SZT ★★★★★
()
Ответ на: комментарий от SZT

Во-первых эти прагмы мимо стандарта

это не важно

во-вторых этими прагмами не все настраивается

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

заставить стек расти в другом направлении

а зачем?

Никаких прагм в Си для этого нет

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

next_time ★★★★★
() автор топика
Ответ на: комментарий от SZT

Функциональщина на FPGA как раз ложиться сильно лучше си, крестов, фортрана и тому подобного.

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

next_time ★★★★★
() автор топика
Ответ на: комментарий от next_time

заставить стек расти в другом направлении

а зачем?

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

SZT ★★★★★
()
Ответ на: комментарий от SZT

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

next_time ★★★★★
() автор топика

Си не настолько низкоуровневый как кажется. Некоторые оптимизации возможны только уровнем ниже.

RazrFalcon ★★★★★
()

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

Можно накидать примеров из истории?

CYB3R ★★★★★
()
Ответ на: комментарий от RazrFalcon

Си не настолько низкоуровневый как кажется.

совершенно верно

Некоторые оптимизации возможны только уровнем ниже.

собственно, вопрос о том, какие именно

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

next_time ★★★★★
() автор топика
Последнее исправление: next_time (всего исправлений: 1)
Ответ на: комментарий от CYB3R

К слову, википедия говорит, что Фортран, по факту, вообще в 80-м появился:

FORTRAN 77 (1980)
Введён блочный оператор IF и конструкция IF THEN — ELSE IF THEN — END IF, а также оператор включения фрагмента программы INCLUDE.

next_time ★★★★★
() автор топика
Ответ на: комментарий от next_time

ада, эйфель, фортран, паскаль, ЕМНИП, хаскель, тысячи их

из списка пожалуй только хаскель, да и то по молодости-глупости :-)

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

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

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

ну-ну http://www.mapusoft.com/ada-to-c-changer

паскаль от рождения использовал пи-коды

использовал, но компиляторы паскаля в С имеются

фортран несколько старше Си

выше уже обсудили

next_time ★★★★★
() автор топика
Ответ на: комментарий от SZT

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

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

Harald ★★★★★
()
Ответ на: комментарий от next_time

Aliasing. Rust гарантирует отсутствие двух мутабельных ссылок на объект. Это равносильно тому, как если бы помечали все переменные в C как restrict. Что позволяет компилятору делать доп. оптимизации. Проблема в том, что как только такой «код» передаётся llvm/gcc - они начинают выдавать мусор, из-за багов в компиляторах. В типичном С коде просто нет такого количества restrict.

Возможно что-то напутал, но общая идея такова.

Детали:

RazrFalcon ★★★★★
()
Ответ на: комментарий от CYB3R

Да элементарно. Они старше не только С, но и x86 архитектуры. А под х86 архитектуру для них существовала уже гора компиляторов, в том числе и трансляторы в С.

next_time ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

то равносильно тому, как если бы помечали все переменные в C как restrict

т.е., в С есть такая возможность

//не все переменные, а все указатели, кстати

next_time ★★★★★
() автор топика
Ответ на: комментарий от next_time

в С есть такая возможность

Как видим по багам - нет. Ну и появился он в C99, а по меркам сишников - это хипстерское ненужно. Ну и MSVC не поддерживает C99.

RazrFalcon ★★★★★
()
Ответ на: комментарий от next_time

Что-то не гуглятся все эти ранние компиляторы в Си.

CYB3R ★★★★★
()
Ответ на: комментарий от next_time

использовал, но компиляторы паскаля в С имеются

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

Трансляторы языков из А в Б делаются не потому что Б быстрый и удобный, а потому что ему нехватает кодовой базы или других средств или он единственный принят в конкретном муравейнике.

наличие трансляторов в С не значит что компиляция массово делается через него. Просто есть транслятор.

MKuznetsov ★★★★★
()
Ответ на: комментарий от Harald

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

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

SZT ★★★★★
()
Ответ на: комментарий от RazrFalcon

Как видим по багам - нет

по багам мы видим, что в данной момент у llvm нет такой возможности, С тут ортогонален

Ну и появился он в C99, а по меркам сишников - это хипстерское ненужно. Ну и MSVC не поддерживает C99.

это малоинтересные подробности, поскольку речь идёт про транслятор в С vs транслятор в байт-код vs прямая компиляция

next_time ★★★★★
() автор топика
Ответ на: комментарий от SZT

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

system

next_time ★★★★★
() автор топика
Ответ на: комментарий от MKuznetsov

Просто есть транслятор

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

Трансляторы языков из А в Б делаются не потому что Б быстрый и удобный, а потому что ему нехватает кодовой базы или других средств или он единственный принят в конкретном муравейнике.

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

сейчас вообще все живые языки работают, либо умеют работать как трансляторы из А в Б. Из самых популярных Б: ассемблер llvm, ассемблер .NET, ассемблер java машины, ast gcc

и тот же железный питон рвёт по скорости оригинал как тузик грелку

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

next_time ★★★★★
() автор топика
Ответ на: комментарий от MKuznetsov

Трансляцию с компиляцией

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

next_time ★★★★★
() автор топика
Ответ на: комментарий от next_time

Если невозможность использовать aliasing для вас является «малоинтересной подробностью», то и в теме нет смысла.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

скорее всего вы не поняли в чём её смысл: речь идёт про транслятор в С vs транслятор в байт-код vs прямая компиляция

если aliasing нельзя использовать ни в 1 ни 2 ни 3, то к теме он отношения не имеет

next_time ★★★★★
() автор топика
Ответ на: комментарий от next_time

system() недостаточно. Нужен еще сам компилятор (с какими ключами его вызывать, чтоб получить объектный файл?) и нужен еще какой-то способ подгрузить скомпилированный код в адресное пространство процесса (dlopen(), dlsym()). В стандарте Си про это ничего не сказано. К тому же в какой-нибудь контроллер ты полноценный компилятор не засунешь, и никакого system(), dlopen(), dlsym() там не будет

SZT ★★★★★
()
Ответ на: комментарий от SZT

тому же в какой-нибудь контроллер ты полноценный компилятор не засунешь

там и интерпретатор почти любого языка не заработает

next_time ★★★★★
() автор топика
Ответ на: комментарий от next_time

Почему нельзя, можно. Просто сейчас оно сломано.

RazrFalcon ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.