Размер стека для потока

Общие вопросы программирования, алгоритмы и т.п.

Модератор: Модераторы

Размер стека для потока

Сообщение VKB » 23.04.2014 11:36:27

Изучая исходники Free Pascal под Windows нетрудно обнаружить, почему каждый поток отжирает много памяти - это зашито в вызове BeginThread:
Код: Выделить всё
    function BeginThread(ThreadFunction : tthreadfunc) : TThreadID;
      var
        dummy : TThreadID;
      begin
        BeginThread:=BeginThread(nil,DefaultStackSize,ThreadFunction,nil,0,dummy);
      end;
,где DefaultStackSize = 4*1024*1024, то есть 4Мб. При этом можно, конечно, вызвать вариант, где явно указать размер стека, но по умолчанию он 4Мб. Я не знаю, исходя из каких потребностей так решили, наверно просто чтобы "точно хватило" для всех случае жизни. Соответственно конструктор класса TThread также имеет неявный параметр StackSize со всё тем же значением по умолчанию
Код: Выделить всё
constructor TThread.Create(CreateSuspended: Boolean;
                           const StackSize: SizeUInt = DefaultStackSize);
begin
  inherited Create;
  FSuspended := CreateSuspended;
  FInitialSuspended := CreateSuspended;
  { Always start in suspended state, will be resumed in AfterConstruction if necessary
    See Mantis #16884 }
  FHandle := BeginThread(nil, StackSize, @ThreadProc, pointer(self), CREATE_SUSPENDED,
                         FThreadID);       
, но кто обычно заморачивается его явным указанием? Всевозможные библиотеки типа Synapse тоже не занимаются этим. Я подозреваю, что для абсолютного большинства случаев такой размер стека - явный перебор. Но при этом если его всё-таки окажется мало, то я не вижу там никаких механизмов защиты. Delphi, кстати, передаёт 0 в BeginThread в качестве размера стека, что интерпретируется как "такой же размер стека, как и для основного потока". Правда Delphi у меня старый, может быть современные версии действуют также, как Free Pascal.

Не знаю, как это решать в общем случае, но для WINDOWS я наткнулся на интересные рекомендации от Микрософт http://support.microsoft.com//kb/315937. Там используется ассемблерная вставка, так что Микрософт не гарантирует работу кода даже для Windows c не x86-архитектурой. Хотя наверно можно по аналогии сделать рабочий вариант для других платформ.

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

Я не очень хорошо знаю Си. Кто-нибудь пытался перевести эти рекомендации на паскаль? Может быть это уже сделано в современных версиях Delphi?
VKB
новенький
 
Сообщения: 33
Зарегистрирован: 07.09.2009 13:57:35

Re: Размер стека для потока

Сообщение hinst » 23.04.2014 14:38:00

Есть у меня такое подозрение, что если поставишь меньше 4 МБ, то операционная система всё равно назначит 4 МБ. Не знаю, как бы это лучше проверить
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: Размер стека для потока

Сообщение Sergei I. Gorelkin » 23.04.2014 14:44:21

Правильное подозрение, только не 4, а 16: http://bugs.freepascal.org/view.php?id=17755
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1405
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Размер стека для потока

Сообщение hinst » 23.04.2014 15:08:09

В общем, на Windows можно сделать, чтобы поток резервировал меньше памяти, если создавать поток функцией из WinAPI и указывать там STACK_SIZE_PARAM_IS_A_RESERVATION (судя по той дискуссии), а на линуксе - не знаю.

Добавлено спустя 2 минуты 15 секунд:
Касательно размера стэка:
Даже если каждая функция выделяет в стеке аж 1000 байт (что маловероятно), то на 100 вложенных вызовов потребуется 100 килобайт
Итого я считаю, что 100 килобайт на размер стека должно хватить, а выделять по 16 мегабайт это бесполезная трата памяти

Добавлено спустя 1 минуту 18 секунд:
В линуксе можно попробовать опцией компилятора -Cs10000 или разбираться в системных вызовах

Добавлено спустя 1 час 48 минут 6 секунд:
Хочу потестировать у себя, но что-то мне боязно. Не известно как система себя поведёт когда кончится вся память, там можно и огрести проблем. Какие-нибудь системные конфигурационные файлы недозапишутся, и придётся винду перставлять
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: Размер стека для потока

Сообщение VKB » 23.04.2014 17:14:46

В http://bugs.freepascal.org/view.php?id=17755 написано, что это для FPC 2.5.1 и эта ошибка была рассмотрена ещё в 2010 году. Я сейчас использую 2.6.2 и там чётко написано
Код: Выделить всё
const
  { includes 16384 bytes margin for stackchecking }
  DefaultStackSize = 4*1024*1024;
, то есть 4Мб. К тому же я смотрел через диспетчер задач - он показывает увеличение занятой процессом виртуальной памяти на 4 Мб при создании каждого нового потока.

Чтобы получить другой размер стека Win API в принципе использовать не обязательно. Достаточно в своём потомке TThread перекрыть конструктор и явно передать конструктору TThread желаемый размер стека.

В ссылке на MSDN речь идёт о CreateThread. Я не вникал, зачем Микрософт реализует два подобных системных вызова (а есть ещё "расширенная" версия _beginthreadex), но в FPC используется только _beginthread, где не предусмотрено STACK_SIZE_PARAM_IS_A_RESERVATION.

Ещё один примерчик работы с PAGE_GUARD - http://msdn.microsoft.com/en-us/library/aa366549.aspx. Но, к сожалению, во всех примерах просто показывается что можно перехватить соответствующее исключение, но как динамически увеличить размер стека примера нет. Я понимаю, что если удастся это реализовать, то это будет сильно в ущерб переносимости, но и плюсы такого решения весьма существенны.
VKB
новенький
 
Сообщения: 33
Зарегистрирован: 07.09.2009 13:57:35

Re: Размер стека для потока

Сообщение hinst » 23.04.2014 17:36:28

В линуксе когда пытаешься указать размер стэка в конструкторе TThread, происходит крэш. Недавно проверял с FPC 2.6.3
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: Размер стека для потока

Сообщение Sergei I. Gorelkin » 23.04.2014 19:17:26

"Смешались в кучу кони, люди" (с)

1) У ошибки нет статуса "resolved", а это значит, что положение дел сохраняется до сих пор. Несмотря на то, что сообщили в 2010 году, ага...

