Сплит-стеки ненужные на amd64

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

Использование типичной многопоточной программы памяти может значительно, поскольку каждый поток не требует наихудшего стека размер. Становится возможным запустить миллионы потоков (либо полный NPTL потоки или совлокальные подпрограммы) в 32-разрядном адресном пространстве. - Ян Лэнс Тейлор

... подразумевая, что 64-разрядное адресное пространство уже может обрабатывать его.

А...

... постоянные накладные расходы разделенных стеков и узкий прецедент (создавая огромное количество задач с привязкой к I/O на 32-разрядных архитектурах) неприемлемо... - bstrie

Два вопроса: Это то, что они говорят? Во-вторых, если да, то почему они не нужны для 64-разрядных архитектур?

Ответ 1

Да, это то, что они говорят.

Сплит-стеки (в настоящее время) не нужны на 64-битных архитектурах, потому что 64-битное виртуальное адресное пространство настолько велико, что может содержать миллионы диапазонов адресов стека, каждый размером до 32-битного адресного пространства, если это необходимо.

В модели Плоская модель памяти, используемой в настоящее время, перевод с виртуальных адресов на физическую память осуществляется с помощью аппаратный MMU. На amd64 выясняется, что лучше (что означает, в целом быстрее) резервировать большие куски 64-битного виртуального адресного пространства для каждого нового стека, который вы создаете, а только отображение первого (4 КБ) в реальную ОЗУ. Таким образом, стек будет иметь возможность расти и сжиматься по мере необходимости, по смежным виртуальным адресам (что означает меньше кода в каждом прологе функции, большой оптимизации), в то время как OS re -конфигурирует MMU для сопоставления каждой страницы виртуальных адресов с фактической бесплатной страницей ОЗУ, когда стек растет или сжимается выше/ниже некоторых настраиваемых пороговых значений.

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

PS: текущая реализация Go значительно отстает от любого из следующих: -)

Ответ 2

Основная группа Go в настоящее время обсуждает возможность использования непрерывных стеков в будущей версии Go.

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

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

Что касается преимуществ/недостатков в 32-битных и 64-битных архитектурах, я не думаю, что они напрямую связаны исключительно с использованием сегментированных стеков.

Ответ 3

Обновление Go 1.4 (Q4 2014)

Изменить на время выполнения:

До Go 1.4, среда выполнения (сборщик мусора, поддержка concurrency, управление интерфейсом, карты, фрагменты, строки,...) в основном написаны на C с поддержкой некоторых ассемблер.
В 1.4, большая часть кода была переведена в Go так, чтобы сборщик мусора мог сканировать стеки программ во время выполнения и получать точную информацию о том, какие переменные активны.

Эта перезапись позволяет сборщику мусора в 1.4 быть полностью точным, что означает, что ему известно о расположении всех активных указателей в программе. Это означает, что куча будет меньше, поскольку не будет ложных срабатываний, сохраняющих ненаговорки в живых. Другие связанные изменения также уменьшают размер кучи, который меньше на 10% -30% в целом по сравнению с предыдущим выпуском.

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


Первоначальный ответ (март 2014 года)

Статья " Смежные стеки в Go " Agis Anastasopoulo также решает эту проблему

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

"Горячий раскол" будет рассмотрен в Go 1.3 путем реализации смежных стеков.

Теперь, когда стек должен расти, вместо выделения нового сегмента происходит следующее:

  • Создайте новый, несколько больший стек
  • Скопировать содержимое старого стека в новый стек
  • Переустановите каждый скопированный указатель, чтобы указать на новые адреса.
  • Уничтожить старый стек

Ниже упоминается одна проблема, встречающаяся главным образом в 32-разрядных архитектурах:

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

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

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

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