G
logo
Glow labs
Создание распределенной ОС для людей
G
logo
0
читателей
6 500 ₽
всего собрано
Glow labs  Создание распределенной ОС для людей
О проекте Просмотр Уровни подписки Фильтры Статистика Обновления проекта Контакты Поделиться Метки
Все проекты
О проекте

A meta-OS for the surround computing


Combine virtual nodes (a single «physical» device may provide multiple nodes) that can communicate via a huge amount of channels ranging from absuing public Internet services to Bluethoth to tty connection.
Consider current (and upcoming) landscape of software technology as «jungle», a natural habitat. Use and abuse anything popular enough to be preinstalled, default or staying forever as legacy to achieve our means.
Each node by definition acts as a relay for any connected «neighbours», it is peer to peer technology. However instead (or in addition to) of producing custom protocols from scratch the goal is to reuse anything popular enough to be widely supported.
If you want to join your digital assets and finally have seamless experience across all of your networked devices Glow is for you.
Pooling community resources across the Internet — Glow fits the bill.
Provide a simple coherent computing fabric over Datacenters, racks, hardware servers, IaaS, PaaS, (common types of) SaaS and more.
Make that tranwreck that is our IT ecosystem work for us.
My personal view — if I have a smartphone, laptop, NAS, a cloud storage account, VPS in Digital Ocean, 2 workstations (at home + at work) and also want to share files (and services) with my friends so that it shouldn’t be hard to:
  1. Continue editing my files on workstation that I started doing on the laptop, switch between the two.
  2. Seemless sharing of work — I should not depend on the internet access so long as both devices stay in the same (e.g. home) network.
  3. Being able to see all these things in my phone (that again may be in the internet or just the same network).
  4. Even when things get out of sync, I should be able to quickly synchronize once I get a connected with any of my devices having latest version. This means if I get my phone in the same WiFi as my laptop AND I worked on the laptop once I bring the phone over to antoher machine it will synchronize the stuff.
  5. Not having to constantly think about placement of files or ways to share them. Any node that have ways to export resources may be used to access shared resource (most with easy hyperlinks).
  6. Thinking about where a program should save its state
  7. Should be trivial to automatically do jobs in steps on a bunch of idle hardware at nights.
  8. And then there is this horrible Smart TV set that I should be able to stream anything to w/o much of any hassle.
  9. I could go on and on… For all the mess we’ve made we are not an inch closer to solving real problems of real people, even if I’m a smartass programmer I’m mostly fucked.

Principles


  1. Zero-configuration and auto-discovery of peers.
  2. Zero-downtime changes in every aspect — configuration, upgrades, etc.
  3. P2P all the things.
  4. Use any communication means to both join new nodes, provide overlay network and allow foreign access (share resources via established standard protocols).
  5. Work on top of any kind of encumbent software ranging from Bare-Metal OSes to *nix servers to Android app to Web Browsers.
  6. Resilency and constant reconfiguration of topology, expect constant node failure and routine abrupt joins/leaves.
  7. Secure communication and anonymity. A user of Glow is a namespace in the «cluster» where a lot of people may pool resources but keep all the stuff private, there are mechanisms for both sharing between users and «the public».
  8. Strong support for versioning both at configuration level and object level (we have shitload of HW resources might as well use them).

Explore ways of communications


Generalize and design as many encapsulations over anything starting with naked L2 all the way up to L7. I do not care if I have to mimick an ancient crappy undocumented protocol in a retarded way if that allows me to connect things together seamlessly or bypass some middle box.
On the surface the overlay network shall provide at minimum:
  • ways to address both low-level and high-level resources
  • ability to establish resilent connections between applications (message / stream)
Classify links between nodes as
  • control links (very limited links, only commands and metdata can be transfered)
  • WAN links (variable speed links crossing potentially large distances)
  • local links (direct connections 100Mbit+, may transfer huge amounts of data)
All of the above needs better and (re-)classification to be able to meaningfully use topology to route traffic.
Therefore a Glow cluster is highly-heterogenious environment where there are clusters of tightly connected nodes, some stand-alone nodes, most of nodes come and go, other stay isolted for a long time. Finally there are some lose and crappy links that try to unite all of that in a coherent whole.

TODO


Solve the NAT problem


One advantage to reap is seamlessly traversing NATs via UDP to establish peer-to-peer links. It’s crucial unless we are willing to waste power on every device to proxy all the things.

Think about bootstraping


What technology to use? How best to adapt to the extreme variety of the platforms? Which things are first?
Again none of the existing tech fits the bill so again we must sit on top of widespread technologies and be polymorphic. Time to explore meta-compilers like Haxe.
First to start we need at least rudimentary Linux node. Second a web-based node is an important consideration.

Concepts


Even the resources model is no walk in the park but I must allow myself to fail as many times as needed. Security is likely a big gotcha but have to go there.
Публикации, доступные бесплатно
Уровни подписки
Единоразовый платёж

Безвозмездное пожертвование без возможности возврата. Этот взнос не предоставляет доступ к закрытому контенту.

Помочь проекту
Бронза 500₽ месяц 5 100₽ год
(-15%)
При подписке на год для вас действует 15% скидка. 15% основная скидка и 0% доп. скидка за ваш уровень на проекте Glow labs
Доступны сообщения

Бронзовый уровень дает доступ к переводам моих постов на https://olshansky.me


Оформить подписку
Серебро 990₽ месяц 10 098₽ год
(-15%)
При подписке на год для вас действует 15% скидка. 15% основная скидка и 0% доп. скидка за ваш уровень на проекте Glow labs
Доступны сообщения

То же самое что и бронзовый + добавление на дискорд канал посвященный деятельности Glow labs.

Оформить подписку
Золото 1 750₽ месяц 17 850₽ год
(-15%)
При подписке на год для вас действует 15% скидка. 15% основная скидка и 0% доп. скидка за ваш уровень на проекте Glow labs
Доступны сообщения

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

Оформить подписку
Платина 5 000₽ месяц 51 000₽ год
(-15%)
При подписке на год для вас действует 15% скидка. 15% основная скидка и 0% доп. скидка за ваш уровень на проекте Glow labs
Доступны сообщения

То же что и золотой уровень, только поддержка по телефону 24/7.

Оформить подписку
Фильтры
Статистика
6 500 ₽ всего собрано
Обновления проекта
Контакты

Контакты

Поделиться
Читать: 1+ мин
G
logo
Glow labs

Уровни программирования

Таксономия

