Pull to refresh

Comments 166

UFO just landed and posted this here
512 метров виртуальной памяти. По факту физическая выделяется только по мере необходимости.
Вы издеваетесь или правда не знаете, как происходит резервирование адресного пространства?
Я, например, не знаю, и думаю многие не знают, а поумничать любят все =) И даже когда я, например, умничаю, мне гораздо приятней слышать в ответ аргумент, что я не так сказал, а не фразы: «Че умничаешь?».
Каждому процессу в операционной системе с поддержкой виртуальной памяти (а это все современные ОС) выделяется виртуальное адресное пространство максимально адресуемого размера (4 Гб в случае 32-битной ОС), которое постранично (то есть блоками, 4 Кб в linux, по-моему) отображается на реальную оперативную память или в своп. Таким образом осуществляется изолирование адресных пространств разных процессов и вообще, повышается гибкость управления памятью — например, если в ОЗУ больше нет места, то старые (те, к которым давно обращались) страницы можно временно скинуть в своп, а освободившийся страничный блок отдать запросившему память процессу.
Пффффф… С чего бы начать? Пожалуй, с DOS. Процессоры были 16-битными, памяти было мало, адресовать её было сложно, делилась она на сегменты по 64 килобайта, каждый из которых можно было адресовать 16-битным целым. А ещё она была одна на всех. Всё это было жутко неудобно. Потом пришли 32-битные процессоры и память стала плоской, т. е. можно сразу обращаться к любому адресу. Но поскольку процессы надо как-то друг от друга огородить, используется ещё и MMU (Memory Management Unit) позволяющий выполнять трансляцию логических адресов в физические. То есть, у каждого процесса теперь своё личное адресное пространство в 4 гигабайта, разделённое на «страницы», каждая из которых указывает на кусочек физической памяти. Или не указывает. Не указывает она в 3 случаях:
1) этот кусочек адресного пространства свободен
2) страница зарезервирована, но физическую память под неё ещё никто не просил
3) память была выделена, но страницу отправили в своп

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

Т. е. это абсолютно нормальная практика, выделить себе адресное пространство одним куском, чтобы к нему обращаться по смещению (base+offset) и не заморачиваться со всякими дурацкими списками выделенных лоскутов.

Проблема заключается вот в чём. 4 гигабайта процессу 32-битная ОС никогда не даст. Форточки верхние 2 гигабайта вообще юзерспейсу никогда не отдают, там сидит ядро (можно перенастроить на 1 гигабайт, правда, но по-умолчанию 2). И всё бы хорошо, но оставшиеся 2 гигабайта одним куском тоже не выделить, т. к. разработчкики форточек не удосужились написать код, который грузит все библиотеки где-то рядом. Вместо этого библиотека (kernelbase.dll) может оказаться ровно посередине адресного пространства. Таким образом выделить 2 гигабайта не получится, только 2 куска по гигабайту разом. А библиотек таких много. Получаем сильную фрагментацию АП, из-за которой не выделить даже 512 метров АП.

Если в 90-е это было нормально (кому было нужно 512 метров адресного пространства?) то сейчас это недопустимо.

