我使用英特尔®架构代码分析器(IACA)发现了一些意想不到的东西(对我而言).
以下指令使用[base+index]
寻址
addps xmm1, xmmword ptr [rsi+rax*1]
Run Code Online (Sandbox Code Playgroud)
根据IACA没有微熔丝.但是,如果我用[base+offset]
这样的
addps xmm1, xmmword ptr [rsi]
Run Code Online (Sandbox Code Playgroud)
IACA报告它确实融合了.
英特尔优化参考手册的第2-11节给出了以下"可以由所有解码器处理的微融合微操作"的示例
FADD DOUBLE PTR [RDI + RSI*8]
Run Code Online (Sandbox Code Playgroud)
和Agner Fog的优化装配手册也给出了使用[base+index]
寻址的微操作融合的例子.例如,请参见第12.2节"Core2上的相同示例".那么正确的答案是什么?
问题
我正在学习HPC和代码优化.我试图在Goto的开创性矩阵乘法论文(http://www.cs.utexas.edu/users/pingali/CS378/2008sp/papers/gotoPaper.pdf)中复制结果.尽管我付出了最大努力,但我无法超过理论CPU最高性能的50%.
背景
请参阅此处的相关问题(优化的2x2矩阵乘法:慢速装配与快速SIMD),包括有关我的硬件的信息
我尝试过的
这篇相关论文(http://www.cs.utexas.edu/users/flame/pubs/blis3_ipdps14.pdf)很好地描述了Goto的算法结构.我在下面提供了我的源代码.
我的问题
我要求一般帮助.我一直在研究这个问题太久了,已经尝试了很多不同的算法,内联汇编,各种尺寸的内核(2x2,4x4,2x8,...,mxn m和n大),但我似乎无法打破50%CPU Gflops.这纯粹是出于教育目的,而不是作业.
源代码
希望是可以理解的.如果没有请问.我设置了宏结构(for循环),如上面第2篇文章中所述.我按照两篇论文中的讨论打包矩阵,并在图11中以图形方式显示(http://www.cs.utexas.edu/users/flame/pubs/BLISTOMSrev2.pdf).我的内核计算2x8块,因为这似乎是Nehalem架构的最佳计算(参见GotoBLAS源代码 - 内核).内核基于计算排名1更新的概念,如此处所述(http://code.google.com/p/blis/source/browse/config/template/kernels/3/bli_gemm_opt_mxn.c)
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <x86intrin.h>
#include <math.h>
#include <omp.h>
#include <stdint.h>
// define some prefetch functions
#define PREFETCHNTA(addr,nrOfBytesAhead) \
_mm_prefetch(((char *)(addr))+nrOfBytesAhead,_MM_HINT_NTA)
#define PREFETCHT0(addr,nrOfBytesAhead) \
_mm_prefetch(((char *)(addr))+nrOfBytesAhead,_MM_HINT_T0)
#define PREFETCHT1(addr,nrOfBytesAhead) \
_mm_prefetch(((char *)(addr))+nrOfBytesAhead,_MM_HINT_T1)
#define PREFETCHT2(addr,nrOfBytesAhead) \
_mm_prefetch(((char *)(addr))+nrOfBytesAhead,_MM_HINT_T2)
// define a min function
#ifndef min
#define min( a, b ) ( …
Run Code Online (Sandbox Code Playgroud) 我用AVX一次计算八个点产品.在我目前的代码中,我做了类似的事情(在展开之前):
常春藤桥/桑迪桥
__m256 areg0 = _mm256_set1_ps(a[m]);
for(int i=0; i<n; i++) {
__m256 breg0 = _mm256_load_ps(&b[8*i]);
tmp0 = _mm256_add_ps(_mm256_mul_ps(arge0,breg0), tmp0);
}
Run Code Online (Sandbox Code Playgroud)
Haswell的
__m256 areg0 = _mm256_set1_ps(a[m]);
for(int i=0; i<n; i++) {
__m256 breg0 = _mm256_load_ps(&b[8*i]);
tmp0 = _mm256_fmadd_ps(arge0, breg0, tmp0);
}
Run Code Online (Sandbox Code Playgroud)
我需要多少次为每个案例展开循环以确保最大吞吐量?
对于使用FMA3的Haswell,我认为答案是每个循环的FLOPS用于沙桥和haswell SSE2/AVX/AVX2.我需要将循环展开10次.
对于Ivy Bridge,我认为它是8.这是我的逻辑.AVX添加的延迟为3,延迟乘以5.Ivy Bridge可以使用不同的端口同时进行一次AVX乘法和一次AVX添加.使用符号m进行乘法,a表示加法,x表示无操作,以及表示部分和的数字(例如m5表示乘以第5部分和)我可以写:
port0: m1 m2 m3 m4 m5 m6 m7 m8 m1 m2 m3 m4 m5 ...
port1: x x x x x a1 a2 a3 a4 a5 a6 a7 a8 ...
Run Code Online (Sandbox Code Playgroud)
因此,通过在9个时钟周期后使用8个部分和(4个来自负载,5个来自乘法),我可以在每个时钟周期提交一个AVX负载,一个AVX加法和一个AVX乘法.
我想这意味着在Ivy …
根据英特尔软件开发人员手册(第14.9节),AVX放宽了内存访问的对齐要求.如果数据直接加载到处理指令中,例如
vaddps ymm0,ymm0,YMMWORD PTR [rax]
Run Code Online (Sandbox Code Playgroud)
加载地址不必对齐.但是,如果使用专用的对齐加载指令,例如
vmovaps ymm0,YMMWORD PTR [rax]
Run Code Online (Sandbox Code Playgroud)
必须对齐加载地址(为32的倍数),否则会引发异常.
令我困惑的是内在函数的自动代码生成,在我的例子中是gcc/g ++(4.6.3,Linux).请查看以下测试代码:
#include <x86intrin.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define SIZE (1L << 26)
#define OFFSET 1
int main() {
float *data;
assert(!posix_memalign((void**)&data, 32, SIZE*sizeof(float)));
for (unsigned i = 0; i < SIZE; i++) data[i] = drand48();
float res[8] __attribute__ ((aligned(32)));
__m256 sum = _mm256_setzero_ps(), elem;
for (float *d = data + OFFSET; d < data + SIZE - 8; d += 8) {
elem = _mm256_load_ps(d);
// …
Run Code Online (Sandbox Code Playgroud) 如果这听起来很疯狂,我很抱歉.无论如何我可以将.g文件从g ++编译器转换为与Visual Studio兼容的*obj.
这就是我考虑进行这种转换的原因.
我想问一下我想用Visual Studio 2010尝试的东西.
我通过在项目属性 - > C/C++ - >输出文件(/ FAs)中设置"汇编程序输出"选项,从.cpp文件生成.asm文件.
我的问题是,我怎样才能在下一步使用该.asm生成的文件再次从该文件链接而不再使用.cpp文件,以防我想在.asm文件中做一些修改然后通过保持再次链接我在汇编级别做的修改.
如果您可以提供确切的步骤,包括项目属性中可能需要的正确配置,将会非常有用.
我想修改 MSVC 生成的 C++ 代码中的一些汇编代码行。我想这样做的原因可以在这里找到Difference in Performance Between MSVC and GCC for heightoptimized matrix multiplication code
所以我尝试用汇编输出来朗姆酒masm,但它得到了一堆错误。相反,我只是尝试了一个“hello world”示例。
include <stdio.h>
int main() {
printf("asdf\n");
}
Run Code Online (Sandbox Code Playgroud)
使用 /FA /O2 以 64 位模式编译它...请参阅下面的输出
当我运行 ml64 /c Source.asm 时出现以下错误
Source.asm(35) : error A2006:undefined symbol : FLAT
Source.asm(17) : error A2006:undefined symbol : $LN3
Source.asm(18) : error A2006:undefined symbol : $LN3
Run Code Online (Sandbox Code Playgroud)
在花了太多时间之后,我发现这两个线程 http://social.microsoft.com/Forums/en-US/e0e541d9-5421-4297-8018-7c6a0f12ae62/compile- assembly- generated-by-cl?forum=什么论坛和http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/4aad9e70-6bb8-4622-a5d9-a3b07b51fc7f/c-compiler-creates-assembler-directives-that-ml64-doesnt-understand?微软所说的forum=windowssdk
在使用 Microsoft C/C++ 编译器针对 x64 开展工作之前,我们呼吁不再支持汇编 C/C++ 生成的列表文件。 换句话说,列表文件仅供参考。
有人可以解释另一种方法来从 MSVC2012 中的 C++ 代码生成汇编代码,以便我可以修改几行然后重新编译/汇编它吗? …
假设我有以下主循环
.L2:
vmulps ymm1, ymm2, [rdi+rax]
vaddps ymm1, ymm1, [rsi+rax]
vmovaps [rdx+rax], ymm1
add rax, 32
jne .L2
Run Code Online (Sandbox Code Playgroud)
我想时间的方式是把它放在另一个像这样的长循环中
;align 32
.L1:
mov rax, rcx
neg rax
align 32
.L2:
vmulps ymm1, ymm2, [rdi+rax]
vaddps ymm1, ymm1, [rsi+rax]
vmovaps [rdx+rax], ymm1
add rax, 32
jne .L2
sub r8d, 1 ; r8 contains a large integer
jnz .L1
Run Code Online (Sandbox Code Playgroud)
我发现的是我选择的对齐方式会对时序产生重大影响(最高可达+ -10%).我不清楚如何选择代码对齐方式.我可以想到三个地方,我可能想要对齐代码
triad_fma_asm_repeat
下面的代码中).L1
上面)重复我的主循环.L2
上图). 我发现的另一件事是,如果我在源文件中放入另一个例程,即更改一条指令(例如删除指令),即使它们是独立函数,也会对下一个函数的时序产生重大影响.我甚至在过去看到过影响另一个目标文件中的例程.
我在Agner Fog的优化装配手册中阅读了第11.5节"代码对齐",但我仍然不清楚调整代码以测试性能的最佳方法.他给出了一个例子,11.5,计时内循环,我并没有真正遵循.
目前,从我的代码中获得最高性能是一种猜测不同值和对齐位置的游戏.
我想知道是否有一种智能方法可以选择对齐方式?我应该对齐内圈和外圈吗?只是内循环?该功能的入口?使用短期或长期NOP是否重要?
我最感兴趣的是Haswell,其次是SNB/IVB,然后是Core2.
我尝试了NASM和YASM,并发现这是一个显着不同的领域.NASM仅插入一个字节的NOP指令,其中YASM插入多字节NOP.例如,通过将上面的内部和外部循环对齐到32字节,NASM插入20条NOP(0x90)指令,其中YASM插入以下内容(来自objdump)
2c: 66 …
Run Code Online (Sandbox Code Playgroud) 我熟悉数据对齐和性能,但对对齐代码相当陌生。我最近开始使用 NASM 在 x86-64 汇编中进行编程,并一直使用代码对齐来比较性能。据我所知,NASM 插入nop
指令来实现代码对齐。
这是我一直在 Ivy Bridge 系统上尝试的一个功能
void triad(float *x, float *y, float *z, int n, int repeat) {
float k = 3.14159f;
int(int r=0; r<repeat; r++) {
for(int i=0; i<n; i++) {
z[i] = x[i] + k*y[i];
}
}
}
Run Code Online (Sandbox Code Playgroud)
我为此使用的程序集如下。如果我不指定对齐方式,我的性能与峰值相比仅为 90% 左右。然而,当我将循环之前的代码以及两个内部循环对齐为 16 字节时,性能跃升至 96%。很明显,这种情况下的代码对齐会产生影响。
但这是最奇怪的部分。如果我将最里面的循环对齐到 32 字节,则该函数的性能没有任何差异,但是,在该函数的另一个版本中,在单独的对象文件中使用内部函数,我链接它的性能从 90% 跃升至 95%!
我做了一个对象转储(使用objdump -d -M intel
)的版本对齐到16字节(我将结果发布到这个问题的末尾)和32字节,它们是相同的!事实证明,在两个目标文件中,最里面的循环无论如何都与 32 字节对齐。但一定有一些区别。
我对每个目标文件进行了十六进制转储,目标文件中有一个字节不同。与 16 字节对齐的目标文件有一个带有 的字节0x10
,与 32 字节对齐的目标文件有一个带有 的字节0x20
。到底是怎么回事!为什么一个目标文件中的代码对齐会影响另一个目标文件中函数的性能?我如何知道将我的代码调整到的最佳值是多少?
我唯一的猜测是,当加载程序重新定位代码时,32 字节对齐的对象文件会使用内在函数影响其他对象文件。 …
我有一个用一些intel-intrinsincs编写的C代码.在我首先使用avx然后使用ssse3标志编译它之后,我得到了两个完全不同的汇编代码.例如:
AVX:
vpunpckhbw %xmm0, %xmm1, %xmm2
Run Code Online (Sandbox Code Playgroud)
SSSE3:
movdqa %xmm0, %xmm2
punpckhbw %xmm1, %xmm2
Run Code Online (Sandbox Code Playgroud)
很明显,vpunpckhbw
只是punpckhbw
使用avx三操作数语法.但是第一条指令的延迟和吞吐量是否等于延迟和最后一条指令的吞吐量相结合?或者答案取决于我正在使用的架构?顺便说一下,这是IntelCore i5-6500.
我试图在Agner Fog的指令表中搜索答案,但找不到答案.英特尔规格也没有帮助(但是,我很可能错过了我需要的那个).
如果可能的话,最好使用新的AVX语法吗?
我正在开发一个矩阵乘法项目.我已经能够编写C代码,并且我能够使用Microsoft visual studio 2012编译器为它生成汇编代码.编译器生成的代码如下所示.编译器使用了SSE寄存器,这正是我想要的,但它不是最好的代码.我想优化这段代码并使用C代码内联编写,但我不理解汇编代码.基本上汇编代码仅适用于矩阵的一个维度,下面的代码仅适用于4乘4矩阵.我怎样才能使它对n*n矩阵大小有益.
C++代码如下所示:
#define MAX_NUM 10
#define MAX_DIM 4
int main () {
float mat_a [] = {1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0};
float mat_b [] = {1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0};
float result [] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
int num_row = 4;
int …
Run Code Online (Sandbox Code Playgroud) 我不太了解CPU时钟,如3.4Ghz.我知道这是每秒3.4亿个时钟周期.
因此,如果机器使用单个时钟周期指令,那么它每秒可以执行大约3.4亿个指令.
但在流水线中,基本上每条指令需要更多周期,但每个周期长度都比单个时钟周期短.
但是虽然管道具有更高的吞吐量,但无论如何cpu每秒可以达到34亿个周期.因此,它可以执行3.4亿/ 5条指令(如果一条指令需要5个周期),这意味着不到单周期执行(3.4> 3.4/5).我错过了什么?
3.4Ghz等CPU时钟仅仅意味着基于流水线循环,而不是基于单周期实现吗?