我正在阅读Agner Fog的优化手册,并且遇到了这个例子:
double data[LEN];
void compute()
{
const double A = 1.1, B = 2.2, C = 3.3;
int i;
for(i=0; i<LEN; i++) {
data[i] = A*i*i + B*i + C;
}
}
Run Code Online (Sandbox Code Playgroud)
Agner 指出,有一种方法可以优化此代码 - 通过认识到循环可以避免使用昂贵的乘法,而是使用每次迭代应用的“增量”。
我用一张纸来证实这个理论,首先......
...当然,他是对的 - 在每次循环迭代中,我们可以通过添加“增量”,基于旧结果计算新结果。该增量从值“A+B”开始,然后每一步增加“2*A”。
所以我们将代码更新为如下所示:
void compute()
{
const double A = 1.1, B = 2.2, C = 3.3;
const double A2 = A+A;
double Z = A+B;
double Y = C;
int i;
for(i=0; i<LEN; i++) {
data[i] …Run Code Online (Sandbox Code Playgroud) 我听说有英特尔在线书籍描述了特定汇编指令所需的CPU周期,但我无法找到它(经过努力).有人能告诉我如何找到CPU周期吗?
下面是一个例子,在下面的代码中,mov/lock是1个CPU周期,xchg是3个CPU周期.
// This part is Platform dependent!
#ifdef WIN32
inline int CPP_SpinLock::TestAndSet(int* pTargetAddress,
int nValue)
{
__asm
{
mov edx, dword ptr [pTargetAddress]
mov eax, nValue
lock xchg eax, dword ptr [edx]
}
// mov = 1 CPU cycle
// lock = 1 CPU cycle
// xchg = 3 CPU cycles
}
#endif // WIN32
Run Code Online (Sandbox Code Playgroud)
顺便说一句:这是我发布的代码的URL:http://www.codeproject.com/KB/threads/spinlocks.aspx
我是指令优化的新手.
我对一个简单的函数dotp进行了简单的分析,该函数用于获取两个浮点数组的点积.
C代码如下:
float dotp(
const float x[],
const float y[],
const short n
)
{
short i;
float suma;
suma = 0.0f;
for(i=0; i<n; i++)
{
suma += x[i] * y[i];
}
return suma;
}
Run Code Online (Sandbox Code Playgroud)
我用昂纳雾在网络上提供的测试框架testp.
在这种情况下使用的数组是对齐的:
int n = 2048;
float* z2 = (float*)_mm_malloc(sizeof(float)*n, 64);
char *mem = (char*)_mm_malloc(1<<18,4096);
char *a = mem;
char *b = a+n*sizeof(float);
char *c = b+n*sizeof(float);
float *x = (float*)a;
float *y = (float*)b;
float *z = (float*)c;
Run Code Online (Sandbox Code Playgroud)
然后我调用函数dotp,n = 2048,repeat …
我正在玩这个答案的代码,稍微修改一下:
BITS 64
GLOBAL _start
SECTION .text
_start:
mov ecx, 1000000
.loop:
;T is a symbol defined with the CLI (-DT=...)
TIMES T imul eax, eax
lfence
TIMES T imul edx, edx
dec ecx
jnz .loop
mov eax, 60 ;sys_exit
xor edi, edi
syscall
Run Code Online (Sandbox Code Playgroud)
没有lfence我,我得到的结果与答案中的静态分析一致.
当我介绍一个单一 lfence我期望的CPU执行imul edx, edx的序列的第k个平行于迭代imul eax, eax的下一个(的序列K + 1个)迭代.
像这样的东西(调用一个的imul eax, eax序列和d的imul edx, edx一个): …
在Computer Systems:A Programmer's Perspective一书中,练习5.5显示了一段代码来计算多项式的值
double poly(double a[], double x, int degree)
{
long int i;
double result = a[0];
double xpwr = x;
for (i = 1; i <= degree; i++) {
result += a[i] * xpwr;
xpwr = x * xpwr;
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
练习假设双精度浮点加法和乘法所需的时钟周期分别为3和5.要求读者解释为什么测量的CPE(每元素周期数)值为5.
按照习题答案,在每次迭代中,我们需要更新变量xpwr和result我们需要的,操作是一个浮点加法(对于result)和浮点乘法(对于xpwr),因此后者占主导地位的延迟,导致最终CPE为5.
但我认为数据流应该是这样的:
xpwr result
| |
+-----+ +--[load] |
| | | |
[mul] [mul] |
| | |
| +---+ +-----+ …Run Code Online (Sandbox Code Playgroud) 我希望能够手动预测任意算术的长度(即没有分支或内存,尽管这也很好)x86-64汇编代码将采用特定的体系结构,考虑到指令重新排序,超标量,延迟,消费者价格指数等
什么/描述必须遵循的规则才能实现这一目标?
我想我已经找到了一些初步规则,但是我没有找到任何关于将任何示例代码分解为这个详细程度的引用,所以我不得不做一些猜测.(例如,英特尔优化手册甚至几乎没有提到指令重新排序.)
至少,我正在寻找(1)确认每条规则是正确的,或者是每条规则的正确陈述,以及(2)我可能忘记的任何规则的列表.
addps并且subps使用相同的功能) unit?我如何确定?).和:4此循环已经发出少于超标量宽度(通常)指令的数量.例如,请考虑以下示例代码(计算交叉产品):
shufps xmm3, xmm2, 210
shufps xmm0, xmm1, 201
shufps xmm2, xmm2, 201
mulps xmm0, xmm3
shufps xmm1, xmm1, 210
mulps xmm1, xmm2
subps xmm0, xmm1
Run Code Online (Sandbox Code Playgroud)
我试图预测Haswell的延迟看起来像这样:
; `mulps` Haswell latency=5, CPI=0.5
; `shufps` Haswell latency=1, CPI=1
; `subps` Haswell latency=3, CPI=1
shufps xmm3, xmm2, 210 ; cycle 1
shufps xmm0, xmm1, 201 ; cycle 2
shufps xmm2, xmm2, 201 ; …Run Code Online (Sandbox Code Playgroud) 我有两段 C++ 代码,它们执行相同的计算。与代码 A 相比,代码 B 确实减少了大约 33% 的指令,大约减少了 17% 的内存访问,但运行速度是代码 A 的四倍(而不是两倍)。会是什么原因呢?此外,我们如何才能确认您的回答所提供的主张?
在这两个代码中,
howmany是 20 000 000testees有 20 000 000 个元素,mt19937在启动时(在这些片段之前)为代码 A 和代码 B 随机生成 ( )。-O1代码 A - 运行时间约为。95 至 110 毫秒
GF2 sum {GF2(1)};
auto a = system_clock::now();
for(size_t i=0;i<howmany;i++){
sum *= testees[i];
}
auto b = system_clock::now();
Run Code Online (Sandbox Code Playgroud)
代码 B - 运行时间约为。25 至 30 毫秒
GF2 sum1 {GF2(1)};
GF2 sum2 {GF2(1)};
GF2 sum3 …Run Code Online (Sandbox Code Playgroud) 我需要一些帮助来了解我尝试的优化是如何工作的。
该cumsum函数获取一个向量,并用累加和写入一个向量。
我尝试了以下方法来优化它:我没有在整个向量上执行一次循环,而是编写了一个循环,该循环在每四分之一的向量上同时运行。然后调整每个部分以考虑前面部分的总和。结果略有不同,但这不是问题。
这是程序:
module cumsum_mod
implicit none
integer, parameter, private :: dp = kind(1d0)
contains
! cumsum in one straight loop
subroutine cumsum1(n, a, b)
integer :: n, i
real(dp) :: a(n), b(n)
b(1) = a(1)
do i = 2, n
b(i) = a(i) + b(i-1)
end do
end subroutine
subroutine cumsum2(n, a, b)
integer :: n, i, m
real(dp) :: a(n), b(n)
m = n/4
! Loop over the four parts
b(1) = a(1)
b(1+m) = …Run Code Online (Sandbox Code Playgroud) 我尝试编写一些函数来使用单个矩阵和源向量数组来执行矩阵向量乘法。我曾经用 C++ 编写过这些函数,并在 x86 AVX512 汇编中编写过一次,以将性能与英特尔 VTune Profiler 进行比较。当使用源向量数组作为目标数组时,汇编变体的执行速度比 C++ 对应版本快 3.5 倍到 10x\xc2\xa0,但是当使用不同的源和目标数组时,汇编变体的性能几乎不比 C++ 对应版本更好,实现几乎相同的性能...有时甚至更糟。
\n我无法理解的另一件事是,为什么在使用不同的源和目标数组时,C++ 对应项甚至可以达到与汇编变体接近相同或更好的性能水平,即使汇编代码要短得多并且也根据静态分析工具 uica 和 llvm-mca 速度提高数倍。uica.uops.info
\n我不想让这篇文章变得太长,所以我只发布执行 mat4-vec4 乘法的函数的代码。
\n这是汇编变体的代码,它假设矩阵要转置:
\nalignas(64) uint32_t mat4_mul_vec4_avx512_vpermps_index[64]{ 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12,\n 1, 1, 1, 1, 5, 5, 5, 5, 9, 9, 9, 9, 13, 13, 13, 13,\n 2, 2, 2, 2, 6, 6, 6, 6, 10, 10, 10, 10, 14, 14, …Run Code Online (Sandbox Code Playgroud) 我认为我对延迟和吞吐量之间的差异有一个很好的理解.但是,对于Intel Intrinsics来说,延迟对指令吞吐量的影响并不清楚,特别是在顺序(或几乎顺序)使用多个内部调用时.
例如,让我们考虑一下:
_mm_cmpestrc
Run Code Online (Sandbox Code Playgroud)
它的延迟为11,Haswell处理器的吞吐量为7.如果我在一个循环中运行这个指令,那么在11个循环后我会得到一个连续的每循环输出吗?由于这需要一次运行11条指令,并且因为我的吞吐量为7,所以我是否会用完"执行单元"?
我不确定如何使用延迟和吞吐量,除了得到一条指令相对于不同版本的代码需要多长时间的印象.
我想知道为什么像这样的简单循环无法达到我的 CPU 时钟速度(4,2Ghz):
float sum = 0;
for (int i = 0; i < 1000000; i+=1) {
sum = sum * 1 + 1;
}
Run Code Online (Sandbox Code Playgroud)
凭直觉,我希望在不到 1 毫秒(例如 0,238 毫秒)的时间内实现这一目标,每秒进行 42 亿次迭代。但我得到的时间约为 3 毫秒,即每秒约 3.33 亿次迭代。
我假设做数学运算需要 2 个周期,一个用于乘法,另一个用于求和。假设我正在执行 6.66 亿次操作……看起来仍然很慢。然后我假设循环比较需要一个周期,循环计数器需要另一个周期......
所以我创建了以下代码来删除循环......
void listOfSums() {
float internalSum = 0;
internalSum = internalSum * 1 + 1;
internalSum = internalSum * 1 + 1;
internalSum = internalSum * 1 + 1;
internalSum = internalSum * 1 + 1;
// Repeated 100k …Run Code Online (Sandbox Code Playgroud) assembly ×9
x86-64 ×4
cpu ×3
optimization ×3
performance ×3
x86 ×3
c ×2
c++ ×2
sse ×2
avx512 ×1
cpu-cycles ×1
cycle ×1
fortran ×1
g++ ×1
intrinsics ×1
latency ×1
perf ×1
pipeline ×1
simd ×1
superscalar ×1