Функция выводит собачий бред

Тема в разделе "WASM.BEGINNERS", создана пользователем Win32Api, 26 ноя 2023.

  1. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    2.000
    изображение_2023-11-27_092813257.png
    Зачем ты дрочишься, имея на руках готовое решение? Убери из приведенного тобой семпла лишнее и готово.

    Код (Text):
    1. function CompareText(const S1, S2: string): Integer; assembler;
    2. asm
    3.         PUSH    ESI
    4.         PUSH    EDI
    5.         PUSH    EBX
    6.         MOV     ESI,EAX
    7.         MOV     EDI,EDX
    8.         OR      EAX,EAX
    9.         JE      @@0
    10.         MOV     EAX,[EAX-4]
    11. @@0:    OR      EDX,EDX
    12.         JE      @@1
    13.         MOV     EDX,[EDX-4]
    14. @@1:    MOV     ECX,EAX
    15.         CMP     ECX,EDX
    16.         JBE     @@2
    17.         MOV     ECX,EDX
    18. @@2:    CMP     ECX,ECX
    19. @@3:    REPE    CMPSB
    20.         JE      @@6
    21.  
    22.  
    23. xor eax,eax
    24. jmp @@7
    25.  
    26.  
    27. ;        MOV     BL,BYTE PTR [ESI-1]
    28. ;        CMP     BL,'a'
    29. ;        JB      @@4
    30. ;        CMP     BL,'z'
    31. ;        JA      @@4
    32. ;        SUB     BL,20H
    33. ;@@4:    MOV     BH,BYTE PTR [EDI-1]
    34. ;        CMP     BH,'a'
    35. ;        JB      @@5
    36. ;        CMP     BH,'z'
    37. ;        JA      @@5
    38. ;        SUB     BH,20H
    39. ;@@5:    CMP     BL,BH
    40. ;        JE      @@3
    41. ;        MOVZX   EAX,BL
    42. ;        MOVZX   EDX,BH
    43. @@6:
    44. ;       SUB     EAX,EDX
    45.  
    46.  
    47.  
    48. xor eax,eax
    49. inc eax
    50. @@7:
    51.  
    52.  
    53.  
    54.         POP     EBX
    55.         POP     EDI
    56.         POP     ESI
    57. end;
     
  2. Win32Api

    Win32Api Member

    Публикаций:
    0
    Регистрация:
    16 окт 2022
    Сообщения:
    109
    f13nd, да чето загнался. какой -- то дроч начался на уровне оптимизации 1-2 инструкций. причем нет полного понимания о связях/составляющих между ними
     
    Последнее редактирование: 27 ноя 2023
  3. Win32Api

    Win32Api Member

    Публикаций:
    0
    Регистрация:
    16 окт 2022
    Сообщения:
    109
    Разобрался, это неправильно, достаточно передать '' для StrCompare :)

    В общем:
    --- Сообщение объединено, 28 ноя 2023 ---
    9 sec
    Код (Text):
    1. function StrCompare(s1, s2: string): bool;
    2. begin
    3.   Result := s1 = s2;
    4. end;
    33 sec
    Код (Text):
    1. function StrCompare(S1, S2: string): bool;
    2. begin
    3.   Result := lstrcmp(PChar(s1), PChar(s2)) = 0;
    4. end;
    4 sec
    Код (Text):
    1. function StrCompare(S1, S2: string): bool; assembler;
    2. asm
    3.   push esi
    4.   push edi
    5.   mov esi, S1
    6.   mov edi, S2
    7.   or esi, esi
    8.   je @1
    9.   or edi, edi
    10.   je @Exit
    11.   mov al, [esi]
    12.   cmp al, [edi]
    13.   jne @Exit
    14.   mov ecx, [esi-4]
    15.   cmp ecx, [edi-4]
    16.   jne @Exit
    17.   repe cmpsb
    18.   je @Equal
    19.   jmp @Exit
    20.   @Equal:
    21.     mov eax, 1
    22.     jmp @Done
    23.   @Exit:
    24.     mov eax, 0
    25.     jmp @Done
    26.   @1:
    27.     or edi, edi
    28.     je @Equal
    29.     jmp @Exit
    30.   @Done:
    31.     pop edi
    32.     pop esi
    33. end;
     
    Последнее редактирование: 28 ноя 2023
  4. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    462
    Тут есть очень интересные дьяволы в деталях. У меня сейчас есть нет под рукой дельфи, да и чтобы ссылки давать на пруфы я буду пользоваться онлайн-компилятором Free Pascal Compiler на godbolt.
    Итак, давайте посмотрим во что она превращается в FPC с -O4, то есть высочайшем уровне оптимизации доступному этому компилятору (правда он хуже чем в Delphi, но даст нам понимание некоторых вещей):
    https://godbolt.org/z/WYT1678Tf
    А превращается он в следующий атас:
    Код (Text):
    1.  
    2. strcompare(ansistring,ansistring):
    3.         pushq   %rbx
    4.         leaq    -112(%rsp),%rsp
    5.         movq    %rdi,(%rsp)
    6.         movq    %rsi,8(%rsp)
    7.         movq    (%rsp),%rdi
    8.         call    fpc_ansistr_incr_ref
    9.         movq    8(%rsp),%rdi
    10.         call    fpc_ansistr_incr_ref
    11.         leaq    16(%rsp),%rdx
    12.         leaq    40(%rsp),%rsi
    13.         movl    $1,%edi
    14.         call    fpc_pushexceptaddr
    15.         movq    %rax,%rdi
    16.         call    fpc_setjmp
    17.         movslq  %eax,%rdx
    18.         movq    %rdx,104(%rsp)
    19.         testl   %eax,%eax
    20.         jne     .Lj6
    21.         movq    8(%rsp),%rsi
    22.         movq    (%rsp),%rdi
    23.         call    fpc_ansistr_compare_equal
    24.         testq   %rax,%rax
    25.         seteb   %bl
    26. .Lj6:
    27.         call    fpc_popaddrstack
    28.         movq    %rsp,%rdi
    29.         call    fpc_ansistr_decr_ref
    30.         leaq    8(%rsp),%rdi
    31.         call    fpc_ansistr_decr_ref
    32.         movq    104(%rsp),%rax
    33.         testq   %rax,%rax
    34.         je      .Lj5
    35.         call    fpc_reraise
    36. .Lj5:
    37.         movb    %bl,%al
    38.         leaq    112(%rsp),%rsp
    39.         popq    %rbx
    40.         ret
    41.  
    Тут можно воскликнуть WTF???!!! Это что за адовое адище?
    Если всмотреться же, то можно увидеть вызовы каких то ненужных нам функций - fpc_ansistr_incr_ref и fpc_ansistr_decr_ref которые еще обмазаны явно какой то заботой об исключениях - fpc_pushexceptaddr и fpc_reraise.
    А что это а зачем это?
    А это затем, что жизнь и смерть строк в паскале разруливается стратегией счётчиков ссылок с copy on write, объяснять долго, но если нам в функцию пришла строка, то мы должны увеличить ей счётчик ссылок и уменьшить его при выходе. А тут у нас еще две строки. Поэтому для простого сравнения на равенство тут явно излишняя работа. Можно ли её избежать? Конечно, мы ведь знаем, что не собираемся модифицировать строки, поэтому можно обозначить их как const и тогда компилятор не будет увеличивать счётчики ссылок, ведь он понимает, что мы не собираемся строку менять:
    Код (Text):
    1.  
    2. function StrCompare(const s1, s2: string): boolean;
    3. begin
    4.   Result := s1 = s2;
    5. end;
    6.  
    Барабанная дробь и асмовыхлоп превращается в: https://godbolt.org/z/7de4qYbhe
    Код (Text):
    1.  
    2. strcompare(ansistring,ansistring):
    3.         leaq    -8(%rsp),%rsp
    4.         call    fpc_ansistr_compare_equal
    5.         testq   %rax,%rax
    6.         seteb   %al
    7.         leaq    8(%rsp),%rsp
    8.         ret
    9.  
    А это довольно таки заметная экономия, хотя код и не вылизан в полный идеал, но всё-же несколько ненужных инструкций мы выкинули.
    Но в целом, конечно, мерять функцию которая является лишь обёрткой для вызова другой функции это понятно, что будет проигрывать вызову сразу нужной - для FPC можно было бы сразу вызывать
    fpc_ansistr_compare_equal, а как в Delphi не знаю.
    --- Сообщение объединено, 28 ноя 2023 ---
    P.S.
    Разобрало всё-таки любопытство - как в Delphi XE реализован <> для строк? И получается что вот так:
    Код (Text):
    1.  
    2. function SameStr(const S1, S2: string): Boolean;
    3. asm //StackAligned
    4.   CMP  EAX,EDX
    5.   JZ  @1
    6.   OR  EAX,EAX
    7.   JZ  @2
    8.   OR  EDX,EDX
    9.   JZ  @3
    10.   MOV  ECX,[EAX-4]
    11.   CMP  ECX,[EDX-4]
    12.   JNE  @3
    13.  
    14. {$IFDEF ALIGN_STACK}
    15.   SUB  ESP, 12
    16. {$ENDIF ALIGN_STACK}
    17.   CALL  CompareStr
    18. {$IFDEF ALIGN_STACK}
    19.   ADD  ESP, 12
    20. {$ENDIF ALIGN_STACK}
    21.   TEST  EAX,EAX
    22.   JNZ  @3
    23. @1:  MOV  AL,1
    24. @2:  RET
    25. @3:  XOR  EAX,EAX
    26. end;
    27. [code]
    28. Сперва сравнивается равны ли сами указатели строк и если да, то возвращаем True. Это интересный ход, т.к. под него сразу же попадает и вариант когда обе строки пусты. Но с другой стороны чаще всего мы всё-таки в реальном коде сравниваем разные строки так что я думаю в реальных случаях это больше замедление, чем ускорение.
    29. Далее уже проверяется не нулевые ли указатели сперва первая, а потом вторая строка - оба случая приводят к возврату False, т.к. мы уже знаем, что строки не равны указателями, значит одна из них точно не nil.
    30. В следующем шаге сравниваются длины и опять таки делается выход с False если они не равны.
    31. Но вот дальше не сравниваются уже буфера напрямую, а вызывается более тяжёлая функция CompareStr (она определяет не только равенство/неравенство, но и какая из строк больше возвращая -1, 0 или 1).
    32. Однако если хоть чуть-чуть взглянуть на тело CompareStr то становится понятно почему тут запускаются такие тяжёлые вещи - всё дело в том, что есть еще локали и CompareStr знает о локалях и может вызывать далее разные штуки в зависимости от текущей локали, т.е. тут реально если мы знаем, что работаем с AnsiString и локали нам нахрен не нужны, то можно добиться заметного ускорения через rep cmpsb.
     
  5. Win32Api

    Win32Api Member

    Публикаций:
    0
    Регистрация:
    16 окт 2022
    Сообщения:
    109
    aa_dav, если на одних и тех же данных закоментировать и раскоментировать эти 3 строки, будет ускорение
    Код (Text):
    1.   //mov al, [esi]
    2.   //cmp al, [edi]
    3.   //jne @Exit
    Если конечно мы не сравниваем пути к файлам, хотя и там наверно проще проверить длину строки, чем перебирать через rep cmpsb.

    На одном и том же файле получилось 35 и 37 секунд :)
    --- Сообщение объединено, 28 ноя 2023 ---
    Если добавить const - 41 секунда
    Код (Text):
    1. function StrCompare(const S1, S2: string): bool;
    Без const - 74 ! :)
    Если на асме, - 35 сек
    --- Сообщение объединено, 28 ноя 2023 ---
    Если учитывать тесты выполнения lstrcmp, получается забавно, я до этого часто юзал:
    Код (Text):
    1. procedure EnumFiles(Path: String; var Files: TStrings);
    2. var
    3.   h: Cardinal;
    4.   sc: _WIN32_FIND_DATA;
    5. begin
    6.   h := FindFirstFile(PChar(PathAppend(Path, '*.*')), sc);
    7.   if h <> INVALID_HANDLE_VALUE then
    8.   repeat
    9.     if (lstrcmp(sc.cFileName, '.') = 0) or
    10.        (lstrcmp(sc.cFileName, '..') = 0) then Continue;
    11.     begin
    12.       Files.Add(PathAppend(Path, sc.cFileName));
    13.     end;
    14.   until not FindNextFile(h, sc);
    15.   Windows.FindClose(h);
    16. end;
     
    Последнее редактирование: 28 ноя 2023
  6. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    462
    Ха, вроде бы оказывается Delphi для оператора = вызывает не SameStr, а следующую функцию из system.pas:
    Код (Text):
    1.  
    2. {$ELSE X86ASMRTL}
    3. {Original code by Pierre le Riche. Licensed under the CodeGear license terms.}
    4. procedure _LStrEqual{const Left, Right: _AnsiStr};
    5. asm
    6.   {On entry:
    7.   eax = @Left[1]
    8.   edx = @Right[1]
    9.   On exit:
    10.   Result in flags:
    11.   ZF = 1 if Left = Right, ZF = 0 otherwise}
    12.  
    13.   CMP  EAX, EDX  //Do Left and Right point to the same string data?
    14.   JE  @CompareDoneNoPop
    15.  
    16.   TEST  EAX, EDX  //Is one of the two string pointers perhaps nil?
    17.   JZ  @PossibleNilString
    18. @BothStringsNonNil:
    19.   MOV  ECX, [EAX - 4] //Compare lengths
    20.   CMP  ECX, [EDX - 4]
    21.   JNE  @CompareDoneNoPop
    22.  
    23.   PUSH  EBX  // Save ebx
    24.   {Get pointers to the 4th last bytes in the strings}
    25.   LEA  EDX, [EDX + ECX - 4]
    26.   LEA  EBX, [EAX + ECX - 4]
    27.   NEG  ECX  // Negate the loop counter
    28.   {Compare the last four bytes. If the string length is less
    29.   than four bytes then part of the length field is compared
    30.   again - no harm done.}
    31.   MOV  EAX, [EBX]
    32.   CMP  EAX, [EDX]
    33.   JNE  @CompareDonePop
    34. @CompareLoop:
    35.   ADD  ECX, 4 // Next four bytes
    36.   JNS  @Match
    37.   {Compare four bytes per iteration}
    38.   MOV  EAX, [EBX + ECX]
    39.   CMP  EAX, [EDX + ECX]
    40.   JE  @CompareLoop
    41. @CompareDonePop:
    42.   POP  EBX
    43. @CompareDoneNoPop:
    44.   RET
    45. @Match:
    46.   XOR  EAX, EAX // Strings match - set the zero flag
    47.   POP  EBX
    48.   RET
    49. @PossibleNilString:
    50.   {There is a good probability that one of the strings are nil
    51.   (but not both)}
    52.   TEST  EAX, EAX
    53.   JZ  @FirstStringNil
    54.   TEST  EDX, EDX
    55.   JNZ  @BothStringsNonNil
    56.   {Right is nil - compare lengths of the strings}
    57.   CMP  [EAX - 4], EDX
    58.   RET
    59. @FirstStringNil:
    60.   {Left is nil - compare lengths of the strings}
    61.   CMP  EAX, [EDX - 4]
    62.   RET
    63. end;
    64. {$ENDIF X86ASMRTL}
    65.  
    По крайней мере такой вид она имеет по умолчанию в Delphi.10.3.3.
    Так вот это на самом деле весьма эффективная штука - сперва он проверяет указатели на равенство и сразу уходит на True если это так.
    Потом он проверяет наложение указателей по AND и если получился 0, то уходит на особую ветку "одна из строк точно не nil, но вторая скорее всего nil".
    Далее сравнивает длины.
    А вот когда доходит до сравнения тел, то делает довольно забавный финт - она начинает сравнивать DWORD-ами с конца строк в начало. Т.е. сразу обнаружит несовпадение если отличаются концы строк.
    При этом все шаги из конца в начало осуществляются всегда полными DWORD-ами и возникает вопрос - а что если размер строки не кратен DWORD-у?
    А это значит, что последнее сравнение может залезть вплоть до 3-х байт в память где лежат длины строк. Но мы сюда попали уже зная, что длины строк равны, поэтому никакой проблемы нет. :lol:
    Гениально даже.
     
  7. Win32Api

    Win32Api Member

    Публикаций:
    0
    Регистрация:
    16 окт 2022
    Сообщения:
    109
    Из за этого счетчика ссылок, иногда появляются трудно-отлавливаемые баги.
    По коду все ок, но с некоторыми данными программы вываливаются
     
  8. lightgreen

    lightgreen New Member

    Публикаций:
    0
    Регистрация:
    29 ноя 2023
    Сообщения:
    1
    А почему нельзя просто скомпилировать код и посмотреть готовый асм?
    Или вы устроились на работу компилятором?
     
  9. Win32Api

    Win32Api Member

    Публикаций:
    0
    Регистрация:
    16 окт 2022
    Сообщения:
    109
    Да. Чтобы не смотреть скомпилированный асм, я устроился на работу компилятором)