相关疑难解决方法(0)

用于测试Collat​​z猜想的C++代码比手写程序集更快 - 为什么?

我为Project Euler Q14编写了这两个解决方案,在汇编和C++中.它们是用于测试Collat​​z猜想的相同蛮力方法.装配解决方案与组装

nasm -felf64 p14.asm && gcc p14.o -o p14
Run Code Online (Sandbox Code Playgroud)

C++是用.编译的

g++ p14.cpp -o p14
Run Code Online (Sandbox Code Playgroud)

部件, p14.asm

section .data
    fmt db "%d", 10, 0

global main
extern printf

section .text

main:
    mov rcx, 1000000
    xor rdi, rdi        ; max i
    xor rsi, rsi        ; i

l1:
    dec rcx
    xor r10, r10        ; count
    mov rax, rcx

l2:
    test rax, 1
    jpe even

    mov rbx, 3
    mul rbx
    inc rax
    jmp c1

even:
    mov rbx, 2 …
Run Code Online (Sandbox Code Playgroud)

c++ optimization performance x86 assembly

803
推荐指数
8
解决办法
14万
查看次数

为什么引入无用的MOV指令会加速x86_64汇编中的紧凑循环?

背景:

在使用嵌入式汇编语言优化某些Pascal代码时,我注意到了一条不必要的MOV指令,并将其删除.

令我惊讶的是,删除不必要的指令会导致我的程序变慢.

我发现添加任意无用的MOV指令可以进一步提高性能.

效果不稳定,并且基于执行顺序进行更改:相同的垃圾指令向上或向下移动一行会产生减速.

我知道CPU会进行各种优化和精简,但这看起来更像是黑魔法.

数据:

我的代码版本有条件地在运行时间的循环中编译三个垃圾操作2**20==1048576.(周围的程序只计算SHA-256哈希值).

在我相当老的机器(英特尔(R)Core(TM)2 CPU 6400 @ 2.13 GHz)上的结果:

avg time (ms) with -dJUNKOPS: 1822.84 ms
avg time (ms) without:        1836.44 ms
Run Code Online (Sandbox Code Playgroud)

程序在循环中运行25次,每次运行顺序随机变化.

摘抄:

{$asmmode intel}
procedure example_junkop_in_sha256;
  var s1, t2 : uint32;
  begin
    // Here are parts of the SHA-256 algorithm, in Pascal:
    // s0 {r10d} := ror(a, 2) xor ror(a, 13) xor …
Run Code Online (Sandbox Code Playgroud)

optimization performance assembly freepascal x86-64

217
推荐指数
3
解决办法
3万
查看次数

为什么英特尔在其处理器中隐藏内部RISC内核?

从Pentium Pro(P6微体系结构)开始,英特尔重新设计了它的微处理器,并在旧的CISC指令下使用了内部RISC内核.由于Pentium Pro所有CISC指令都分为较小的部分(uops),然后由RISC内核执行.

一开始我很清楚英特尔决定隐藏新的内部架构并强迫程序员使用"CISC shell".由于这一决定,英特尔可以在不破坏兼容性的情况下完全重新设计微处理器架构,这是合理的.

但是我不明白一件事,为什么英特尔仍然保留了多年内隐藏的内部RISC指令集?为什么他们不让程序员使用RISC指令,比如使用旧的x86 CISC指令集?

如果英特尔长期保持向后兼容性(我们仍然在64位模式旁边有虚拟8086模式),为什么它们不允许我们编译程序以便它们绕过CISC指令并直接使用RISC核心?这将开启自然的方式来慢慢放弃x86指令集,现在已弃用(这是英特尔决定在内部使用RISC核心的主要原因,对吧?).

看看新的英特尔'酷睿i'系列,我看到,他们只扩展了CISC指令集,增加了AVX,SSE4等.

x86 intel cpu-architecture

85
推荐指数
5
解决办法
4万
查看次数

浮点除法与浮点乘法

通过编码是否有任何(非微优化)性能增益

float f1 = 200f / 2
Run Code Online (Sandbox Code Playgroud)

在比较中

float f2 = 200f * 0.5
Run Code Online (Sandbox Code Playgroud)

几年前我的一位教授告诉我,浮点除法比浮点乘法慢,但没有详细说明原因.

这句话适用于现代PC架构吗?

UPDATE1