Итого. Косяк не в рантайме Go, косяк в ядре ОС. И решать его можно либо исправлением загрузчика библиотек, либо переходом на 64 бита.
Получается в винде с переодичностью в 500 мегабайт висит какая-то системная библиотека, и поэтому нельзя попросить эти 500 или больше мегабайт одним куском? Как тогда работают программы которые жрут по гектару? Игры например. Они берут меньше но много что ли?
Большая часть софта никогда много памяти одним куском не просит. Ну там 50-100 метров максимум. Вместо этого выделяется много маленьких кусочков памяти. Более того, для большинства объектов даже страница — это много, так что используется менеджер кучи, который внутри работает со страницами, а наружу выделяет блоки произвольной величины. В Go свой рантайм, занимающийся ко всему прочему ещё и сборкой мусора, который удобнее писать, оперируя одним большим регионом памяти, чего 32-битные форточки сделать не дают.
Может ща и глупость скажу, но было бы логичней, раз память у программ итак виртуальная, в программу давать единое адресное пространство, а в реальной памяти этот кусок фрагментировать. Тогда программа может попросить и 1 гиг, но ссылаться этот один гиг, допустим, будет на 10 кусков по 100 метров. И волки сыты и овцы целы.
Дык так и делается. Проблема в том, что до перехода точку входа exeшника система подгружает по зависимостям dll-ки (в т. ч. системные), которые фрагментируют именно виртуальную память.
Кстати, об х64. Я так понимаю, что системные библиотеки там тоже грузятся а бы куда? То есть, под х64 тоже есть вероятность получить ситуацию, когда невозможно выделить 512 мб памяти?
Эм. Там адресное пространство в 16 экзабайт (2^64) памяти. Вы себе хорошо представляете, как надо фрагментировать память, чтобы добиться невозможности выделения 512 метров? Нарисую картинку. Это надо каждые 0x1fffffff байт резервировать 1 байт. Т. е. намеренно выделить в именно таком порядке 34359738368 блоков памяти. То есть, ситуация намного труднее достижимая, чем когда невозможно выделить полмегабайта памяти на 32-битной системе.
Только что поискал «каково доступное адресное пространство в 64-битных приложениях» — несколько первых ссылок дали ответ 7 или 8 ТБ (в зависимости от процессора). Это несколько меньше, чем 2^64. А резервировать 1 байт не надо, можно захватывать по 512 МБ, как это делает GC.
несколько первых ссылок дали ответ 7 или 8 ТБ
Вам наврали, плюньте им в глаз.
А, ну это опять рукожопые мелкомягкие. Linux спокойно адресует 128 терабайт на процесс.
Как бы то ни было, даже в форточках на 64 битах блоков по 512 метров столько же, сколько блоков по 128 килобайт в 32-битных. Вы же согласитесь с тем, что невозможность выделить 128 килобайт АП — нонсенс?
Не знаю, как 128 К, но сообщение о невозможности выделить 1 МБ встречаю часто. Потому что адресное пространство кончилось. 2 ГБ, все-таки, это очень мало.
Наврали, но погодите плеваться-то. Да, адресное пространство теоретически 2^64, но ни в одном процессоре (мы говорим о x86-64) это не реализовано. Задействовано 48 линий адреса, что даёт максимум 256TiB.
Мой перфекционизм требует нулевой вероятности такой ситуации :)
У вас есть ненулевая вероятность забить вообще всё адресное пространство на 32-битной системе. Что теперь, вообще код не писать?
У меня есть мозги, чтобы этого не сделать. Это подконтрольный мне процесс. А вот то, что в адресном пространстве не окажется 17000 библиотек (ага создадут Windows 19 x128) — я гарантировать не могу :)
Вы так же не можете гарантировать, что какая-то библиотека будет кушать памяти больше, чем она же, но другой версии.
Да, если 17000 библиотек захотят захватить по 512 МБ каждая. Но пока такая ситуация маловероятна.
Но если сборщик мусора получил эти 512 МБ, то получается, что из 2 ГБ адресного пространства у меня осталось только полтора? За что же они так? Ведь адреса выделяются не по мере необходимости, а навсегда… И даже C# забирает только 200 с чем-то МБ… Наверное, они правы — «переходите на 64 бита». Правда, если там придется запускать 32-битную версию программы (мало ли, сторонней 64-битной библиотеки не нашлось), проблема вернется :(
2) страница зарезервирована, но физическую память под неё ещё никто не просил

Добавлю, что и в свопе на этом этапе память не выделяется и отсутствие свопа в принципе тоже не мешает страницу зарезервировать. Вот когда приложение скажет «я тут страницу резервировало, хочу получить» только тогда начинаются манипуляции с физической памятью и свопом (если он есть). Само это резервирование — это резервирование адресного пространства. а не памяти.
Косяк не в рантайме Go, косяк в ядре ОС


У вас может быть динамическая загрузка библиотеки в любой момент времени. Или открытие mapped file или просто выделение множества кусков памяти. Все это приводит к фрагментации, так что выделение 512 мегабайт одним кусом это очень самонадеянно в любой 32х разрядной операционной системе.

А, ну это опять рукожопые мелкомягкие.


Меня всегда удивляло отсутствие критики у фанатов GNU/Linux.

The maximum 2TB limit of 64-bit Windows Server 2008 Datacenter doesn't come from any implementation or hardware limitation, but Microsoft will only support configurations they can test. As of the release of Windows Server 2008, the largest system available anywhere was 2TB and so Windows caps its use of physical memory there.
Microsoft will only support configurations they can test

Что именно они не могут протестировать? Как ведёт себя софтина, пытающаяся зарезервировать больше 8 гигабайт адресного пространства? Про физическую память вообще разговора не было.
И что не так с резервированием 8Гб памяти в 2-терабайтном адресном пространстве?

Кстати, Вы бы проверили свое утверждение о том, что ASLR не пытается расположить все библиотеки рядом с друг другом прежде чем выступать с заявлениями о рукожопости, ага.
И что не так с резервированием 8Гб памяти в 2-терабайтном адресном пространстве?
То, что это опечатка, и имелось ввиду >8Тб.
Ну да, а Линукс значит поддерживает. Проверяли? Или та же история, что и со 100500 ядрами — обнаруживаем (тоже чисто теоретически), значит поддерживаем, а что глобальные локи до сих пор используют (то что BKL в 2011 наконец выпилили — это, конечно, хорошо, но RCU все еще глобальный — просто неэкслюзивный, как BKL) — так это ничего — на РЕАЛЬНУЮ поддержку многоядерности оно совершенно не влияет.
Проверяли?
Ну вообще-то да, вчера прогнал тест с вызовом mmap с резервированием 100 терабайт адресного пространства.
Что вы ко мне со своим оверкоммитом прицепились, выключите его, если он вам так не нравится.
Но ведь если его выключить, то не получится мепить 100терабайтные секции — как же с этим жить? Ну и еще всякие мелочи типа гениального изобретения — форка, из-за которого требования к памяти (закоммиченной — не физической) будут в несколько раз превышать реальные (в большинстве случаев, ага).
то не получится мепить 100терабайтные секции
Что за ересь вы несёте? У mmap есть комбинация флагов по действию аналогичная MEM_RESERVE у VirtualAlloc. Причём тут вообще оверкоммит?
Это MAP_NORESERVE чтоль? Не совсем аналогичная, но таки да — позволяет резервировать 100 терабайт. Здаюсь.
Эм, нет. MAP_ANONYMOUS в комбинации с PROT_NONE. А потом на нужные страницы mprotect.
Пытался отследить как работает MAP_ANONYMOUS и в конце концов потерялся в логике обработки фолтов на hugetlb файлах (тяжело без нормального kernel debugger-а). Ни документация ни комментарии не упоминают, что MAP_ANONYMOUS не чарджит коммит (в комментариях только указано, что у hugetlb собственный аккаунтинг и поэтому системный использоваться не будет).