2) Стек в Windows и так динамически расширяемый, в диапазоне от первоначально подключенного (commit) размера и до зарезервированного (reserve), с использованием ровно того же механизма, что и в примере. Только пример пытается расширить стек еще дальше, надеясь на то, что перед адресами стека остается незанятое адресное пространство.

3) _beginthread и _beginthreadex - это вообще из системной библиотеки языка C, и к FPC не имеет никакого отношения. Но они обе в конце концов вызывают CreateThread, равно как и функция BeginThread из FPC.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1405
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Размер стека для потока

Сообщение hinst » 23.04.2014 20:05:28

Sergei I. Gorelkin писал(а):Стек в Windows и так динамически расширяемый, в диапазоне от первоначально подключенного (commit) размера и до зарезервированного (reserve)

Что-то я не очень врубаюсь. Если он зарезервирован, то не значит ли это, что оперативная память в любом случае занята? Всё равно другие приложения не получат доступа к этому участку памяти, так как менеджер памяти его не выделит. Иначе как будет стек, кусочками? Когда стек достигнет commit, то выделится новый участок памяти в динамической памяти? Если нет, и память для стека должна быть одним куском, то получается, что зарезервированная память потрачена.
Я не знаю, как в программах на C#, C++ или Python, но в FPC по идее в стеке должно быть очень мало данных, так что 15 мегабайт из 16 получается гарантировано потрачено
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: Размер стека для потока

Сообщение Sergei I. Gorelkin » 23.04.2014 20:33:58

Именно так, резервируется неперерывный кусок, только не памяти, а адресного пространства. Количество доступной для использования памяти при этом не уменьшается. Потом по мере надобности подключается память по зарезервированным адресам и только при этом она "расходуется". Если потратить все адресное пространство, то дальнейшее выделение памяти в данном процессе станет невозможно (некуда подключать), несмотря на то, что свободной памяти может быть еще достаточно и остальные процессы могут ей пользоваться.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1405
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Размер стека для потока

Сообщение Kemet » 23.04.2014 23:30:12

Kemet
постоялец
 
Сообщения: 241
Зарегистрирован: 10.02.2010 19:28:32
Откуда: Временно оккупированная территория

Re: Размер стека для потока

Сообщение wavebvg » 24.04.2014 11:36:06

Ребят, там все очень тухло с потоками, потому что:
1. Модульное разделение с использованием инклудов не очень популярное и совсем не модное из-за отсутствия нормального редактора, без которого придется вначале изучить все вызовы, потом рассматривать конкретные реализации. Как результат - шлифовать некому, нервов просто не хватит...
2. Один факт того, что при завершении потока стоит Sleep в 100мс дает понять всю серьёзность проблемы
wavebvg
постоялец
 
Сообщения: 354
Зарегистрирован: 28.02.2008 04:57:35

Re: Размер стека для потока

Сообщение VKB » 24.04.2014 12:25:02

Sergei I. Gorelkin писал(а):"Смешались в кучу кони, люди" (с)

1) У ошибки нет статуса "resolved", а это значит, что положение дел сохраняется до сих пор. Несмотря на то, что сообщили в 2010 году, ага...

2) Стек в Windows и так динамически расширяемый, в диапазоне от первоначально подключенного (commit) размера и до зарезервированного (reserve), с использованием ровно того же механизма, что и в примере. Только пример пытается расширить стек еще дальше, надеясь на то, что перед адресами стека остается незанятое адресное пространство.

