Попробуем некоторые варианты и посмотрим, как они исполняются. Наиболее существенно является строка:
cmp al,[edx+ecx]
Она генерирует две микроинструкции. Одна для загрузки байта по адресу [EDX+ECX] и вторая для сравнения его со значением в AL. Данная строка может быть закодирована также как:
mov ah, byte ptr [edx+ecx]
cmp al, ah
Данный вариант также генерирует две микроинструкции, но это также требует и дополнительный регистр (AH).
Если мы готовы выделить лишний регистр, то это также можно сделать также как:
movzx efx, byte ptr [edx+ecx]
cmp al, fh
Инструкция MOVZX это пересылка с расширением нуля. Она загружает один байт в младшую часть регистра EFX и заполняет отставшие биты нулями. Конечно, нет такой вещи как регистр efx, но два неиспользуемых регистра ESI и EDI не могут быть доступны на байтовой основе. Поэтому если есть свободный регистр EAX, EBX, ECX или EDX, подставьте это место EDI или ESI и используйте подстановку EBX вместо "EFX".
Данная функция демонстрирует первый вариант.
Code: |
function CharPos30(Chr : Char; const Str : AnsiString) : Cardinal; asm push ebx //if StrLenght > 0 then test edx,edx jz @Else1Begin //StrLenght := Length(Str); mov ebx,[edx-$04] //I := 0; xor ecx,ecx dec edx @RepeatBegin : //Inc(I); inc ecx //until((Str[I] = Chr) or (I > StrLenght)); mov ah, [edx+ecx] //cmp al,[edx+ecx] cmp al,ah jz @Exit cmp ebx,ecx jnl @RepeatBegin @Else1Begin : //Result := 0; xor eax,eax pop ebx ret @Exit : mov eax,ecx pop ebx end; |
А эта функция демонстрирует второй вариант.
Code: |
function CharPos31(Chr : Char; const Str : AnsiString) : Cardinal; asm push ebx push edi //if StrLenght > 0 then test edx,edx jz @Else1Begin //StrLenght := Length(Str); mov edi,[edx-$04] //I := 0; xor ecx,ecx dec edx @RepeatBegin : //Inc(I); inc ecx //until((Str[I] = Chr) or (I > StrLenght)); movzx ebx, byte ptr [edx+ecx] //cmp al,[edx+ecx] cmp al, bl jz @Exit cmp edi,ecx jnl @RepeatBegin @Else1Begin : //Result := 0; xor eax,eax pop edi pop ebx ret @Exit : mov eax,ecx pop edi pop ebx end; |
Вместо сложения EDX и ECX при расчете адреса в каждой итерации, мы можем их сложить до цикла. Затем если необходимо вычитать их друг из друга для получения счетчика цикла при возврате результата. Это выполняется с помощью инструкции SUB во второй строке поле метки Exit.
Code: |
function CharPos32(Chr : Char; const Str : AnsiString) : Cardinal; asm push ebx push edi //if StrLenght > 0 then test edx,edx jz @Else1Begin //StrLenght := Length(Str); mov edi,[edx-$04] //I := 0; xor ecx,ecx //dec edx add ecx,edx @RepeatBegin : //Inc(I); //until((Str[I] = Chr) or (I > StrLenght)); movzx ebx, byte ptr [ecx] inc ecx cmp al, bl jz @Exit //cmp edi,ecx test bl, bl jnz @RepeatBegin @Else1Begin : //Result := 0; xor eax,eax pop edi pop ebx ret @Exit : mov eax,ecx sub eax,edx pop edi pop ebx end; |
Теперь у нас есть четыре функции для сравнения производительности: CharPos29, CharPos30, CharPos31 и CharPos32.
Результаты на P4 1920 следующие:
CharPos29 716
CharPos30 973
CharPos31 710
CharPos32 702
Победитель функция CharPos32
Результаты на P3 1400 следующие:
CharPos29 949
CharPos30 921
CharPos31 950
CharPos32 1403
Победитель функция CharPos30
Суммарное время
CharPos29 716 + 949 = 1665
CharPos30 973 + 921 = 1894
CharPos31 710 + 950 = 1660
CharPos32 702 + 1403 = 2105
Winner is CharPos31
На P4 выигрышный цикл следующий:
@RepeatBegin :
movzx ebx, byte ptr [ecx]
inc ecx
cmp al, bl
jz @Exit
test bl, bl
jnz @RepeatBegin
На P3 выигрышный цикл следующий:
@RepeatBegin :
inc ecx
mov ah, [edx+ecx]
cmp al,ah
jz @Exit
cmp ebx,ecx
jnl @RepeatBegin
При работе на обеих платформах выигрышный цикл следующий:
@RepeatBegin :
inc ecx
movzx ebx, byte ptr [edx+ecx]
cmp al, bl
jz @Exit
cmp edi,ecx
jnl @RepeatBegin
Победитель на P4 очень плох на P3 и не может быть использован в библиотеках на других платформах, кроме P4, таких как Delphi RTL. Победитель на P3 выполняется очень отвратительно на P4 и поэтому не должен быть использован в библиотеках для обеих платформ. Победитель для обеих платформ, это функция CharPos31, которая имеет результаты близкие к оптимальным для P4 и также достаточно оптимальные и для P3. Данная функция подходящий выбор для библиотек класса Delphi RTL. Это показывает, что можно оптимизировать функцию для обоих типов процессоров, без потери производительности не более, чем на несколько процентов.
Сравнение производительности P3 и P4 на основе такт-такт всегда немного спектакль. Появляется необоснованная тенденция думать, что P4 имеет as having an inferior design, но это не подтверждается нашим кодом. Взяв победителя для смешанной платформы и сделав приведение результата к 1400 MHz процессору, то мы получим 950 для P3 и 710 * (1920/1400) = 973 для P4. Производительность процессоров почти одинаковая при одинаковой частоте.
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!