В любом случае, я уже сдался и поверил, что 100терабайтные файлы в линуксе мепить можно. Это просто невообразимо круто :-)
> разработчкики форточек не удосужились написать код, который грузит все библиотеки где-то рядом.

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

О вашей ненависти к форточкам узнали уже все в этом топике, но держите себя в рамках здравого смысла.
Ну пусть рандомизируют АП, но грузят все либы хотя бы во втором гигабайте, релоки же позволяют.
Ну так и грузит же. Вы проверьте, да. То что на ОДНОЙ системе kernelbase.dll вылез куда не следует как раз и говорит, что на всех остальных он сидит там, где ему и положено — в районе 0x70000000:
0:000> lmm kernelbase
start    end        module name
76110000 761b2000   KERNELBASE   (deferred)

То что на ОДНОЙ системе kernelbase.dll вылез куда не следует как раз и говорит
Это говорит, что где-то в загрузчике баг, возможно, уже пофикшеный. Чем виноват рантайм Go мне не ясно.
Да нет там бага. Никто и никогда не гарантировал, что будет доступен непрерывный кусок размеров в четверть адресного пространства. Best effort, все дела.
Вы уж определитесь, нет бага или dll-ке всё же положено быть в районе 0x70000000.
Хм, лоадер ПЫТАЕТСЯ загрузить все dll-ки в кучу, но не гарантирует этого. Каких конкретно гарантий Вы ждете?
> Косяк не в рантайме Go, косяк в ядре ОС. И решать его можно
> либо исправлением загрузчика библиотек, либо переходом на 64 бита.

С философской точки зрения — да. А с практической — неработоспособность Go в Windows ударит по популярности Go, но не окажет никакого влияния на популярность Windows. Если разработчики Go готовы принести распространённость своего языка в жертву своему перфекционизму, то ради Бога.
Сорри. Почитал, понял что не прав.
Винда, в отличие от рукожопого линукса, не оверкоммитит, соответственно вопрос совершенно справедливый. Если кто-то закоммитит (MEM_COMMIT) полгига памяти, то даже если он не будет ее использовать — никто другой тоже не сможет (ну кроме системного кеша).
И? Вы знаете зачем оверкоммит введен? Почему несмотря на всю тупость его использования он до сих пор включен по дефолту? Серьезно, из-за жопорукости линукса (хотя конкретно здесь руки растут из жопы оригинальных дизайнеров юникса) Вы вот таким оригинальным образом напрашиваетесь на еще большие проблемы, чем OOM Killer (хотя сложно себе такое представить, да).
Кстати, вот Вы, видно, человек разбирающийся, не подскажете чего делать с
echo fffffffuuuuuu >/proc/sys/vm/overcommit_memory

А с
echo 5 >/proc/sys/vm/overcommit_memory

Как мне отловить ошибку и откуда вообще взялась идея о том, что парсинг строк — это хороший метод работы с настройками?

PS: Вы видимо имели в виду «echo 2 ...», потому как 0 все еще оверкоммитит
1) берём kernelbase.dll
2) меняем ImageBase и проходимся по секции .reloc с целью поправить бинарник так, чтобы он нормально грузился с новым ImageBase
3) ???????
4) профит
О, круто, Вы изобрели rebase.exe. Ну что ж, осталось только попробовать и убедиться в том, что виндовый ASRL делает этот самый rebase в памяти при каждой загрузке.
Ну тогда CreateProcess с флагом приостановки и вызов VirtualAllocEx до того как успеют прогрузиться библиотеки. Хотя тоже костыль, да.
Хм, а может лучше не коммитить 500 метров памяти на hello, world?
Сейчас много кто будет писать собственные менеджеры памяти. А винда этому вставляет палки в колеса.
Не вставляет. Просто не стоит рассчитывать, что можно выделить четверть всего доступного адресного пространства одним куском в любых условиях даже если В БОЛЬШИНСТВЕ случаев это возможно. На любой системе.
Черт возьми, значит в винде нужно сделать такую фичу. Чтобы всё это можно было сделать, гарантированно на уровне ядра ОС. Тогда написать свою виртуалку с собственным менеджером памяти сможет любой школьник — всё для людей!

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

Тока походу, раз мелкомягкие ниасилили даже со своим .NET'ом разобраться (что будет, если запустить .NET'овский сервер на виртуалке? Пипец придет виртуалке. У него даже ограничителя памяти нету как в жаве.), ждать нам этих новых фич по поддержке виртуалок джва года.
Какую фишку? Обеспечить гарантированную возможность выделения 512 метров одним куском на 32-битной системе? А если приложение линкуется с dll-ками, базированными через каждые 400 метров и без релоков? А если нужно не 512, а 768 — откуда вообще взялось магическое число 512?

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

