我想知道各种大小的循环如何在最近的x86处理器上执行,作为uop数的函数.
以下是彼得·科德斯(Peter Cordes)的一句话,他在另一个问题中提出了非多数的问题:
我还发现,如果循环不是4 uop的倍数,则循环缓冲区中的uop带宽不是每个循环的常数4.(即它是abc,abc,......;不是abca,bcab,......).遗憾的是,Agner Fog的microarch doc对循环缓冲区的这种限制并不清楚.
问题是关于循环是否需要是N uop的倍数才能以最大uop吞吐量执行,其中N是处理器的宽度.(即最近的英特尔处理器为4).在谈论"宽度"和计算微动时,有很多复杂因素,但我大多想忽略这些因素.特别是,假设没有微观或宏观融合.
Peter给出了以下一个循环,其中包含7个uop的循环:
一个7-uop循环将发出4 | 3 | 4 | 3 | ...的组我没有测试更大的循环(不适合循环缓冲区),看看是否有可能从下一个指令开始迭代发布在与其分支相同的组中,但我不假设.
更一般地说,声称是x在其体内具有uops 的循环的每次迭代将至少进行ceil(x / 4)迭代,而不是简单地迭代x / 4.
对于部分或全部最新的x86兼容处理器,这是真的吗?
performance x86 assembly cpu-architecture micro-optimization
我正在尝试学习汇编(所以忍受我),我在这一行得到了一个编译错误:
mov byte [t_last], [t_cur]
Run Code Online (Sandbox Code Playgroud)
错误是
error: invalid combination of opcode and operands
Run Code Online (Sandbox Code Playgroud)
我怀疑这个错误的原因只是因为一个mov指令不可能在两个内存地址之间移动,但是半小时的谷歌搜索并且我无法确认这一点 - 是这样的吗?
另外,假设我是对的,这意味着我需要使用寄存器作为复制内存的中间点:
mov cl, [t_cur]
mov [t_last], cl
Run Code Online (Sandbox Code Playgroud)
什么是推荐使用的寄存器(或者我应该使用堆栈)?
我发现了
mov al, bl
mov ah, bh
Run Code Online (Sandbox Code Playgroud)
比...快得多
mov ax, bx
Run Code Online (Sandbox Code Playgroud)
谁能解释我为什么?我在Windows XP下以32位模式运行Core 2 Duo 3 Ghz.使用NASM进行编译,然后与VS2010进行链接.Nasm编译命令:
nasm -f coff -o triangle.o triangle.asm
Run Code Online (Sandbox Code Playgroud)
这是我用来渲染三角形的主循环:
; some variables on stack
%define cr DWORD [ebp-20]
%define dcr DWORD [ebp-24]
%define dcg DWORD [ebp-32]
%define dcb DWORD [ebp-40]
loop:
add esi, dcg
mov eax, esi
shr eax, 8
add edi, dcb
mov ebx, edi
shr ebx, 16
mov bh, ah
mov eax, cr
add eax, dcr
mov cr, eax
mov ah, bh ; …Run Code Online (Sandbox Code Playgroud) 注册变量是一种众所周知的快速访问(register int i)方式.但为什么寄存器位于层次结构的顶层(寄存器,缓存,主存储器,辅助存储器)?访问寄存器如此之快的所有事情是什么?
我看到术语软件基准测试和分析有时可以互换,但据我的理解,这是一个微妙的差异.
两者都是按时间连接的.但是,虽然基准测试主要是确定可以与其他应用程序进行比较的特定速度分数,但分析可以为您提供有关应用程序在大部分时间(或周期数)上花费的确切信息.
对我来说,它总是如下:集成测试是基准测试和单元测试对应的分析测试的对应物.但微基准测试如何适用于此?
有人在这里说:
分析和基准测试是相同硬币的另一面,分析可帮助您缩小到最有用的优化位置,基准测试可让您轻松隔离优化并对它们进行交叉比较.
另一个人在这里谈到分析:
分析在不同时间意味着不同的东西.有时它意味着衡量绩效.有时它意味着诊断内存泄漏.有时它意味着可以了解多线程或其他低级别活动.
那么,这些技术在概念上是不同的还是不是那种黑白?
我在Delphi中编写一个简单的BigInteger类型.它主要由TLimb的动态数组组成,其中TLimb是32位无符号整数,32位大小字段,它还保存BigInteger的符号位.
要添加两个BigIntegers,我创建一个适当大小的新BigInteger然后,在一些簿记之后,调用以下过程,将三个指针传递给左右操作数和结果的数组的相应开始,以及左右肢的数量分别为.
普通代码:
class procedure BigInteger.PlainAdd(Left, Right, Result: PLimb; LSize, RSize: Integer);
asm
// EAX = Left, EDX = Right, ECX = Result
PUSH ESI
PUSH EDI
PUSH EBX
MOV ESI,EAX // Left
MOV EDI,EDX // Right
MOV EBX,ECX // Result
MOV ECX,RSize // Number of limbs at Left
MOV EDX,LSize // Number of limbs at Right
CMP EDX,ECX
JAE @SkipSwap
XCHG ECX,EDX // Left and LSize should be largest
XCHG ESI,EDI // so swap
@SkipSwap:
SUB EDX,ECX // …Run Code Online (Sandbox Code Playgroud) 我将一些程序集与一些c链接起来测试函数调用的成本,使用以下程序集和c源代码(分别使用fasm和gcc)
部件:
format ELF
public no_call as "_no_call"
public normal_call as "_normal_call"
section '.text' executable
iter equ 100000000
no_call:
mov ecx, iter
@@:
push ecx
pop ecx
dec ecx
cmp ecx, 0
jne @b
ret
normal_function:
ret
normal_call:
mov ecx, iter
@@:
push ecx
call normal_function
pop ecx
dec ecx
cmp ecx, 0
jne @b
ret
Run Code Online (Sandbox Code Playgroud)
c来源:
#include <stdio.h>
#include <time.h>
extern int no_call();
extern int normal_call();
int main()
{
clock_t ct1, ct2;
ct1 = clock();
no_call();
ct2 = clock(); …Run Code Online (Sandbox Code Playgroud) 完全知道这些完全人为的基准测试并不重要,但我对"大4"编译器选择编写一个简单的片段的几种方式感到有点惊讶.
struct In {
bool in1;
bool in2;
};
void foo(In &in) {
extern bool out1;
extern bool out2;
out1 = (in.in1 == true);
out2 = in.in2;
}
Run Code Online (Sandbox Code Playgroud)
注意:所有编译器都设置为x64模式,具有最高的"通用"(=没有指定特定的处理器体系结构)"优化速度"设置; 你可以通过自己看到的结果/和他们一起玩了,在https://gcc.godbolt.org/z/K_i8h9)
带有-O3的Clang 6似乎产生了最直接的输出:
foo(In&): # @foo(In&)
mov al, byte ptr [rdi]
mov byte ptr [rip + out1], al
mov al, byte ptr [rdi + 1]
mov byte ptr [rip + out2], al
ret
Run Code Online (Sandbox Code Playgroud)
在符合标准的C++程序中,== true比较是多余的,因此两个分配都成为从一个内存位置到另一个内存位置的直接副本,al因为内存没有内存mov.
但是,由于这里没有寄存器压力,我原本期望它使用两个不同的寄存器(完全避免两个赋值之间的错误依赖链),可能先启动所有读操作,然后执行所有写操作,以帮助指令级并行; 由于寄存器重命名和积极无序的CPU,这种优化是否已经完全淘汰了最近的CPU?(稍后会详细介绍)
带有-O3的GCC 8.2 …
write(1,"hi",3)在linux上反汇编,gcc -s -nostdlib -nostartfiles -O3结果如下:
ba03000000 mov edx, 3 ; thanks for the correction jester!
bf01000000 mov edi, 1
31c0 xor eax, eax
e9d8ffffff jmp loc.imp.write
Run Code Online (Sandbox Code Playgroud)
我不是到编译器的开发,但由于移动到这些寄存器的每一个值是恒定的和已知的编译时间,我很好奇,为什么不GCC使用dl,dil和al来代替.也许有人会说,此功能不会让任何性能上的差异,但有一个在之间的可执行文件的大小有很大的区别mov $1, %rax => b801000000,并mov $1, %al => b001当我们谈论数千寄存器的程序访问.如果软件的优雅部分不仅体积小,它确实会对性能产生影响.
有人可以解释为什么"海湾合作委员会决定"它无所谓?
我有一个巨大的内存块(位向量),在一个内存页中大小为N位,考虑N平均为 5000,即 5k 位来存储一些标志信息。
在某个时间点(超级频繁 - 关键),我需要在整个大位向量中找到第一个位集。现在我每 64 个字都这样做,即在 ) 的帮助下__builtin_ctzll。但是当N增长并且搜索算法无法改进时,可以通过扩展内存访问宽度来扩展此搜索。这是几句话的主要问题
有一条被调用的汇编指令BSF 给出了最高设置位(GCC's __builtin_ctzll())的位置。因此,在x86-64 arch 中,我可以在 64 位字中廉价地找到最高位。
但是通过内存宽度进行缩放呢?
例如,有没有办法用 128 / 256 / 512 位寄存器有效地做到这一点?
基本上我对一些 C API 函数来实现这个感兴趣,但也想知道这个方法是基于什么的。
UPD:至于 CPU,我对这种优化感兴趣,以支持以下 CPU 阵容:
英特尔至强 E3-12XX、英特尔至强 E5-22XX/26XX/E56XX、英特尔酷睿 i3-5XX/4XXX/8XXX、英特尔酷睿 i5- 7XX、英特尔赛扬 G18XX/G49XX(英特尔凌动 N2600、英特尔赛扬 N2807、Cortex-A53/72 可选)
PS在最终位扫描之前提到的算法中,我需要将k(平均 20-40)个N位向量与 CPU AND相加(AND 结果只是位扫描的准备阶段)。这也适用于内存宽度缩放(即比每 64 位字 AND 更有效)
另请阅读:查找第一组
assembly ×8
x86 ×7
c ×2
performance ×2
x86-64 ×2
avx ×1
benchmarking ×1
c++ ×1
delphi ×1
fasm ×1
gcc ×1
instructions ×1
mov ×1
profiling ×1