Pull to refresh

Comments 103

*val1 = *val2; # ОК - присваивается значение

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

Циклические ссылки уже не являются проблемой, так как все они будут слабыми, а звездочка, это как раз и есть оператор преобразования слабой ссылки в сильную на время операции. Точнее обращения к данным (для слабой ссылки - преобразование в сильную и обращение к данным, а для сильной - сразу обращение к данным).

так как все они будут слабыми

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

А никакой новой сущности и не требуется. Вершина каждого графа - это переменная владелец.

Ничего не понял - предлагается под каждую вершину заводить отдельную переменную?

Что значит "заводить"? Это может быть обычный std::shared_ptr с собственным счетчиком ссылок.

Всё таки непонятно, как оно работает, если, скажем, есть три вершины (A, B, C) и каждая ссылается на все остальные, после чего мы убираем рёбра A-B и A-C, при этом в глобальных структурах есть ссылка только на A.

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

Чтобы избежать двусмысленности владельцем разрешаем быть только одному литералу. Но остальные могут "одалживать" значение. Или забирать его себе на совсем. И...

Так, минуточку!.. :-D

Да, фактически это Borrow checker в Rust :-D

Насколько слышал, написание сложных структур данных с нетривиальными ссылками между элементами на Rust - это как раз таки постоянная борьба с borrow checker. Но пока руками не трогал.

Вы правильно слышали. Поэтому я упомянул Rust как источник вдохновения для самой идеи, но писать на нем реальные приложения - для меня сплошная мука.

Ну в реальности на сложных структурах просто отказываются от bc в пользу счетчиков ссылок(Rc/Arc).

Но в этому случае появляется возможность получить циклическую ссылку!

Появляется, но что делать. Ну если есть серьезные опасения, то можно Weak использовать. В borrow checker уровня компиляции принципиально нельзя записать ссылку на самого себя, потому что это потребует одновременно взять структуру на изменение и одалживание(borrow), что запрещенно правилами. Более того, когда еще асинхронный код требуется и clone делать дорого, то в реальных приложениях часто в какой-то момент вылезает что-то типа Arc<Mutex<HashMap>>, такое вот неприятно отличие практики от теории.

Появляется, но что делать.

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

 и циклических ссылок можно будет избежать

В смысле избежать? Если есть миллион равноправных объектов с перекрёстными ссылками друг на друга, циклические ссылки возникают из сути задачи - так что вопрос не как их избежать, а что с ними делать.

Не совсем так. Есть же два типа ссылок (сильные и слабые), но проблему циклических ссылок создают только сильные ссылки (владельцы объектов). Тогда как перекрестные слабые ссылки (weak_ptr) висящих ссылок не создают, т.к. не владеют ими.

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

Сильные ссылки могут быть только у владельцев объекта (как borrow checker в Rust), но их можно копировать в локальные переменные более низкого уровня (т.е. без обязательного перемещения владельца, как это реализовано в Rust), так как после завершения области видимости локальной переменной, её владеющая ссылка будет удалена.

Во всех остальных случаях (в том числе и для равноправных объектов) можно делать только слабые ссылки и для работы с объектом "по ссылке" будет требоваться их захват.

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

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

Так вы же сами написали сильная ссылка только одна (точнее владелец объекта только один), а самих ссылок может быть несколько, но за удаление объекта отвечает счетчик ссылок, Все это - классический std::shared_ptr

Сильная ссылка - только на одну вершину графа, на все остальные - только ссылки из других вершин (то есть слабые).

Сильная ссылка, это та, которая увеличивает счетчик владения.

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

Я же говорю - все вершины равноправны, нет никакой иерархии.

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

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

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

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

Я не понимаю, в чем там может быть сложность?

class Leaf {
	Leaf next;
	Leaf prev;	
};

Leaf head;
Leaf tail;

head.next = &tail;
head.prev = &tail;

tail.next = &head;
tail.prev = &head;

# head.*next  -> tail   # tail.*prev -> head;

Leaf middle;
head.next = &middle;
tail.prev = &middle;
# head.*next  -> middle   # tail.*prev -> middle;

middle.prev = &head;
middle.next = &tail;
# middle.*prev  -> head   # middle.*next -> tail;