Вы реально абсолютно не понимаете происходящего, но при этом выступаете с заявлениями. Физическую память дефрагментировать не нужно — адресное пространство можно «дефрагментировать» простой перенастройкой таблиц/директории страниц для процесса. Вот только с вероятностью около единицы ни один процесс подобного не переживет.

что будет, если запустить .NET'овский сервер на виртуалке? Пипец придет виртуалке. У него даже ограничителя памяти нету как в жаве.
Слова не мальчика но иксперта. Всего доброго
> Слова не мальчика но иксперта.

Ну объясните же мне, юродивому, как сделать чтобы .NET не зажрал всю память VPS.

> Физическую память дефрагментировать не нужно

это кому как.

> Вот только с вероятностью около единицы ни один процесс подобного не переживет.

и я о том же. Пилить и пилить. (Для случая когда приложение просто выполняется — хорошо. Но у нас другой случай — на ОС выполняется виртуалка, в которой вполне возможно напишут еще одну виртуалку. Программа (виртуалка, виртуалка-в-виртуалке) должна иметь возможность управлять менеджером памяти на очень низком уровне, вплоть до полного замещения собственным менеджером памяти)
Если под сервером Вы имели в виду IIS, то возможность существует уже очень давно. В общем случае потребление памяти процессом (и/или группой процессов) в винде ограничивается при помощи Job Object-ов. Это если процесс недоверенный и не только менять, но и знать о своих лимитах. Если же достаточно более «мягких» ограничений (читать можно, для увеличения нужна SeIncreaseWorkingSetPrivilege) и нельзя контроллировать сразу группу процессов (включая всех «детей»), то можно воспользоваться SetProcessWorkingSetSizeEx (в дотнете это Process.MaxWorkingSet)
> Физическую память дефрагментировать не нужно
это кому как.


Единственный случай, когда нужны куски непрерывной физической памяти — это DMA без поддержки scatter/gather. Может я упускаю из виду что-то важное? Напомните, пожалуйста.

и я о том же. Пилить и пилить.

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

Управляемые языки еще могут двигать кучу туда-сюда, но вторичная трансляция здесь ни при чем, и любые манипуляции с адресным пространством, не запрошенные напрямую рантаймом этих языков МГНОВЕННО вызовет AV в этом самом рантайме и процесс умрет мучительной смертью.
UFO just landed and posted this here
Ещё есть вариант сделать свой загрузчик, который тянет по зависимостям только ntdll. Но это немало работы + необходимость заменять собой системные реализации LoadLibrary, GetProcAddress и ещё нескольких функций.
Хуле, можно прикрутить 500 метровую bss-секцию к го-рантайму, чтоб жопорукость его разработчиков была сразу видна.
Дык для .bss память не резервируется, а сразу выделяется.
А это смотря где. Не знаю как в линуксе, а винда деманд-пейджит. То есть bss будет закоммичен, а страницы туда будут вставляться из zeroed list-а по мере доступа.
kernel32.dll (а следовательно kernelbase) грузится всегда. Именно там происходит загрузка dll'ек и инициализация кода (и все это происходит еще до того, как начнет исполняться основная программа).
UFO just landed and posted this here
Ему некогда, он память выделяет.
Кстати, спокойно может съесть 1-1.5 гига на 32битной машине.
Счастливый вы 3… у меня до 6ти гектар отжирает бывает :)
Съест-то он съест, да кто ему даст? У меня всего 4. :)
Не факт кстати, у меня подозрения (проверять лень :) ) что Хром отъедает только доступную физическую память, т.е. меняет размер кеша в оперативке в зависимости от её объёма.
Вот, именно такие подозрения у меня. Был гиг мозгов — кушал почти гиг, стало 4 — стал кушать 3. Думаю если дать 8, то он и 6 скушает.

Видимо намекает, что он должен быть единственной прикладной программой на компе, подготавливает массы к ChromeOS :)
стесняюсь спросить, а сколько вкладок открыто когда он 3гб отнимает?
Разделение на процессы — это хорошо, но процесс — не поток, накладные ресурсы немалые требуются. В результате держать большое количество вкладок в хроме очень накладно.
А по поводу памяти: верю в то, что она должна работать, а не простаивать. Хотя и обидно видеть увеличение аппетитов программ при увеличении оперативки: поставил побольше, а опять вся забита.
После последнего обновления Хрома перешел на Fx обратно. Жуткие тормоза появились.
Лично я наоборот почувствовал улучшение, по ощущениям, флэш перестал исполняться в фоне и перестал тормозить, когда сжирал одно ядро полностью.
У меня флэш перестал выполняться не только в фоне, но и в активной вкладке. Вернее выполняется, но очень медленно, картинка дёрганная, реакция на мышь запаздывает на несколько секунд…
На Mac Os X последний Хром стал наоборот шустрее…
UFO just landed and posted this here
Такие сообщения мне напоминают анекдот про японскую лесопилку и советских лесорубов с ломиком. При особом желании можно сломать реализацию практически на любом языке. Но по факту такие глюки вылезают лишь при достаточно редком стечении обстоятельств и криворукости «новых программистов», пытающихся решать задачи в лоб и «по микрософтовски», т.е. не заботясь о качественном подходе и об экономии ресурсов.

