Как стать автором
Обновить

Комментарии 15

А еще можно упомянуть коварную функцию alloca, которую хоть и не часто используют явно, но неявно она живее всех живых:

for (size_t i = 0; i < n; ++i)
if (wcscmp(strings[i], A2W(pszSrc[i])) == 0)
{
...
}
мы делаем так (стек в MySQL традиционно маленький):

во-первых, в gcc можно компилировать с -Wframe-larger-than. Честно говоря в Makefile-ы я этого еще не прописал, все собираюсь. Но вручную иногда проверяю. Где-то в 8-16K всегда укладываемся.

а главное — если надо выделить больше, чем несколько килобайт, то используем alloca(), и (!) проверяем, на переполнение стека. Для этого при создании треда адрес его стека запоминается в thread-local переменной.
Все верно, но есть два момента.

Во-первых, нужно еще обстоятельно проверить, что с -Wframe-larger-than gcc вычисляет расход стека верно. Например, в Visual C++ есть /analyze:stacksize, который в общем работает, но во многих случаях вычисляет расход неверно. Может быть, в gcc это отлажено лучше и аналогичных проблем нет.

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

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

А не получится ли аналогичная ситуация здесь?
[code]

inline
void f()
{
char buff[1000*1000];

}

void ff()
{
if( rand() )
f();
else
f();
}

[/code]
Конечно, если изменить код так, чтоб компилятор его не прооптимизировал. Но идею именно в inline. Конечно, компилятор не должен хотеть подставить функцию, которая отожрет много памяти на стеке, но он может не правильно подсчитать эту память (или еще что-нибудь).

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

В том и дело, что это считается оптимизацией в смысле «сделаем как-нибудь, а потом оптимизируем», в то время как стек является дефицитным ресурсом.

А не получится ли аналогичная ситуация здесь?

Будет зависеть от компилятора, это надо проверять. На Visual C++ 10 так сразу не сломалось.

как перегрузка new, что может позволить выделить большой кусок в куче, а потом уже его использовать «как стек» и удалить в конце работы целиком.

Расскажите, пожалуйста, как заставить компилятор использовать выделенный в куче блок памяти для локальных и временных переменных?
Заставить не как, все равно придется явно или не явно использовать new. Но его можно перегрузить и сделать куда более легким, чем по умолчанию.
Да, это тоже путь. Например, можно использовать блочный распределитель памяти.

Правда есть одно «но». Куча в Visual C++ runtime потокобезопасная (и это одна из причин, почему она такая относительно небыстрая), в случае своего распределителя памяти о потокобезопасности придется хотя бы просто подумать.
ну автоматические переменные «не потокобезопасные» (если вообще этот термин к ним применять разумно). Так что если блочный распределитель установлен только для текущего потока, то это будет безопасно. Главное, не забыть включить обычные, если надо выделять «нормальные» объекты, а не временные.
Почему автоматические переменные не потокобезопасные? У каждого потока свой стек.
Ну если к ним полезет другой поток, то ничего хорошего не будет.

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

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

способ аллокации я предлагаю менять только для одного потока.

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

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

А не получится ли аналогичная ситуация здесь?
inline
void f()
{
char buff[1000*1000];
…
}

void ff()
{
if( rand() )
f();
else
f();
}


Конечно, если изменить код так, чтоб компилятор его не прооптимизировал. Но идею именно в inline. Конечно, компилятор не должен хотеть подставить функцию, которая отожрет много памяти на стеке, но он может не правильно подсчитать эту память (или еще что-нибудь).

Еще вы забыли упоменять о таких замечательных вещах (которыми меня учили у вас), как перегрузка new, что может позволить выделить большой кусок в куче, а потом уже его использовать «как стек» и удалить в конце работы целиком.
>Visual C++ 10 справляется со вторым примером, но зато не справляется в этом случае
область видимости у обоих временных обьектов одна.
Это не отменяет того факта, что компилятор генерирует машинный код, который выделяет память под обе переменные, хотя их время жизни не пересекается.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий