小编Noa*_*oah的帖子

x86_64 将 64 位寄存器减少到 32 位并保留零或非零状态的最佳方法

我正在寻找最快/最节省空间的方法,将 64 位寄存器减少为 32 位寄存器,仅保留 64 位寄存器的零/非零状态。

我目前适用于所有值的最佳想法是popcntq
(1c tput,主流英特尔上的 3c 延迟,5 字节代码大小):

// rax is either zero or non-zero
popcntq %rax, %rax
// eax will be zero if rax was zero, otherwise it will be non-zero
Run Code Online (Sandbox Code Playgroud)

注意:直接使用 32 位是行不通的eax:如果rax说 的2^61零/非零状态eax与 的不同rax

有没有更好的巧妙方法?

assembly x86-64 micro-optimization

38
推荐指数
2
解决办法
3239
查看次数

将查找表优化为简单的 ALU

问题

假设您有一个简单的函数,它根据查找表返回一个值,例如:

请参阅有关假设的编辑。

uint32_t
lookup0(uint32_t r) {
    static const uint32_t tbl[] = { 0, 1, 2, 3 };
    if(r >= (sizeof(tbl) / sizeof(tbl[0]))) {
        __builtin_unreachable();
    }

    /* Can replace with: `return r`.  */
    return tbl[r];
}


uint32_t
lookup1(uint32_t r) {
    static const uint32_t tbl[] = { 0, 0, 1, 1 };
    if(r >= (sizeof(tbl) / sizeof(tbl[0]))) {
        __builtin_unreachable();
    }

    /* Can replace with: `return r / 2`.  */
    return tbl[r];
}
Run Code Online (Sandbox Code Playgroud)

是否有任何超级优化基础设施或算法可以从查找表到优化的 ALU 实现。

动机

动机是我正在为 NUMA 机器构建一些锁,并且希望能够通用地配置我的代码。在 NUMA 锁中,您需要执行 …

c optimization code-generation clang micro-optimization

9
推荐指数
1
解决办法
514
查看次数

Why does does a NOP (as a 5th uop) speed up a 4 uop loop on Ice Lake?

All benchmarks are done on: Icelake: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz (ark)

Edit: I was not able to reproduce this on broadwell and @PeterCordes was unable to reproduce it on skylake

I was trying to benchmark different methods of doing integer min(a, b) but ran into some unexplained behavior that I've boiled down to the following benchmark:

#define BENCH_FUNC_ATTR __attribute__((aligned(64), noinline, noclone))