Если за '&'стоит счетчик ссылок, то

Leaf head;

head.next = &head;

циклическая ссылка и лик память.

Если за '&' не стоит счетчик ссылок, то

Leaf head;

Leaf tail;

head.next = &tail;

return head

prev удаляется и получается dangling pointer

Если за '&'стоит счетчик ссылок

Нет, не стоит, так как это объекты одного уровня и ссылка может быть только слабой.

prev удаляется и получается dangling pointer

Вы возвращаете из функции ссылку на локальную переменную, поэтому это действительно dangling pointer. Вот только для получения данных указателя по правилам синтаксиса вам необходимое его "захватить" (оператор "звездочка"), т.е. превратить weak_ptr в shared_ptr. Поэтому тут у вас ошибка в логике, а при работе с памятью ошибки не будет.

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

Вы возвращаете из функции ссылку на локальную переменную

А как вернуть структуру по значению тогда?(это к вопрос о читабельность синтаксиса).

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

Причем тут это? У меня есть функция create_list, которая должна вернуть мне структуру Leaf. Подскажите, что мне надо писать?

Поэтому тут у вас ошибка в логике, а при работе с памятью ошибки не будет.

Давайте даже рассмотрим случай когда все ссылки

Leaf head = new Leaf(); //ссылка, объект в куче

Leaf tail = new Leaf(); //ссылка, объект в куче

head.next = &tail;

return head // или что тут у вас надо написать, *head?

В какой момент времени будет удален tail? Что при этом будет в поле next?

return head // или что тут у вас надо написать, *head?

Да, этим вы возвращаете сильную ссылку, а фактически владельца объекта или std::move, если так будет проще.

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

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

Но в этом случае вы можете вернуть только объект head, а вот ссылка на tail протухнет

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

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

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

Тут скорее всего нужно создавать какой нибудь контейнер для хранения всех листьев списка

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

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

Даже если это сделать, это никак не решает проблему dangling pointer. Вот у вас head.next = &tail; Положит программист этой фунции в контейнер tail или забудет и положит только head - получается как повезет. Еще хуже, что контейнер мутабельный, а значит tail можно из него удалить и опять dangling pointer. Как-будто бы вашу грамматику нужно дополнять проверками на владение, лайфтаймы и мутабельностью, но тогда получится Rust.

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

Обязательно! Более того, я хочу сделать полный контроль ссылок, включая автоматическую межпотоковую синхронизацию, поэтому Rust в любом случае не получится :-)

Как вы этого избежите, если вы не знаете в какой момент времени будет создана ссылка?

Вот у вас в одном потоке происходит

let ref_mt2 := &&ref_mt; #

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

В результате все равно к счетчику придете.

В данном случае, не к счетчику, а объекту синхронизации. Точнее к счетчику, но после захвата объекта синхронизации доступа.

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

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

Вот структура

struct Node{ sub_node: &&Node}

В какой момент вы будете принимать решение о том, что ее можно удалить?

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

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

Ну а в расте вы явно этот счетчик запрашиваете, оборачивая все в Arc, а если не сделаете, получаете ошибку компиляции. То есть в расте есть выбор (обернуть в Arc или подумать, как без этого обойтись), а у вас нет (сразу автоматом оборачиваете).

у вас нет (сразу автоматом оборачиваете).

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

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

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

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

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

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

Двойную буферизацию не рассматриваем, как неэффективное решение.

Наверно не "в свой буфер", а сохраняет в тот буфер, который был её передан для сохранения прочитанных из файла данных.
Для ОС это обычный адрес в памяти, а для языка программирования, это либо переменная-владелец буфера, либо захваченная слабая ссылка.

Я так понимаю, вопрос скорее про mmap (и дальнейшую передачу отдельных кусков в другие функции без копирования), чем read/write.

Вряд имеет смыл совмещать низкоуровневое ядерное управление памятью и прикладное программирование.

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

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

Не понял, где тут требования к ресурсам и к производительности?

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

Ну и часто ли используется mmap для чтения файлов в прикладных задачах?

В серьёзном промышленном коде, часто работающем с вводом/выводом - вполне себе используется. Даже "игрушечный" sqlite на нём живёт.

