Аннотация:
Бесплатные средства разработки, основанные на C и C-подобных языках (MinGW, LCC32-Win, Digital Mars), и на Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки.
Без указания версий компиляторов, а также без четкого указания с какими настройками запускались компиляторы, оценка оптимизации превращается в профанацию.
Re: Альтернативные средства разработки под Windows
Здравствуйте, Петр Каньковски, Вы писали:
ПК>Статья:
ПК>Авторы: ПК> Петр Каньковски
ПК>Аннотация: ПК>Бесплатные средства разработки, основанные на C и C-подобных языках (MinGW, LCC32-Win, Digital Mars), и на Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки.
Не могу прочитать статью по заявленой ссылке.
Re[2]: Альтернативные средства разработки под Windows
Она пока только в бумажном виде (в журнале RSDN Magazine, 2003, #5)
Re[2]: Версии и настройки компиляторов
От:
Аноним
Дата:
18.02.04 15:30
Оценка:
Уважаемому DarkGray:
>Без указания версий компиляторов, а также без четкого указания >с какими настройками запускались компиляторы, оценка оптимизации >превращается в профанацию.
VC 6.0. В свойствах файлов Cl*.* указана версия 12.00.8168.0.
Intel Compiler 4.1.1.0
gcc version 3.2 (mingw special 20020817-1)
lcc-win32 version 3.8.
Borland C++ 5.5.1 for Win32
D Compiler Beta v0.63
Free Pascal Compiler version 1.0.10 [2003/06/27] for i386
Настройки или ключи командой строки: -O3 для gcc, -O2 для BCC
(хотя особой разницы между -O1 и -O2 я не обнаружил). Когда
проверял регистровую оптимизацию в BCC, пытался указать ему
опцию -r -- не помогло, оптимизировал именно так отвратительно,
как это описано в статье.
Ключи для Free Pascal: -OG -Or. В LCC и D Compiler есть только
одна опция: либо "оптимизировать", либо "не оптимизировать".
Естественно, я выбирал "оптимизировать"
Для компиляторов Microsoft и Intel тесты повторялись в режимах
с оптимизацией по скорости и по размеру. Если результаты в этих
режимах различаются, то об этом упомянуто в тексте статьи.
>Такую оптимизацию делать нельзя, потому что значение >в стеке может быть изменено самой функцией.
А компилятор может определить, было ли оно изменено,
или хотя бы, является ли оно константным или содержит
переменные.
В том примере, который я привожу в статье, s — это
константное выражение (адрес строки в памяти, который
остается неизменным во время выполнения программы).
>меняем переданный параметр
Извините, пожалуйста, но это неверно. В данном коде
Вы передаете указатель p по значению, а затем модифицируете
этот указатель внутри функции. В основной программе
указатель q не меняется и не может меняться, поскольку
это совсем другой указатель на строку из 5 символов
в памяти, которую Вы неявно создаете конструкцией:
char *q = "abcd";
Прогоните этот пример под отладчиком и убедитесь сами.
Вообще, проверяйте примеры, прежде чем отправить их
на форум или описать в своей статье. Со мною тоже такое
бывало: пишу вроде бы очевидный пример, а потом
оказывается, что он не работает.
Вот если бы Вы передавали этот параметр по ссылке, то
есть передавали указатель на указатель p, то сам указатель p
изменился бы. Понятно?
Ну хорошо, вот у нас функция, в которую передается параметр.
Как это выглядит на уровне ассемблера? Сначала вызывающая
функция загоняет в стек параметры, затем делает CALL
на код вызываемой функции. Та, в свою очередь, читает
эти параметры из стека. Она может изменять их как угодно.
Но когда выполнение функции закончится, параметры функции
и локальные переменные будут удалены из стека. В вызывающую
функцию они никак не попадут.
А когда нужно передать параметр по ссылке, в стек помещают
указатель на ячейку памяти, которая хранит значение параметра.
Функция читает и/или изменяет содержимое этой ячейки. Когда
ее выполнение закончится, значение сохранится в данной ячейке
памяти (здесь неважно, является ли эта ячейка глобальной
переменной или локальной переменной вызывающей функции).
Всего наилучшего, будьте здоровы и пишите еще.
Петр.
>>Такую оптимизацию делать нельзя, потому что значение >>в стеке может быть изменено самой функцией. А>А компилятор может определить, было ли оно изменено, А>или хотя бы, является ли оно константным или содержит А>переменные. А>В том примере, который я привожу в статье, s — это А>константное выражение (адрес строки в памяти, который А>остается неизменным во время выполнения программы).
Неважно, константное выражение или неконстантное.
Все равно, вызываемая функция может изменить значения передаваемых параметров в стеке.
А>Вот если бы Вы передавали этот параметр по ссылке, то А>есть передавали указатель на указатель p, то сам указатель p А>изменился бы. Понятно?
Причем тут это? Я говорю, что значение в стеке будет изменено после вызова функции, и поэтому его нельзя использовать повторно.
А>Ну хорошо, вот у нас функция, в которую передается параметр. А>Как это выглядит на уровне ассемблера? Сначала вызывающая А>функция загоняет в стек параметры, затем делает CALL А>на код вызываемой функции. Та, в свою очередь, читает А>эти параметры из стека. Она может изменять их как угодно. А>Но когда выполнение функции закончится, параметры функции А>и локальные переменные будут удалены из стека. В вызывающую А>функцию они никак не попадут.
Кем они будут удалены?
Ты же сам говоришь, что компилятор не должен чистить переданные параметры, а должен их использовать повторно.
Замечу, что строго говоря p не является локальной переменной функции func, поэтому после выхода из func значение (в том числе и измененное) p не будет удалено из стека самой функцией func.
Вот смотри:
push q
//на стеке лежит значение переменной q
call func
//переменная p маппится на стек (причем на то место, где лежит за-push-енное значение переменной q)
p = strchr(p, 'a');
//значения переменной q в стеке теперь нет, на том месте лежит новое значение переменной preturn//если мы сейчас сразу вызывем func, То func полученное испорченное значение.
Re[4]: Версии и настройки компиляторов
От:
Аноним
Дата:
19.02.04 12:01
Оценка:
Привет, DarkGray!
Дружок, прогони эту программу на своем любимом
компиляторе, а потом побеседуем:
А то сдается мне, что говорим мы о разных вещах.
>строго говоря p не является локальной переменной функции func
Правильно, p — это параметр функции func.
>Неважно, константное выражение или неконстантное.
Если оно константное, функция не сможет его изменить. Сам
язык Си не позволит. Попробуй откомпилировать такой пример:
char s[] = "abcd";
s = '2';
s = "23";
Ты получишь ошибку. О каком изменении строки s здесь может
идти речь?
>Я говорю, что значение в стеке будет изменено после вызова >функции, и поэтому его нельзя использовать повторно.
Значение чего? Указателя или переменной, на которую
указывает указатель?
Значение где? В твоем примере есть указатель q — локальная
переменная функции main(). И есть указатель p — формальный
параметр функции func(). Это не одно и то же, и находятся
эти два *разных* указателя в разных частях стека.
Посмотри на ассемблерный код, который выдает VC++
для примера выше:
func PROC NEAR
;p = strchr(p, 'b');
mov eax, DWORD PTR _p$[esp-4] ; Формальный параметр p помещаем в eax
push'b' ; Передаем функции strchr параметр 'b'push eax ; Передаем функции strchr параметр p
call _strchr
add esp, 8 ; Очищаем стек при возврате из процедуры
ret 0
func ENDP
_main PROC NEAR
;func(q);
push'b'push offset abcd
call _strchr
;func(q);
push'b'push offset abcd
call _strchr
ret 0
_main ENDP
Еще раз проверь свои предположения с компилятором в руках.
И если остались еще вопросы, пиши.
Данной программе соответствует, следующий ассемблерный листинг:
//func
push esi
mov esi,dword ptr [esp+8]
push esi
push offset string "1 - %s\n" (4120D4h)
call printf (401070h)
inc esi
push esi
push offset string "2 - %s\n" (4120C8h)
mov dword ptr [esp+18h],esi
call printf (401070h)
lea eax,[esp+18h]
push eax
call dummy (40100Fh)
add esp,14h
pop esi
ret
//main
push offset string "abcdef" (4120E0h)
call func (40100Ah)
push offset string "abcdef" (4120E0h)
call func (40100Ah)
add esp,8
Вспоминаем твою цитату из статьи:
ПК> Ни один компилятор не смог "догадаться", что после вызова strchr адрес строки s все еще находится в стеке, и не нужно заталкивать его еще раз
На основе этой цитаты, я делаю вывод, что ты считаешь, что, например, для данной задачи
в функции main можно убрать второй push offset "abcdef", а add esp, 8 заменить на add esp, 4.
Ну вот, теперь проблема понятна, примеры работают
(то есть наоборот, не работают, вызывают ошибку).
За фамильярность прошу прощения.
Решения могут быть следующими:
1) Определять, меняла ли функция входные параметры
(были ли в ее теле операторы p++, p-=2 или p=что-то-там).
Далеко не все функции это делают. Библиотечные
strchr, printf, о которых шла речь в статье,
этим делом не занимаются (я проверил, прогнав через
ассемблер листинг, в котором убрал лишний push).
Можно хранить в объектном файле с каждой функцией
флаг, показывающей, модифицирует ли она параметры.
Это очевидное, оптимальное и работоспособное решение.
Можно (хотя это плохое решение) ввести флаг типа "assume
no aliasing" (в VC++ программист должен сбросить
этот флаг, если он обращается к одной и той же
переменной и через указатель, и через имя переменной;
почти никто так не делает, поэтому этот флаг установлен,
и его установка помогает улучшить оптимизацию).
2) Проводить регистровую оптимизацию. Это не гарантирует
правильного выполнения программы, однако же хороший
компилятор будет переносить значение p из стека в регистр,
увеличивать его, но потом не возвращать в стек (зачем оно
там нужно после выполнения функции, когда стек уже будут
очищать?). Хотя здесь обнаруживается другое обстоятельство:
вызываемая функция printf может менять регистры, поэтому
параметр p придется записывать в стек. То есть если мы напишем
inc ebx ; Увеличить p
call printf
и дальше попытаемся использовать ebx, то в нем окажется
все что угодно, но не (p+1). Важное замечание: это не относится
к регистру esi, который (по крайней мере в VC++) принято
сохранять между вызовами процедур.
В твоем примере кроме того нужно находить адрес &(p+1),
для чего (p+1) нужно так или иначе записывать в память.
3) Здесь напрашивается третье решение. Мы практически ничего
не потеряем (ни скорости выполнения, ни размера программы),
если введем дополнительную локальную переменную:
//func
push esi
mov esi,dword ptr [esp+8]
push esi
push offset string "1 - %s\n" (4120D4h)
call printf (401070h)
mov eax, esi ; Вставляем всего одну инструкцию
inc eax
push eax
push offset string "2 - %s\n" (4120C8h)
mov dword ptr [esp+18h],eax
call printf (401070h)
lea eax,[esp+18h]
push eax
call dummy (40100Fh)
add esp,14h
pop esi
ret
Тогда в случае функции, меняющей свои входные параметры,
компилятор должен генерировать код с дополнительной локальной
переменной.
Суть твоего примера в том, что нам так или иначе нужна
дополнительная ячейка (локальная переменная или параметр)
в стеке. Нам приходится копировать переменную, так как
она изменяется (p++), а нам нужно получить ее исходное
значение.
Хотя в твоем примере есть другое решение. Сравни этот
код со своим:
//func
push esi
mov esi,dword ptr [esp+8]
push esi
push offset string "1 - %s\n" (4120D4h)
call printf (401070h)
inc esi
push esi ; Занесли в стек увеличенный p
push offset string "2 - %s\n" (4120C8h)
call printf (401070h)
;Теперь (p+1) уже лежит в стеке, зачем же заносить
;его туда снова? Просто находим &(p+1) и вызываем
;функцию:
lea eax, [esp+4] ; eax = то значение (p+1), которое мы передавали в printf
push eax
call dummy (40100Fh)
add esp,14h
pop esi
ret
и все будет работать нормально. Этот код я считаю почти лучшим
возможным для данного примера. Но компиляторы нескоро додумаются
до такого. Человек-ассемблерщик, конечно, всегда может дать лучший
код с нестандартными трюками. А для компилятора это слишком сложно:
определять, что находится в стеке, как он изменяется при выполнении
той или иной команды, где сохраняется увеличенное значение.
Кстати, насчет лучшего возможного кода. Знаешь, что выдал VC++
в режиме "Inline — Any Suitable"? Ты будешь долго смеяться:
Кажется, это радикальное решение проблемы: взять и заинлайнить все
функции .
Спасибо. Теперь когда до меня (старого тупого хама) наконец-то
дошло, о чем ты хотел сказать, я признаю, что это очень важное
замечание к статье. Спасибо еще раз.
Удачи!
Петр Каньковски.
Re[6]: Версии и настройки компиляторов
От:
Аноним
Дата:
20.02.04 06:31
Оценка:
Привет, DarkGray!
Дураков нам не надо, мы сами – дураки. Да, и хамство по отношению к читателю — страшнейший грех. Нам хамов не надо — мы сами и хамы тоже.
(Из внутренних документов журнала "Хакер";
насчет фамильярности)
Обнаружил ошибку в своем листинге под номером 3:
push esi
mov esi,dword ptr [esp+8]
push esi
push offset string "1 - %s\n" (4120D4h)
call printf (401070h)
mov eax, esi ; Вставляем всего одну инструкцию
inc eax
push eax
push offset string "2 - %s\n" (4120C8h)
mov dword ptr [esp+18h],eax
call printf (401070h)
lea eax,[esp+18h]
push eax
call dummy (40100Fh)
mov dword ptr [esp+2Bh],esi ; Нужно вставить еще одну инструкцию
add esp,14h
pop esi
ret
Всего доброго!
Петр.
Re[6]: Версии и настройки компиляторов
От:
Аноним
Дата:
20.02.04 07:55
Оценка:
Привет, DarkGray!
Вот уж воистинну, "Нам дураков не надо, мы сами дураки".
Точнее, я дурак. Терпеть не могу, когда мне присылают
непроверенные, непонятно как работающие исходники и пытаются
на основании их доказать что-то. А теперь оказалось,
что и я сам страдаю тем же.
Короче, вот ссылка на полностью проверенные и работающие
исходники для MASM:
При дальнейших рассуждениях можешь опираться на них.
Хотя мне по-прежнему кажется, что самый
простой и эффективный способ решить
проблему -- это найти и пометить специальным
образом функции, меняющие свои входные параметры.
И не выполнять оптимизацию для таких функций.
Петр Каньковски.
Re[2]: Альтернативные средства разработки под Windows
> ПК>Статья: > > > ПК>Авторы: > ПК> Петр Каньковски > > ПК>Аннотация: > ПК>Бесплатные средства разработки, основанные на C и C-подобных языках (MinGW, LCC32-Win, Digital Mars), и на Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки. > Не могу прочитать статью по заявленой ссылке.
я её успешно просмотрел
Posted via RSDN NNTP Server 1.8
Re: Альтернативные средства разработки под Windows
Здравствуйте, Петр Каньковски, Вы писали:
ПК>Аннотация: ПК>Бесплатные средства разработки, основанные на <..skipped..> Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки.
а не было желания рассмотреть такую штуку, как Virtual Pascal (http://vpascal.com) с точки зрения оптимизации? помнится, писал как-то на нём (когда ещё на паскале сидел) — приятная штука была в смысле юзабельности и код он генерировал вроде ничего — поменьше Borland Pascal'ного точно...
Re: Альтернативные средства разработки под Windows
А можно спросить, почему функции стандартной библиотеки в DLL работают медленнее, чем статически подлинкованные? Я был уверен, что после загрузки DLL на старте, её функции неотличимы от тех, что определены в самом EXE файле
Евгений Коробко
Re[2]: Альтернативные средства разработки под Windows
Здравствуйте, Евгений Коробко, Вы писали:
ЕК>А можно спросить, почему функции стандартной библиотеки в DLL работают медленнее, чем статически подлинкованные? Я был уверен, что после загрузки DLL на старте, её функции неотличимы от тех, что определены в самом EXE файле
При использовании функции из dll — используется косвенный вызов, при использовании из либы — делается прямой вызов.
Re[2]: Альтернативные средства разработки под Windows