malloc выделяет память лениво - закрепляет за процессом диапазон виртуальных адресов. Если вы полезете писать в эту память, возникнет соответствие виртуальных и физических адресов (page table/TLB). В случае, когда это невозможно - процесс прибьют.
Т.е. дополнительная память в виде заголовка блока. То же самое но чуть компактнее можно сделать масками. Плюс бонус - защита от double free/невыделенная память.
Вот, кстати, поэтому недо-buddy аллокатор в BSD 4.2 и работал так быстро, что не пытался слить соседей во время free. В самом деле, как узнать без дополнительной памяти, что твой сосед тоже свободен? Пробежать по списку?
Близнецы и работают помедленней (в сравнении с граничными дескрипторами) плюс дополнительную память просят под маски занятости. Поэтому близнецами выделяют до какого-то предела (пусть килобайт), а всё что меньше, агрегируют в килобайтных блоках. С точки зрения многопоточности дескрипторам опять же попроще жить.
Превращение программы в ациклический граф - одна из стадий компиляции. Это не совсем новая вычислительная парадигма. А как насчет зависимостей по данным?
Т.е. в конечном счете всё упирается в количество регистров (шутка).
Огромное спасибо за развёрнутый ответ, с вашего разрешения я со сноской включу его в текст статьи.
Хотелось бы конечно для объективности услышать мнение кого-нибудь из разработчиков CISC, насколько указанные проблемы критичны с их точки зрения для итоговой производительности процессоров. Впрочем, факты говорят, что к началу 90-х для равной производительности CISC требовалось вдвое большее к-во транзисторов.
Да, кстати, а как вы собираетесь узнавать какие регистры свободны? Вот, например, прилетела инструкция ADD R1,R2,R3; R1=R2+R3 R1, понятно, занят, а как насчет других? Компилятор, конечно, знает, нужны ли ему значения в R2 & R3 далее, но процесор то нет.
Можно, пожалуй, добавить пару флагов в инструкцию, но в результате код распухнет.
Есть еще проблема. Пусть у нас самый первый вызов и все регистры свободны. При работе функции регистры не обязаны быть заняты подряд, минимально сложный код неизбежно породит мешанину из занятых и свободных регистров.
Вот что-то вызвали и еще и еще, пришла пора вытолкать кого-то в стек. Хорошо бы вытолкать не абы кого, а регистры функции с самой вершины стека.
Для этого функция обязана хранить не только потребное число регистров, но и их фактические индексы в пуле регистров. Т.е. фактически появляется потребность еще в одном стеке - для индексов регистров. Ну или стеке контекстов функций. Вытолкали регистры в обычный стек, в стек контексов положили контекст функции, которая их использовала.
Можно ли всё сериализовать в одном стеке? Можно, но в догонку появятся проблемы с выравниванием.
И чем всё это лучше регистровых окон в духе AMD29K или Итаниума? Или регистровой машины с volatile|non-volatile регистрами?
Ваш (довольно экстремальный) пример на С соответствует *R1++ += *(--R2); *R2++ -= *(R1++); писать в таком стиле - верный способ призвать дьявола.
Но разве, если скомпилировать это на RISC архитектуре, не вылезут эти задержки и конфликты так или иначе? Что такого умеет (в данном контексте) компилятор RISC, чего не может сделать декодер CISC?
Это сильносвязанные вещи, IMHO. Да, в CISC меньше регистров и больше работы по обнаружению и избежанию конфликтов. Да, в CISC меньше регистров и их приходится экономить в ассемблере в том числе.
Проблема еще в том, что нет операционной системы, написанной на Haskell, т.е. для работы с TRIPS всё равно нужет компилятор С, а он сильно неэффективный.
malloc выделяет память лениво - закрепляет за процессом диапазон виртуальных адресов.
Если вы полезете писать в эту память, возникнет соответствие виртуальных и физических адресов (page table/TLB). В случае, когда это невозможно - процесс прибьют.
На всякий случай, я уже как-то выкладывал
исходники BuddyAllocator под BSD лицензией
Т.е. дополнительная память в виде заголовка блока.
То же самое но чуть компактнее можно сделать масками.
Плюс бонус - защита от double free/невыделенная память.
Вот, кстати, поэтому недо-buddy аллокатор в BSD 4.2 и работал так быстро, что не пытался слить соседей во время free. В самом деле, как узнать без дополнительной памяти, что твой сосед тоже свободен? Пробежать по списку?
а как насчет защиты от недобросовестного пользователя?
double free того же ?
Близнецы и работают помедленней (в сравнении с граничными дескрипторами) плюс дополнительную память просят под маски занятости. Поэтому близнецами выделяют до какого-то предела (пусть килобайт), а всё что меньше, агрегируют в килобайтных блоках.
С точки зрения многопоточности дескрипторам опять же попроще жить.
sbrk and brk were considered legacy even by 1997 standards (Single UNIX Specification v2 or POSIX.1-1998).They were removed in POSIX.1-2001 (отсюда)
https://vk.com/reciprocal
Превращение программы в ациклический граф - одна из стадий компиляции.
Это не совсем новая вычислительная парадигма.
А как насчет зависимостей по данным?
Т.е. в конечном счете всё упирается в количество регистров (шутка).
Огромное спасибо за развёрнутый ответ, с вашего разрешения я со сноской включу его в текст статьи.
Хотелось бы конечно для объективности услышать мнение кого-нибудь из разработчиков CISC, насколько указанные проблемы критичны с их точки зрения для итоговой производительности процессоров. Впрочем, факты говорят, что к началу 90-х для равной производительности CISC требовалось вдвое большее к-во транзисторов.
Да, кстати, а как вы собираетесь узнавать какие регистры свободны?
Вот, например, прилетела инструкция ADD R1,R2,R3; R1=R2+R3
R1, понятно, занят, а как насчет других? Компилятор, конечно, знает, нужны ли ему значения в R2 & R3 далее, но процесор то нет.
Можно, пожалуй, добавить пару флагов в инструкцию, но в результате код распухнет.
1) спасибо
2) это и называется отсутствие масштабирования
Есть еще проблема.
Пусть у нас самый первый вызов и все регистры свободны.
При работе функции регистры не обязаны быть заняты подряд, минимально сложный код неизбежно породит мешанину из занятых и свободных регистров.
Вот что-то вызвали и еще и еще, пришла пора вытолкать кого-то в стек. Хорошо бы вытолкать не абы кого, а регистры функции с самой вершины стека.
Для этого функция обязана хранить не только потребное число регистров, но и их фактические индексы в пуле регистров.
Т.е. фактически появляется потребность еще в одном стеке - для индексов регистров.
Ну или стеке контекстов функций. Вытолкали регистры в обычный стек, в стек контексов положили контекст функции, которая их использовала.
Можно ли всё сериализовать в одном стеке? Можно, но в догонку появятся проблемы с выравниванием.
И чем всё это лучше регистровых окон в духе AMD29K или Итаниума?
Или регистровой машины с volatile|non-volatile регистрами?
Ваш (довольно экстремальный) пример на С соответствует
*R1++ += *(--R2);
*R2++ -= *(R1++);
писать в таком стиле - верный способ призвать дьявола.
Но разве, если скомпилировать это на RISC архитектуре,
не вылезут эти задержки и конфликты так или иначе?
Что такого умеет (в данном контексте) компилятор RISC,
чего не может сделать декодер CISC?
Это сильносвязанные вещи, IMHO.
Да, в CISC меньше регистров и больше работы по обнаружению и избежанию конфликтов.
Да, в CISC меньше регистров и их приходится экономить в ассемблере в том числе.
Это программа DARPA Polymorphous Computing Architectures,
"пусть расцветают сто цветов" и всё такое.
Проблема еще в том, что нет операционной системы, написанной на Haskell,
т.е. для работы с TRIPS всё равно нужет компилятор С, а он сильно неэффективный.
MSVC, GCC
В моей практике (С++) это не так.
Функции вызываются часто и глубоко.
Особенно это касается виртуальных функций.
Что касается не торчащих наружу функций, для них вообще необязательно ABI соблюдать,
компилятор и так знает какие регистры использованы, а какие нет.
Что же, на Nios II/f есть и предсказания переходов и какой-никакой кэш.
В данный момент нет у меня таких планов.