Парадокс?

Тема в разделе "WASM.A&O", создана пользователем gradient, 10 окт 2006.

  1. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Привет!

    Поразило одно обстоятельство.


    Код (Text):
    1. class A
    2. {
    3. float x;
    4. float y;
    5. float z;
    6. __fastcall A(void);
    7.   __fastcall ~A(void);
    8.  
    9. void __fastcall mul(void);
    10. };
    11.  
    12. void __fastcall A::mul(void)
    13. {
    14. z=x*y;//z=x+y неважно
    15. }
    16.  
    17. class Bclass
    18. {
    19.  A* a;
    20. __fastcall B(void);
    21.   __fastcall ~B(void);
    22. };
    пусть
    Код (Text):
    1. Bclass *B;
    2. B->a->z=B->a->x*B->b->y;
    3. интересно ,что это работает быстрее чем это:
    4. B->a->mul();
    Мне всегда казалось, что лишнее обращение к памяти это плохо и поэтому всегда индексировал длинные ссылки.
    Теперь я в этом не уверен.
    Может быть это связано с вызовом функции?
    Но я не случайно использовал __fastcall.

    Короче, что-то непонятное!

    Пожалуйста, разясните ситуацию!
     
  2. nobodyzzz

    nobodyzzz New Member

    Публикаций:
    0
    Регистрация:
    13 июл 2005
    Сообщения:
    475
    __fastcall влияет на порядок передачи параметров. так что в данном случае ты только тратиш время на вызов процедуры.
     
  3. dr_dred

    dr_dred Сергей

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    301
    Адрес:
    Russia
    Вполне объяснимо. Во втором случае происходит два обращения к памяти для чтения переменных и еще одно для вызова функции, а, как известно, рассчет адреса (в данном случае функции) происходит долго, и обычное произведение, выполняемое командой процессора mul, выполняется намного быстрее.
    IMHO, в случае, когда нужен быстрый код, использовать классы не нужно.
     
  4. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Но я трачу время и на обращение к памяти, причем в данном случае три раза!
    В оригинале это умножение производиться в цикле, причем длина цикла-переменная класса А(извините, что не указал сразу). Так что обращений к памяти может быть сколько угодно(у меня порядка 100)!
    Нужели вызов функции-члена такая дорогая операция.
     
  5. nobodyzzz

    nobodyzzz New Member

    Публикаций:
    0
    Регистрация:
    13 июл 2005
    Сообщения:
    475
    Да любое изменение code-flow серьезно сказываеться на производительность т.к. происходит перезаполнение кэша инструкций
     
  6. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    AFAIK "прямые" call и jmp (и jcc в случае правильно предсказанного ветвления) не приводят к перезагрузке конвеера.

    А чем компилировалось и с какими опциями?

    ---
    А насчёт fastcall, то в данном случае он ничем не лучше thiscall.
     
  7. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Компилил TASM32 (С++Builder5)
    Code optimization-speed
    Instruction set-Pentium (У меня Pentium-3)
    Data Alignment-Quad Word
    Register variables-Automatic
    Floating point-fast

    Может нужны какие нибудь директивы? Project Options->TASM->Add directive:
    Подскажите.

    А насчет __fastcall, если нужно передать переменные, какие лучше значения или указатели?

    Насчет предсказания ветвлений, в моем коде(циклах) нет условных операторов да и Pentium3, наверное, не знает что это такое.

    Я по своей наивности, честно говоря, не думал, что между данными и инструкциями такая большая разница!

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

    С филосовской точки зрения получается ООП опередило железо?
     
  8. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    gradient
    В билдере я не разбираюсь.
    А как ты меряешь время выполнения? Может просто в цикле крутишь это умножение? Если так, тогда понятно - компилятор вынес его из цикла как независимое от кол. итераций (и, скорее всего, вообще убрал цикл). Т.е. умножение вероятно выполняется один раз, в отличие от вызова ф-ции.

    не понял.
     
  9. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Время замеряю так:

    Код (Text):
    1.    int TICS;
    2.    asm
    3.    {
    4.   RDTSC
    5.   MOV     [TICS],EAX
    6.   }
    7.  
    8. здесь либо одно либо другое
    9.  
    10.         asm
    11.      {
    12.       RDTSC              
    13.         SUB     EAX,[TICS]
    14.         mov [TICS],eax
    15.       }
    16.  
    17.  время выполнения-TICS
    Пример утрирован для наглядности. В реальности вычисляется скалярное произведение, так что в цикле есть индексы да и в ассемблерном листинге это видно. я смотрел.
    Непонятно все таки!

    Я имел ввиду вопрос-что лучше передавать в __fastcall-функции в качестве аргумента значение или адрес?
    например:
    Код (Text):
    1. __fastcall funct(float &)
    2. или
    3. __fastcall funct(float )
    с точки зрения производительности.
    Или это все равно?

    Заранее благодарен всем за любой совет по производительности.
    Вопросы не праздные. Программа может работать часами при полной загрузке CPU. Памяти много не использует.
    Так что борюсь за каждую наносекунду!
     
  10. Quantum

    Quantum Паладин дзена

    Публикаций:
    0
    Регистрация:
    6 янв 2003
    Сообщения:
    3.143
    Адрес:
    Ukraine
    gradient
    Для float особой разницы нет, если компилятор не полный отстой.
     
  11. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Я так понял, что простого объяснения нет.
    Жаль. Парадокс!
     
  12. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    gradient
    Посмотри для начала на код, который билдер сгенерил. Он вообще-то в этом смысле того... не очень...
     
  13. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Наверное он не очень.
    Но дело в другом. Я сравниваю два кода скомпилированных одним компилятором! Один код вазывает функцию-член, а другой код носится по памяти как угорелый, но выполняется быстрей!?

    Что-то не то.

    Если дело в вызове функции, то как с этим бороться?
    Мне всетаки кажется дело в другом...
    Может быть прав nobodyzzz
    Сейчас подумал, может быть вызвать функцию через указатеь на нее, а не как член класса. Попробую.
     
  14. Quantum

    Quantum Паладин дзена

    Публикаций:
    0
    Регистрация:
    6 янв 2003
    Сообщения:
    3.143
    Адрес:
    Ukraine
    Ustus дело говорит! Не глядя на ассемблерный код, пытаться понять почему производительность страдает - гадать на кофейной гуще.
     
  15. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    Вариант с вызовом ф-ции должен по любому работать медленнее прямого умножения. Только разница должна быть небольшой, а у тебя, видимо, в разы получается. :)

    Может быть ты думаешь, что компилятор делает 3 раза двойное разыменовывание указателя? Вовсе нет, адрес объекта класса А загружается в регистр 1 раз и доступ к множителям x, y, z выглядит наподобие
    [eax+offset_z]=[eax+offset_x]*[eax+offset_y]
     
  16. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    вот чего наделал мой билдер:
    Код (Text):
    1.     .586p
    2.     ifdef ??version
    3.     if    ??version GT 500H
    4.     .mmx
    5.     endif
    6.     endif
    7.     model flat
    8.     ifndef  ??version
    9.     ?debug  macro
    10.     endm
    11.     endif
    12.     ?debug  S "main.cpp"
    13.     ?debug  T "main.cpp"
    14. _TEXT   segment dword public use32 'CODE'
    15. _TEXT   ends
    16. _DATA   segment dword public use32 'DATA'
    17. _DATA   ends
    18. _BSS    segment dword public use32 'BSS'
    19. _BSS    ends
    20. DGROUP  group   _BSS,_DATA
    21. _TEXT   segment dword public use32 'CODE'
    22. @A@mul$qqrv segment virtual
    23.     align   2
    24. @@A@mul$qqrv    proc    near
    25. ?live16385@0:
    26.    ;    
    27.    ;    void __fastcall A::mul(void)
    28.    ;    {
    29.    ;    z=x*y;//z=x+y неважно
    30.    ;    
    31. @1:
    32.     fld       dword ptr [eax]
    33.     fmul      dword ptr [eax+4]
    34.     fstp      dword ptr [eax+8]
    35.    ;    
    36.    ;    }
    37.    ;    
    38. @2:
    39.     ret
    40. @@A@mul$qqrv    endp
    41. @A@mul$qqrv ends
    42. _TEXT   ends
    43. _TEXT   segment dword public use32 'CODE'
    44. @f$qp1Bt1   segment virtual
    45.     align   2
    46. @@f$qp1Bt1  proc    near
    47. ?live16386@0:
    48.    ;    
    49.    ;    void f(B* B1, B* B2)
    50.    ;    
    51. @3:
    52.     push      ebp
    53.     mov       ebp,esp
    54.     mov       eax,dword ptr [ebp+8]
    55.    ;    
    56.    ;    {
    57.    ;        B1->a->z=B1->a->x*B1->a->y;
    58.    ;    
    59. ?live16386@16: ; EAX = B1
    60.     mov       edx,dword ptr [eax]
    61.     fld       dword ptr [edx]
    62.     fmul      dword ptr [edx+4]
    63.     fstp      dword ptr [edx+8]
    64.    ;    
    65.    ;        B2->a->mul();
    66.    ;    
    67. ?live16386@32: ;
    68.     mov       eax,dword ptr [ebp+12]
    69.     mov       eax,dword ptr [eax]
    70.     call      @@A@mul$qqrv
    71.    ;    
    72.    ;    }
    73.    ;    
    74. @4:
    75.     pop       ebp
    76.     ret
    77. @@f$qp1Bt1  endp
    78. @f$qp1Bt1   ends
    79. _TEXT   ends
    80. _TEXT   segment dword public use32 'CODE'
    81. _TEXT   ends
    82.     ?debug  D "main.cpp" 13643 43119
    83.     end
    видим, что код идентичен за исключением call.

    а если A::mul() явно или неявно объявить inline то код будет абсолютно идентичен:
    Код (Text):
    1.     .586p
    2.     ifdef ??version
    3.     if    ??version GT 500H
    4.     .mmx
    5.     endif
    6.     endif
    7.     model flat
    8.     ifndef  ??version
    9.     ?debug  macro
    10.     endm
    11.     endif
    12.     ?debug  S "main.cpp"
    13.     ?debug  T "main.cpp"
    14. _TEXT   segment dword public use32 'CODE'
    15. _TEXT   ends
    16. _DATA   segment dword public use32 'DATA'
    17. _DATA   ends
    18. _BSS    segment dword public use32 'BSS'
    19. _BSS    ends
    20. DGROUP  group   _BSS,_DATA
    21. _TEXT   segment dword public use32 'CODE'
    22. @f$qp1Bt1   segment virtual
    23.     align   2
    24. @@f$qp1Bt1  proc    near
    25. ?live16385@0:
    26.    ;    
    27.    ;    void f(B* B1, B* B2)
    28.    ;    
    29. @1:
    30.     push      ebp
    31.     mov       ebp,esp
    32.     mov       eax,dword ptr [ebp+8]
    33.    ;    
    34.    ;    {
    35.    ;        B1->a->z=B1->a->x*B1->a->y;
    36.    ;    
    37. ?live16385@16: ; EAX = B1
    38.     mov       edx,dword ptr [eax]
    39.     fld       dword ptr [edx]
    40.     fmul      dword ptr [edx+4]
    41.     fstp      dword ptr [edx+8]
    42.    ;    
    43.    ;        B2->a->mul();
    44.    ;    
    45. ?live16385@32: ;
    46.     mov       eax,dword ptr [ebp+12]
    47.     mov       eax,dword ptr [eax]
    48.     fld       dword ptr [eax]
    49.     fmul      dword ptr [eax+4]
    50.     fstp      dword ptr [eax+8]
    51.    ;    
    52.    ;    }
    53.    ;    
    54. @2:
    55.     pop       ebp
    56.     ret
    57. @@f$qp1Bt1  endp
    58. @f$qp1Bt1   ends
    59. _TEXT   ends
    60. _TEXT   segment dword public use32 'CODE'
    61. _TEXT   ends
    62.     ?debug  D "main.cpp" 13643 43387
    63.     end
     
  17. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Вот что наделал мой Builder:
    Код (Text):
    1. для
    2. T->z=0;
    3. for(int i=T->nn;i>=0;i--)
    4. {
    5.  T->z+=T->x[i]*T->y[i];
    6. }
    7. ?live16414@176: ; EBX = this, ESI = tt
    8.     mov       ecx,dword ptr [ebx+160772]
    9.     xor       eax,eax
    10.     mov       dword ptr [ecx+12],eax   
    11. @88:
    12.     mov       eax,esi
    13.     test      eax,eax
    14.     jl        short @90
    15. ?live16414@208: ; EBX = this, EAX = i
    16. @89:
    17.     mov       edx,dword ptr [ebx+160772]
    18.     mov       ecx,dword ptr [edx+4]
    19.     fld       dword ptr [ecx+4*eax]
    20.     mov       ecx,dword ptr [edx+8]
    21.     fmul      dword ptr [ecx+4*eax]
    22.     fadd      dword ptr [edx+12]
    23.     fstp      dword ptr [edx+12]   
    24. @91:
    25. @92:
    26.     dec       eax
    27.     test      eax,eax
    28.     jge       short @89
    29. @90:
    Код (Text):
    1. а для T->mul();
    2. где
    3. mul(void)
    4. {
    5. z=0;
    6.  for(int i=nn;i>=0;i++)
    7.  z+=x[i]*y[i];
    8. }
    9. ?live16391@16: ; EAX = this
    10.     xor       edx,edx
    11.     mov       dword ptr [eax+12],edx
    12.     mov       ebp,esp
    13. @15:
    14.     mov       edx,dword ptr [eax]
    15.     test      edx,edx
    16.     jl        short @17
    17. ?live16391@64: ; EDX = i, EAX = this
    18. @16:
    19.     mov       ecx,dword ptr [eax+4]
    20.     fld       dword ptr [ecx+4*edx]
    21.     mov       ecx,dword ptr [eax+8]
    22.     fmul      dword ptr [ecx+4*edx]
    23.     fadd      dword ptr [eax+12]
    24.     fstp      dword ptr [eax+12]
    25. @18:
    26.     dec       edx
    27.     test      edx,edx
    28.     jge       short @16
    29. @17:
    видно, что в первом случае в цикле добавляется обращение к вложенному объекту:
    Код (Text):
    1. mov       edx,dword ptr [ebx+160772]
    тоесть, чем больше длина цикла, тем эффективней должен быть второй вариант. И даже если велики расходы на вызов функции(?), то при некоторой ,достаточно большой длине цикла эти расходы будут почти незаметны.
    На самом деле в этом примере так и происходит!(неудачный пример).

    На самом деле у меня более сложный вариант. Тройная вложенность объектов-слоистая структура(нейросеть). Там в потоке вытисляется градиент нейросети(обратное распростронение ошибки).
    И там все с точностью наоборот!?
    Хотя принципиальной разницы нет.
     
  18. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    gradient
    Весь парадокс - в "тупости" компилятора, который в теле цикла грузит неизменяющиеся переменные, вместо того чтобы держать их в регистрах.
    Если речь идет о массивах, то лучше реализовать mul() на асме, исключив ненужные загрузки\выгрузки (в т.ч. z), да еще и распараллелить вычисление z в 2\4 раза
    Для одиночного же умножения z=x*y разумеется вызов функции (не inline) будет выполняться дольше прямого умножения

    PS: а разве в C++ нет оператора with, чтобы не пудрить мозги себе и компилятору такими нецензурными выражениями B->a->z=B->a->x*B->a->y ? ;)))
     
  19. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Конечно все это написано у меня на ассемблере. В z не перезаписывается. Компиляторы конечно тупят.
    Но вопрос остается. Ведь компилится все одним компилятором!
    А насчет with-я никогда им не пользуюсь. Зачем это нужно когда есть стуктура и класс. Я привел утрированный пример для простоты.

    Короче, я окончательно запутался. Может нужно как то выравнивать инструкции (вызовы функций)?
     
  20. gradient

    gradient New Member

    Публикаций:
    0
    Регистрация:
    10 окт 2006
    Сообщения:
    30
    Кстати, в C++ оператора with нет! Да он и не нужен, мне кажется, в ООП.