Иногда ‎чтобы‏ ‎можно ‎было ‎понять, ‎чем ‎схожи‏ ‎те ‎или‏ ‎иные‏ ‎объекты ‎исследования, ‎мы‏ ‎вводим ‎таксономию,‏ ‎классификацию. ‎В ‎этом ‎посте‏ ‎я‏ ‎провожу ‎естественную‏ ‎классификацию ‎языков‏ ‎программирования, ‎в ‎5 ‎уровней ‎от‏ ‎0‏ ‎до ‎4-го.‏ ‎При ‎этом‏ ‎чем ‎выше ‎уровень, ‎тем ‎дальше‏ ‎язык‏ ‎от‏ ‎машинных ‎кодов.

Уровень‏ ‎4

Это ‎уровень‏ ‎скриптов, ‎простых‏ ‎программ‏ ‎объединяющих ‎несколько‏ ‎других ‎программ ‎или ‎библиотек. ‎Самыми‏ ‎знаковыми ‎являются‏ ‎тройка‏ ‎из ‎LAMP ‎стека‏ ‎— ‎PHP,‏ ‎Perl, ‎Python. ‎Ruby ‎on‏ ‎Rails‏ ‎приобрела ‎широкую‏ ‎популярность, ‎так‏ ‎же ‎Lua, ‎Groovy.

Уровень ‎3

Управляемые ‎языки,‏ ‎где‏ ‎в ‎основу‏ ‎положена ‎продуманная‏ ‎виртуальная ‎машина. ‎Цель ‎проста ‎—‏ ‎отсутствие‏ ‎багов‏ ‎связанных ‎с‏ ‎нарушением ‎целостности‏ ‎доступа ‎к‏ ‎памяти.‏ ‎Из ‎средств‏ ‎— ‎сборщик ‎мусора ‎и ‎JIT‏ ‎компилятор. ‎Основные‏ ‎языки‏ ‎— ‎Java ‎(JVM)‏ ‎и ‎C#‏ ‎(CLR). ‎Отдельного ‎внимания ‎заслуживает‏ ‎Beam‏ ‎(Erlang), ‎как‏ ‎распределенная ‎виртуальная‏ ‎машина.

Уровень ‎2

Системные ‎языки ‎с ‎опциональным‏ ‎сборщиком‏ ‎мусора. ‎Таких‏ ‎совсем ‎немного‏ ‎— ‎D ‎и ‎Go, ‎также‏ ‎Nim.‏ ‎Особенность‏ ‎таких ‎языков‏ ‎большая ‎гибкость‏ ‎применения. ‎Компиляция‏ ‎при‏ ‎это ‎AOT.

Уровень‏ ‎1

Языки ‎класса ‎C/C++. ‎На ‎них‏ ‎пишутся ‎операционки‏ ‎и‏ ‎драйверы ‎к ‎ним.‏ ‎Основной ‎критерий‏ ‎— ‎можно ‎ли ‎преобразовать‏ ‎число‏ ‎в ‎указатель‏ ‎и ‎наоборот.

Уровень‏ ‎0

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

Читать: 7+ мин
G
logo
Glow labs

Самая большая ошибка D

Самая ‎большая‏ ‎ошибка ‎D ‎— ‎транзитивные ‎квалификаторы

Не‏ ‎стоит ‎заблуждаться,‏ ‎thread-local‏ ‎по ‎умолчанию ‎это‏ ‎замечательно! ‎Но‏ ‎shared ‎должен ‎был ‎быть‏ ‎квалификатором‏ ‎хранения, ‎а‏ ‎static ‎и‏ ‎простые ‎глобальные ‎переменные ‎остались ‎бы‏ ‎неявно‏ ‎с ‎квалификатором‏ ‎хранения ‎thread-local.

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

Транзитивные ‎квалификаторы‏ ‎взятые ‎вместе‏ ‎с‏ ‎«wildcard» ‎квалификатором ‎inout‏ ‎являются ‎самым‏ ‎большим ‎ломающим ‎изменением ‎при‏ ‎переходе‏ ‎от ‎D1‏ ‎к ‎D2‏ ‎с ‎огромными ‎затратами, ‎которые ‎все‏ ‎еще‏ ‎«оплачиваются» ‎тк‏ ‎они ‎дают‏ ‎стабильный ‎поток ‎багов ‎компилятора, ‎хотя‏ ‎и‏ ‎в‏ ‎основном ‎решенный‏ ‎к ‎настоящему‏ ‎моменту. ‎Побочные‏ ‎эффекты‏ ‎транзитивности ‎не‏ ‎только ‎очевидны ‎в ‎пользовательских ‎базах‏ ‎кода, ‎они‏ ‎так‏ ‎же ‎(если ‎не‏ ‎хуже) ‎проникают‏ ‎в ‎виде ‎изменений ‎в‏ ‎компиляторе.

Продвигаемые‏ ‎как ‎требование‏ ‎для ‎поддержки‏ ‎функционального ‎программирования ‎и ‎гарантий ‎чистых‏ ‎(pure)‏ ‎функций. ‎Что‏ ‎ж, ‎давайте‏ ‎разобьем ‎это ‎утверждение. ‎Во-первых ‎я‏ ‎программировал‏ ‎на‏ ‎Scala, ‎часто‏ ‎используемой ‎как‏ ‎«заменитель» ‎Haskel‏ ‎на‏ ‎JVM, ‎при‏ ‎этом ‎многие ‎идиомы ‎прекрасно ‎переносятся.‏ ‎Смотрите ‎на‏ ‎Cats,‏ ‎Scalaz ‎и ‎связанные‏ ‎проекты ‎для‏ ‎примеров ‎пуристического ‎FP ‎на‏ ‎Scala.‏ ‎Здесь ‎как‏ ‎и ‎в‏ ‎других ‎языках, ‎которые ‎я ‎к‏ ‎сожалению‏ ‎не ‎знаю‏ ‎достаточно ‎хорошо,‏ ‎мутабельность ‎решается ‎очень ‎прагматично:

  • val ‎это‏ ‎обозначение‏ ‎тип‏ ‎переменной ‎по‏ ‎умолчанию, ‎означающее‏ ‎неглубокий ‎const‏ ‎(непереприсваиваемое)
  • var‏ ‎это ‎мутабельное/переприсваиваемоме‏ ‎обозначение, ‎сильно ‎порицаемое ‎инструментами ‎экосистемы‏ ‎(особенно ‎подсвечивается‏ ‎в‏ ‎редакторах ‎и ‎тд)
  • коллекции‏ ‎предоставляют ‎иммутабельность‏ ‎через ‎интерфейс ‎— ‎здесь‏ ‎просто‏ ‎нет ‎write/remove‏ ‎операций ‎в‏ ‎API ‎List-а.
  • Мутабельные ‎коллекции ‎предоставляют ‎надмножество‏ ‎иммутабельного‏ ‎API
  • Структуры ‎по‏ ‎построению ‎состоят‏ ‎из ‎val ‎или ‎var, ‎но‏ ‎case‏ ‎class-ы‏ ‎повседневная ‎сокращенная‏ ‎нотация ‎для‏ ‎просто ‎данных‏ ‎состоит‏ ‎только ‎из‏ ‎val-ов.

