Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/delphi/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Delphi 某些CPU上的ADC/SBB和INC/DEC在紧循环中存在问题_Delphi_Assembly_X86_Cpu Architecture_Bigint - Fatal编程技术网

Delphi 某些CPU上的ADC/SBB和INC/DEC在紧循环中存在问题

Delphi 某些CPU上的ADC/SBB和INC/DEC在紧循环中存在问题,delphi,assembly,x86,cpu-architecture,bigint,Delphi,Assembly,X86,Cpu Architecture,Bigint,我正在用Delphi编写一个简单的BigInteger类型。它主要由TLimb的动态数组(其中TLimb是32位无符号整数)和32位大小字段组成,该字段还保存BigInteger的符号位 为了添加两个BigInteger,我创建了一个大小合适的新BigInteger,然后在簿记之后,调用以下过程,将三个指针分别传递给左操作数和右操作数的数组开始和结果,以及左操作数和右操作数的分支数 普通代码: 这段代码运行得很好,我对它非常满意,直到我注意到,在我的开发设置(iMac上的Parallels VM

我正在用Delphi编写一个简单的BigInteger类型。它主要由TLimb的动态数组(其中TLimb是32位无符号整数)和32位大小字段组成,该字段还保存BigInteger的符号位

为了添加两个BigInteger,我创建了一个大小合适的新BigInteger,然后在簿记之后,调用以下过程,将三个指针分别传递给左操作数和右操作数的数组开始和结果,以及左操作数和右操作数的分支数

普通代码:

这段代码运行得很好,我对它非常满意,直到我注意到,在我的开发设置(iMac上的Parallels VM中的Win7)上,一个简单的纯PASCAL加法例程,在用一个变量和几个
if
子句模拟进位的同时也做了同样的操作,比我的普通程序快,简单的手工汇编程序

我花了一段时间才发现,在某些CPU(包括我的iMac和旧笔记本电脑)上,
DEC
INC
ADC
SBB
的组合可能非常慢。但在我的大多数其他电脑上(我还有五台电脑要测试,尽管其中四台完全相同),速度相当快

因此,我编写了一个新版本,使用
LEA
JECXZ
模拟
INC
DEC
,如下所示:

模拟代码的一部分:

这使得我在“慢”机器上的代码速度几乎是“快”机器上的三倍,但在“快”机器上大约慢了20%。所以现在,作为初始化代码,我做了一个简单的定时循环,并用它来决定是将单元设置为调用普通例程还是模拟例程。这几乎总是正确的,但有时它选择(较慢的)普通例程,而它本应选择模拟例程

但我不知道这是否是最好的方法

问题: 我给出了我的解决方案,但是这里的asm专家是否知道一种更好的方法来避免某些CPU上的速度慢

更新 彼得和尼尔斯的回答帮助我走上了正确的道路。这是我针对
DEC
版本的最终解决方案的主要部分:

普通代码:

我删除了很多空白,我想读者可以完成剩下的程序。它类似于主回路。大整数的速度提高约20%,小整数的速度提高约10%(只有几个肢体)

64位版本现在在可能的情况下使用64位加法(在主循环中以及在Main3和Main2中,它们不是像上面那样的“失败”),在此之前,64位比32位慢很多,但现在它比32位快30%,比原来的简单64位循环快两倍

更新2 英特尔在其《英特尔64和IA-32体系结构优化参考手册》中提出3.5.2.6部分标志寄存器暂停——示例3-29:

标志保存在
AL
中,并通过
EAX
中的
MOVZX
保存。它是通过循环中的第一个
ADD
添加的。然后需要一个
ADC
,因为
ADD
可能会生成进位。另见评论


因为进位保存在
EAX
中,所以我还可以使用
ADD
来更新指针。循环中的第一个
ADD
也会更新所有标志,因此
ADC
不会出现部分标志寄存器暂停。

有太多的x86芯片在使用中,它们的时间相差悬殊,因此您不可能为所有这些芯片都提供最佳的代码。您在使用之前拥有两个已知的好函数和基准的方法已经相当先进了

但是,根据大整数的大小,您可能可以通过简单的循环展开来改进代码。这将大大减少循环开销

例如,您可以执行一个专门的块,该块将八个整数相加,如下所示:

@AddEight:
        MOV     EAX,[ESI + EDX*CLimbSize + 0*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 0*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 0*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 1*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 1*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 1*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 2*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 2*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 2*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 3*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 3*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 3*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 4*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 4*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 4*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 5*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 5*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 5*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 6*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 6*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 6*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 7*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 7*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 7*CLimbSize],EAX
        LEA     ECX,[ECX - 8]
现在重建循环,只要有8个以上的元素要处理,就执行上面的块,并使用已有的单元素添加循环执行剩余的几个元素

对于大型咬人者,你将把大部分时间花在展开部分,现在应该执行得更快

如果您想要更快,那么再写七个专用于剩余元素计数的附加块,并基于元素计数分支到它们。最好将这七个地址存储在一个查找表中,从中加载地址并直接跳转到专用代码中


对于小元素计数,这将完全删除整个循环,对于大元素,您将获得展开循环的全部好处。

您在旧P6系列CPU上看到的是部分标志暂停。
早期的Sandybridge家族更有效地处理合并,而后来的SnB家族(如Skylake)根本没有合并成本:

英特尔CPU(P4除外)分别重命名每个标志位,因此
JNE
仅取决于设置其使用的所有标志的最后一条指令(在本例中,仅限
Z
标志)。事实上,最近的英特尔CPU甚至可以(宏融合)。但是,当读取上次更新任何标志的指令未修改的标志位时,就会出现问题

表示英特尔CPU(甚至PPro/PII)不会在
inc/jnz
上暂停。实际上不是
inc/jnz
停止,而是下一次迭代中的
adc
必须在
inc
写入其他标志后读取
CF
标志,但未修改
CF

; Example 5.21. Partial flags stall when reading unmodified flag bits
cmp eax, ebx
inc ecx
jc xx
; Partial flags stall  (P6 / PIII / PM / Core2 / Nehalem)
Agner Fog还更笼统地说:“避免使用依赖INC或DEC保持进位标志不变的代码。”。完全避免
inc
/
dec
的建议已经过时,只适用于P4。其他CPU分别重命名EFLAG的不同部分,并且只有troubl
class procedure BigInteger.PlainAdd(Left, Right, Result: PLimb; LSize, RSize: Integer);
asm
        PUSH    ESI
        PUSH    EDI
        PUSH    EBX
        MOV     ESI,EAX                         // Left
        MOV     EDI,EDX                         // Right
        MOV     EBX,ECX                         // Result
        MOV     ECX,RSize
        MOV     EDX,LSize
        CMP     EDX,ECX
        JAE     @SkipSwap
        XCHG    ECX,EDX
        XCHG    ESI,EDI
@SkipSwap:
        SUB     EDX,ECX
        PUSH    EDX
        XOR     EDX,EDX
        XOR     EAX,EAX
        MOV     EDX,ECX
        AND     EDX,$00000003
        SHR     ECX,2
        CLC
        JE      @MainTail
@MainLoop:
        // Unrolled 4 times. More times will not improve speed anymore.
        MOV     EAX,[ESI]
        ADC     EAX,[EDI]
        MOV     [EBX],EAX
        MOV     EAX,[ESI + CLimbSize]
        ADC     EAX,[EDI + CLimbSize]
        MOV     [EBX + CLimbSize],EAX
        MOV     EAX,[ESI + 2*CLimbSize]
        ADC     EAX,[EDI + 2*CLimbSize]
        MOV     [EBX + 2*CLimbSize],EAX
        MOV     EAX,[ESI + 3*CLimbSize]
        ADC     EAX,[EDI + 3*CLimbSize]
        MOV     [EBX + 3*CLimbSize],EAX
        // Update pointers.
        LEA     ESI,[ESI + 4*CLimbSize]
        LEA     EDI,[EDI + 4*CLimbSize]
        LEA     EBX,[EBX + 4*CLimbSize]
        // Update counter and loop if required.
        DEC     ECX                             
        JNE     @MainLoop
@MainTail:
        // Add index*CLimbSize so @MainX branches can fall through.
        LEA     ESI,[ESI + EDX*CLimbSize]
        LEA     EDI,[EDI + EDX*CLimbSize]
        LEA     EBX,[EBX + EDX*CLimbSize]
        // Indexed jump.
        LEA     ECX,[@JumpsMain]
        JMP     [ECX + EDX*TYPE Pointer]
        // Align jump table manually, with NOPs. Update if necessary.
        NOP
// Jump table.
@JumpsMain:
        DD      @DoRestLoop
        DD      @Main1
        DD      @Main2
        DD      @Main3
@Main3:
        MOV     EAX,[ESI - 3*CLimbSize]
        ADC     EAX,[EDI - 3*CLimbSize]
        MOV     [EBX - 3*CLimbSize],EAX
@Main2:
        MOV     EAX,[ESI - 2*CLimbSize]
        ADC     EAX,[EDI - 2*CLimbSize]
        MOV     [EBX - 2*CLimbSize],EAX
@Main1:
        MOV     EAX,[ESI - CLimbSize]
        ADC     EAX,[EDI - CLimbSize]
        MOV     [EBX - CLimbSize],EAX
@DoRestLoop:

// etc...    
        XOR     EAX,EAX

        .ALIGN  16

@MainLoop:

        ADD     EAX,[ESI]       // Sets all flags, so no partial flag register stall
        ADC     EAX,[EDI]       // ADD added in previous carry, so its result might have carry
        MOV     [EBX],EAX
        MOV     EAX,[ESI + CLimbSize]
        ADC     EAX,[EDI + CLimbSize]
        MOV     [EBX + CLimbSize],EAX
        MOV     EAX,[ESI + 2*CLimbSize]
        ADC     EAX,[EDI + 2*CLimbSize]
        MOV     [EBX + 2*CLimbSize],EAX
        MOV     EAX,[ESI + 3*CLimbSize]
        ADC     EAX,[EDI + 3*CLimbSize]
        MOV     [EBX + 3*CLimbSize],EAX
        SETC    AL              // Save carry for next iteration
        MOVZX   EAX,AL
        ADD     ESI,CUnrollIncrement*CLimbSize  // LEA has slightly worse latency
        ADD     EDI,CUnrollIncrement*CLimbSize
        ADD     EBX,CUnrollIncrement*CLimbSize
        DEC     ECX
        JNZ     @MainLoop
@AddEight:
        MOV     EAX,[ESI + EDX*CLimbSize + 0*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 0*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 0*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 1*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 1*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 1*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 2*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 2*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 2*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 3*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 3*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 3*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 4*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 4*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 4*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 5*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 5*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 5*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 6*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 6*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 6*CLimbSize],EAX
        MOV     EAX,[ESI + EDX*CLimbSize + 7*CLimbSize]
        ADC     EAX,[EDI + EDX*CLimbSize + 7*CLimbSize]
        MOV     [EBX + EDX*CLimbSize + 7*CLimbSize],EAX
        LEA     ECX,[ECX - 8]
; Example 5.21. Partial flags stall when reading unmodified flag bits
cmp eax, ebx
inc ecx
jc xx
; Partial flags stall  (P6 / PIII / PM / Core2 / Nehalem)
        ; pure loads are always one uop, so we can still index it
        ; with no perf hit on SnB
        add     esi, ecx   ; point to end of src1
        neg     ecx

UNROLL equ 4
@MainLoop:
        MOV     EAX, [ESI + 0*CLimbSize + ECX*CLimbSize]
        ADC     EAX, [EDI + 0*CLimbSize]
        MOV     [EBX + 0*CLimbSize], EAX

        MOV     EAX, [ESI + 1*CLimbSize + ECX*CLimbSize]
        ADC     EAX, [EDI + 1*CLimbSize]
        MOV     [EBX + 1*CLimbSize], EAX

        ; ... repeated UNROLL times.  Use an assembler macro to repeat these 3 instructions with increasing offsets

        LEA     ECX, [ECX+UNROLL] ; loop counter

        LEA     EDI, [EDI+ClimbSize*UNROLL]  ; Unrolling makes it worth doing
        LEA     EBX, [EBX+ClimbSize*UNROLL]  ; a separate increment to save a uop for every ADC and store on SnB & later.

        JECXZ   @DoRestLoop                     // LEA does not modify Zero flag, so JECXZ is used.
        JMP     @MainLoop
@DoRestLoop:
lahf
# clobber flags
sahf              ; cheap on AMD and Intel.  This doesn't restore OF, but we only care about CF

# or

setc al
# clobber flags
add  al, 255      ; generate a carry if al is non-zero