关于评论,请同时考虑这个案例:

float f1;
float f2 = 2
float f3 = 3;
for( i =0 ; i < 1e8; i++)
{
  f1 = (i * f2 + i / f3) * 0.5; //or divide by 2.0f, respectively
}
Run Code Online (Sandbox Code Playgroud)

更新2 从评论中引用:

[我想]知道什么是算法/架构要求导致>除法在硬件上比复制要复杂得多

c++ floating-point micro-optimization

67
推荐指数
5
解决办法
5万
查看次数

为什么循环指令慢?英特尔无法有效实施吗?

LOOP(英特尔参考手动输入)递减ecx/rcx,然后如果非零则跳转.这很慢,但是英特尔不能廉价地把它变得很快吗? dec/jnz已经将宏观融合成 Sandybridge家族的一个 uop; 唯一的区别是设置标志.

loop关于各种微体系结构,来自Agner Fog的说明表:

  • K8/K10:7 m-ops
  • Bulldozer-family/Ryzen:1 m-op(与宏观融合测试和分支相同,或者jecxz)

  • P4:4次(相同jecxz)

  • P6(PII/PIII):8次
  • Pentium M,Core2:11 uops
  • Nehalem:6个uops.(11为loope/ loopne).吞吐量= 4c(loop)或7c(loope/ne).
  • SnB家族:7个uops.(11为loope/ loopne). 吞吐量=每5个循环一个,这是将循环计数器保留在内存中的瓶颈!jecxz只有2 uops,吞吐量与普通吞吐量相同jcc
  • Silvermont:7次
  • AMD Jaguar(低功耗):8 uops,5c吞吐量
  • 通过Nano3000:2 uops

难道解码器不能像lea rcx, [rcx-1]/ 那样解码jrcxz吗?这将是3 uops.至少那是没有地址大小前缀的情况,否则它必须使用ecx和截断RIP,EIP如果跳转; 也许奇怪的地址大小选择控制减量的宽度解释了许多uops?

或者更好,只需将其解码为不设置标志的融合分支和分支? dec ecx …

performance x86 assembly intel cpu-architecture

53
推荐指数
3
解决办法
6096
查看次数

微融合和寻址模式

我使用英特尔®架构代码分析器(IACA)发现了一些意想不到的东西(对我而言).

以下指令使用[base+index]寻址

addps xmm1, xmmword ptr [rsi+rax*1]
Run Code Online (Sandbox Code Playgroud)

根据IACA没有微熔丝.但是,如果我用[base+offset]这样的

addps xmm1, xmmword ptr [rsi]
Run Code Online (Sandbox Code Playgroud)

IACA报告它确实融合了.

英特尔优化参考手册的第2-11节给出了以下"可以由所有解码器处理的微融合微操作"的示例

FADD DOUBLE PTR [RDI + RSI*8]
Run Code Online (Sandbox Code Playgroud)

Agner Fog的优化装配手册也给出了使用[base+index]寻址的微操作融合的例子.例如,请参见第12.2节"Core2上的相同示例".那么正确的答案是什么?

cpu x86 assembly intel iaca

44
推荐指数
4
解决办法
4504
查看次数

x86-64汇编的性能优化 - 对齐和分支预测

我目前编码的一些C99标准库字符串函数高度优化的版本,例如strlen(),memset()等等,采用x86-64的组件,SSE-2指令.

到目前为止,我已经在性能方面取得了很好的成绩,但是当我尝试优化更多时,我有时会遇到奇怪的行为.

例如,添加或甚至删除一些简单的指令,或者只是重新组织一些用于跳转的本地标签会完全降低整体性能.在代码方面绝对没有理由.

所以我的猜测是代码对齐存在一些问题,和/或有错误预测的分支.

我知道,即使使用相同的架构(x86-64),不同的CPU也有不同的分支预测算法.

但是,在开发x86-64的高性能时,是否存在一些关于代码对齐和分支预测的一般建议?

特别是关于对齐,我应该确保跳转指令使用的所有标签都在DWORD上对齐吗?

_func:
    ; ... Some code ...
    test rax, rax
    jz   .label
    ; ... Some code ...
    ret
    .label:
        ; ... Some code ...
        ret
Run Code Online (Sandbox Code Playgroud)

在前面的代码中,我之前应该使用align指令.label:,例如:

