标签: x86-64

为什么我的 Intel Skylake / Kaby Lake CPU 在简单的哈希表实现中会出现神秘的 3 倍速度下降?

简而言之:

我已经实现了一个简单的(多键)哈希表,其中包含完全适合缓存行的存储桶(包含多个元素)。插入缓存行存储桶非常简单,也是主循环的关键部分。

我已经实现了三个版本,它们产生相同的结果并且行为应该相同。

然而,尽管所有版本都具有完全相同的缓存行访问模式并产生相同的哈希表数据,但我发现性能差异惊人地大到了 3 倍。

与我的 CPU (i7-7700HQ)相比,最佳实现insert_ok速度慢了大约 3 倍。一个变体 insert_bad 是一种简单的修改,它在缓存行中添加了额外的不必要的线性搜索,以找到要写入的位置(它已经知道),并且不会遭受 3 倍的减速。insert_badinsert_altinsert_ok

与其他 CPU(AMD 5950X (Zen 3)、Intel i7-11800H (Tiger Lake))相比,完全相同的可执行文件显示insert_ok速度快 1.6 倍。insert_badinsert_alt

# see https://github.com/cr-marcstevens/hashtable_mystery
$ ./test.sh
model name      : Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
==============================
CXX=g++    CXXFLAGS=-std=c++11 -O2 -march=native -falign-functions=64
tablesize: 117440512 elements: 67108864 loadfactor=0.571429
- test insert_ok : 11200ms
- test insert_bad: 3164ms
  (outcome identical to insert_ok: true)
