Всем доброго времени суток.
Вот, натолкнулся на проблему.
Есть некий кусок кода:
class CReceiver
{
public:
void OnData(void *p)
{
}
};
template <class T>
void fff(T member)
{
void *p = _alloca(65546);
member.OnData(p);
}
/// main
CReceiver a;
for (int i = 0; i < 20000; i++)
fff(a);
В дебаг версии все работает, в релизе выдается ошибка переполнения стрека. Вскрытие показало, что добрый компилер VC6sp5 на это формирует примерно следующее:
mov esi, 20000 ; 00004e20H
$L91248:
; 210 : CReceiver a;
; 211 : for (int i = 0; i < 20000; i++)
; 212 : fff(a);
mov eax, 65548 ; 0001000cH
call __alloca_probe
dec esi
jne SHORT $L91248
Очевидно, что стек не очищается после выполнения тела inline функции fff. Интересно, почему этот гад не восстанавливает esp после вызова fff
Какие варианты кроме использования malloc или отключения оптимизации? Это на самом деле критичный к производительности участок кода, где выделение при помощи malloc может очень плохо сказаться на производительности. Вынесение аллокации памяти и работу с ней в отдельную функцию тоже нежелательно, тем более что там придется извращаться с параметрами темплайта — заводить указатель на функцию или что еще похуже
Здравствуйте, Andrew S, Вы писали:
AS>Очевидно, что стек не очищается после выполнения тела inline функции fff. Интересно, почему этот гад не восстанавливает esp после вызова fff
AS>Какие варианты кроме использования malloc или отключения оптимизации? Это на самом деле критичный к производительности участок кода, где выделение при помощи malloc может очень плохо сказаться на производительности. Вынесение аллокации памяти и работу с ней в отдельную функцию тоже нежелательно, тем более что там придется извращаться с параметрами темплайта — заводить указатель на функцию или что еще похуже
Очевидно что в Debug fff () был просто напросто не inline
насколько мне известно стек после _alloca чистится после выхода их функции/метода т.е. в твоем случае после подстановки ff () как inline получилось что в цикле стек просто скушался т.е.
CReceiver a;
for (int i = 0; i < 20000; i++)
{
void *p = _alloca(65546);
a.OnData(p);
}
Эээ, конечно, спасибо, но я как то сам понял, что происходит. Вопрос был не _что_ происходит, а _как_ это лучше всего поправить. Почитайте внимательнее
Судя по документации, стек читстится после выхода из функции. Выход из функции (синтаксически) есть? Есть. В документации сказано, что _alloca нельзя использовать или что после него не восстанавливается стек при выходе из inline функции?
_alloca allocates size bytes from the program stack. The allocated space is automatically freed when the calling function exits.
Нет, не сказано. То, что компилер ее экспандит как inline — это его проблемы. А он магическим образом превращает их в мои
SWA>насколько мне известно стек после _alloca чистится после выхода их функции/метода т.е. в твоем случае после подстановки ff () как inline получилось что в цикле стек просто скушался т.е.
SWA>
Здравствуйте, Andrew S, Вы писали:
AS>Судя по документации, стек читстится после выхода из функции. Выход из функции (синтаксически) есть? Есть. В документации сказано, что _alloca нельзя использовать или что после него не восстанавливается стек при выходе из inline функции?
В документации сказанно что при выходе именно из функции а не из шаблона тем более что вскрытие показало что тело ff как таковое в результате отсутсвует...
AS>Нет, не сказано. То, что компилер ее экспандит как inline — это его проблемы. А он магическим образом превращает их в мои
Если честно то я не вижу проблем со стороны компилятора — по логике он все сделал правильно или же ему надо заводить список функции при использовании которых не применять inline ?!
Как это поправить — вариантом множество плюсы или минусы которых естественно зависят от конкретной реализации и я не думаю что можно будет при этом оставить именно эту схему разве что каким либо образом отменить inline
Если участок критичный по скорости то я не вижу причин не вынести его в отдельную специальным образом оптимизированную функцию.
Можно конечно попробовать дать более конкретные советы но я не думаю что по данному коду можно дать действительно оптимальный совет тем более что он критичен по скорости да и давать банальные советы человеку с таким Experience на RSDN наверно будет смешно
P.S.Sorry за ошибочную интерпретацию вопроса но фраза "Очевидно, что стек не очищается после выполнения тела inline функции fff" заставила подумать меня именно так как я подумал
Ну, я могу написать инлайн функцию, а не темплайт функцию с ровно таким же глюком. К тому же компилер все равно при инстанцировании генерит экземпляр функции. И повторюсь — синтаксически функция и возврат из нее есть, так что правила документации честно соблюдены.
SWA>Если честно то я не вижу проблем со стороны компилятора — по логике он все сделал правильно или же ему надо заводить список функции при использовании которых не применять inline ?!
Всего то что надо — увеличить esp на размер, который забран вызовами _alloca после выполнения кода функции. Проблем для компилера тут особо наверное нет.
SWA>Как это поправить — вариантом множество плюсы или минусы которых естественно зависят от конкретной реализации и я не думаю что можно будет при этом оставить именно эту схему разве что каким либо образом отменить inline
SWA>Если участок критичный по скорости то я не вижу причин не вынести его в отдельную специальным образом оптимизированную функцию.
Но как? Это темплайт. Значит, придется по меньшей мере делать не инлайн функию, заводить указатель на нужную функцию объекта, передавать в не инлайн функцию указатель на экземпляр объекта и эту функцию, а так же параметры. В общем, некрасиво, да и у того, кто потом будет смотреть такой код, появятся соменения во вменяемости автора. Но опять — компилер может и не инлайн функцию подставить inline при оптимизации. Чего он только не может, чтобы нагадить
SWA>Можно конечно попробовать дать более конкретные советы но я не думаю что по данному коду можно дать действительно оптимальный совет тем более что он критичен по скорости да и давать банальные советы человеку с таким Experience на RSDN наверно будет смешно
Ну почему смешно. Иногда истина очень близко — так, что можно просто ее не увидеть.
Hello, Andrew!
You wrote on Sun, 03 Aug 2003 17:22:30 GMT:
[]
AS> Какие варианты кроме использования malloc или отключения оптимизации? AS> Это на самом деле критичный к производительности участок кода, где AS> выделение при помощи malloc может очень плохо сказаться на AS> производительности. Вынесение аллокации памяти и работу с ней в AS> отдельную функцию тоже нежелательно, тем более что там придется AS> извращаться с параметрами темплайта — заводить указатель на функцию или AS> что еще похуже
Попробуй скомпилять с #pragma optimize( "", off ).
Еще можно с элипсисами извернуться (для меня всегда работало).
В MSDN написано:
You cannot force the compiler to inline a function when conditions other
than cost/benefit analysis prevent it. You cannot inline a function if:
— ...
— The function has a variable argument list.
— ...
В добавление к вышесказанному:
Если в alloca у тебя стоит константа, почему бы тогда не объявить это дело как локальную переменную? Весь смысл alloca, наверное, все-таки в динамическом выделении на стеке НЕИЗВЕСТНОГО количества памяти...
Posted via RSDN NNTP Server 1.7 beta
It's kind of fun to do the impossible (Walt Disney)
Ну, я ж не буду приводить _реальный_ участок кода, верно? Там на самом деле все более сложно — а тут просто жмых, чтобы проще разобраться в сути проблемы.
_>В добавление к вышесказанному: _> Если в alloca у тебя стоит константа, почему бы тогда не объявить это дело как локальную переменную? Весь смысл alloca, наверное, все-таки в динамическом выделении на стеке НЕИЗВЕСТНОГО количества памяти...
Ага, это я уже пробовал. Прямо ASProtect вспоминается. А еще я пробовал __try и __except — тоже помогает. А вот с переменным количеством аргументов как то не догадался. Интересно, попробую. Спасибо!
И все таки интересно узнать мнение — это таки можно считать багом документации или багом компилера?
AS>Попробуй скомпилять с #pragma optimize( "", off ). AS>Еще можно с элипсисами извернуться (для меня всегда работало). AS>В MSDN написано: AS>
You cannot force the compiler to inline a function when conditions other
AS>than cost/benefit analysis prevent it. You cannot inline a function if:
AS> — ...
AS> — The function has a variable argument list.
AS> — ...
Здравствуйте, Andrew S, Вы писали:
AS>Какие варианты кроме использования malloc или отключения оптимизации?
1. Сменить компилятор. Хотя не уверен, что поможет.
2. Использовать фиксированный буфер достаточно большого размера и вызывать malloc только тогда, когда требуемый размер больше размера фиксированного буфера (что должно быть достаточно редко).
Здравствуйте, Andrew S, Вы писали:
AS>И все таки интересно узнать мнение — это таки можно считать багом документации или багом компилера?
Думаю ни то ни другое
по первому — скушать стек размер которого по default 1Mb (если не ошибаюсь) довольно таки сложно — это надо постараться посему считаю данную ситуацию частным случаем но упомянуть конечно о нем не помешало бы и в доке — возможно такой KB таки появится
по второму — функция _alloca с точки зрения компилятора наверняка такая же как и любая другая CRT функция а посему невижу причин ему именно дле нее делать какие то дополнительные телодвижения (типа делать возврат ESP) тем более что она основанная на других принципах — чистка стека при выходе
Здравствуйте, Andrew S, Вы писали:
AS>Какие варианты кроме использования malloc или отключения оптимизации? Это на самом деле критичный к производительности участок кода, где выделение при помощи malloc может очень плохо сказаться на производительности. Вынесение аллокации памяти и работу с ней в отдельную функцию тоже нежелательно, тем более что там придется извращаться с параметрами темплайта — заводить указатель на функцию или что еще похуже
Выправляй стэк сам:
int main()
{
void* stack;
__asm mov [stack],esp;
void* p = _alloca(0x100);
// ...__asm mov esp,[stack];
return 0;
}
AS>>Какие варианты кроме использования malloc или отключения оптимизации?
AF>1. Сменить компилятор. Хотя не уверен, что поможет.
AF>2. Использовать фиксированный буфер достаточно большого размера и вызывать malloc только тогда, когда требуемый размер больше размера фиксированного буфера (что должно быть достаточно редко).
Hello, Alex!
You wrote on Mon, 04 Aug 2003 06:44:24 GMT:
AS>> Какие варианты кроме использования malloc или отключения оптимизации?
AF> 1. Сменить компилятор. Хотя не уверен, что поможет.
Спасибо за совет!
Тогда придется заводить отдельно дебужные и релизные версии функций
Тоже весело. Опять же — непортабельно на другой компилер — а ну ка тот сам вздумает справится с такой ситуацией.
Меня в последнее время как то не тянет на эксперименты с таким кодом в коммерческих продуктах
ME>Выправляй стэк сам:
ME>
Здравствуйте, Alexey Shirshov, Вы писали:
AS>>> Какие варианты кроме использования malloc или отключения оптимизации?
AF>> 1. Сменить компилятор. Хотя не уверен, что поможет.
AS>Именно это и делает CTempBuffer из седьмой ATL.
Она основана на том, что компилер использует ebp для кадра стека локальных переменных. В соответствии с этим, он легко восстанавливает значение esp при выходе. А мог бы, кстати, использовать только esp, как делают многие компилеры — тогда ebp освободился бы для общих нужд.
А вообще — хорошо, если была бы функция, выделяющая стековую память только в пределах операторных скобок:
void f()
{
void *p;
DWORD dwSize = xxxxx;
for (int i = 0; i < 20000; i++)
{
// выделяем память
p = __alloca(dwSize)
// используем память
} // память освободилась
}
Это позволило бы писать циклы с динамическим выделением стековой памяти. Все, что надо компилеру — просто восстанавливать esp.
SWA>по второму — функция _alloca с точки зрения компилятора наверняка такая же как и любая другая CRT функция а посему невижу причин ему именно дле нее делать какие то дополнительные телодвижения (типа делать возврат ESP) тем более что она основанная на других принципах — чистка стека при выходе