Если ‎мы ‎прибавим ‎к ‎этому‏ ‎явный ‎shared‏ ‎квалификатор,‏ ‎финальный ‎гвоздь ‎в‏ ‎гроб ‎багов‏ ‎concurrency ‎— ‎глобальные ‎переменные‏ ‎в‏ ‎основном ‎будут‏ ‎закрыты. ‎В‏ ‎Scala ‎это ‎было ‎бы ‎очень‏ ‎неудобно‏ ‎ввести ‎shared‏ ‎в ‎основном‏ ‎потому, ‎что ‎ей ‎надо ‎бесшовно‏ ‎интегрироваться‏ ‎с‏ ‎Java/JVM ‎и‏ ‎вводить ‎плохо‏ ‎переводимые ‎квалификаторы‏ ‎хранения‏ ‎/ ‎поведения‏ ‎вызвало ‎бы ‎расхождение ‎импеданса. ‎D‏ ‎не ‎ограничен‏ ‎подобными‏ ‎соображениями.

Не ‎полностью ‎уничтожены,‏ ‎но ‎с‏ ‎установленными ‎легкими ‎и ‎корректными‏ ‎умолчаниями,‏ ‎а ‎также‏ ‎с ‎остальным‏ ‎языком ‎и ‎инструментарием ‎подталкивающими ‎в‏ ‎правильном‏ ‎направлении ‎это‏ ‎делает ‎90%‏ ‎работы ‎без ‎ограничения ‎нишевых ‎случаев.‏ ‎Таких‏ ‎как,‏ ‎например, ‎lock-free‏ ‎программирование, ‎которое‏ ‎очень ‎болезненно‏ ‎писать‏ ‎с ‎транзитивным‏ ‎shared ‎в ‎D ‎— ‎никуда‏ ‎не ‎дется‏ ‎от‏ ‎привидения ‎типов ‎к‏ ‎thread-local ‎мутабельным‏ ‎данным. ‎Проблему ‎легко ‎изложить‏ ‎—‏ ‎многие ‎lock-free‏ ‎алгоритмы ‎держаться‏ ‎на ‎идее ‎получения ‎владения ‎чем-то‏ ‎через‏ ‎операцию ‎CAS‏ ‎(или ‎фиксацию‏ ‎ваших ‎локальных ‎изменений). ‎По ‎самому‏ ‎определению‏ ‎она‏ ‎переводит ‎shared‏ ‎в ‎thread-local.‏ ‎Однако ‎никто‏ ‎не‏ ‎уведомил ‎нашего‏ ‎друга ‎компилятора ‎о ‎подобных ‎договоренностях‏ ‎и ‎я‏ ‎не‏ ‎вижу ‎твердых ‎планов‏ ‎(и ‎никаких‏ ‎сообщений ‎об ‎их ‎потенциале)‏ ‎в‏ ‎этом ‎направлении.

Но‏ ‎что ‎насчет‏ ‎блокировок, ‎я ‎слышу ‎ваш ‎вопрос.‏ ‎Та‏ ‎же ‎история‏ ‎в ‎основном‏ ‎— ‎вы ‎берете ‎блокировку ‎shared‏ ‎объекта‏ ‎и‏ ‎… ‎ничего‏ ‎не ‎происходит,‏ ‎он ‎все‏ ‎еще‏ ‎shared ‎и‏ ‎только ‎CAS ‎является ‎допустимой ‎операцией.‏ ‎При ‎всей‏ ‎своей‏ ‎сложности ‎система ‎типаов‏ ‎D ‎недостаточно‏ ‎умна, ‎чтобы ‎выучить ‎передачу‏ ‎владения‏ ‎и ‎вставить‏ ‎«умные ‎приведения‏ ‎типов» ‎(как ‎Kotlin) ‎или ‎что-то‏ ‎в‏ ‎этом ‎роде.

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

Обмен‏ ‎сообщениями ‎сам‏ ‎по ‎себе ‎(например ‎библиотека‏ ‎std.concurrency)‏ ‎не ‎решают‏ ‎проблему ‎транзитивности‏ ‎— ‎вы ‎все ‎еще ‎приводите‏ ‎типы‏ ‎на ‎стороне‏ ‎приемника ‎(ведь‏ ‎отправитель ‎должен ‎отпустить ‎этот ‎объект,‏ ‎не‏ ‎правда-ли?‏ ‎Владение ‎—‏ ‎снова ‎не‏ ‎отслеживается ‎и‏ ‎не‏ ‎может ‎быть‏ ‎использовано).

Так ‎в ‎транзитивным ‎shared ‎мы‏ ‎разочаровались, давайте ‎перейдем‏ ‎к‏ ‎дизайнам ‎открывающимся ‎засчет‏ ‎транзитивной ‎иммутабельности.‏ ‎Может ‎быть ‎здесь ‎мы‏ ‎найдем‏ ‎сокровища, ‎из-за‏ ‎которых ‎мы‏ ‎столько ‎страдали.