sqlite отнюдь не игрушечный, да и файловый ввод/вывод для него - самое узкое место.

Дело же не в sqlite, а в принципиальном вопросе.

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

  2. Если мы не упарываемся по производительности (т.е. в 99.9% случаев), то более чем достаточно обычного автоматического управления памятью со сборщиком мусора, и при этом не требуются никакие сильные и слабые ссылки, деструкторы и прочая хтонь.

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

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

Я как немного спец по скулайту скажу что ммап для него не принципиально нужен. До реализации вол мода его вообще не было, да и с волом в режиме эксклюзив ммап не используется. В кратце там ммап только для координации работы вол между разными процессами, но НЕ для ИО.

Спасибо, не знал - то есть собственно данные базы читаются/пишутся через read/write (если база в память не влезает)?

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

По сравнению с нормальным базами типа Оракла или SAP HANA - игрушка. Хотя и полезная в определённых ситуациях.

Это двойная буферизация, не пойдёт. Тогда проще и всё остальное по значению копировать.

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

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

ОК, но по сути "мусор" надо собирать в конце каждого блока, в котором что-то было объявлено? А если я создал объект внутри функции, а потом его вернул? А он в свою очередь, имеет в своем составе другие объекты

По сути, без описания границ видимости и времени жизни объекта ничего не понятно :(

Вы совершено правы про границы видимости!

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

owner := 123;
ref := & owner;
{
  local := *ref; # В local будет сильная ссылка на owner во вложенном блоке
}

Ну теперь вы пришли к концепции сильных/слабых ссылок в *nix файловых системах. И по сути, к тому же сборщику мусора, когда чисто сильных ссылок стало равно 0 :)

Все равно, без точного описание границ видимости и критериев "удаления объекта" вам концепции на построить

Удаление объекта, когда число сильных ссылок стало равно 0, это не сборщик мусора, а работа обычного деструктора.

Не ... ненужно никакой адресной арифметики.

"Не ... ненужно никакой адресной арифметики." - Золотые слова. Немного не в тему данного ответа, выражение зацепило, хотел сказать про низкоуровневую адресацию. По идее програмисту не надо знать про кэши, страницы и прочее аппаратное, т.к. везде разное, это дело процессора, драйверов или ОС (в крайнем случае). Программисту нужно просто выразить то что он хочет, желательно на человеческом языке. И когда я смотрю на эти закорючные символы и нечитаемую семантику в разных языках, то понимаю что будет много ошибок и непоняток. Почему нельзя написать язык где всё на человеческом языке, ведь всё равно сегодняшние закорючки нужно прогонять через компилятор. Самое сложное перевести высокий уровень в код для проца, ведь сегодняшнее железо не умеет работать с высоким уровнем и даже память ОС не может защитить аппаратно, Поэтому тяжол труд программиста в наше время. Хочется о высоком а приходится на низком. Это так, мысли вслух.

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

Это как раз самое простое. Самое сложное, это записать нужный алгоритм в семантике языка, а потом искать и исправлять ошибки.

По идее програмисту не надо знать про кэши, страницы и прочее аппаратное

Если надо получить максимум производительности - очень даже надо.

И когда я смотрю на эти закорючные символы

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

ref1 = 3; # Ошибка - переменная является ссылкой!
...
ref1 = 5;
# val1 = 5, а val2 = 5

А вы говорите, все наглядно-очевидно... (не спрашиваю о том, как в val1 и val2 оказалось одинаковое значение... когда от val1 уже все ссылки отцепились). Если уж нужна очевидность, то пусть ссылки всегда носят с собой дополнительный символ, говорящий -- "я ссылка". А доступ к данным -- это убирание этого символа. Это и символично -- убираем уровень косвенности -- получаем данные. Хотя тогда могут возникнуть проблемы с обобщенным кодом -- даже если он не зависит от того, ссылка там или объект, синтаксис чисто формально может требоваться разный.

Спасибо! Это действительно косяк в примере. Тут должно быть по другому:

...


ref1 = ref2; # ОК - одна ссылка присваивается другой ссылке
# ref1 -> val2, а ref2 -> val2