В моей объективности вполне можно усомниться, поскольку я тут в роли «адвоката дьявола» (или Евангелиста GO — смотря с какой позиции смотреть), но факты остаются фактами — за год использования GO в продакшне мне не доводилось сталкиваться с такими ситуациями и проблемами. Может благодаря хорошей привычке работать исключительно под Linux и в 64-битных версиях.

Go, как и любой другой язык, обладает своими особенностями (см. Effective Go), которые следует учитывать, а не изображать Google Translate переводя напрямую привычные алгоритмы с одного языка на другой.

Даже на эталонных языках (C/C++) случаются проблемы с memory leaks, если разработчик не будет внимательно следить за использованием памяти.

Языка программирования «Панацея» ещё не существует, как и не существует компиляторов, способных преобразовать программу «Хочу, чтобы всё работало зашибенно, без глюков и желательно даже на старом железе» в совершенный код. И этому стоило бы порадоваться — появись такой волшебный компилятор — легион программистов останется без работы :)
Если VM или компилятор генерирует для целевой платформы нерабочий код, при корректном исходном алгоритме — это плохой и не заслуживающий доверия компилятор.
Если программист не удосуживается изучить механизм работы сборщика мусора и рекомендации по применению языка, то это плохой, заслуживающий лишь увольнения программист :)

С точки зрения компилятора, бесконечный цикл тоже выглядит корректным алгоритмом. Как и попытка деления, без проверки делителя (на ноль).
А где в документации Go сказано, что нельзя выделить гиг памяти в 32бит окружении?
Это на лбу у каждого программиста должно быть вытатуирован принцип «Всегда экономь системные ресурсы». На других частях тела можно дополнить и другими полезными принципами RTFM. Если программе требуются значительные ресурсы, то нужно трижды подумать о реализации и изучить матчасть. Проблема-то не только в Go, а и в других GC-языках, использующих сходный механиз выделения памяти, о чём и автор самой статьи упомянул.
Проблема гораздо глубже, иначе она бы легко лечилась.
Почему Java не имеет таких проблем с GC?
Спросим иначе — почему по факту Java жрёт памяти значительно больше? Периодически столько, что вешает систему даже без «странностей» в GC? Можно даже не обращаться к серверным приложениям, достаточно запустить Eclipse и убедиться в тормознутости.
Есть подозрение, что это «куплено» ценой эффективности. Менее эффективный, но более надёжный механизм выделения памяти.
Java вроде бы имеет такие же проблемы — во всяком случае у меня не получалось запустить 32-битную Java с -Xmx более 750m (плюсминус не помню). И здесь даже приводится цитата на неоткрывающуюся уже страницу о том, что:
The reason we need a contiguous memory region for the heap...
Интересно, насколько скажется на производительности введение второго уровня виртуализации памяти. Чтобы из фрагментированной памяти хоста собирать непрерывную память гостя.
SLAT существует, но в данном случае он абсолютно не решает проблем фрагментации адресного пространства (при этом решает другие — в частности оверхед на сброс TLB кешей при переключении VM контекстов): для того, чтобы пересобрать физические страницы в любом порядке второй уровень трансляции не нужен, но любая пересборка адресного пространства просто «сломает» все указатели, которые хранит код в данном адресном пространстве.
Положим, я захотел написать эмулятор типа Qemu или VirtualBox на Go. В случае Go я должен просто развести руками и отказаться от разработки только потому, что он не может выделить гиг памяти для виртуальной машины?

А если мне просто необходимо написать код, которому реально нужен гиг памяти? Берёшь новенький комп, ставишь на него ОС, и только эту программу — и всё. Какое дело языку до того, как я расходую память? Память вообще создана не для того, чтобы простаивать. Если я хочу выделить гиг памяти, у меня должна быть такая возможность, а иначе фраза из спецификации выглядит шуткой:
Go is a general-purpose language designed with systems programming in mind.

Это как — general-purpose, но кроме софта, требующего много памяти? Короче, у текущей реализации есть проблема, которую нужно решать. А об экономии системных ресурсов я уж сам позабочусь, если нужно — на то я и программист.
Дополнение: если даже проблема в ОС, всё равно это не должно быть моей заботой. На C, C++ и D я могу выделить гиг памяти в 32-битной винде. При этом, D имеет GC, но он отключаемый.
Вы не можете в C++ выделить сразу гиг. Вы можете выделить несколько кусочков по 100-200 метров, а потом думать, как их попрозрачнее склеить. Проблема в месте, из которого растут руки разработчиков ОС.
Вообще-то, могу.

#include <cstdio>
#include <cstdlib>
#include <cstring>

int main(int argc, char **argv)
{
	enum { MEMORY_SIZE = 1024*1024*1024 };
	
	void *memory = malloc(MEMORY_SIZE);

	if (memory == NULL)
	{
		perror(argv[0]);
		return EXIT_FAILURE;
	}

	memset(memory, 0, MEMORY_SIZE);
	printf("Well, actually I allocated a gig of RAM :P\n");
	free(memory);

	return EXIT_SUCCESS;
}