align 4
.label:
Run Code Online (Sandbox Code Playgroud)

如果是这样,使用SSE-2时是否足以对齐DWORD?

关于分支预测,是否有一种"优先"的方式来组织跳转指令使用的标签,以帮助CPU,或者今天的CPU是否足够聪明,可以通过计算分支的计数来确定在运行时?

编辑

好的,这是一个具体的例子 - 这是strlen()SSE-2 的开始:

_strlen64_sse2:
    mov         rsi,    rdi
    and         rdi,    -16
    pxor        xmm0,   xmm0
    pcmpeqb     xmm0,   [ rdi ]
    pmovmskb    rdx,    xmm0
    ; ...
Run Code Online (Sandbox Code Playgroud)

使用1000个字符串运行10'000'000次约为0.48秒,这很好.
但它不会检查NULL字符串输入.显然,我会添加一个简单的检查:

_strlen64_sse2:
    test       rdi,    rdi
    jz          .null
    ; ... …
Run Code Online (Sandbox Code Playgroud)

performance assembly x86-64 sse2 branch-prediction

27
推荐指数
3
解决办法
5817
查看次数

REP做什么设置?

引用英特尔 ®64 和IA-32架构优化参考手册,§2.4.6"REP String Enhancement":

使用REP字符串的性能特征可归因于两个组件: 启动开销和数据传输吞吐量.

[...]

对于较大粒度数据传输的REP字符串,随着ECX值的增加,REP String的启动开销呈逐步增加:

  • 短串(ECX <= 12):REP MOVSW/MOVSD/MOVSQ的延迟约为20个周期,
  • 快速字符串(ECX> = 76:不包括REP MOVSB):处理器实现通过移动尽可能多的16字节数据来提供硬件优化.如果其中一个16字节数据传输跨越缓存行边界,则REP字符串延迟的延迟会有所不同:

    • 无拆分:延迟包括大约40个周期启动成本,每个64字节的数据增加4个周期,
    • 高速缓存拆分:延迟包括大约35个周期启动成本,每64个字节的数据增加6个周期.
  • 中间字符串长度:REP MOVSW/MOVSD/MOVSQ的延迟具有大约15个周期启动成本加上word/dword/qword中数据移动的每次迭代的一个周期.

(强调我的)

没有进一步提及这种启动成本.它是什么?它做了什么,为什么总是需要更多的时间?

optimization performance x86 assembly

13
推荐指数
2
解决办法
1615
查看次数

为什么32字节的循环对齐使代码更快?

看看这段代码:

one.cpp:

bool test(int a, int b, int c, int d);

int main() {
        volatile int va = 1;
        volatile int vb = 2;
        volatile int vc = 3;
        volatile int vd = 4;

        int a = va;
        int b = vb;
        int c = vc;
        int d = vd;

        int s = 0;
        __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop");
        __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop");
        __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop");
        __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop");
        for (int i=0; i<2000000000; i++) {
                s += test(a, b, …
Run Code Online (Sandbox Code Playgroud)

performance benchmarking gcc x86-64 clang

12
推荐指数
1
解决办法
735
查看次数

无符号64位到双转换:为什么这个算法来自g ++

如果我编译,使用g ++ 4.9.2

bool int_dbl_com(const unsigned long long x, const double y)
{
    return x <= y;
}
Run Code Online (Sandbox Code Playgroud)

那么汇编程序输出(对于Windows x64调用约定)是:

testq     %rcx, %rcx            # x in RCX
js        .L2
pxor      %xmm0, %xmm0
cvtsi2sdq %rcx, %xmm0
ucomisd   %xmm0, %xmm1          # y in XMM1
setae     %al
ret
Run Code Online (Sandbox Code Playgroud)

该命令cvtsi2sdq签名转换,第一个测试和跳转组合是检查是否%rcx < 0.如果是这样,我们去L2,我不明白:

.L2:
movq       %rcx, %rax
andl       $1, %ecx
pxor       %xmm0, %xmm0
shrq       %rax
orq        %rcx, %rax
cvtsi2sdq  %rax, %xmm0
addsd      %xmm0, %xmm0
ucomisd    %xmm0, %xmm1
setae      %al …
Run Code Online (Sandbox Code Playgroud)

floating-point assembly g++ x86-64

11
推荐指数
1
解决办法
1559
查看次数