Увы, ‎нет, ‎все ‎в‏ ‎целом‏ ‎так ‎же‏ ‎— ‎решение‏ ‎в ‎поисках ‎проблемы. ‎И ‎она‏ ‎создают‏ ‎кучу‏ ‎своих ‎проблем:

  1. Копия‏ ‎immutable ‎(T)‏ ‎может ‎передаваться‏ ‎как‏ ‎(неявно) ‎head-mutable,‏ ‎но ‎только ‎если ‎это ‎указатель‏ ‎или ‎слайс.‏ ‎Определяемым‏ ‎пользователем ‎типам ‎не‏ ‎везет ‎и‏ ‎это ‎делает ‎написание ‎общего‏ ‎кода‏ ‎корректного ‎относительно‏ ‎const ‎квалификатора‏ ‎прелестной ‎пыткой.
  2. Обобщенный ‎код ‎должен ‎использовать‏ ‎const‏ ‎(T) ‎как‏ ‎надтип ‎T‏ ‎и ‎immutable ‎(T). ‎К ‎сожалению‏ ‎это‏ ‎все‏ ‎еще ‎имеет‏ ‎проблему ‎#1.
  3. Шаблоны‏ ‎без ‎радикальной‏ ‎предосторожности‏ ‎и ‎Unqual!‏ ‎T ‎повсюду ‎будут ‎инстанцированны ‎до‏ ‎3x ‎(!)‏ ‎раз.‏ ‎Это ‎может ‎быть‏ ‎причиной ‎почему‏ ‎время ‎компиляции ‎не ‎лучше‏ ‎на‏ ‎рынке ‎(хотя‏ ‎и ‎среди‏ ‎быстрейших, ‎упущенная ‎возможность ‎прямо ‎здесь).
  4. inout.‏ ‎Это.‏ ‎Я ‎был‏ ‎с ‎D‏ ‎с ‎2010-2018 ‎и ‎это ‎было‏ ‎прекрасное‏ ‎безумное‏ ‎приключение, ‎но.‏ ‎Я. ‎Никогда.‏ ‎Полностью. ‎Не‏ ‎понимал.‏ ‎Как ‎inout‏ ‎может ‎работать ‎за ‎приделами ‎простейших‏ ‎(предусмотренных?) ‎примеров.‏ ‎Также‏ ‎имейте ‎в ‎виду,‏ ‎что ‎это‏ ‎смешивается ‎с ‎проблемой ‎#3.
  5. Пожалуй‏ ‎единственные‏ ‎типы ‎хорошо‏ ‎работающие ‎с‏ ‎immutable ‎это ‎указатели ‎и ‎массивы,‏ ‎спасибо‏ ‎#1. ‎И‏ ‎честно ‎говоря,‏ ‎простой ‎оберточный ‎тип ‎запрещающий ‎модификацию‏ ‎легко‏ ‎решает‏ ‎этот ‎сценарий,‏ ‎покрывая ‎80+%‏ ‎применения ‎immutable‏ ‎«в‏ ‎природе».
  6. Сделать ‎свои‏ ‎типы ‎дружелюбными ‎к ‎immutable/const ‎это‏ ‎адская ‎боль‏ ‎в‏ ‎заднице ‎и ‎не‏ ‎пройдет ‎достаточно‏ ‎времени ‎прежде, ‎чем ‎ваши‏ ‎пользователи‏ ‎попробуют ‎const‏ ‎blah ‎=‏ ‎YourType ‎(…); ‎и ‎будут ‎громко‏ ‎жаловаться,‏ ‎что ‎вы‏ ‎не ‎поддерживаете‏ ‎корректность ‎относительно ‎const. ‎Реализовывать ‎сложные‏ ‎типы,‏ ‎которые‏ ‎работают ‎с‏ ‎транзитивным ‎const‏ ‎некрасиво ‎и‏ ‎более‏ ‎того ‎это‏ ‎перекладывает ‎нагрузку ‎не ‎на ‎то‏ ‎место. ‎Пользователи‏ ‎должны‏ ‎подвергаться ‎давлению, ‎чтобы‏ ‎использовать ‎const,‏ ‎но ‎не ‎делая ‎жизнь‏ ‎автора‏ ‎библиотеки ‎значительно‏ ‎труднее.

В ‎отличает‏ ‎от ‎транзитивного ‎const ‎поверхностный ‎const‏ ‎(~‏ ‎final ‎из‏ ‎Java) ‎тривиально‏ ‎применим ‎к ‎любому ‎определенному ‎пользователем‏ ‎типу,‏ ‎практически‏ ‎без ‎какой-либо‏ ‎дополнительной ‎работы‏ ‎со ‎стороны‏ ‎автора‏ ‎библиотеки. ‎Более‏ ‎того, ‎теперь ‎автор ‎может ‎предоставить‏ ‎типы, ‎которые‏ ‎иммутабельны‏ ‎за ‎счет ‎интерфейса‏ ‎(Коллекции! ‎Строки!).

Наконец.‏ ‎Существуеь ‎одно ‎(!) ‎хорошее‏ ‎взаимодействие‏ ‎с ‎транзитивностью‏ ‎и ‎это‏ ‎(честные) ‎чистые ‎(pure) ‎функции. ‎Однако‏ ‎D‏ ‎прославляет ‎себя‏ ‎как ‎прагматичный‏ ‎и ‎я ‎спорю ‎на ‎что‏ ‎угодно,‏ ‎что‏ ‎«нечестные» ‎(логически‏ ‎иммутабельные) ‎чистые‏ ‎функции ‎в‏ ‎Scala/Haskel/любом‏ ‎другом ‎функциональном‏ ‎языке ‎прекрасно ‎работают ‎и ‎гораздо‏ ‎проще ‎пишутся.

Каков‏ ‎же‏ ‎выход?

Я ‎не ‎знаю‏ ‎и ‎не‏ ‎имею ‎никакой ‎силы ‎изменить‏ ‎направление‏ ‎языка ‎D.‏ ‎Это ‎вдохновляющий‏ ‎современный ‎системный ‎язык ‎программирования, ‎но‏ ‎без‏ ‎решения ‎изъяна‏ ‎такого ‎масштаба‏ ‎будет ‎трудно ‎вырасти ‎за ‎приделы‏ ‎текущей‏ ‎(значительно‏ ‎большей ‎нуля‏ ‎кстати, ‎впечатляюще)‏ ‎аудитории.

Мое ‎предложение‏ ‎(одевая‏ ‎шляпу ‎BDDL‏ ‎языка ‎D) ‎будет ‎таким ‎…

Выбросить‏ ‎их! ‎И‏ ‎сжечь‏ ‎в ‎огне.

Шаги ‎будут‏ ‎следующими:

  1. Проверки ‎иммутабельности‏ ‎поверхностные, ‎но ‎компилятор ‎предупреждает‏ ‎о‏ ‎нарушении ‎транзитивного‏ ‎const/immutable. ‎С‏ ‎preview ‎опцией ‎вы ‎не ‎получаете‏ ‎таких‏ ‎предупреждений.
  2. Shared ‎является‏ ‎квалификатором ‎хранения‏ ‎и ‎preview ‎опция ‎предупреждает ‎о‏ ‎неверном‏ ‎применении,‏ ‎заглушаемые ‎флагом.‏ ‎Такого ‎применение‏ ‎игнорируется ‎пару‏ ‎релизов.
  3. 2&‏ ‎4 ‎со‏ ‎своим ‎ритмом ‎переводятся ‎в ‎умолчания,‏ ‎с ‎явными‏ ‎опциями‏ ‎для ‎отката.
  4. Никаких ‎опций‏ ‎больше ‎нет‏ ‎и ‎мы ‎покончили ‎с‏ ‎«черепахами‏ ‎до ‎самого‏ ‎конца» ‎и‏ ‎надеюсь ‎навсегда.


