Альтернативные средства разработки под Windows
От: Петр Каньковски  
Дата: 25.11.03 16:14
Оценка: 541 (24) +1 -1
Статья:
Альтернативные средства разработки под Windows
Автор(ы): Петр Каньковски
Дата: 11.04.2004
Бесплатные средства разработки, основанные на C и C-подобных языках (MinGW, LCC32-Win, Digital Mars), и на Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки.





Авторы:
Петр Каньковски

Аннотация:
Бесплатные средства разработки, основанные на C и C-подобных языках (MinGW, LCC32-Win, Digital Mars), и на Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки.
Re: Оптимизация call-ов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 18.02.04 04:46
Оценка:
А> Ни один компилятор не смог "догадаться", что после вызова strchr адрес строки s все еще находится в стеке, и не нужно заталкивать его еще раз

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

void func(char * p)
{
  p = strchr(p, 'b'); //меняем переданный параметр
  ...
}
void test()
{
  char *q = "abcd";
  func(q);
  func(q);
}
Re: Версии и настройки компиляторов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 18.02.04 04:52
Оценка: 27 (1)
Без указания версий компиляторов, а также без четкого указания с какими настройками запускались компиляторы, оценка оптимизации превращается в профанацию.
Re: Альтернативные средства разработки под Windows
От: VeryBadBoy Россия  
Дата: 18.02.04 07:08
Оценка:
Здравствуйте, Петр Каньковски, Вы писали:

ПК>Статья:



ПК>Авторы:

ПК> Петр Каньковски

ПК>Аннотация:

ПК>Бесплатные средства разработки, основанные на C и C-подобных языках (MinGW, LCC32-Win, Digital Mars), и на Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки.


Не могу прочитать статью по заявленой ссылке.
Re[2]: Альтернативные средства разработки под Windows
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 18.02.04 07:53
Оценка:
VBB>Не могу прочитать статью по заявленой ссылке.