На той системе, где проявляется проблема у Go — не сможете.
Смог. Смотрите скриншот внимательней — ось 32 бита.
А багрепорт про 32-битный Go в режиме WoW на 64-разрядной Windows 7.
32-битная программа на C++ под 64-битными Windows 7 смогла захватить одним куском 1840 МБ памяти. А 1850 — не смогла. Попытки захватить 1843 отличались от запуска к запуску. Причем при использовании malloc граница примерно на 10 МБ меньше, чем при операторе new.
У меня сейчас нет возможности собирать тестовый стенд и пытаться воспроизвести ошибку. Но в багрепорте приведено достаточно технической информации чтобы перестать сомневаться в том, что на *его* системе действительно kernelbase.dll грузится прямо в середину АП. Причину этого — «интересные» драйверы с хуками, необычная комбинация установленных обновлений или что-либо третье — предстоит установить. Никто же не говорит, что эта проблема проявляется всегда и у всех.

Если хотите покопаться — начните с рассмотрения карты памяти на вашей системе, куда грузится kernelbase.dll? Загружены ли в вашей программе остальные DLL, загруженные в Go?
Конечно, dll, которые нужны Go, в программу на C++ не загружены — зачем они ей? Но утверждение, что «на той системе» «вы не можете в C++ выделить сразу гиг» верно, скажем так, не всегда. Что и показывают эксперименты.
Вызов VirtualAlloc тянет за собой kernelbase.dll, есличо.
Строго говоря kernelbase.dll находится в списке импортов kernel32.dll
Помойму суть бага в том, что вначале нужно загадить память dllками, и только потом выделить. Если память предварительно загажена, то в общем случае утверждение верно.
Давно не писал на C++, уже забыл про new. Так тоже работает:

char *memory = new char[MEMORY_SIZE];
Поздравляю, у вас адресное пространство не забито кучей dll-ок. Нет никакой гарантии, что на соседней машине код не упадёт.
Для таких случаев я предусмотрю обработку ошибок (это же очевидно, так ведь?). Вы сказали, что я не могу выделить гиг памяти — я опровергнул это утверждение. Где я не прав?
Т. е. если АП фрагментировано, то вы на старте «обработаете» ошибку и выплюнете сообщение о том, что программа не запустится, так?
Вообще-то нет. Можно продолжать ждать, пока память не очистится. Или предпринять какие-то действия для ее очистки. Проверено — работает на серверах, которым не хватает памяти на обработку запросов. Пришел лям задач, памяти хватило на сто тыщ — остальные стоят в очереди и ждут, когда им памяти дадут.

Читая этот тред, все интересней становится, что если специально для случая с «загаженной памятью» подключать второй уровень виртуализации, собирая последоватлеьное адресное пространство из фрагментированной памяти хоста. Раз уж Go все равно сам справляется с адресацией, он так должен смочь.
Можно продолжать ждать, пока память не очистится.
Очистится от чего? От загруженных по зависимостям исполняемого файла системных dll-ок?
Ах да. Следуя логике вашего сообщения, его можно перефразировать так:
Вы сказали, что я не могу использовать указатели на переменные на стеке после выхода из функции — я опровергнул это утверждение. Где я не прав?
Вам придёт в голову писать VirtualBox на Visual Basic (или как он нынче именуется?). А на PHP или Erlang-е? Всё это тоже general-purpose языки. А вот что касается системного программирования, то тут всё иначе. С моей точки зрения «системность» подразумевает написание чего-либо маленького и шустренького. И для таких задач Go подходит замечательно.

Я согласен — проблемы с GC у Go имеются. Но всплывают они лишь тогда, когда кто-то пытается «забить микроскопом гвоздь».
UFO just landed and posted this here
А что происходит с Go когда у него заканчивается память? Как у в8, дают по шапке с возможностью исполнить одно предсмертное желание? Есть какой-нибудь способ изящно перезапустить систему («быстро поднятое не считается упавшим»). Наверняка найдутся люди, которые захотят использовать Go как базу данных.
Хорошо, что программирование — лишь моё хобби :)
«Может благодаря хорошей привычке работать исключительно под Linux и в 64-битных версиях.»
Да, Вы правы, благодаря хорошей привычке. Но другие, вот, писали программу для 32-х битной платформы, и у них случилась такая проблема. Или Вы думаете что все клиенты сидят на 64 битных ОС? Я сомневаюсь. Если с Вами не случилось ничего плохого, не значит что плохого нигде нет.
BTW где продолжение лихоГо цикла про иГоГо?
Прочёл «жалобу» по ссылке внимательнее.

Go needs the space because it is garbage collected — presumably, all GC languages, or any program that takes a similar approach to memory management, will be susceptible to the same problem.


Т.е. проблема «ожидаема» на всех языках, входящих в содружество GC.

The problem is hard to replicate and test — most of the time, Go programs run perfectly fine

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

I love programming in Go. And I agree with Rob Pike that «programmers complain too much when they should be fixing the problem instead».

А уж я-то как согласен с Робом! По этому поводу есть более ёмкая русская пословица — «взялся за гуж — не говори, что не дюж». Можно выразиться и иначе — «Не можешь… ть, не мучай… пу» :)

Unfortunately, I am not familiar enough with the Go runtime code to dive and make the required changes with confidence that I have both actually fixed the problem and not introduced bugs into the memory management code. I don’t have time before my deadline to look into the issue either.