Читать: 1+ мин
G
logo
Glow labs

Medium posts

Читать: 11+ мин
G
logo
Glow labs

Внутри сборщика мусора D


На ‎конференции‏ ‎DConf ‎2017 ‎в ‎хакатоне ‎я‏ ‎вел ‎группу‏ ‎(из‏ ‎2 ‎человек) ‎занимающуюся‏ ‎druntime ‎—‏ ‎ковырявшуюся ‎в ‎сборщике ‎мусора‏ ‎D.‏ ‎Спустя ‎пару‏ ‎часов ‎я‏ ‎не ‎мог ‎стряхнуть ‎чувство ‎—‏ ‎черт‏ ‎это ‎все‏ ‎неплохо ‎бы‏ ‎переписать. ‎Так ‎что ‎я ‎решил‏ ‎начать‏ ‎задачу‏ ‎построения ‎лучшего‏ ‎сборщика ‎мусора‏ ‎для ‎D.‏ ‎Первая‏ ‎итерация ‎должна‏ ‎быть ‎более ‎быстрым ‎классическим ‎mark-sweep‏ ‎сборщиком.

Чтобы ‎объяснить‏ ‎мою‏ ‎мотивацию ‎я ‎покажу‏ ‎внутренности ‎текущего‏ ‎сборщика ‎мусора, ‎перечислю ‎все‏ ‎проблемы‏ ‎его ‎дизайна.‏ ‎Так ‎или‏ ‎иначе, ‎мы ‎должны ‎анализировать ‎то,‏ ‎что‏ ‎мы ‎имеем,‏ ‎чтобы ‎понять‏ ‎куда ‎идти ‎дальше.

Пулы, ‎пулы ‎повсюду

Если‏ ‎бы‏ ‎мы‏ ‎проигнорировали ‎некоторую‏ ‎глобальную ‎машинерию‏ ‎то ‎сборщик‏ ‎мусора‏ ‎это ‎просто‏ ‎массив ‎пулов. ‎Каждый ‎пул ‎это‏ ‎кусок ‎mmap-нутой‏ ‎памяти‏ ‎+ ‎пачка ‎malloc-нутых‏ ‎метаданных ‎таких‏ ‎как ‎таблицы ‎для ‎mark‏ ‎битов,‏ ‎free ‎битов‏ ‎и ‎так‏ ‎далее. ‎Все ‎аллокации ‎происходят ‎внутри‏ ‎пула,‏ ‎если ‎ни‏ ‎один ‎пул‏ ‎не ‎может ‎аллоцировать ‎память, ‎то‏ ‎создается‏ ‎новый‏ ‎пул. ‎Размер‏ ‎пула ‎определяется‏ ‎арифметической ‎прогрессией‏ ‎по‏ ‎числу ‎пулов‏ ‎или ‎150% ‎размера ‎аллокации, ‎в‏ ‎зависимости ‎от‏ ‎того,‏ ‎что ‎больше.

Любая ‎маленькая‏ ‎аллокация ‎сперва‏ ‎округляется ‎до ‎одной ‎из‏ ‎степеней‏ ‎2 ‎(класс‏ ‎размера) ‎—‏ ‎16, ‎32, ‎64, ‎128,256, ‎512,‏ ‎1024,‏ ‎2048.Затем ‎проверяется‏ ‎глобальный ‎freelist‏ ‎для ‎этих ‎классов ‎размеров, ‎если‏ ‎не‏ ‎удается‏ ‎найти ‎мы‏ ‎ищем ‎маленький‏ ‎пул. ‎Этот‏ ‎маленький‏ ‎пул ‎будет‏ ‎аллоцировать ‎новую ‎страницу ‎связывать ‎ее‏ ‎с ‎free‏ ‎list‏ ‎объектов ‎это ‎класса‏ ‎размеров. ‎И‏ ‎тут ‎видна ‎первая ‎ошибка‏ ‎дизайна‏ ‎— ‎класс‏ ‎размера ‎назначается‏ ‎каждой ‎странице, ‎и ‎следовательно ‎нам‏ ‎нужна‏ ‎таблица, ‎которая‏ ‎отражает ‎страницу‏ ‎пула ‎в ‎класс ‎размер ‎(называемой‏ ‎pagetable).

Теперь‏ ‎чтобы‏ ‎найти ‎начало‏ ‎объекта ‎из‏ ‎внутреннего ‎указателя‏ ‎мы‏ ‎сперва ‎обнаруживаем‏ ‎страницу ‎к ‎которой ‎он ‎относится,‏ ‎затем ‎находим‏ ‎ее‏ ‎класс ‎и ‎наконец‏ ‎маскируем ‎биты‏ ‎чтобы ‎получить ‎начало ‎объекта.‏ ‎Более‏ ‎того ‎метаданные‏ ‎это ‎просто‏ ‎пачка ‎простых ‎побитовых ‎таблиц, ‎которым‏ ‎нужно‏ ‎подстроится ‎под‏ ‎гетерогенные ‎страницы,‏ ‎что ‎делается ‎путем ‎выделения ‎~7‏ ‎бит‏ ‎на‏ ‎16 ‎байт‏ ‎вне ‎зависимости‏ ‎от ‎размера‏ ‎объекта.

Что‏ ‎смотивировало ‎такой‏ ‎дизайн? ‎У ‎меня ‎две ‎гипотезы.‏ ‎Первая ‎это‏ ‎нежелание‏ ‎резервировать ‎память ‎для‏ ‎недостаточно ‎утилизированных‏ ‎пулов, ‎что ‎не ‎является‏ ‎проблемой‏ ‎из-за ‎того‏ ‎как ‎устроена‏ ‎виртуальная ‎память ‎с ‎ленивым ‎выделением‏ ‎ресурсов.‏ ‎Вторая ‎это‏ ‎опасения ‎выделения‏ ‎слишком ‎большого ‎числа ‎пулов, ‎замедляя‏ ‎выделение‏ ‎памяти‏ ‎и ‎что‏ ‎любопытно ‎фазу‏ ‎mark ‎сборщика‏ ‎мусора.‏ ‎Последнее ‎скорее‏ ‎всего ‎и ‎есть ‎причина, ‎поскольку‏ ‎сборщик ‎мусора‏ ‎делает‏ ‎линейный ‎проход ‎по‏ ‎пулам ‎довольно‏ ‎часто ‎и ‎двоичный ‎поиск‏ ‎для‏ ‎каждого ‎потенциального‏ ‎указателя ‎во‏ ‎время ‎фазы ‎mark.

