请考虑以下代码:
!pip install -U -q PyDrive
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials
# 1. Authenticate and create the PyDrive client.
auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)
# 2. Load a file by ID and create local file.
downloaded = drive.CreateFile({'id':'fileid'}) # replace fileid with Id of file you want to access
downloaded.GetContentFile('export.csv') # now you can use export.csv
此complies(最大限度优化,但没有展开或矢量化)到...
GCC 7.2:
void foo(int* __restrict__ a)
{
int i; int val = 0;
for (i = 0; i < 100; i++) {
val = 2 * i;
a[i] = val;
}
}
clang 5.0:
foo(int*):
xor eax, eax
.L2:
mov DWORD PTR [rdi], eax
add eax, 2
add rdi, 4
cmp eax, 200
jne .L2
rep ret
海湾合作委员会与铿锵的方法有什么利弊?即一个额外的变量是单独递增的,与通过更复杂的寻址模式相乘?
注意:
foo(int*): # @foo(int*)
xor eax, eax
.LBB0_1: # =>This Inner Loop Header: Depth=1
mov dword ptr [rdi + 2*rax], eax
add rax, 2
cmp rax, 200
jne .LBB0_1
ret
而不是float
相关。答案 0 :(得分:4)
是的,利用x86寻址模式的强大功能来保存uop,如果索引没有释放到比指针增量花费更多的额外uop 。
(在许多情况下,展开和使用指针增量是一个胜利,因为在英特尔Sandybridge系列上没有分层,但是如果你没有展开或者你只是使用mov
加载而不是将内存操作数折叠成ALU选择微融合,然后索引寻址模式在某些CPU上通常是收支平衡的,而在其他CPU上则是胜利。)
如果您想在此处做出最佳选择,请务必阅读并理解Micro fusion and addressing modes。(并注意IACA错误,并且不会模拟Haswell以及后来保留一些uops微融合,所以你甚至不能通过对你进行静态分析来检查你的工作。)
索引寻址模式通常很便宜。在最坏的情况下,它们会为前端(on Intel SnB-family CPUs in some situations)花费一个额外的uop,和/或防止存储地址uop使用port7(它只支持基本+位移寻址模式)。有关英特尔在Haswell中添加的port7上的store-AGU的更多信息,请参阅Agner Fog's microarch pdf以及David Kanter's Haswell。 在Haswell +上,如果你需要你的循环每个时钟维持超过2个内存操作,那么避免索引存储。
除了机器代码编码中额外字节的代码大小成本之外,它们最多是免费的。 (索引寄存器需要编码中的SIB(Scale Index Base)字节。)
在英特尔Sandybridge系列CPU上,更常见的唯一损失是1个额外的负载使用延迟周期与简单的[base + 0-2047]
寻址模式。
如果您要在多个指令中使用该寻址模式,通常只需要使用额外的指令来避免索引寻址模式。 (例如加载/修改/存储)。
如果您已经使用2寄存器寻址模式,则可以免费扩展索引(至少在现代CPU上)。对于lea
,Agner Fog的表格列出AMD Ryzen具有2c延迟,lea
具有缩放 - 索引寻址模式(或3分量)的每时钟吞吐量仅为2;否则1c延迟和0.25c
吞吐量。例如lea rax, [rcx + rdx]
比lea rax, [rcx + 2*rdx]
快,但不足以值得使用额外的指令。)由于某种原因,Ryzen也不喜欢64位模式下的32位目标。但最坏情况的LEA仍然没有坏。无论如何,主要与负载的地址模式选择无关,因为大多数CPU(除了有序Atom)在ALU上运行LEA,而不是用于实际加载/存储的AGU。
主要问题是单寄存器未缩放(因此它可以是机器码编码中的“基本”寄存器:[base + idx*scale + disp]
)或双寄存器。请注意,对于英特尔的微融合限制,[disp32 + idx*scale]
(例如索引静态数组)是索引寻址模式。
这两种功能都不是完全最优的(即使不考虑展开或矢量化),但clang看起来非常接近。
唯一可以做得更好的是通过避免使用add eax, 2
和cmp eax, 200
的REX前缀来节省2个字节的代码大小。它将所有操作数提升为64位,因为它使用了指针,我猜测C环不需要它们包装,因此在asm中它使用64位。这毫无意义; 32位操作始终至少与64一样快,隐式零扩展是免费的。但是这只需要2个字节的代码大小,并且除了间接前端效果之外不会产生任何性能。
你已经构造了你的循环,所以编译器需要在寄存器中保留一个特定的值,并且不能完全将问题转换为指针增量+与结束指针的比较(编译器经常在它们不执行时进行比较)除了数组索引之外,还需要循环变量。
你也无法转换为将负索引计数到零(编译器从不这样做,但是将循环开销减少到Intel CPU上总共1个宏融合add + branch uop(可以融合{{1虽然AMD只能融合测试或cmp / jcc)。
Clang做得很好,注意到它可以使用add + jcc
作为数组索引(以字节为单位)。这是tune = generic的一个很好的优化。索引商店将在英特尔Sandybridge和Ivybridge上取消层压,但在Haswell及更高版本上保持微观融合。 (在其他CPU上,比如Nehalem,Silvermont,Ryzen,Jaguar等等,没有任何劣势。)
gcc的循环在循环中有1个额外的uop。理论上它仍然可以在Core2 / Nehalem上以每个时钟1个存储运行,但它正好与每个时钟限制的4个uop相对应。 (实际上,Core2无法在64位模式下将cmp / jcc宏熔合,因此它在前端存在瓶颈)。
答案 1 :(得分:2)
索引寻址(在加载和存储中,lea
仍然不同)有一些权衡,例如
因此对于加载,如果它在某处保存了一个add,那么使用索引寻址通常是好的(或者不坏),除非它们是依赖加载链的一部分。对于商店来说,使用索引寻址更危险。在示例代码中,它不应该产生很大的差异。保存add
并不真正相关,ALU说明不会成为瓶颈。端口2或3中发生的地址生成无关紧要,因为没有负载。