- test insert_alt: 3366ms
  (outcome identical …
Run Code Online (Sandbox Code Playgroud)

c++ performance gcc hashtable x86-64

79
推荐指数
1
解决办法
6022
查看次数

71
推荐指数
1
解决办法
6万
查看次数

x86_64寄存器rax/eax/ax/al覆盖完整寄存器内容

正如广泛宣传的那样,现代x86_64处理器具有64位寄存器,可以以向后兼容的方式用作32位寄存器,16位寄存器甚至8位寄存器,例如:

0x1122334455667788
  ================ rax (64 bits)
          ======== eax (32 bits)
              ====  ax (16 bits)
              ==    ah (8 bits)
                ==  al (8 bits)
Run Code Online (Sandbox Code Playgroud)

这样的方案可以从字面上理解,即,总是可以使用指定的名称仅访问寄存器的一部分用于读取或写入目的,并且这将是高度逻辑的.实际上,对于高达32位的所有内容都是如此:

mov  eax, 0x11112222 ; eax = 0x11112222
mov  ax, 0x3333      ; eax = 0x11113333 (works, only low 16 bits changed)
mov  al, 0x44        ; eax = 0x11113344 (works, only low 8 bits changed)
mov  ah, 0x55        ; eax = 0x11115544 (works, only high 8 bits changed)
xor  ah, ah          ; eax = 0x11110044 (works, only high 8 …
Run Code Online (Sandbox Code Playgroud)

assembly x86-64 cpu-registers zero-extension

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

为什么memcmp(a,b,4)有时只针对uint32比较进行优化?

鉴于此代码:

#include <string.h>

int equal4(const char* a, const char* b)
{
    return memcmp(a, b, 4) == 0;
}

int less4(const char* a, const char* b)
{
    return memcmp(a, b, 4) < 0;
}
Run Code Online (Sandbox Code Playgroud)

x86_64上的GCC 7引入了第一种情况的优化(Clang已经做了很长时间):

    mov     eax, DWORD PTR [rsi]
    cmp     DWORD PTR [rdi], eax
    sete    al
    movzx   eax, al
Run Code Online (Sandbox Code Playgroud)

但第二种情况仍然是memcmp():

    sub     rsp, 8
    mov     edx, 4
    call    memcmp
    add     rsp, 8
    shr     eax, 31
Run Code Online (Sandbox Code Playgroud)

是否可以对第二种情况应用类似的优化?什么是最好的装配,有没有明确的理由为什么它没有完成(由GCC或Clang)?

在Godbolt的Compiler Explorer上看到它:https://godbolt.org/g/jv8fcf

c gcc x86-64 clang compiler-optimization

68
推荐指数
3
解决办法
3930
查看次数

使用intel编译器在Windows和Linux之间的性能差异:查看程序集

我在Windows和Linux(x86-64)上运行程序.它使用相同的编译器(Intel Parallel Studio XE 2017)编译,具有相同的选项,Windows版本比Linux版本快3倍.罪魁祸首是对std :: erf的调用,在两种情况下都在英特尔数学库中解析(默认情况下,它在Windows上动态链接,在Linux上静态链接,但在Linux上使用动态链接可以提供相同的性能).

这是一个重现问题的简单程序.

#include <cmath>
#include <cstdio>

int main() {
  int n = 100000000;
  float sum = 1.0f;

  for (int k = 0; k < n; k++) {
    sum += std::erf(sum);
  }

  std::printf("%7.2f\n", sum);
}
Run Code Online (Sandbox Code Playgroud)

当我使用vTune分析这个程序时,我发现Windows和Linux版本之间的程序集有点不同.这是Windows上的调用站点(循环)

Block 3:
"vmovaps xmm0, xmm6"
call 0x1400023e0 <erff>
Block 4:
inc ebx
"vaddss xmm6, xmm6, xmm0"
"cmp ebx, 0x5f5e100"
jl 0x14000103f <Block 3>
Run Code Online (Sandbox Code Playgroud)

并在Windows上调用erf函数的开头

Block 1:
push rbp
"sub rsp, 0x40"
"lea rbp, ptr [rsp+0x20]"
"lea rcx, …
Run Code Online (Sandbox Code Playgroud)

c++ assembly x86-64 icc intel-vtune

65
推荐指数
1
解决办法
3792
查看次数

为什么std :: optional <int>的构造比std :: pair <int,bool>更昂贵?

考虑这两种可以代表"可选int"的方法:

using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;
Run Code Online (Sandbox Code Playgroud)

鉴于这两个功能......

auto get_std_optional_int() -> std_optional_int 
{
    return {42};
}

auto get_my_optional() -> my_optional_int 
{
    return {42, true};
}
Run Code Online (Sandbox Code Playgroud)

... g ++ trunkclang ++ trunk (with -std=c++17 -Ofast -fno-exceptions -fno-rtti)产生以下程序集:

get_std_optional_int():
        mov     rax, rdi
        mov     DWORD PTR [rdi], 42
        mov     BYTE PTR [rdi+4], 1
        ret

get_my_optional():
        movabs  rax, 4294967338 // == 0x 0000 0001 0000 002a
        ret
Run Code Online (Sandbox Code Playgroud)

godbolt.org上的实例


为什么get_std_optional_int()需要三个mov指令,而get_my_optional()只需要一个 …

c++ performance assembly x86-64 c++17

65
推荐指数
3
解决办法
3365
查看次数

强制gcc在64位平台上编译32位程序

我有一个专有程序,我试图在64位系统上使用.

当我启动设置它可以正常工作,但在它尝试更新自己并编译一些模块后,它无法加载它们.

我怀疑它是因为它使用gcc和gcc尝试为64位系统编译它们,因此该程序不能使用这些模块.

是否有任何方法(一些环境变量或类似的东西)迫使gcc为32位平台做所有事情.32位chroot会工作吗?

linux gcc 32-bit x86-64

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

内存对齐:如何使用alignof/alignas?

我现在正在使用共享内存.

我无法理解alignofalignas.

cppreference不清楚:alignof返回"对齐"但什么是"对齐"?要为要对齐的下一个块添加的字节数?填充尺寸?堆栈溢出 /博客条目也不清楚.

有人能解释清楚alignofalignas

c++ x86-64 memory-alignment c++11

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

objdump如何发出英特尔语法

如何判断objdump以英特尔语法而不是默认的AT&T语法发出汇编?

linux assembly x86-64 objdump

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

为什么GCC在不使用结果的情况下调用libc的sqrt()?

使用GCC 6.3,以下C++代码:

#include <cmath>
#include <iostream>

void norm(double r, double i)
{
    double n = std::sqrt(r * r + i * i);
    std::cout << "norm = " << n;
}
Run Code Online (Sandbox Code Playgroud)

生成以下x86-64程序集:

norm(double, double):
        mulsd   %xmm1, %xmm1
        subq    $24, %rsp
        mulsd   %xmm0, %xmm0
        addsd   %xmm1, %xmm0
        pxor    %xmm1, %xmm1
        ucomisd %xmm0, %xmm1
        sqrtsd  %xmm0, %xmm2
        movsd   %xmm2, 8(%rsp)
        jbe     .L2
        call    sqrt
.L2:
        movl    std::cout, %edi
        movl    $7, %edx
        movl    $.LC1, %esi
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, …
Run Code Online (Sandbox Code Playgroud)

c++ assembly gcc x86-64

56
推荐指数
1
解决办法
3106
查看次数