Это ‎подводит ‎нас‏ ‎ко‏ ‎второй ‎ошибке‏ ‎поиск ‎пула‏ ‎за ‎logP, ‎где ‎P ‎число‏ ‎пулов,‏ ‎что‏ ‎делает ‎mark‏ ‎NlogP ‎задачей.‏ ‎Хештаблица ‎могла‏ ‎бы‏ ‎сэкономить ‎изрядное‏ ‎число ‎циклов.

Завершая ‎наш ‎обзор ‎малых‏ ‎пулов ‎мы‏ ‎также‏ ‎рассмотрим ‎выбор ‎классов‏ ‎размера. ‎Это‏ ‎третья ‎проблема ‎(не ‎ошибка,‏ ‎но‏ ‎спорное ‎решение)‏ ‎в ‎выборе‏ ‎степеней ‎двойки, ‎что ‎гарантирует ‎нам‏ ‎вплоть‏ ‎до ‎50%‏ ‎внутренней ‎фрагментации.‏ ‎Современные ‎аллокаторы ‎такие ‎как ‎jemalloc‏ ‎предоставляют‏ ‎по‏ ‎одному ‎классы‏ ‎между ‎степенями‏ ‎двоек. ‎Деление‏ ‎на‏ ‎константу ‎не‏ ‎степени ‎двойки ‎немного ‎дороже ‎чем‏ ‎один ‎AND,‏ ‎но‏ ‎все ‎же ‎вполне‏ ‎приемлимы ‎по‏ ‎скорости.

Давайте ‎посмотрим ‎на ‎пулы‏ ‎для‏ ‎больших ‎объектов.‏ ‎Первое, ‎что‏ ‎бросается ‎в ‎глаза ‎это ‎то,‏ ‎что‏ ‎гранулярность ‎одна‏ ‎страница ‎памяти‏ ‎— ‎4кб ‎для ‎метаданных ‎и‏ ‎аллокаций.‏ ‎Ряды‏ ‎свободных ‎страниц‏ ‎связаны ‎в‏ ‎free ‎list,‏ ‎который‏ ‎сканируется ‎линейно‏ ‎для ‎каждой ‎аллокации. ‎Это ‎четвертая‏ ‎ошибка, ‎то‏ ‎есть‏ ‎с ‎производительностью ‎больших‏ ‎аллокаций ‎решили‏ ‎не ‎заморачиваться. ‎Чтобы ‎обнаружить‏ ‎начало‏ ‎объекта ‎поддерживается‏ ‎отдельная ‎таблица,‏ ‎где ‎для ‎каждой ‎страницы ‎хранится‏ ‎индекс‏ ‎начала ‎относящегося‏ ‎к ‎ней‏ ‎объекта. ‎Эта ‎схема ‎кажется ‎разумной‏ ‎пока‏ ‎мы‏ ‎не ‎задумаемся‏ ‎над ‎большими‏ ‎аллокациями ‎100+‏ ‎Mb,‏ ‎поскольку ‎почти‏ ‎наверняка ‎не ‎сможет ‎релоцироваться ‎по‏ ‎месту ‎и‏ ‎создаст‏ ‎новый ‎пул, ‎что‏ ‎израсходует ‎впустую‏ ‎огромный ‎объем ‎памяти ‎на‏ ‎мета‏ ‎данные ‎того,‏ ‎что ‎по‏ ‎сути ‎является ‎одним ‎объектом.

Сборка

До ‎сих‏ ‎пор‏ ‎мы ‎рассматривали‏ ‎путь ‎аллокации,‏ ‎деаллокация ‎проходит ‎через ‎те ‎же‏ ‎стадии.‏ ‎Что‏ ‎более ‎интересно‏ ‎это ‎автоматический‏ ‎возврат ‎памяти,‏ ‎что‏ ‎и ‎есть‏ ‎суть ‎сборки ‎мусора. ‎Позволим ‎себе‏ ‎заметить, ‎что‏ ‎сборка‏ ‎мусора ‎в ‎D‏ ‎консервативная, ‎то‏ ‎есть ‎сборщик ‎не ‎знает‏ ‎является‏ ‎ли ‎что-то‏ ‎указателем ‎или‏ ‎нет. ‎Также ‎он ‎поддерживает ‎финализацию,‏ ‎то‏ ‎есть ‎действия‏ ‎которые ‎вызываются‏ ‎на ‎объекте ‎прежде, ‎чем ‎возвращать‏ ‎его‏ ‎память.‏ ‎Эти ‎два‏ ‎фактора ‎сильно‏ ‎ограничивают ‎архитектуру‏ ‎сборщика.

На‏ ‎верхнем ‎уровне‏ ‎сборка ‎неожиданно ‎занимает ‎аж ‎4‏ ‎фазы: ‎prepare,‏ ‎markAll,‏ ‎sweep ‎и ‎recover.

Стадия‏ ‎prepare ‎самая‏ ‎странная, ‎поскольку ‎по ‎сути‏ ‎она‏ ‎должна ‎быть‏ ‎просто ‎«скопировать‏ ‎free ‎биты ‎в ‎mark ‎биты»‏ ‎(чтобы‏ ‎избежать ‎сканирования‏ ‎свободной ‎памяти).‏ ‎Ситуация ‎осложнена ‎тем ‎что ‎мы‏ ‎вычисляем‏ ‎свободное‏ ‎простоанство ‎путем‏ ‎обхода ‎всех‏ ‎free ‎list.‏ ‎Это‏ ‎5-я ‎(?)‏ ‎ошибка ‎— ‎скакание ‎дополнительно ‎по‏ ‎неизвестному ‎числу‏ ‎указателей‏ ‎это ‎последнее ‎что‏ ‎стоит ‎делать‏ ‎во ‎время ‎stop ‎the‏ ‎world‏ ‎паузы. ‎Более‏ ‎удачный ‎дизайн‏ ‎это ‎переключать ‎биты ‎при ‎аллокации,‏ ‎особенно‏ ‎с ‎учетом‏ ‎того, ‎что‏ ‎free ‎list ‎поддерживает ‎указатели ‎на‏ ‎пулы‏ ‎для‏ ‎каждого ‎объекта‏ ‎чтобы ‎не‏ ‎выполнять ‎поиск‏ ‎пула.

