- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在编写C++代码以查找内存中的第一个非0xFF字节。为了利用bitscanforward,我编写了非常喜欢的内联汇编代码。但是为了“可读性”以及将来的证明(即SIMD矢量化),我想我会给g++优化器一个机会。 g++没有向量化,但是它确实达到了与我相同的非SIMD解决方案。但是由于某种原因,它的版本运行速度要慢得多,速度要慢260000倍(即我必须循环260,000倍才能获得相同的执行时间)。我除了有一些区别,但没有那么多!可以指出为什么会这样吗?我只是想知道,以免在将来的内联汇编代码中出错。
接下来是C++的起点(就计数准确性而言,此代码中有一个错误,但是为了进行速度测试,我已经对其进行了简化):
uint64_t count3 (const void *data, uint64_t const &nBytes) {
uint64_t count = 0;
uint64_t block;
do {
block = *(uint64_t*)(data+count);
if ( block != (uint64_t)-1 ) {
/* count += __builtin_ctz(~block); ignore this for speed test*/
goto done;
};
count += sizeof(block);
} while ( count < nBytes );
done:
return (count>nBytes ? nBytes : count);
}
_Z6count3PKvRKm:
.LFB33:
.cfi_startproc
mov rdx, QWORD PTR [rsi]
xor eax, eax
jmp .L19
.p2align 4,,10
.p2align 3
.L21:
add rax, 8
cmp rax, rdx
jnb .L18
.L19:
cmp QWORD PTR [rdi+rax], -1
je .L21
.L18:
cmp rax, rdx
cmova rax, rdx
ret
.cfi_endproc
_Z6count2PKvRKm:
.LFB32:
.cfi_startproc
push rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
mov rbx, QWORD PTR [rsi]
# count trailing bytes of 0xFF
xor rax, rax
.ctxff_loop_69:
mov r9, QWORD PTR [rdi+rax]
xor r9, -1
jnz .ctxff_final_69
add rax, 8
cmp rax, rbx
jl .ctxff_loop_69
.ctxff_final_69:
cmp rax,rbx
cmova rax,rbx
pop rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
for (uint64_t i=0; i < ((uint64_t)1<<15); i++) {
n = count3(a,N);
}
for (uint64_t i=0; i < ((uint64_t)1<<33); i++) {
n = count2(a,N);
}
count1()
,x64-64
count()
,然后是普通的C++版本
count0()
和
count3()
。我掉进了这个兔子洞,希望我可以让g++拿起自己的
count0()
并独自到达
count1()
甚至
count2()
。但是可惜它什么也没做,绝对没有优化:(我应该补充一点,我的平台没有AVX2,这就是为什么我希望让g++自动矢量化,以便代码在我更新平台时自动更新。
nBytes
和
count
重用相同的寄存器。
count2()
中复制该效果。
uint32_t count0(const uint8_t *data, uint64_t const &nBytes) {
for (int i=0; i<nBytes; i++)
if (data[i] != 0xFF) return i;
return nBytes;
}
uint32_t count1(const void *data, uint64_t const &nBytes) {
uint64_t count;
__asm__("# count trailing bytes of 0xFF \n"
" xor %[count], %[count] \n"
" vpcmpeqb xmm0, xmm0, xmm0 \n" // make array of 0xFF
".ctxff_next_block_%=: \n"
" vpcmpeqb xmm1, xmm0, XMMWORD PTR [%[data]+%[count]] \n"
" vpmovmskb r9, xmm1 \n"
" xor r9, 0xFFFF \n" // test if all match (bonus negate r9)
" jnz .ctxff_tzc_%= \n" // if !=0, STOP & tzcnt negated r9
" add %[count], 16 \n" // else inc
" cmp %[count], %[nBytes] \n"
" jl .ctxff_next_block_%= \n" // while count < nBytes, loop
" jmp .ctxff_done_%= \n" // else done + ALL bytes were 0xFF
".ctxff_tzc_%=: \n"
" tzcnt r9, r9 \n" // count bytes up to non-0xFF
" add %[count], r9 \n"
".ctxff_done_%=: \n" // more than 'nBytes' could be tested,
" cmp %[count],%[nBytes] \n" // find minimum
" cmova %[count],%[nBytes] "
: [count] "=a" (count)
: [nBytes] "b" (nBytes), [data] "d" (data)
: "r9", "xmm0", "xmm1"
);
return count;
};
uint64_t count2 (const void *data, uint64_t const &nBytes) {
uint64_t count;
__asm__("# count trailing bytes of 0xFF \n"
" xor %[count], %[count] \n"
".ctxff_loop_%=: \n"
" mov r9, QWORD PTR [%[data]+%[count]] \n"
" xor r9, -1 \n"
" jnz .ctxff_final_%= \n"
" add %[count], 8 \n"
" mov r9, QWORD PTR [%[data]+%[count]] \n" // <--loop-unroll
" xor r9, -1 \n"
" jnz .ctxff_final_%= \n"
" add %[count], 8 \n"
" cmp %[count], %[nBytes] \n"
" jl .ctxff_loop_%= \n"
" jmp .ctxff_done_%= \n"
".ctxff_final_%=: \n"
" bsf r9, r9 \n" // do tz count on r9 (either of first QWORD bits or XMM bytes)
" shr r9, 3 \n" // scale BSF count accordiningly
" add %[count], r9 \n"
".ctxff_done_%=: \n" // more than 'nBytes' bytes could have been tested,
" cmp %[count],%[nBytes] \n" // find minimum of count and nBytes
" cmova %[count],%[nBytes] "
: [count] "=a" (count)
: [nBytes] "b" (nBytes), [data] "D" (data)
: "r9"
);
return count;
}
inline static uint32_t tzcount(uint64_t const &qword) {
uint64_t tzc;
asm("tzcnt %0, %1" : "=r" (tzc) : "r" (qword) );
return tzc;
};
uint64_t count3 (const void *data, uint64_t const &nBytes) {
uint64_t count = 0;
uint64_t block;
do {
block = *(uint64_t*)(data+count);
if ( block != (uint64_t)-1 ) {
count += tzcount(~block);
goto done;
};
count += sizeof(block);
} while ( count < nBytes );
done:
return (count>nBytes ? nBytes : count);
}
uint32_t N = 1<<20;
int main(int argc, char **argv) {
unsigned char a[N];
__builtin_memset(a,0xFF,N);
uint64_t n = 0, j;
for (uint64_t i=0; i < ((uint64_t)1<<18); i++) {
n += count2(a,N);
}
printf("\n\n %x %x %x\n",N, n, 0);
return n;
}
最佳答案
回答问题标题
现在,您已经发布了完整的代码:,对count2(a,N)
的调用已从main
中的循环中吊起了。循环次数(例如1<<18
)仍然会稍微增加执行时间,但循环所要做的只是一个add
。编译器对其进行了优化,使其看起来更像此源:
uint64_t hoisted_count = count2(a,N);
for (uint64_t i=0; i < ((uint64_t)1<<18); i++) {
n += hoisted_count; // doesn't optimize to a multiply
}
没有寄存器冲突:
%rax
保留从
count2
内联的asm语句的结果。然后将其用作微小循环中的源操作数,通过重复加法将其乘以
n
。
void*
s上算术的所有编译器警告:clang拒绝编译您的代码):
## the for() loop in main, when using count2()
.L23:
addq %rax, %r12
subq $1, %rdx
jne .L23
%rdx
是这里的循环计数器,
%r12
是保存
n
的累加器。 IDK为什么gcc不能将其优化为恒定时间乘法。
count2
脱离循环。从gcc的角度来看,内联的asm版本要简单得多:asm语句被视为其输入的纯函数,而gcc甚至不知道它与内存有关的任何信息。 C版本占用大量内存,要证明它可以被吊起则要复杂得多。
"memory"
clobber确实可以防止在我检查Godbolt时将其吊起。您可以从 vector 块之前的
main
中判断是否存在分支目标。
n + rep_count
与n * rep_count
。
asm
语句不使用
"memory"
破坏符或任何内存输入来告诉gcc它读取了输入指针指向的内存。
可能会发生错误的优化,例如被悬卡在修改数组元素的循环之外。 (有关使用虚拟匿名
struct
内存输入而不是毯子
"memory"
Clobber的示例,请参阅
Clobbers section in the manual。不幸的是,当内存块没有编译时常数时,我认为这种方法不可用。)
-fno-inline
阻止了提升,因为该函数未标记
__attribute__((const))
或
__attribute__((pure))
稍弱,以指示没有副作用。内联后,优化器可以在asm语句中看到这一点。
count0
并未针对任何好的进行优化,因为gcc和clang无法自动向量化循环,而循环在开始时就不知道迭代次数。也就是说,即使他们被告知可以安全地访问超出搜索循环结束点的末尾(例如使用
strlen
作为函数arg),也可以安全地访问
memchr
或
char buf[static 512]
之类的东西,或者通常是搜索循环。
xor reg, 0xFFFF
/
jnz
相比,使用
cmp reg, 0xFFFF
/
jnz
是愚蠢的,因为cmp/jcc可以将宏融合到一个比较分支的uop中。
cmp reg, mem
/
jne
也可以进行宏熔合,因此执行加载/异或运算的标量版本每次比较使用3倍的微指令。 (当然,如果不使用索引寻址模式,Sandybridge只能对其进行微熔合。此外,SnB只能对每个解码块进行一对宏熔合,但是您可能会得到第一个cmp/jcc和无论如何,
xor
是一个坏主意。最好只在
xor
之前添加
tzcnt
,因为将uops保存在循环中比代码大小或uops total更重要。
count += __builtin_ctz
与
if
处于同一级别,这使我认为您正在计算不匹配块,而不仅仅是找到第一个。
pcmpeq
的结果比分支到
cmp
的花费更多。如果我们的搜索数组很大,我们可以使用一个循环来一次测试多个 vector ,然后找出中断循环后哪个字节被我们命中了。
-masm=intel
语法一起使用。 (尤其是当进行内联时,内部函数可能会产生更好的结果,因为编译器了解内部函数,因此可以通过内部函数进行常量传播,诸如此类。OTOH,如果您了解交易,通常可以使用手写asm击败编译器。 -offs和您所针对的微体系结构。此外,如果您可以安全地做出一些假设,但是您无法轻松地将它们传达给编译器。)
#include <stdint.h>
#include <immintrin.h>
// compile with -masm=intel
// len must be a multiple of 32 (TODO: cleanup loop)
// buf should be 16B-aligned for best performance
size_t find_first_zero_bit_avx1(const char *bitmap, size_t len) {
// return size_t not uint64_t. This same code works in 32bit mode, and in the x32 ABI where pointers are 32bit
__m128i pattern, vtmp1, vtmp2;
const char *result_pos;
int tmpi;
const char *bitmap_start = bitmap;
asm ( // modifies the bitmap pointer, but we're inside a wrapper function
"vpcmpeqw %[pat], %[pat],%[pat]\n\t" // all-ones
".p2align 4\n\t" // force 16B loop alignment, for the benefit of CPUs without a loop buffer
//IACA_START // See the godbolt link for the macro definition
".Lcount_loop%=:\n\t"
// " movdqu %[v1], [ %[p] ]\n\t"
// " pcmpeqb %[v1], %[pat]\n\t" // for AVX: fold the load into vpcmpeqb, making sure to still use a one-register addressing mode so it can micro-fuse
// " movdqu %[v2], [ %[p] + 16 ]\n\t"
// " pcmpeqb %[v2], %[pat]\n\t"
" vpcmpeqb %[v1], %[pat], [ %[p] ]\n\t" // Actually use AVX, to get a big speedup over the OP's scalar code on his SnB CPU
" vpcmpeqb %[v2], %[pat], [ %[p] + 16 ]\n\t"
" vpand %[v2], %[v2], %[v1]\n\t" // combine the two results from this iteration
" vpmovmskb %k[result], %[v2]\n\t"
" cmp %k[result], 0xFFFF\n\t" // k modifier: eax instead of rax
" jne .Lfound%=\n\t"
" add %[p], 32\n\t"
" cmp %[p], %[endp]\n\t" // this is only 2 uops after the previous cmp/jcc. We could re-arrange the loop and put the branches farther apart if needed. (e.g. start with a vpcmpeqb outside the loop, so each iteration actually sets up for the next)
" jb .Lcount_loop%=\n\t"
//IACA_END
// any necessary code for the not-found case, e.g. bitmap = endp
" mov %[result], %[endp]\n\t"
" jmp .Lend%=\n\t"
".Lfound%=:\n\t" // we have to figure out which vector the first non-match was in, based on v1 and (v2&v1)
// We could just search the bytes over again, but we don't have to.
// we could also check v1 first and branch, instead of checking both and using a branchless check.
" xor %k[result], 0xFFFF\n\t"
" tzcnt %k[result], %k[result]\n\t" // runs as bsf on older CPUs: same result for non-zero inputs, but different flags. Faster than bsf on AMD
" add %k[result], 16\n\t" // result = byte count in case v1 is all-ones. In that case, v2&v1 = v2
" vpmovmskb %k[tmp], %[v1]\n\t"
" xor %k[tmp], 0xFFFF\n\t"
" bsf %k[tmp], %k[tmp]\n\t" // bsf sets ZF if its *input* was zero. tzcnt's flag results are based on its output. For AMD, it would be faster to use more insns (or a branchy strategy) and avoid bsf, but Intel has fast bsf.
" cmovnz %k[result], %k[tmp]\n\t" // if there was a non-match in v1, use it instead of tzcnt(v2)+16
" add %[result], %[p]\n\t" // If we needed to force 64bit, we could use %q[p]. But size_t should be 32bit in the x32 ABI, where pointers are 32bit. This is one advantage to using size_t over uint64_t
".Lend%=:\n\t"
: [result] "=&a" (result_pos), // force compiler to pic eax/rax to save a couple bytes of code-size from the special cmp eax, imm32 and xor eax,imm32 encodings
[p] "+&r" (bitmap),
// throw-away outputs to let the compiler allocate registers. All early-clobbered so they aren't put in the same reg as an input
[tmp] "=&r" (tmpi),
[pat] "=&x" (pattern),
[v1] "=&x" (vtmp1), [v2] "=&x" (vtmp2)
: [endp] "r" (bitmap+len)
// doesn't compile: len isn't a compile-time constant
// , "m" ( ({ struct { char x[len]; } *dummy = (typeof(dummy))bitmap ; *dummy; }) ) // tell the compiler *which* memory is an input.
: "memory" // we read from data pointed to by bitmap, but bitmap[0..len] isn't an input, only the pointer.
);
return result_pos - bitmap_start;
}
这个
actually compiles and assembles到asm看起来像我期望的那样,但是我没有对其进行测试。请注意,它将所有寄存器分配留给编译器,因此更加内联。即使没有内联,它也不会强制使用必须保存/恢复的保留 call 的寄存器(例如,使用
"b"
约束)。
-no_interiteration
:
g++ -masm=intel -Wall -Wextra -O3 -mtune=haswell find-first-zero-bit.cpp -c -DIACA_MARKS
iaca -64 -arch IVB -no_interiteration find-first-zero-bit.o
Intel(R) Architecture Code Analyzer Version - 2.1
Analyzed File - find-first-zero-bit.o
Binary Format - 64Bit
Architecture - SNB
Analysis Type - Throughput
Throughput Analysis Report
--------------------------
Block Throughput: 2.50 Cycles Throughput Bottleneck: Port1, Port5
Port Binding In Cycles Per Iteration:
-------------------------------------------------------------------------
| Port | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 |
-------------------------------------------------------------------------
| Cycles | 2.0 0.0 | 2.5 | 1.0 1.0 | 1.0 1.0 | 0.0 | 2.5 |
-------------------------------------------------------------------------
N - port number or number of cycles resource conflict caused delay, DV - Divider pipe (on port 0)
D - Data fetch pipe (on ports 2 and 3), CP - on a critical path
F - Macro Fusion with the previous instruction occurred
* - instruction micro-ops not bound to a port
^ - Micro Fusion happened
# - ESP Tracking sync uop was issued
@ - SSE instruction followed an AVX256 instruction, dozens of cycles penalty is expected
! - instruction not supported, was not accounted in Analysis
| Num Of | Ports pressure in cycles | |
| Uops | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | |
---------------------------------------------------------------------
| 2^ | | 1.0 | 1.0 1.0 | | | | CP | vpcmpeqb xmm1, xmm0, xmmword ptr [rdx]
| 2^ | | 0.6 | | 1.0 1.0 | | 0.4 | CP | vpcmpeqb xmm2, xmm0, xmmword ptr [rdx+0x10]
| 1 | 0.9 | 0.1 | | | | 0.1 | CP | vpand xmm2, xmm2, xmm1
| 1 | 1.0 | | | | | | | vpmovmskb eax, xmm2
| 1 | | | | | | 1.0 | CP | cmp eax, 0xffff
| 0F | | | | | | | | jnz 0x18
| 1 | 0.1 | 0.9 | | | | | CP | add rdx, 0x20
| 1 | | | | | | 1.0 | CP | cmp rdx, rsi
| 0F | | | | | | | | jb 0xffffffffffffffe1
在SnB上:
pcmpeqb
可以在p1/p5上运行。融合的比较分支只能在p5上运行。非融合的
cmp
可以在p015上运行。无论如何,如果分支之一没有宏熔丝,则循环可以每8/3 = 2.666个循环进行一次迭代。使用宏融合时,最佳情况是7/3 = 2.333个周期。 (IACA并没有尝试完全按照硬件动态做出决定的方式来模拟将uops分配到端口。但是,我们也不能期望从硬件中获得完美的调度,因此每2.5个周期2个 vector 对于两个宏都可能是合理的可能会使用port0的Uop有时会窃取port1或port5,从而降低吞吐量。)
vpcmpeqb
/
vpand
仅为2 uops(或3而不使用AVX,我们将其加载到暂存器中,然后将其用作pcmpeqb的目的地。)因此,有了足够的展开,我们应该能够做2每个时钟 vector 负载。没有AVX,没有
PAND
技巧是不可能的,因为 vector load/compare/movmsk/test-and-branch为4 oups。展开次数更大,需要更多的工作来解码找到匹配项的最终位置:一旦进入该区域,基于scalt
cmp
的清理循环可能是一个好主意。您可能会使用相同的标量循环来清除非32B大小。
movdqu
/
pcmpeqb xmm,xmm
,我们可以使用索引寻址模式,而不会花费我们的成本,因为
movdqu
负载始终是单个负载uop,而与寻址模式无关。 (与商店不同,它不需要对任何东西进行微熔合)。这使我们可以使用指向数组末尾的基本指针以及从零开始递增的索引来节省一小段循环开销。例如当索引为负数时,
add %[idx], 32
/
js
循环。
vpcmpeqb %[v1], %[pat], [ %[p] + 16 ]
可以微熔断。这意味着我们需要在示例中使用的add/cmp/jcc循环结构。 AVX2也是如此。
关于c++ - 为什么其中一个比另一个快得多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36817660/
这看起来很基础,但我想不通。是否有一种简单的 CSS 唯一方法可以使 cssa 真正快速淡入并缓慢淡出。这是为了改变多个 div 的颜色。大约 0.5 秒的缓入和 2 秒的缓出。 谢谢! 最佳答案 你
我一直在用各种语言和实现实现相同的代码(在 Blackjack 中发牌而不爆牌的方法的数量)。我注意到的一个奇怪之处是,Python 在 C 中调用分区函数的实现实际上比用 C 编写的整个程序快一点。
如果我没看错,/ 意味着它右边的节点必须是左边节点的直接子节点,例如/ul/li 返回 li 项,它们是作为文档根的 ul 项的直接子项。 //ul//li 返回 li 项,它们是文档中某处任何 ul
如何随机更新一个表。所以你给一列一个随机值。并且该列(例如“顶部”)是唯一的。如果您在数字 10 到 20 之间进行选择,并且您有 10 行,那么您就不能有未使用的数字。如果你有 Test table
这在一小部分是一个问题(因为我不明白为什么它会有所不同),在很大程度上是一篇希望能帮助其他一些可怜的程序员的帖子。 我有一个代码库,是我大约 5-7 年前第一次开始 Android 编程时编写的,它具
我正在尝试过滤关系表以获得满足两个条件的表子集(即:我想要 color_ids 为 1 或 2 的条目的所有 ID)。这是一张结实的 table ,所以我正在尝试尽可能多地进行优化。 我想知道是否有人
在上一篇《聊聊PHP中require_once()函数为什么不好用》中给大家介绍了PHP中require_once()为什么不好用的原因,感兴趣的朋友可以去阅读了解一下~ 那么本文将给大家介绍PH
很难说出这里问的是什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或言辞激烈,无法以目前的形式合理回答。如需帮助澄清此问题以便可以重新打开,visit the help center . 10年前关
有没有办法提高glReadPixels的速度?目前我做: Gdx.gl.glReadPixels(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeig
通常,我以函数形式`:=`()来计算data.table中的多列,认为这是最有效的方法。但是我最近发现它比简单地重复使用:=慢。至少在我的电脑上。 我猜想:=的功能形式可能会产生一些开销,但这是它变慢
我的问题是针对 Windows 环境中多线程的性能问题。 在测试我的代码后,我得到的结果是增加线程数不会提高并行计算的性能,并且在经过一些计数后变得更少。到底是怎么回事?是否可以找出最佳线程数的公式:
我看到很少有相同问题的主题,但我仍然无法解决我的问题。这是我的代码 - 使用 XOR 加密的 C 套接字编程 当服务器和客户端连接时:- 用户发送消息,例如:你好- 服务器响应,例如:(服务器):你好
我正在定义继承自 Shape 类并实现“几何”属性的形状。 这是一个例子: public class Landmark : Shape { public override bool IsInB
相同代码在 Android(1Ghz Snapdragon)上的执行速度比我在 3.3 Ghz Core 2 Duo 的 PC(在桌面应用程序中)快 2 倍(PC 的类被复制到 Android 项目)
我需要将一个值与一组数组进行比较。但是,我需要比较 foreach 中的多个值。如果使用 in_array,它可能会很慢,真的很慢。有没有更快的选择?我当前的代码是 foreach($a as $b)
这个问题在这里已经有了答案: How do I write a correct micro-benchmark in Java? (11 个答案) 关闭 9 年前。 今天我做了一个简单的测试来比较
如果比较不应该以这种方式进行,我深表歉意。我是编程新手,只是很好奇为什么会这样。 我有一个包含词嵌入的大型二进制文件 (4.5gb)。每行都有一个单词,后面跟着它的嵌入,它由 300 个浮点值组成。我
我经历了几个不同的四元数乘法实现,但我很惊讶地发现引用实现是迄今为止我最快的实现。这是有问题的实现: inline static quat multiply(const quat& lhs, cons
我写了一个简单的例子,估计调用虚函数的平均时间,使用基类接口(interface)和dynamic_cast和调用非虚函数。这是它: #include #include #include #in
有没有人知道比“StackWalk”更好/更快的获取调用堆栈的方法?我还认为 stackwalk 在有很多变量的方法上也会变慢......(我想知道商业分析员是做什么的?)我在 Windows 上使用
我是一名优秀的程序员,十分优秀!