*ref1 = 5;
# *ref1 -> 5,  *ref2 -> 5

ref1 = 3; # Ошибка - переменная является ссылкой!

А ниже, последняя строка

ref1 = 5;

Вы описались? Или я что то не понимаю?

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

В питоне всё всегда передаётся по ссылке, просто некоторые примитивные/иммутабельные типы не предоставляют официальных способов изменить себя.

Но если очень хочется...
import ctypes

def fuck(x: int) -> None:
    ctypes.memset(id(x) + 24, 255, 4)

def main() -> None:
    a = int("1000")
    print(a)  # 1000
    fuck(a)
    print(a)  # 4294967295

main()

Слишком сложно, в старом Фортране значение литерала менялось легче )

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

Отличается способ копирования переменной:

либо мы создаем еще один описатель этой области памяти, то есть переменной. Описатель, который обязательно содержит адрес этой области памяти,

либо мы копируем содержимое этой области памяти в область памяти с такими же параметрами.

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

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

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

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

но аналогию можно продолжить конечно:

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

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

вот если вы будете код портировать с Х86 платформы на ARM платформу, получится у вас от абстрагироваться от машинных инструкций?

Это же в общем то вопрос области разработки в том числе, или вы не согласны?

Самое интересное в вашем вопрос то, что мне приходится работать как X86, так и с ARM платформой и писать такой код, который приходится в дальнейшем портировать. Поэтому абстракция для разработчиков, это все.

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

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

Поэтому мне очень нравится подход Python и Java (любой объект - это ссылка на область в памяти), очень удобно не заботится о распределении памяти, но мне очень не нравится плата за это - необходимость использования сборщиков мусора и лишние накладные расходы. Мне нравится подход С++ за его минимализм оверхеда, но все убивает необходимость ручного управления памятью, в которой очень легко ошибиться.

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

очень нравится подход Python и Java (любой объект - это ссылка на область в памяти),

...

Мне нравится подход С++ за его минимализм оверхеда, но все убивает необходимость ручного управления памятью, в которой очень легко ошибиться.

Я на С# пишу много теперь, сборщик мусора там вроде как не является необходимостью, никто не мешает удалить объект вручную, насколько я знаю на Java также можно действовать.

Главная проблема С++ по моему хидера и ручное описание интерфейсов-отсутствие полной рефлексии по типам. Для управления памятью все уже придумано например COM-объекты. Проблема только с оформлением через те же хидера, по моему. Ну и однажды применив технику надо не лениться ее следовать последовательно, а не так что тут у нас было так, а потом как попало.

Для большей части прикладного кода компилятор всё абстрагирует - проблемы будут скорее не с ISA, а со слабой моделью памяти, что решается высокоуровневыми средствами. Но, скажем, спортировать на новую архитектуру Javascript движок - это уже задачка интересная )

спортировать на новую архитектуру Javascript движок - это уже задачка интересная 

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

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

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

Да просто броузиться, чтобы всяческие Google Docs/Maps адекватно работали.

какой-то Mpeg аудио декодер, или не дай бог видео декодер

Согласен, там нужно как минимум доступные SIMD инструкции/регистры запользовать. Вроде есть переносимые подходы (скажем, Google Highway) - но не знаю, насколько эффективный код они генерируют.

Вроде есть переносимые подходы (скажем, Google Highway) - но не знаю, насколько эффективный код они генерируют.

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

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

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

они генерируют код для каких-то общих случаев

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

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

Но всё таки для сайтов с нетривиальными скриптами главное - эффективная JIT компиляция JS кода, при этом llvm там не особо применим, так что backend для новой архитектуры придётся писать руками.

только руками на ассемблере или интринсиках, получается Highway где то посередине, только вот насколько она широкая...

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

То есть КАЖДЫЙ новый алгоритм требует такой специальной работы это нельзя автоматизировать пока все возможные алгоритмы для всех принципиально разных платформ не будут написаны и сведены в какую-то общую базу алгоритмов.

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

