我正在寻找最快/最节省空间的方法,将 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
有没有更好的巧妙方法?
假设您有一个简单的函数,它根据查找表返回一个值,例如:
请参阅有关假设的编辑。
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 锁中,您需要执行 …
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) 基本上我试图理解代码: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) 我得到了错误的结果,因为我在测量时没有包括这里讨论的预取触发事件。话虽如此,AFAIKrep movsb
与临时存储相比,我只看到 RFO 请求减少,memcpy
因为在加载时预取更好,而没有对存储进行预取。不是因为 RFO 请求针对完整缓存行存储进行了优化。这种有意义的,因为我们没有看到RFO请求优化掉了vmovdqa
一个zmm
寄存器,我们预计如果真的在那里为整个缓存线存储情况。话虽如此,存储上缺乏预取和非临时写入的缺乏使得很难看出如何rep movsb
具有合理的性能。
编辑:RFO 可能来自rep movsb
不同的请求vmovdqa
,因为rep movsb
它可能不请求数据,只需在独占状态下取行即可。对于有收银机的商店,情况也可能如此zmm
。但是,我没有看到任何性能指标来测试这一点。有谁知道吗?
rep movsb
的memcpy
作为相比,memcpy
与实现的vmovdqa
?rep movsb
的memcpy
作为相比,memcpy
与实现vmovdqa
两个单独的问题,因为我相信我应该看到 RFO 请求减少了rep movsb
,但如果不是这种情况,我是否也应该看到增加?
CPU - Icelake: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz
我试图在使用不同的方法时测试 RFO 请求的数量,memcpy
包括:
vmovdqa
我正在寻找一种方法来有效且无需 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
.
举个简单的例子:
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
在一般情况下,可以使用内存或寄存器操作数的指令如何使用内存操作数变慢然后 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) 基本上我有一个__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) …
我想知道是否可以按照以下方式做一些事情:
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
有一个掩码目的地,但可能我遗漏了一些东西,或者直接用字节编码来做到这一点。
谢谢!
基本上我想要做的是获取一个__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) 是否有更好的方法来做到这一点(根本没有掩码或生成掩码的更好方法)。