Реальный‏ ‎mark ‎выполняется‏ ‎в ‎вызове ‎markAll, ‎которая ‎просто‏ ‎передает ‎подходящие‏ ‎диапазоны‏ ‎памяти ‎в ‎функцию‏ ‎mark. ‎Эта‏ ‎функция ‎заслуживает ‎более ‎глубокого‏ ‎рассмотрения.

  1. Для‏ ‎каждого ‎указателя‏ ‎в ‎диапазоне‏ ‎памяти ‎она ‎сперва ‎проверяет ‎попадает‏ ‎ли‏ ‎адрес ‎в‏ ‎диапазон ‎адресов‏ ‎кучи ‎сборщика ‎мусора ‎(от ‎наименьшего‏ ‎адреса‏ ‎в‏ ‎пуле ‎до‏ ‎наибольшего ‎адреса‏ ‎в ‎пуле).
  2. После‏ ‎этого‏ ‎идет ‎пугающий‏ ‎двоичный ‎поиск, ‎чтобы ‎найти ‎соответствующий‏ ‎пул ‎для‏ ‎указателя.
  3. Независимо‏ ‎от ‎типа ‎пула‏ ‎делается ‎чтение‏ ‎из ‎таблицы ‎страниц ‎текущего‏ ‎пула‏ ‎чтобы ‎посмотреть‏ ‎класс ‎размера‏ ‎или ‎является ‎ли ‎это ‎большим‏ ‎объектом,‏ ‎или ‎даже‏ ‎свободной ‎памятью.‏ ‎Небольшая ‎оптимизация ‎состоит ‎в ‎том,‏ ‎что‏ ‎это‏ ‎чтение ‎также‏ ‎говорит ‎нам,‏ ‎является ‎ли‏ ‎это‏ ‎началом ‎большого‏ ‎объекта ‎или ‎его ‎продолжением. ‎У‏ ‎нас ‎3‏ ‎случая‏ ‎— ‎маленький ‎объект,‏ ‎большой ‎объект‏ ‎или ‎продолжение ‎большого ‎объекта.‏ ‎Последние‏ ‎два ‎случая‏ ‎одинаковы ‎за‏ ‎исключением ‎дополнительного ‎чтения ‎из ‎таблицы.
  4. Определение‏ ‎начала‏ ‎объекта ‎маскирование‏ ‎бит ‎с‏ ‎соответствующей ‎маской, ‎плюс ‎в ‎случае‏ ‎продолжения‏ ‎большого‏ ‎объекта ‎дополнительное‏ ‎чтение ‎из‏ ‎таблицы ‎смещений.
  5. В‏ ‎случае‏ ‎большого ‎объекта‏ ‎существует ‎бит ‎«нет ‎внутренних ‎указателей»,‏ ‎что ‎позволяет‏ ‎игнорировать‏ ‎указатели ‎внутрь ‎объекта.
  6. Наконец‏ ‎проверить ‎и‏ ‎выставить ‎бит ‎mark, ‎если‏ ‎он‏ ‎не ‎был‏ ‎выставлен ‎или‏ ‎стоит ‎noscan ‎бит ‎добавить ‎объект‏ ‎к‏ ‎стеку ‎памяти‏ ‎под ‎сканирование.

Пропуская‏ ‎любопытные ‎манипуляции ‎со ‎стеком ‎(чтобы‏ ‎избежать‏ ‎переполнения‏ ‎стека ‎и‏ ‎в ‎то‏ ‎же ‎время‏ ‎использовать‏ ‎стековую ‎аллокацию)‏ ‎это ‎все, ‎что ‎у ‎нас‏ ‎есть ‎в‏ ‎mark‏ ‎функции. ‎Кроме ‎уже‏ ‎упоминавшегося ‎поиска‏ ‎пулов, ‎неэффективностей ‎полно. ‎Смещивание‏ ‎памяти‏ ‎без ‎указателей‏ ‎с ‎обычной‏ ‎памятью ‎в ‎одном ‎пуле ‎дает‏ ‎нам‏ ‎дополнительное ‎чтение‏ ‎битовой ‎таблицы‏ ‎на ‎критическом ‎пути. ‎Аналогично ‎чтение‏ ‎таблицы‏ ‎страниц‏ ‎можно ‎было‏ ‎бы ‎избежать‏ ‎если ‎бы‏ ‎мы‏ ‎разделяли ‎пулы‏ ‎по ‎классу ‎размера. ‎И ‎конечно‏ ‎спорная ‎оптимизация‏ ‎«нет‏ ‎внутренних ‎указателей», ‎которая‏ ‎не ‎только‏ ‎дает ‎небезопасный ‎код ‎(объект‏ ‎может‏ ‎быть ‎собран,‏ ‎несмотря ‎на‏ ‎ссылки ‎на ‎него), ‎но ‎и‏ ‎добавляет‏ ‎дополнительные ‎проверки‏ ‎на ‎критическом‏ ‎пути ‎для ‎всех ‎больших ‎объектов,‏ ‎добавляя‏ ‎потенциальное‏ ‎чтение ‎битовой‏ ‎таблицы.

Что ‎же‏ ‎это ‎было‏ ‎довольно‏ ‎сложно, ‎но‏ ‎помните ‎что ‎mark ‎фаза ‎это‏ ‎сердце ‎любого‏ ‎сборщика.‏ ‎Теперь ‎к ‎третьей‏ ‎фазе ‎—‏ ‎sweep. ‎Иронично ‎sweep ‎не‏ ‎подчищает‏ ‎память ‎в‏ ‎free ‎list-ы‏ ‎как ‎можно ‎было ‎бы ‎ожидать.‏ ‎Вместо‏ ‎этого ‎все‏ ‎чем ‎она‏ ‎занимается ‎это ‎вызов ‎finalizer-ов ‎(если‏ ‎есть)‏ ‎и‏ ‎установкой ‎свободных‏ ‎битов ‎и‏ ‎прочих ‎таблиц.‏ ‎Имеем‏ ‎в ‎виду,‏ ‎что ‎при ‎этом ‎выполняется ‎линейный‏ ‎проход ‎по‏ ‎памяти‏ ‎каждого ‎пула ‎и‏ ‎просмотр ‎mark‏ ‎битов.