Вообще существуют два подхода: высокоуровневый и низкоуровневый. При высокоуровневом нам все равно как хранится объект - по значению или по ссылке, мы абстрагируемся от способа хранения и работаем просто с объектами. Так устроены Java и C#. Единый синтаксис доступа, никаких звездочек и стрелочек - только прямой доступ и доступ к полям через точку. Наверное, для специальных операций (таких как манипуляции с самими указателями, а не с объектами) можно придумать специальный синтаксис, или даже специальные встроенные в язык функции.

Второй подход - низкоуровневый, в нем в программе явно задается способ хранения объекта. Лучший представитель этого подхода - язык Си. Явные указатели, явное разыменование, явное взятие адреса, всё тоже просто и понятно, но явно. Иногда чуть больше звездочек, но это цена явности и прозрачности кода. В Rust тоже вроде бы все кодируется в явном виде. Где-то рядом Go, хотя там чуть срезали некоторые некритические углы.

А иногда делаются попытки совместить. Как в С++, где есть и явные указатели, и неявные ссылки. Иногда это удобно, иногда кажется избыточным. Посмотрите еще как забавно и в то же время мозгодробительно сделано в языке CForAll - они в дополнение к сишным указателям взяли концепцию ссылок из с++ и вывернули ее наизнанку, построив аналогию указателей на указатели.

int x,
*p1 = &x, **p2 = &p1, ***p3 = &p2,
&r1 = x, &&r2 = r1, &&&r3 = r2;
***p3 = 3;  // change x
r3 = 3;     // change x, ***r3
**p3 = ...; // change p1
&r3 = ...;  // change r1, (&*)**r3, 1 cancellation
*p3 = ...;  // change p2
&&r3 = ...; // change r2, (&(&*)*)*r3, 2 cancellations
&&&r3 = p3; // change r3 to p3, (&(&(&*)*)*)r3, 3 cancellations

ИМХО, пример того, когда сделали чисто для языковой симметрии в ущерб читаемости и практической применимости. Я считаю, что указатель и ссылка, при всей внутренней схожести, на уровне языкового дизайна все-же разные конструкции: указатель - мощный низкоуровневый инструмент для построения явных связей на уровне адресов; ссылка - высокоуровневая абстракция, предназначенная как раз для того чтобы уйти от адресов, явных размыменований и прочего.

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

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

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

К тому в эту канву очень удачно будет вписываться вообще полный контроль за ссылками, в том числе и за межпоточным взаимодействием и все это опять же во время компиляции, а такого нет даже в Rust :-)

В C# не "всё есть объект". Есть вполне себе значимые типы (структуры) и ссылочные типы (классы). Значимые типы передаются по значению, ссылочные по ссылке (указателю). При этом можно иметь ссылку на значение или указатель. И всё это разнообразие доступно, понятно и работает без звёздочек, амперсандов и стрелочек типа ->.

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

При работе с сылками в С# вы на уровне синтаксиса не можете отличить аргумент по ссылке от аргумента по значению.

А ещё на уровне синтаксиса я не могу отличить локальную переменную от типа, свойства или поля, также не могу однозначно сказать какого типа переменная.

Обращение к локальной переменной легко отличается от доступа к полю (свойству) на уровне синтаксиса.

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

class Foo {
  int x;
  void bar(int y) {
    x = 10;
    y = 10;
  }
}

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

Вроде во многих языках можно так обращаться к полям класса. Та же java, c++.

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

Либо через точку, если идет обращение к полям структуры.

И всё это разнообразие доступно, понятно и работает без звёздочек, амперсандов и стрелочек типа ->

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

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

Так вот, первое я чёт не особо вижу разницы с аргентумом в плане указателей который уже хоть как то работает, второе я так и не понял какую проблему решаем? Универсальный синтаксис - ассемблер. Безопасность памяти эта идея не решает за исключением юз афтер фрии и дабл делет, но эти проблемы не очень сложно чинить руками. Где решение проблемы дата рэйсов? Ну и производительность, локать шаред поинтер для каждого доступа к данным это точно не взлетит там где нужна ну хоть какая то производительность, по сути почти весь кэш данных просто отключается.

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

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

Кстати Валя куда идейно более проработана чем аргентумом и тем более ваша идея. Так что я вашу идею не куплю.

Sign up to leave a comment.

Articles