#define SIX_BYTES_COMPUTATION 1
#define WITH_NOP_BEFORE_DECL  0
#define BREAK_DEPENDENCY      0
void BENCH_FUNC_ATTR
bench() {
    uint64_t       start, …
Run Code Online (Sandbox Code Playgroud)

assembly x86-64 cpu-architecture micro-optimization

7
推荐指数
0
解决办法
173
查看次数

为什么 gcc 在 uint64_t * 内存区域中有条件地设置位时将 btq 与 btcq 结合使用

基本上我试图理解代码:https : //gcc.godbolt.org/z/7xxb3G

void __attribute__((noinline))
cond_unset_bit(uint64_t * v, uint32_t b) {
    if(__builtin_expect(!!(*v & ((1UL) << b)), 1)) {
        *v ^= ((1UL) << b);
    }
}
Run Code Online (Sandbox Code Playgroud)

编译为:

cond_unset_bit(unsigned long*, unsigned int):
        movq    (%rdi), %rax
        btq     %rsi, %rax
        jnc     .L6
        btcq    %rsi, %rax
        movq    %rax, (%rdi)
.L6:
        ret
Run Code Online (Sandbox Code Playgroud)

基于Agner Fog 的指令表(skylake 是第 238 页)btq并且btcq在寄存器上操作时具有完全相同的成本。btcq还将进位标志设置为前一位,因此似乎可以在没有btq指令的情况下实现完全相同的逻辑(具有更好的性能),即:

cond_unset_bit(unsigned long*, unsigned int):
        movq    (%rdi), %rax
        btcq    %rsi, %rax
        jnc     .L6
        movq    %rax, (%rdi) …
Run Code Online (Sandbox Code Playgroud)

c assembly gcc x86-64 micro-optimization

5
推荐指数
0
解决办法
129
查看次数

为什么我看到使用 REP MOVSB 的 RFO(读取所有权)请求比使用 vmovdqa 的请求多

结帐 Edit3

我得到了错误的结果,因为我在测量时没有包括这里讨论的预取触发事件。话虽如此,AFAIKrep movsb与临时存储相比,我只看到 RFO 请求减少,memcpy因为在加载时预取更好,而没有对存储进行预取。不是因为 RFO 请求针对完整缓存行存储进行了优化。这种有意义的,因为我们没有看到RFO请求优化掉了vmovdqa一个zmm寄存器,我们预计如果真的在那里为整个缓存线存储情况。话虽如此,存储上缺乏预取和非临时写入的缺乏使得很难看出如何rep movsb具有合理的性能。

编辑:RFO 可能来自rep movsb不同的请求vmovdqa,因为rep movsb它可能不请求数据,只需在独占状态下取行即可。对于有收银机的商店,情况也可能如此zmm。但是,我没有看到任何性能指标来测试这一点。有谁知道吗?

问题

  1. 为什么我没有看到RFO请求减少时,我使用rep movsbmemcpy作为相比,memcpy与实现的vmovdqa
  2. 为什么我看到越来越多的RFO请求时,我用rep movsbmemcpy作为相比,memcpy与实现vmovdqa

两个单独的问题,因为我相信我应该看到 RFO 请求减少了rep movsb,但如果不是这种情况,我是否也应该看到增加?

背景

CPU - Icelake: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz

我试图在使用不同的方法时测试 RFO 请求的数量,memcpy包括:

  • 时间商店 -> vmovdqa
  • 非临时存储 …

x86-64 intel cpu-architecture memcpy micro-optimization

5
推荐指数
0
解决办法
225
查看次数

将 std::vector&lt;uint8_t&gt; 转换为打包 std::vector&lt;uint64_t&gt;

我正在寻找一种方法来有效且无需 UB 将 a 转换std::vector<uint8_t>std::vector<uint64_t>st 中的每个元素都保存std::vector<uint64_t>来自 8 个元素的信息std::vector<uint8_t>。其余元素应该用零填充,但这可以稍后完成。

到目前为止我想出的最好的方法是:

std::vector<uint64_t> foo(std::vector<uint8_t> const & v8) {
    std::vector<uint64_t> v64;
    v64.reserve((v8.size() + 7) / 8);
    size_t i = 0;
    uint64_t tmp;
    for(; i + 8 < v8.size(); i += 8) {
        memcpy(&tmp, v8.data() + i, 8);
        v64.push_back(tmp);
    }
    tmp = 0; // fill remainder with 0s.
    memcpy(&tmp, v8.data() + i, v8.size() - i);
    v64.push_back(tmp);
    return v64;
}

Run Code Online (Sandbox Code Playgroud)

但我希望有一些更干净/更好的方法。

Edit1:关于丢失字节顺序问题的解决方案。由@VainMain 指出。

可以在memcpy.

c++ vector c++17

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

为什么引入析构函数的行为会导致更糟糕的代码生成?(通过引用而不是寄存器中的值传递)

举个简单的例子:

struct has_destruct_t {
    int a;
    ~has_destruct_t()  {}
};

struct no_destruct_t {
    int a;
};


int bar_no_destruct(no_destruct_t);
int foo_no_destruct(void) {
    no_destruct_t tmp{};
    bar_no_destruct(tmp);
    return 0;
}

int bar_has_destruct(has_destruct_t);
int foo_has_destruct(void) {
    has_destruct_t tmp{};
    bar_has_destruct(tmp);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

foo_has_destruct代码生成稍差一些,因为析构函数似乎强制tmp进入堆栈:

foo_no_destruct():                   # @foo_no_destruct()
        pushq   %rax
        xorl    %edi, %edi
        callq   bar_no_destruct(no_destruct_t)@PLT
        xorl    %eax, %eax
        popq    %rcx
        retq
foo_has_destruct():                  # @foo_has_destruct()
        pushq   %rax
        movl    $0, 4(%rsp)
        leaq    4(%rsp), %rdi
        callq   bar_has_destruct(has_destruct_t)@PLT
        xorl    %eax, %eax
        popq    %rcx
        retq
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/388K1EfYa

但是,考虑到析构函数是 1)普通内联的并且 2)空的,为什么需要这样的情况呢? …

c++ abi calling-convention micro-optimization compiler-optimization

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

内存目标 BTS 如何比 load / BTS reg,reg / store 慢得多?

在一般情况下,可以使用内存或寄存器操作数的指令如何使用内存操作数变慢然后 mov + mov -> 指令 -> mov + mov

