FillChar и StringOfChar в Delphi 10.2 для Win64 Release Target

У меня есть вопрос о конкретной проблеме программирования в языке программирования Delphi 10.2 Pascal.

StringOfChar и FillChar не работают должным образом в Win64 Release build на процессорах, выпущенных до 2012 года.

  • Ожидаемый результат FillChar - это просто последовательность повторяющихся 8-битных символов в данном буфере памяти.

  • Ожидаемый результат StringOfChar тот же, но результат сохраняется внутри строкового типа.

Но на самом деле, когда я компилирую наши приложения, которые работали в Delphi до 10.2 с помощью версии Delphi 10.2, наши приложения, скомпилированные для Win64, перестали нормально работать на процессорах, выпущенных до 2012 года.

StringOfChar и FillChar работают неправильно - они возвращают строку разных символов, хотя в повторяющемся шаблоне - не просто последовательность того же символа, что и они.

Вот минимальный код, достаточный для демонстрации проблемы. Обратите внимание, что длина последовательности должна быть не менее 16 символов, а символ не должен быть nul (# 0). Код ниже:

procedure TestStringOfChar;
var
  a: AnsiString;
  ac: AnsiChar;
begin
  ac := #1;
  a := StringOfChar(ac, 43);
  if a <> #1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1#1 then
  begin
    raise Exception.Create('ANSI StringOfChar Failed!!');
  end;
end;

Я знаю, что в StackOverflow есть много программистов Delphi. Вы испытываете ту же проблему? Если да, как вы его разрешаете? Каково решение? Кстати, Я связался с разработчиками Delphi, но они до сих пор не подтвердили и не отрицали эту проблему. Я использую Embarcadero Delphi 10.2 Version 25.0.26309.314.

Update:

Если ваш процессор выпущен в 2012 году или позже, дополнительно включите следующие строки перед вызовом StringOfChar, чтобы воспроизвести проблему:

const
  ERMSBBit    = 1 shl 9; //$0200
begin
  CPUIDTable[7].EBX := CPUIDTable[7].EBX and not ERMSBBit;

Как о Апрель 2017 RAD Studio 10.2 Исправление для проблем с инструментами - пробовал с ним и без него - это не помогло. Проблема существует независимо от исправления.

Ответы

Ответ 1

StringOfChar(A: AnsiChar, count) использует FillChar под капотом.

Для устранения проблемы можно использовать следующий код:

(*******************************************************
 System.FastSystem
 A fast drop-in addition to speed up function in system.pas
 It should compile and run in XE2 and beyond.
 Alpha version 0.5, fully tested in Win64
 (c) Copyright 2016 J. Bontes
   This Source Code Form is subject to the terms of the
   Mozilla Public License, v. 2.0.
   If a copy of the MPL was not distributed with this file,
   You can obtain one at http://mozilla.org/MPL/2.0/.
********************************************************
FillChar code is an altered version FillCharsse2 SynCommons.pas
which is part of Synopse framework by Arnaud Bouchez
********************************************************
Changelog
0.5 Initial version:
********************************************************)

unit FastSystem;

interface

procedure FillChar(var Dest; Count: NativeInt; Value: ansichar); inline; overload;
procedure FillChar(var Dest; Count: NativeInt; Value: Byte); overload;
procedure FillMemory(Destination: Pointer; Length: NativeUInt; Fill: Byte); inline;
{$EXTERNALSYM FillMemory}
procedure ZeroMemory(Destination: Pointer; Length: NativeUInt); inline;
{$EXTERNALSYM ZeroMemory}

implementation

procedure FillChar(var Dest; Count: NativeInt; Value: ansichar); inline; overload;
begin
  FillChar(Dest, Count, byte(Value));
end;

procedure FillMemory(Destination: Pointer; Length: NativeUInt; Fill: Byte);
begin
  FillChar(Destination^, Length, Fill);
end;

procedure ZeroMemory(Destination: Pointer; Length: NativeUInt); inline;
begin
  FillChar(Destination^, Length, 0);
end;

//This code is 3x faster than System.FillChar on x64.

{$ifdef CPUX64}
procedure FillChar(var Dest; Count: NativeInt; Value: Byte);
//rcx = dest
//rdx=count
//r8b=value
asm
              .noframe
              .align 16
              movzx r8,r8b           //There no need to optimize for count <= 3
              mov rax,$0101010101010101
              mov r9d,edx
              imul rax,r8            //fill rax with value.
              cmp rdx,59             //Use simple code for small blocks.
              jl  @Below32
@Above32:     mov r11,rcx
              mov r8b,7              //code shrink to help alignment.
              lea r9,[rcx+rdx]       //r9=end of array
              sub rdx,8
              rep mov [rcx],rax
              add rcx,8
              and r11,r8             //and 7 See if dest is aligned
              jz @tail
@NotAligned:  xor rcx,r11            //align dest
              lea rdx,[rdx+r11]
@tail:        test r9,r8             //and 7 is tail aligned?
              jz @alignOK
@tailwrite:   mov [r9-8],rax         //no, we need to do a tail write
              and r9,r8              //and 7
              sub rdx,r9             //dec(count, tailcount)
@alignOK:     mov r10,rdx
              and edx,(32+16+8)      //count the partial iterations of the loop
              mov r8b,64             //code shrink to help alignment.
              mov r9,rdx
              jz @Initloop64
@partialloop: shr r9,1              //every instruction is 4 bytes
              lea r11,[rip + @partial +(4*7)] //start at the end of the loop
              sub r11,r9            //step back as needed
              add rcx,rdx            //add the partial loop count to dest
              cmp r10,r8             //do we need to do more loops?
              jmp r11                //do a partial loop
@Initloop64:  shr r10,6              //any work left?
              jz @done               //no, return
              mov rdx,r10
              shr r10,(19-6)         //use non-temporal move for > 512kb
              jnz @InitFillHuge
@Doloop64:    add rcx,r8
              dec edx
              mov [rcx-64+00H],rax
              mov [rcx-64+08H],rax
              mov [rcx-64+10H],rax
              mov [rcx-64+18H],rax
              mov [rcx-64+20H],rax
              mov [rcx-64+28H],rax
              mov [rcx-64+30H],rax
              mov [rcx-64+38H],rax
              jnz @DoLoop64
@done:        rep ret
              //db $66,$66,$0f,$1f,$44,$00,$00 //nop7
@partial:     mov [rcx-64+08H],rax
              mov [rcx-64+10H],rax
              mov [rcx-64+18H],rax
              mov [rcx-64+20H],rax
              mov [rcx-64+28H],rax
              mov [rcx-64+30H],rax
              mov [rcx-64+38H],rax
              jge @Initloop64        //are we done with all loops?
              rep ret
              db $0F,$1F,$40,$00
@InitFillHuge:
@FillHuge:    add rcx,r8
              dec rdx
              db $48,$0F,$C3,$41,$C0 // movnti  [rcx-64+00H],rax
              db $48,$0F,$C3,$41,$C8 // movnti  [rcx-64+08H],rax
              db $48,$0F,$C3,$41,$D0 // movnti  [rcx-64+10H],rax
              db $48,$0F,$C3,$41,$D8 // movnti  [rcx-64+18H],rax
              db $48,$0F,$C3,$41,$E0 // movnti  [rcx-64+20H],rax
              db $48,$0F,$C3,$41,$E8 // movnti  [rcx-64+28H],rax
              db $48,$0F,$C3,$41,$F0 // movnti  [rcx-64+30H],rax
              db $48,$0F,$C3,$41,$F8 // movnti  [rcx-64+38H],rax
              jnz @FillHuge
@donefillhuge:mfence
              rep ret
              db $0F,$1F,$44,$00,$00  //db $0F,$1F,$40,$00
@Below32:     and  r9d,not(3)
              jz @SizeIs3
@FillTail:    sub   edx,4
              lea   r10,[rip + @SmallFill + (15*4)]
              sub   r10,r9
              jmp   r10
@SmallFill:   rep mov [rcx+56], eax
              rep mov [rcx+52], eax
              rep mov [rcx+48], eax
              rep mov [rcx+44], eax
              rep mov [rcx+40], eax
              rep mov [rcx+36], eax
              rep mov [rcx+32], eax
              rep mov [rcx+28], eax
              rep mov [rcx+24], eax
              rep mov [rcx+20], eax
              rep mov [rcx+16], eax
              rep mov [rcx+12], eax
              rep mov [rcx+08], eax
              rep mov [rcx+04], eax
              mov [rcx],eax
@Fallthough:  mov [rcx+rdx],eax  //unaligned write to fix up tail
              rep ret

@SizeIs3:     shl edx,2           //r9 <= 3  r9*4
              lea r10,[rip + @do3 + (4*3)]
              sub r10,rdx
              jmp r10
@do3:         rep mov [rcx+2],al
@do2:         mov [rcx],ax
              ret
@do1:         mov [rcx],al
              rep ret
@do0:         rep ret
end;
{$endif}

Самый простой способ исправить вашу проблему - Загрузить Mormot и включить SynCommon.pas в ваш проект. Это приведет к исправлению System.FillChar к вышеуказанному коду и добавит еще пару других улучшений производительности.

Обратите внимание, что вам не нужен весь Mormot, просто SynCommons сам по себе.

Ответ 2

Я взял тестовый пример из задачи FastCode - http://fastcode.sourceforge.net/

Я скомпилировал инструмент тестирования FillChar под Win64 и удалил все 32-битные версии FillChar, присутствующие в тесте.

Я оставил 2 версии 64-битного FillChar:

  • FC_TokyoBugfixAVXEx - тот, что присутствует в Delphi Tokyo 64-bit, с исправленными ошибками и добавлены регистры AVX. Существует ветвление для обнаружения возможностей процессора ERMSB, AVX1 и AVX2. Это ветвление происходит при каждом вызове FillChar. Нет адресации точки доступа или отображения адреса функции.
  • FillChar_J_Bontes - еще одна версия FillChar, функция из System.FastSystem, которую вы разместили здесь.

Я не тестировал Vanilla FillChar из Delphi Tokyo, потому что он содержит ошибку, описанную в моем первоначальном сообщении, и он неправильно обрабатывает ERMSB.

Озеро Каби - i7-7700K

Результаты FillChar

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

CPU в первом тесте - это Kaby Lake i7-7700K.

Ivy Bridge - E5-2603 v2

Ниже приведены результаты второго теста на предыдущей микроархитектуре: Xeon E5-2603 v2 (Ivy Bridge), дата выпуска 10 сентября 2013 г., частота 1,8 ГГц, кэш L2 4 × 256 КБ, кеш L3 10 МБ, RAM 4 × DDR3-1333.

Результаты Xeon E5-2603 v2

Ivy Bridge - E5-2643 v2

Ниже приведены результаты тестирования третьего набора аппаратных средств: Intel Xeon E5-2643 v2, частота 3,5 ГГц, кэш L2 4 × 256 КБ, кэш L3 50 МБ, оперативная память 4 × DDR3-1600.

Результаты Xeon E5-2643 v2