О, как! «Не читал, но осуждаю!» Человек взялся за новый язык для продакшн версии, не особенно вникая в детали.

Одно слово — индус.
============
code.google.com/p/go/issues/detail?id=909#c29

2. The «fake» references are from the static variables defined in various go packages. These variables are not pointers. However, as GC scans the data section for potential references to the heap, they are treated as «pointers» and therefore the entire heap blocks which these «pointers» happen to «reference» can never be reclaimed even when they should be.
============
Хороший пример плохой реализации GC. Если переменная может выглядеть как указатель — это плохая реализация ГЦ, это значит он (в языке, где тип, в общем-то, известен!) не использует ту информацию, которую должен использовать.
func fs() []byte {
        //allocate 64 MB chunks
        r := make([]byte, 64*1024*1024)
        return r
}

Это, по вашему мнению, нормальный подход? Я повторюсь — на любом языке можно создать такой правильный код, который напрочь убьёт программу при определённых условиях. Можно даже считать это определённым видом спорта.
Вы же знаете, что когда ищут причину бага — реально действующие программы упрощают до того минимума, который вызывает ошибку. Программы, в которых реально необходимо несколько больших кусков памяти на некоторое время — это нормально (например, надо прочитать массив, обработать, забыть его).
Проблемы в имитации её через выделение 64M блока не вижу.
Я правильно понимаю, что в случае использования GCCGO проблема не проявляется?
С gccgo ситуация действительно лучше, там такой баг (вроде бы) не повторили.
Но основная проблема-то остается — использование консервативного сборщика мусора приводит к коллизиям при активном использовании небольшого адресного пространства. А небольшое адресное пространство по нынешним временам — это 32 бита.
Это нормально. У Visual Studio 2010 был баг — при сборке больших программ в 64-битном режиме они падали при запуске (там для больших структур оптимизатор MMX оптимизировал код до нерабочего состояния). Официальное решение проблеммы от техподдержки Microsoft было «не используйте Visual Studio 2010 для компиляции 64-битных приложений». Патч более полугода делали и первое время раздавали только по запросу в платную техподдержку.
В принципе если рассмотреть целевую нишу для Go, то вполне вероятно, что 32 бита не особо и надо, вот они видимо и не тестировали толком. Теперь напишут как в монго — мы гарантируем вам проблемы при сборке под 32 бита :)
И я бы одобрил такой подход. Go планировался и создавался как новый язык, изначально «заточенный» под новое железо (многоядерность и многопроцессорность). И не совсем ясно зачем они решились тянуть лишний груз совместимости. Более того, я не совсем понимаю зачем они полезли на Windows-платформу, погнавшись за всеми зайцами сразу.
Вроде как один из плюсов или даже фич Go — экономия ресурсов. 32-бит программа и её окружение по идее занимают меньше памяти (если не используются >32-бит вычисления и т. п., то есть 64 разрада не нужно эмулировать).
Основная ниша Go — золотая середина. Т.е. разумное соотношение простоты и скорости разработки к быстродействию и затрате ресурсов. И для достижения золотой середины приходится чем-то жертвовать. А с учётом реальности, 32-битностью жертвовать приходится уже достаточно часто. Например, при работе с Mongo.

Можно даже выстроить классическую диаграмму-треугольник, где на вершинах будут простота, быстродействие и совместимость.
Почитал баг. Go выделяет не 512Mb, а 768Mb. Было бы 512, программа бы не упала судя по карте памяти.
Кстати, NaCl всегда выделяет 1Gb адресного пространства на 32-битных системах. В windows NaCl процесс создается в приостановленном состоянии, выделяется 1Gb, процесс запускается. В linux используется специальная секция размером 1Gb и статический ELF'ник. После его запуска используется exec для превращения его в NaCl процесс.
На самом деле, да реально резервируются 512 + 256 = 768 мегов. В комментария кода go по этому поводу пишут:

// Set up the allocation arena, a contiguous area of memory where
// allocated data will be found. The arena begins with a bitmap large
// enough to hold 4 bits per allocated word.

и далее для 32-битного случая

// On a 32-bit machine, we can't typically get away
// with a giant virtual address space reservation.
// Instead we map the memory information bitmap
// immediately after the data segment, large enough
// to handle another 2GB of mappings (256 MB),
// along with a reservation for another 512 MB of memory.
// When that gets used up, we'll start asking the kernel
// for any memory anywhere and hope it's in the 2GB
// following the bitmap (presumably the executable begins
// near the bottom of memory, so we'll have to use up
// most of memory before the kernel resorts to giving out
// memory before the beginning of the text segment).
//
// Alternatively we could reserve 512 MB bitmap, enough
// for 4GB of mappings, and then accept any memory the
// kernel threw at us, but normally that's a waste of 512 MB
// of address space, which is probably too much in a 32-bit world.

Ну что тут скажешь. Инженерное дело — это искусство компромисса.
4 бита на слово? Хмм, немало, я бы сказал…
Мда, сегодня особый день, когда багрепорты транслируются в Хабр? Утром был (уже удаленный) топик про то, что последний Хром, мол не работает Gmail-ом (видимо, по мнению автора, у всех), теперь это. Нее, всякие баги это интересно, тем, кого они касаются, но не писать же статью на каждый багрепорт?!
Эээ, «тут стратегия на стратегию попёрла».
Google выпустил в свет действительно хороший инструмент, что в каком-то роде ставит под угрозу безбедное существование адептов других инструментов.