根据Agner Fog 指令表中的吞吐量和延迟(在我的案例中查看 Skylake,p238),我看到以下btr/bts指令数字:

instruction, operands, uops fused domain, uops unfused domain, latency, throughput
mov          r,r       1                  1                    0-1      .25
mov          m,r       1                  2                    2        1
mov          r,m       1                  1                    2        .5
... 
bts/btr      r,r       1                  1                    N/A      .5
bts/btr      m,r       10                 10                   N/A      5
Run Code Online (Sandbox Code Playgroud)

我不明白这些数字怎么可能是正确的。即使在没有可用寄存器的最坏情况下,并且您将一个寄存器存储在临时内存位置,这样做也会更快:

## hypothetical worst-case microcode that saves/restores a scratch register
mov m,r  // + 1  throughput , save a register
mov …
Run Code Online (Sandbox Code Playgroud)

performance assembly x86-64 cpu-architecture microcoding

4
推荐指数
1
解决办法
122
查看次数

将 __m256i 寄存器转换为 uint64_t 位掩码,使得每个字节的值是输出中的一个设置位

基本上我有一个__m256i变量,其中每个字节代表一个需要在uint64_t. 请注意,所有字节值都将 < 64。

我对如何远程有效地做到这一点感到有些茫然。

我正在考虑的一种选择是在某些情况下字节之间有很多重复项,因此类似于:

__m256i indexes = foo();

uint64_t result         = 0;
uint32_t aggregate_mask = ~0;
do {
    uint32_t idx = _mm256_extract_epi8(indexes, __tzcnt_u32(aggregate_mask));

    uint32_t idx_mask =
        _mm256_movemask_epi8(_mm256_cmpeq_epi(indexes, _mm256_set1_epi8(idx)));
    aggregate_mask ^= idx_mask;
    result |= ((1UL) << idx);
} while (aggregate_mask);
Run Code Online (Sandbox Code Playgroud)

有了足够多的重复项,我相信这可能会有些效率,但我不能保证总是有足够的重复项来使这比仅遍历字节并按顺序设置更快。

我的目标是找到一些东西,这总是比感觉最坏的情况要快:

__m256i indexes = foo();
uint8_t index_arr[32];
_mm256_store_si256((__m256i *)index_arr, indexes);

uint64_t result = 0;
for (uint32_t i = 0; i < 32; ++i) {
    result |= ((1UL) << index_arr[i];
}
Run Code Online (Sandbox Code Playgroud)

如果可能,我正在寻找可以在 Skylake (wo AVX512) …

c++ simd avx micro-optimization avx2

4
推荐指数
1
解决办法
461
查看次数

是否可以将 ymm16 - ymm31 用于 AVX2 vpcmpeq{size} 指令?

我想知道是否可以按照以下方式做一些事情:

vpcmpeqb %ymm16, %ymm17, %ymm16
Run Code Online (Sandbox Code Playgroud)

尝试使用 gcc 进行编译,我得到:

Assembler messages: Error: unsupported instruction `vpcmpeqb'
Run Code Online (Sandbox Code Playgroud)

AFAICT 这是不可能的felixcloutier说唯一的 EVEX 前缀指令cmpeq有一个掩码目的地,但可能我遗漏了一些东西,或者直接用字节编码来做到这一点。

谢谢!

assembly x86-64 avx avx2 avx512

4
推荐指数
1
解决办法
118
查看次数

使用 AVX/AVX2/SSE __m128i 将所有负字节设置为 -128 (0x80) 并保留所有其他字节

基本上我想要做的是获取一个__m128i寄存器,并为每个负字节将其值设置为 -128 (0x80) 并且不更改任何正值。

确切地说是:

signed char __m128_as_char_arr[16] = {some data};
for(int i = 0; i < 16; i++) {
     if (__m128_as_char_arr[i] < 0) { //alternative __m128_as_char_arr[i] & 0x80
           __m128_as_char_arr[i] = 0x80;
     }

}
Run Code Online (Sandbox Code Playgroud)

我认为最好的方法是:

__m128i v = some data;
int mask = _mm_movemask_epi8(_mm_cmpgt_epi8(_mm_set1_epi8(0xff), v));

// use mask in some way to only set chars with 1s bit set
Run Code Online (Sandbox Code Playgroud)

但我不知道 (1) 使用什么指令来仅设置与关联的字节mask以及 (2) 是否有更好的方法来做到这一点(根本没有掩码或生成掩码的更好方法)。

sse simd avx avx2

2
推荐指数
1
解决办法
226
查看次数