Финальная ‎стадия ‎— ‎recover,‏ ‎действительно‏ ‎строит ‎free‏ ‎list-ы. ‎Это‏ ‎опять ‎линейный ‎проход, ‎но ‎только‏ ‎по‏ ‎маленьким ‎пулам.‏ ‎Снова ‎чтение‏ ‎таблицы ‎страниц ‎для ‎каждой ‎страницы‏ ‎чтобы‏ ‎узнать‏ ‎класс ‎размера,‏ ‎просто ‎хочется‏ ‎плакать. ‎Но‏ ‎основной‏ ‎вопрос ‎без‏ ‎ответа ‎— ‎зачем? ‎Зачем ‎дополнительный‏ ‎проход? ‎Я‏ ‎сильно‏ ‎старался ‎чтобы ‎придумать‏ ‎разумное ‎объяснение,‏ ‎но ‎не ‎смог, ‎«для‏ ‎простоты»‏ ‎слабая, ‎но‏ ‎вероятная ‎причина.‏ ‎Это ‎последняя ‎большая ‎ошибка ‎по‏ ‎моим‏ ‎подсчетам.

Чего ‎здесь‏ ‎нет

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

Thread ‎cache ‎явный ‎недосмотр, ‎имея‏ ‎в ‎виду‏ ‎что‏ ‎сборщик ‎мусора ‎D‏ ‎уходит ‎корнями‏ ‎в ‎ранние ‎2000ые ‎это‏ ‎не‏ ‎так ‎уж‏ ‎неожиданно. ‎Практически‏ ‎каждый ‎современный ‎аллокатор ‎имеет ‎некий‏ ‎thread‏ ‎cache, ‎некоторые‏ ‎пытаются ‎поддерживать‏ ‎по ‎процессорный ‎кеш. ‎Кеш ‎работает‏ ‎так,‏ ‎что‏ ‎каждый ‎thread‏ ‎выделяет ‎аллокации‏ ‎пачками, ‎сохраняя‏ ‎запас‏ ‎аллокаций ‎на‏ ‎будущее. ‎Это ‎амортизирует ‎цену ‎блокирования‏ ‎общих ‎структур‏ ‎кучи.‏ ‎Кстати ‎присутствует ‎немного‏ ‎fine ‎grained‏ ‎блокирование, ‎но ‎не ‎на‏ ‎уровне‏ ‎отдельных ‎пулов,‏ ‎к ‎примеру.

[Обновление‏ ‎2024] ‎Сборщик ‎D ‎получил ‎параллельный‏ ‎mark‏ ‎и ‎есть‏ ‎даже ‎версия‏ ‎concurrent ‎сборщика ‎для ‎Posix.

Параллельный ‎mark‏ ‎еще‏ ‎один‏ ‎пример ‎современной‏ ‎фичи, ‎которая‏ ‎ожидается ‎от‏ ‎любого‏ ‎сборщика ‎мусора.‏ ‎Также ‎популяны ‎concurrent ‎или ‎mostly‏ ‎concurrent ‎сборщики,‏ ‎в‏ ‎которых ‎mark ‎и‏ ‎немного ‎реже‏ ‎sweep/compaction ‎выполняются ‎одновременно ‎с‏ ‎работой‏ ‎пользовательских ‎потоков.

В‏ ‎заключение

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

Теперь ‎подведем‏ ‎итог ‎моей‏ ‎первой ‎итерации ‎изменений ‎по ‎сравнению‏ ‎с‏ ‎статус-кво.

  • Разделить‏ ‎маленькие ‎пулы‏ ‎по ‎классу‏ ‎размера.
  • Сделать ‎поиск‏ ‎пула‏ ‎O ‎(1).
  • Попытаться‏ ‎использовать ‎классы ‎размера ‎не ‎степеней‏ ‎двойки ‎как‏ ‎jemalloc,‏ ‎чтобы ‎побороть ‎внутренную‏ ‎фрагментацию.
  • Разделит ‎все‏ ‎пулы ‎на ‎основе ‎noscan‏ ‎атрибута,‏ ‎это ‎ускоряет‏ ‎mark.
  • Предусмотреть ‎3-ий‏ ‎класс ‎«пулов» ‎для ‎огромных ‎аллокаций‏ ‎рассчитанных‏ ‎только ‎на‏ ‎один ‎объект.
  • Аллокация‏ ‎объектов ‎для ‎больших ‎пуллов ‎требует‏ ‎проработки,‏ ‎нужно‏ ‎какое-то ‎дерево‏ ‎с ‎ключом‏ ‎по ‎размеру‏ ‎блока.‏ ‎jemallox ‎использует‏ ‎красно-черные ‎деревья.
  • Игнорировать ‎атрибут ‎«нет ‎внутренних‏ ‎указателей» ‎он‏ ‎пытается‏ ‎побороть ‎ложные ‎указатели‏ ‎по ‎причине‏ ‎консервативности ‎сборщика ‎мусора. ‎Однако‏ ‎это‏ ‎фундаментально ‎небезапасно‏ ‎и ‎цена‏ ‎слишком ‎высока, ‎ну ‎и ‎наконец,‏ ‎это‏ ‎абсолютно ‎бессмысленно‏ ‎на ‎64-битных‏ ‎системах, ‎где ‎живут ‎ваши ‎серверы.
  • Никаких‏ ‎сомнительных‏ ‎мультифазных‏ ‎сборок, ‎это‏ ‎mark ‎и‏ ‎sweep ‎и‏ ‎точка.
  • Fine‏ ‎grained ‎блокировки‏ ‎с ‎самого ‎начала, ‎я ‎не‏ ‎вижу ‎проблем‏ ‎с‏ ‎блокировками ‎по ‎отдельным‏ ‎пулам.

Вторая ‎итерация‏ ‎будет ‎сосредоточенна ‎на ‎более‏ ‎мясистых‏ ‎вещах ‎вроде‏ ‎thread ‎cache,‏ ‎параллельного ‎mark ‎и ‎concurrent ‎mark‏ ‎с‏ ‎использованием ‎fork.

Третья‏ ‎итерация, ‎если‏ ‎мы ‎до ‎нее ‎доберемся, ‎будет‏ ‎абсолютно‏ ‎новый‏ ‎дизайн ‎—‏ ‎mark-region ‎сборщик,‏ ‎с ‎архитектурой‏ ‎вдохновленной‏ ‎immix.

На ‎этом‏ ‎мои ‎планы ‎завершаются ‎и ‎на‏ ‎этой ‎оптимистичной‏ ‎ноте,‏ ‎я ‎должен ‎предупредить,‏ ‎что ‎поначалу‏ ‎это ‎будет ‎Linux ‎специфичный‏ ‎сборщик,‏ ‎постепенно ‎эволюционирующий‏ ‎до ‎любой‏ ‎Posix ‎системы, ‎а ‎поддержка ‎Windows‏ ‎является‏ ‎отдаленной ‎возможностью.



Обновления проекта

Статистика

6 500 ₽ всего собрано

Контакты

Фильтры

Подарить подписку

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

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

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

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

Добавить карту
0/2048