Мало кто из успешных (на своём языке) разработчиков хочет терять свою долю хлебушка с маслицем из-за появления чего-то нового. Эдакая гремучая смесь консерватизма, луддизма и цехового сговора.
Вы серьезно думаете, что программисты на C#, Java, Python и прочих языках видят в GO угрозу и начали проводить на него атаку?
В каждой шутке лишь доля шутки. У вас есть другие логичные объяснения неприязни к появляющимся новым языкам и технологиям?
Я считаю, что никакой неприязни у психически здоровых людей(коих на хабре, хочется верить, большинство) нет. Обычно есть некий скептицизм, поскольку новые языки и технологии появляются часто, а вот выживают из них довольно не многие. Лично мне, что касается это бага, удивительно слышать про выделение 700 метров памяти одним куском в серьезном продукте. Тем более я бы удивился, если бы мое приложение в стиле hello world потребовало почти гиг памяти. А что касается GO в целом, то как любой новый язык, он интересен как концепция, хотя мне трудно представить нишу, которую он мог бы занять.
Я считаю, что никакой неприязни у психически здоровых людей(коих на хабре, хочется верить, большинство) нет.
О, сколько уже тут было войн тупоконечников с остроконечниками!

а вот выживают из них довольно не многие

Go развивается уже два года. Я использую в продакшене (банковский сектор) больше года. Инфраструктурные издержки снизились в 5-10 раз (относительно старых версий на Java и Python). Снизились издержки — демпируется цена. Демпируются цены — вышибаются конкуренты. Profit.

если бы мое приложение в стиле hello world потребовало почти гиг памяти

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


Не более чем флейм. «Лишь бы не работать»

Go развивается уже два года. Я использую в продакшене (банковский сектор) больше года. Инфраструктурные издержки снизились в 5-10 раз (относительно старых версий на Java и Python). Снизились издержки — демпируется цена. Демпируются цены — вышибаются конкуренты. Profit.


Ну во-первых два года все таки не срок. Во-вторых снижение издержек конкретно у вас ничего не говорит о том, что будет в других областях. Это же во многом вопрос экосистемы — наличия фреймворков, IDE, статических анализаторов, байдингов, сообщества и т.д. Вред ли в ближайшие годы GO приблизится тут к C# или Java.

Выше уже объясняли разницу между потреблением памяти и резервацией адресного пространства.


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

Зависит от подхода. Сливки с любой новой IТехнологии снимает тот, кто не дожидается её выхода в мейнстрим. Простой пример: сейчас я плачу Go-программисту $5K/m и это оправданная цена как экономически, так и с точки зрения дефицита кадров. Придёт ли мне в голову платить столько же JAVA-разработчику, пусть даже эффективность будет той же? Нет, не придёт. Потому что на собеседование придут десять соискателей, готовых работать и за $1-3K/m.
Java-программист за месяц не выучит go?
Если бы Java-программисты могли за месяц выучить Go, они бы давно писали на C++ :)
М, а чем c++ сходен с go? c++ для меня по уровню сложнее хаскела — если хаскель изучить есть какие-то шансы, то понять все хитросплетения плюсов я даже не надеюсь.
А go по большому счету тривиален на этом фоне, там учить мало чего есть, ну конечно для знакомых с применяемыми концепциями.
Ждём ваш S01E03. А то, пока суть да дело, война началась. Удивительные у нас люди…
Название фирмы — секрет?
Может, мне тогда изучить Go и постучаться к вам. Пару лет пишу ынтерпрайз на жаве — и это чудовищно скучно.
это неверное предположение. Например, если брать программу на C#, выводящую hello world, то она занимается 25М private bytes, но из этого не следует, что C# в целом невероятно неэффективен по памяти, просто есть стартовый оверхед.
Ну и уже пояснили, что этот гиг идёт в виртуальном пространстве, это гигабайт памяти, который программа займёт, если потребуется (т.е. по алгоритму, а не на пустом месте).
Критиковать, не попробовав гораздо проще и приятнее чем хвалить оценив.
Кстати, одна из альтернатив — использовать LAA бит на 64-битных системах. Тогда проблемы останутся только на 32-битной Windows 7. Ну и отрицательных чисел мне кажется в программах меньше чем положительных — лучше будет работать консервативный сборщик мусора.
Это ж надо специально такой фрагментации добиваться, что-бы кусочка в 512 Мб не нашлось =)
Большой противник теории заговора, но в данном случае язык чешется написать «пост проплачен» :(

Обсудили же этот вопрос — что случилось?! Зачем отдельный пост и 100+ комментариев?!
* расслабленно, пересчитывая пачки зеленых купюр *

И неплохо, знаете ли, проплачен. Заказчика, понятное дело, раскрывать не буду, но если и вы заинтересованы в коммерческом сотрудничестве — черкните пару строк на pr@python.org.
> Go не рекомендуется использовать для разработки

Точка.
> 32bit не рекомендуется использовать для разработки

Точка.
Sign up to leave a comment.

Articles