C — не низкоуровневый язык: ваш компьютер — не быстрый PDP-11
C Is Not a Low-Level Language (2018)
В статье Дэвида Чизнэлла, опубликованной в Communications of the ACM, утверждается, что C, вопреки распространённому мнению, не является низкоуровневым языком. Автор связывает уязвимости Meltdown и Spectre с попытками производителей процессоров сохранить иллюзию, что C «близок к железу». Современные процессоры — это сложные суперскалярные машины с конвейерами, спекулятивным исполнением и многоуровневым кэшем, что далеко от абстрактной модели C с последовательным исполнением и плоской памятью. Чтобы C-код работал быстро, компиляторы выполняют сложные преобразования, которые нарушают простоту и предсказуемость, ожидаемые от низкоуровневого языка. В результате C требует от программиста понимания деталей реализации, а не абстрактной машины.
Процессорные архитекторы пытались построить не просто быстрые процессоры, а быстрые процессоры, которые предоставляют ту же абстрактную машину, что и PDP-11.
- weitendorf
Это, на мой взгляд, очень педантичное замечание. C является низкоуровневым, потому что он позволяет легко работать с машинным языком/ассемблером и делать такие вещи (пример с помощью LLM):
int main() {
__m512i vecA = _mm512_setr_epi32(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
__m512i vecB = _mm512_setr_epi32(0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75);
unsigned short mask = 0;
__asm__ (
"vp2intersectd %[B], %[A], %%k2"
: "=@cck2" (mask)
: [A] "v" (vecA), [B] "v" (vecB)
: "k3"
);
printf("Intersection Mask: 0x%04X\n", mask);
return 0;
}
Это то, что программисты "низкого уровня" очень часто используют, чтобы получить преимущества высокоуровневого языка, одновременно осуществляя явный контроль над использованием конкретных аппаратных инструкций (vp2intersectd — это инструкция AVX-512, используемая в высокооптимизированных реализациях алгоритмов поиска).
Очевидно, что если вы полагаетесь на неявное поведение компилятора для оптимизации вашего кода, вы больше не "низкоуровневый". Но если вы можете быстро и легко опуститься до машинных инструкций, чтобы обеспечить явную семантику реализации, и язык действительно делает это относительно простым и легким, то это, безусловно, кажется мне "низкоуровневым".
- bee_rider
«Низкоуровневый язык» — это один из тех терминов, как «VLSI» (very large scale integration), которые были определены в 70-х или около того, поэтому академическое определение рассинхронизировано с тем, что большинство людей ожидают.
Это нормально, это термин искусства, и они не обязаны быть сразу очевидными.
Однако мне не нравится название этой статьи по этой причине. На самом деле, лучше было бы название типа «современный x86 процессор — это не PDP-11». Подзаголовок в основном идеален.
Примечание: также, на мой взгляд, не совсем справедливо наезжать на C за это, проблема не в низкоуровневости. Язык, который действительно раскрывал бы сложность спекулятивного выполнения и всего такого, мог бы быть довольно высокоуровневым. Просто его было бы труднее читать в линейном текстовом редакторе, верно? Нам было бы лучше рисовать граф зависимостей или что-то в этом роде.
- legobmw99
Я уже много лет являюсь поклонником этой статьи, хотя она часто заставляет меня думать, что на самом деле не существует настоящих низкоуровневых языков для наших суперскалярных современных процессоров. Кто-нибудь знает о таких?
- melodyogonna
Ну, C действительно дает вам контроль, когда это важно, даже если они кажутся прикрученными для более современных функций.
Я на самом деле считаю, что Mojo — единственный современный язык, который не пытается притворяться, что каждый компьютер — это PDP-11. C был настолько успешен, что многие последующие языки просто делали вещи на C как само собой разумеющееся.
В Mojo все разработано с учетом сложности современного компьютера, и на каждом этапе программист имеет полный контроль над результатами. Вы решаете, что инлайнить, что передавать в регистрах, что разворачивать и т.д. Язык имеет отличную... нет, вероятно, лучшую переносимую поддержку SIMD; все целые числа построены на основе SIMD, а скалярные целые — это просто SIMD с длиной 1. У вас есть полный контроль над тем, что компилируется, благодаря мощному программированию на этапе компиляции, которое похоже на Zig, но более мощное (на мой взгляд, потому что вы можете предоставить гораздо больше информации). В то время как C и Rust позволяют встроенный ассемблер, Mojo идет дальше, позволяя вам предоставлять встроенный MLIR и LLVM, так что в ситуациях, когда это оправдано, вы можете сказать компилятору скомпилировать в конкретную интринсику LLVM. Язык также не предполагает, что вы компилируете для работы на одной машине; каждый современный компьютер гетерогенен по своей природе и может содержать несколько программируемых блоков, поэтому конвейер компиляции спроектирован так, чтобы позволять компилировать определенные части кода для одной цели, а другие части для других целей... как одна единица компиляции.
- glouwbug
Может быть, тогда нет, но сейчас у нас есть своя система шаблонов для бедных:
#define array(T, N) struct array##T##N { T value[N]; }
void copy(array(int, 32)* x, array(int, 32)* y) {
*x = *y;
}
int main() {
array(int, 32) x;
array(int, 32) y = { 1, 2, 3, 4 };
copy(&x, &y);
}
С (слухами о) лямбдах и defer на подходе, C идет по пути классического World of Warcraft.