3) _beginthread и _beginthreadex - это вообще из системной библиотеки языка C, и к FPC не имеет никакого отношения. Но они обе в конце концов вызывают CreateThread, равно как и функция BeginThread из FPC.

1. Ну собственно в описании ошибки заявлено "No matter how small I make the stack size I cannot create more than 118 threads using FPC's TThread object.". То есть прямо указано, что количество памяти, выделяемой для стека не влияет на возможность создания более чем 118 потоков. Соответственно решённость (или нерешённость) этой проблемы никак связана с тем, какое значение по умолчанию установлено при создании потока в BeginThread. Я вижу в современном (2.6.2) коде число 4*1024*1024. Плюс я вижу что мне показывает диспетчер задач. На мой взгляд этого более чем достаточно, чтобы убедиться в том, что там сейчас именно 4Мб.
2. То есть в том примере от Микрософт используется то, что, заполняя виртуальное адресное пространство однопоточного процесса, стек и куча растут с разных сторон? И если изначально распределённое для стека место кончилось, но куча ещё туда не доползла, то мы можем передвинуть нижнюю границу стека и расширить его? Но в случае многопоточных процессов такой финт реализовать сложнее. Потому что у каждого потока будет свой стек и виртуальное адресное пространство будет фрагментировано.
3. Вполне верю, ибо не копался как они дальше реализованы.

Главный вопрос - сколько стека может потребоваться для различных системных вызовов в процессе работы потока. "Свои"-то потребности в стеке вычислить легко. Выделение неоправданно большого объёма не только уменьшает объём доступной памяти в адресном пространстве процесса, но и увеличивает количество занятой виртуальной памяти в целом для ОС. И хотя это не приводит к лишнему свопингу, этой памяти тоже может не хватить.
VKB
новенький
 
Сообщения: 33
Зарегистрирован: 07.09.2009 13:57:35

Re: Размер стека для потока

Сообщение hinst » 24.04.2014 14:16:27

У меня на Windows выделяется по 16 MB, на Linux по 4 MB
На линуксе количество памяти которое указываешь в конструкторе TThread по-нормальному учитывается: если поставишь меньше, то будет меньше
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: Размер стека для потока

Сообщение VKB » 24.04.2014 16:22:46

hinst писал(а):У меня на Windows выделяется по 16 MB, на Linux по 4 MB
На линуксе количество памяти которое указываешь в конструкторе TThread по-нормальному учитывается: если поставишь меньше, то будет меньше

У Вас и BeginThread и TThread.Create на Windows выделяет по 16М? Какая версия FPC? Как определяете, что именно 16М? Совпадает ли у Вашего FPC код BeginThread и значение константы DefaultStackSize (если она есть), которые я приводил в своём первом сообщении?

Я смотрю в Диспетчере задач (WinXP, 32bit) столбец "Вирт.п." (при выборе столбцов он называется "Объем виртуальной памяти"). Увеличивается адекватно тому, что я задаю. Если ничего не задаю, увеличивается на 4Мб, что полностью объяснимо глядя на исходный код.
VKB
новенький
 
Сообщения: 33
Зарегистрирован: 07.09.2009 13:57:35

Re: Размер стека для потока

Сообщение hinst » 24.04.2014 17:03:41

@VKB: я оказывается немного не то проверял.
Если создавать поток функцией Windows.CreateThread и передать размер стека 0, то он выделит 16 мегабайт. То есть, можно считать, что 16 мегабайт это размер стека для потока, который выделяет операционная система по умолчанию

Вот что я ещё интересного нашёл:

Код: Выделить всё

program StackTest1;

{$S-}

procedure ProcA;
var
  x: byte;
begin
  WriteLN(PtrUInt(@x));
end;

procedure ProcB;
var
  x: byte;
begin
  try
    WriteLN(PtrUInt(@x));
    ProcA;
  finally
  end;
end;

procedure CallA;
var
  x: byte;
begin
  WriteLN(PtrUInt(@x));
  ProcA;
end;

begin
  CallA;
  ProcB;
end.



Выдаёт у меня:
21102416
21102400
21102416
21102356

При простом вызове процедуры прибавилось 16 байт, а при вызове процедуры с try-finally прибавилось 60 байт

Добавлено спустя 2 минуты 38 секунд:
Похоже, что каждый простой вызов сам по себе прибавляет по 16 байт (хотя на другом компе у меня выдавало 4 байта, разрядность у обоих прог 32),
а каждый try-finally вызов прибавляет по 60 байт

Добавлено спустя 1 минуту 41 секунду:
Есть подозрение, что на intel-процессорах будет прибавляться по 16 байт, а на AMD по 4. Почему - не знаю, просто предположил, так как на тех двух компьютерах, на которых я проверял, разница была именно в этом
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

След.

Вернуться в Общее

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

Рейтинг@Mail.ru