Она пока только в бумажном виде (в журнале 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
на код вызываемой функции. Та, в свою очередь, читает
эти параметры из стека. Она может изменять их как угодно.
Но когда выполнение функции закончится, параметры функции
и локальные переменные будут удалены из стека. В вызывающую
функцию они никак не попадут.

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

Всего наилучшего, будьте здоровы и пишите еще.
Петр.
Re[3]: Версии и настройки компиляторов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 19.02.04 07:15
Оценка:
>>Такую оптимизацию делать нельзя, потому что значение
>>в стеке может быть изменено самой функцией.
А>А компилятор может определить, было ли оно изменено,
А>или хотя бы, является ли оно константным или содержит
А>переменные.
А>В том примере, который я привожу в статье, s — это
А>константное выражение (адрес строки в памяти, который
А>остается неизменным во время выполнения программы).

Неважно, константное выражение или неконстантное.
Все равно, вызываемая функция может изменить значения передаваемых параметров в стеке.

А>Вот если бы Вы передавали этот параметр по ссылке, то

А>есть передавали указатель на указатель p, то сам указатель p
А>изменился бы. Понятно?

Причем тут это? Я говорю, что значение в стеке будет изменено после вызова функции, и поэтому его нельзя использовать повторно.

А>Ну хорошо, вот у нас функция, в которую передается параметр.

А>Как это выглядит на уровне ассемблера? Сначала вызывающая
А>функция загоняет в стек параметры, затем делает CALL
А>на код вызываемой функции. Та, в свою очередь, читает
А>эти параметры из стека. Она может изменять их как угодно.
А>Но когда выполнение функции закончится, параметры функции
А>и локальные переменные будут удалены из стека. В вызывающую
А>функцию они никак не попадут.

Кем они будут удалены?
Ты же сам говоришь, что компилятор не должен чистить переданные параметры, а должен их использовать повторно.
Замечу, что строго говоря p не является локальной переменной функции func, поэтому после выхода из func значение (в том числе и измененное) p не будет удалено из стека самой функцией func.

Вот смотри:
push q
//на стеке лежит значение переменной q
call func
//переменная p маппится на стек (причем на то место, где лежит за-push-енное значение переменной q)
  p = strchr(p, 'a');
//значения переменной q в стеке теперь нет, на том месте лежит новое значение переменной p
return

//если мы сейчас сразу вызывем func, То func полученное испорченное значение.
Re[4]: Версии и настройки компиляторов
От: Аноним  
Дата: 19.02.04 12:01
Оценка:
Привет, DarkGray!

Дружок, прогони эту программу на своем любимом
компиляторе, а потом побеседуем:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void func(char * p)
{ p = strchr(p, 'b'); //меняем переданный параметр
}

int main(int argc,char *argv[])
{char *q = "abcd";
  puts(q); // Печатает "abcd"
  func(q);
  puts(q); // Печатает "abcd"
  func(q);
  puts(q); // Печатает "abcd"

 char s[] = "abcd";
  puts(s); // Печатает "abcd"
  func(s);
  puts(s); // Печатает "abcd"
  func(s);
  puts(s); // Печатает "abcd"
return 0;

}


А то сдается мне, что говорим мы о разных вещах.

>строго говоря 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


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

Всего доброго!
Петр Каньковски.
Re[5]: Версии и настройки компиляторов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 19.02.04 13:48
Оценка:
А>Дружок,

Прошу обойтись без фамильярности.


Пример:
__declspec(noinline)
void dummy(char ** q)
{
    printf(*q);
}

__declspec(noinline)
void func(char * p)
{
    printf ("1 - %s\n",p);
    p++;
    printf ("2 - %s\n",p);
        //подавляем регистровую оптимизацию для p.
        dummy(&p);
}

int _tmain(int argc, _TCHAR* argv[])
{
    char * q = "abcdef";
    func(q);
    func(q);

    return 0;
}


Данная программа выдает следующее:
1 - abcdef
2 - bcdef
bcdef
1 - abcdef
2 - bcdef
bcdef


Данной программе соответствует, следующий ассемблерный листинг:
//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.

Отлично! меняем (убираем второй push):

//main

push        offset string "abcdef" (4120E0h) 
call        func (40100Ah) 

call        func (40100Ah) 
add         esp,4



Но.. о боже.. такая "оптимизация" привела к тому, что программа теперь выдает
1 - abcdef
2 - bcdef
bcdef
1 - bcdef
2 - cdef
cdef

Что совсем не соответствует тому, что должно было выводиться.
Вывод второго вызова func не соответствует выводу от первого вызова func.

Вывод:
Компиляторы правы в том, что не проводят такого рода оптимизацию.
Re[2]: Альтернативные средства разработки под Windows
От: Аноним  
Дата: 19.02.04 14:19
Оценка:
Здравствуйте, VeryBadBoy, Вы писали:

VBB>Не могу прочитать статью по заявленой ссылке.


Попробуйте эту: http://www.rsdn.ru/mag/0503/Devtools.xml
У меня она работает.

Удачи!
Петр Каньковски.
Re[6]: Версии и настройки компиляторов
От: Аноним  
Дата: 20.02.04 04:54
Оценка:
Привет, DarkGray!

Ну вот, теперь проблема понятна, примеры работают
(то есть наоборот, не работают, вызывают ошибку).
За фамильярность прошу прощения.

Решения могут быть следующими:
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


Это примерно соответствует такому коду на Си:
void func(char * p)
{
    register char* tmp = p;
    printf ("1 - %s\n",p);
    tmp++;
    printf ("2 - %s\n",tmp);
    dummy(&tmp);
}



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




Суть твоего примера в том, что нам так или иначе нужна
дополнительная ячейка (локальная переменная или параметр)
в стеке. Нам приходится копировать переменную, так как
она изменяется (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


Эту функцию можно вызывать кодом

//main
push        offset string "abcdef" (4120E0h) 
call        func (40100Ah) 
call        func (40100Ah) 
add         esp,4


и все будет работать нормально. Этот код я считаю почти лучшим
возможным для данного примера. Но компиляторы нескоро додумаются
до такого. Человек-ассемблерщик, конечно, всегда может дать лучший
код с нестандартными трюками. А для компилятора это слишком сложно:
определять, что находится в стеке, как он изменяется при выполнении
той или иной команды, где сохраняется увеличенное значение.

Кстати, насчет лучшего возможного кода. Знаешь, что выдал VC++
в режиме "Inline — Any Suitable"? Ты будешь долго смеяться:

; 208  :     char *q = "abcd";
; 209  :     func(q);

    push    OFFSET string "abcd"
    push    OFFSET string "1 - %s\n"
    call    _printf
    push    OFFSET string "abcd"
    push    OFFSET string "2 - %s\n"
    call    _printf
    push    OFFSET string "abcd"
    call    _printf
    add    esp, 48

; 210  :     func(q);
    push    OFFSET string "abcd"
    push    OFFSET string "1 - %s\n"
    call    _printf
    push    OFFSET string "abcd"
    push    OFFSET string "2 - %s\n"
    call    _printf
    push    OFFSET string "abcd"
    call    _printf
    add    esp, 48

Кажется, это радикальное решение проблемы: взять и заинлайнить все
функции .




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

Удачи!
Петр Каньковски.
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:

http://kankowski.nm.ru/EXAMPLES.zip

При дальнейших рассуждениях можешь опираться на них.
Хотя мне по-прежнему кажется, что самый
простой и эффективный способ решить
проблему -- это найти и пометить специальным
образом функции, меняющие свои входные параметры.
И не выполнять оптимизацию для таких функций.

Петр Каньковски.
Re[2]: Альтернативные средства разработки под Windows
От: RvRom  
Дата: 24.04.04 14:36
Оценка:
> ПК>Статья:
>
>
> ПК>Авторы:
> ПК> Петр Каньковски
>
> ПК>Аннотация:
> ПК>Бесплатные средства разработки, основанные на C и C-подобных языках (MinGW, LCC32-Win, Digital Mars), и на Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки.
> Не могу прочитать статью по заявленой ссылке.
я её успешно просмотрел
Posted via RSDN NNTP Server 1.8
Re: Альтернативные средства разработки под Windows
От: Bob Kotl Россия  
Дата: 24.04.04 17:20
Оценка:
Здравствуйте, Петр Каньковски, Вы писали:

ПК>Аннотация:

ПК>Бесплатные средства разработки, основанные на <..skipped..> Pascal (Free Pascal). Сравнение оптимизации, многочисленные ссылки.
а не было желания рассмотреть такую штуку, как Virtual Pascal (http://vpascal.com) с точки зрения оптимизации? помнится, писал как-то на нём (когда ещё на паскале сидел) — приятная штука была в смысле юзабельности и код он генерировал вроде ничего — поменьше Borland Pascal'ного точно...
Re: Альтернативные средства разработки под Windows
От: Шевченко Александр http://alexsoft.home.nov.ru
Дата: 25.04.04 10:45
Оценка:
Поправьте форматирование, видны тэги.


xor <kw>eax</kw>,<kw>eax</kw> ; a = 0</com>
shl <kw>eax</kw>,2 ; a *= 4</com>
jmp <kw>short</kw> @2
inc <kw>eax</kw> ; a++</com>
@2:
push <kw>eax</kw>
push <kw>offset</kw> s
call _printf
add <kw>esp</kw>,8
Re: Альтернативные средства разработки под Windows
От: Рома Мик Россия http://romamik.com
Дата: 25.04.04 15:07
Оценка:
Здравствуйте, Петр Каньковски, Вы писали:

Mingw успешно существует и без dev-cpp: http://www.mingw.org
... << RSDN@Home 1.1.3 beta 2 >>
Re: Альтернативные средства разработки под Windows
От: Евгений Коробко  
Дата: 26.04.04 05:44
Оценка:
А можно спросить, почему функции стандартной библиотеки в DLL работают медленнее, чем статически подлинкованные? Я был уверен, что после загрузки DLL на старте, её функции неотличимы от тех, что определены в самом EXE файле
Евгений Коробко
Re[2]: Альтернативные средства разработки под Windows
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 26.04.04 07:39
Оценка:
Здравствуйте, Евгений Коробко, Вы писали:

ЕК>А можно спросить, почему функции стандартной библиотеки в DLL работают медленнее, чем статически подлинкованные? Я был уверен, что после загрузки DLL на старте, её функции неотличимы от тех, что определены в самом EXE файле


При использовании функции из dll — используется косвенный вызов, при использовании из либы — делается прямой вызов.
Re[2]: Альтернативные средства разработки под Windows
От: Odi$$ey Россия http://malgarr.blogspot.com/
Дата: 26.04.04 10:07
Оценка:
Здравствуйте, Шевченко Александр, Вы писали:

ША>Поправьте форматирование, видны тэги.


ША> xor <kw>eax</kw>,<kw>eax</kw> ; a = 0</com>


в упор не вижу
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.