我正在研究大型矩阵乘法并运行以下实验来形成基线测试:
这是天真的C++实现:
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
constexpr size_t dim = 4096;
float* x = new float[dim*dim];
float* y = new float[dim*dim];
float* z = new float[dim*dim];
random_device rd;
mt19937 gen(rd());
normal_distribution<float> dist(0, 1);
for (size_t i = 0; i < dim*dim; i++)
{
x[i] = dist(gen);
y[i] = dist(gen);
}
for (size_t row = 0; row < dim; row++)
for (size_t col = 0; col < …Run Code Online (Sandbox Code Playgroud) 哪些指令用于比较由4*32位浮点值组成的两个128位向量?
是否存在将双方的NaN值视为相等的指令?如果不是,提供反身性的解决方案(即NaN等于NaN)的性能影响有多大?
我听说,与IEEE语义相比,确保反身性会产生显着的性能影响,因为NaN不等于自己,我想知道这种影响是否会很大.
我知道您在处理浮点值时通常需要使用epsilon比较而不是精确的质量.但是这个问题是关于完全相等的比较,例如,您可以使用它来消除哈希集中的重复值.
要求
+0并且-0必须相等.NaN 必须与自己相等.true如果两个向量中的所有四个float元素相同,则结果应为布尔值,如果至少一个元素不同,则结果为false.其中true由标量整数1和falseby表示0.测试用例
(NaN, 0, 0, 0) == (NaN, 0, 0, 0) // for all representations of NaN
(-0, 0, 0, 0) == (+0, 0, 0, 0) // equal despite different bitwise representations
(1, 0, 0, 0) == (1, 0, 0, 0)
(0, 0, 0, 0) != (1, 0, 0, 0) // at least one different element => not equal …Run Code Online (Sandbox Code Playgroud) 我有以下NASM组装程序,运行时间约为9.5秒:
section .text
global _start
_start:
mov eax, 0
mov ebx, 8
loop:
inc dword [esp + ebx]
inc eax
cmp eax, 0xFFFFFFFF
jne loop
mov eax, 1
mov ebx, 0
int 0x80
Run Code Online (Sandbox Code Playgroud)
但是,如果我替换[esp + ebx]为[esp + 8](自ebx = 8以来相同的内存位置)或甚至只是[esp],它在10.1秒内运行...
这怎么可能?是不是[esp]为CPU比计算更容易[esp + ebx]?
我正在对代码的性能关键部分进行微优化,并且遇到了指令序列(在AT&T语法中):
add %rax, %rbx
mov %rdx, %rax
mov %rbx, %rdx
Run Code Online (Sandbox Code Playgroud)
我以为我终于有一个用例xchg可以让我刮一个指令并写:
add %rbx, %rax
xchg %rax, %rdx
Run Code Online (Sandbox Code Playgroud)
然而,根据Agner Fog的指令表,我发现这xchg是一个3微操作指令,在Sandy Bridge,Ivy Bridge,Broadwell,Haswell甚至Skylake上有2个周期延迟.3个完整的微操作和2个周期的延迟!3微操作抛出了我的4-1-1-1的节奏和2周期延迟使得它比在最好的情况下原来的,因为在原来的并行执行可能最后2条指令差.
现在......我得知CPU可能会将指令分解为相当于以下内容的微操作:
mov %rax, %tmp
mov %rdx, %rax
mov %tmp, %rdx
Run Code Online (Sandbox Code Playgroud)
哪里tmp是匿名内部寄存器,我想最后两个微操作可以并行运行,因此延迟是2个周期.
鉴于寄存器重命名发生在这些微架构上,但对我来说这是以这种方式完成的.为什么寄存器重命名器不会交换标签?理论上,这将只有1个周期(可能是0?)的延迟,并且可以表示为单个微操作,因此它会便宜得多.
我想更好地理解为什么两个非常相似的代码片段在我的计算机上表现得截然不同.这些测试是在Ryzen处理器上使用gcc-trunk和Julia 0.7-alpha(LLVM 6.0).gcc-8看似相似,而Julia 0.6.3(LLVM 3.9)略慢于v0.7.
我编写了生成函数(想想C++模板),为矩阵运算生成展开代码,以及一个简单的转换器,可以将简单的代码转换为Fortran.
对于8x8矩阵乘法,这是Fortran代码的样子:
module mul8mod
implicit none
contains
subroutine mul8x8(A, B, C)
real(8), dimension(64), intent(in) :: A, B
real(8), dimension(64), intent(out) :: C
C(1) = A(1) * B(1) + A(9) * B(2) + A(17) * B(3) + A(25) * B(4)
C(1) = C(1) + A(33) * B(5) + A(41) * B(6) + A(49) * B(7) + A(57) * B(8)
C(2) = A(2) * B(1) + A(10) * B(2) + A(18) * B(3) + A(26) * B(4) …Run Code Online (Sandbox Code Playgroud) 据我所知,BigInts通常在大多数编程语言中实现为包含数字的数组,例如:当添加其中两个时,每个数字都是一个接一个地添加,就像我们从学校知道的那样,例如:
246
816
* *
----
1062
Run Code Online (Sandbox Code Playgroud)
其中*表示存在溢出.我在学校这样学习,所有BigInt添加函数我已经实现了类似于上面例子的工作.
所以我们都知道我们的处理器只能本地管理从0到2^32/的整数2^64.
这意味着大多数脚本语言为了高级并提供具有大整数的算术,必须实现/使用BigInt库,这些库使用整数作为上面的数组.但当然这意味着它们将比处理器慢得多.
所以我问自己的是:
它可以像任何其他BigInt库一样工作,只是(很多)更快,更低一级:处理器从缓存/ RAM中取一个数字,添加它,然后再将结果写回来.
对我来说似乎是一个好主意,为什么不是那样的?
我正在编写C++代码来查找内存中非0xFF的第一个字节.为了利用bitscanforward,我编写了一个我非常喜欢的内联汇编代码.但是对于"可读性"以及未来的校对(即SIMD矢量化),我想我会给g ++优化器一个机会.g ++没有矢量化,但它确实得到了我所做的几乎相同的非SIMD解决方案.但由于某种原因,它的版本运行速度慢得多,速度慢260000倍(即我必须循环我的版本260,000x才能达到相同的执行时间).我除了一些差异,但不是那么多!有人可以指出它为什么会这样吗?我只是想知道在未来的内联汇编代码中出错.
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);
}
Run Code Online (Sandbox Code Playgroud)
汇编代码g ++提出的是:
_Z6count3PKvRKm:
.LFB33:
.cfi_startproc
mov rdx, QWORD PTR [rsi]
xor eax, eax
jmp .L19
.p2align 4,,10
.p2align 3 …Run Code Online (Sandbox Code Playgroud) 我正在尝试优化一种算法,该算法将处理可能受益于AVX SIMD指令的大量数据集.不幸的是,输入存储器布局对于所需的计算并不是最佳的.必须通过组合__m256i恰好相隔4个字节的单个字节的值来重新排序信息:
开始编辑
我的目标CPUS不支持AVX2指令,所以像@Elalfer和@PeterCordes指出的那样,我不能使用__m256i值,代码必须转换为使用__m128i值而不是)
结束编辑
内存中的DataSet布局
Byte 0 | Byte 1 | Byte 2 | Byte 3
Byte 4 | Byte 5 | Byte 6 | Byte 7
...
Byte 120 | Byte 121 | Byte 122 | Byte 123
Byte 124 | Byte 125 | Byte 126 | Byte 127
Run Code Online (Sandbox Code Playgroud)
__m256i变量中的期望值:
| Byte 0 | Byte 4 | Byte 8 | ... | Byte 120 | Byte 124 |
Run Code Online (Sandbox Code Playgroud)
除了这个简单的代码之外,是否有更有效的方法来收集和重新排列跨步数据?
union { __m256i reg; uint8_t bytes[32]; …Run Code Online (Sandbox Code Playgroud) 我正在阅读http://www.realworldtech.com/sandy-bridge/,我在理解一些问题时面临一些问题:
专用堆栈指针跟踪器也存在于Sandy Bridge中并重命名堆栈指针,消除了串行依赖性并删除了多个uop.
什么是dedicated stack pointer tracker实际?
对于Sandy Bridge(和P4),英特尔仍然使用术语ROB.但重要的是要理解,在这种情况下,它只引用了飞行中uops的状态数组
事实上它意味着什么?请说清楚.
我正在测试一个非常简单的程序,该程序使用C++表达式模板来简化编写在值数组上运行的SSE2和AVX代码.
我有一个svec代表一组值的类.
我有一个sreg代表SSE2双重寄存器的类.
我有expr并add_expr代表添加svec数组.
与手动代码相比,编译器为每个循环生成三个额外的指令用于表达式模板测试用例.我想知道是否有这样的原因,或者我可以做任何改变让他编译器产生相同的输出?
完整的测试工具是:
#include <iostream>
#include <emmintrin.h>
struct sreg
{
__m128d reg_;
sreg() {}
sreg(const __m128d& r) :
reg_(r)
{
}
sreg operator+(const sreg& b) const
{
return _mm_add_pd(reg_, b.reg_);
}
};
template <typename T>
struct expr
{
sreg operator[](std::size_t i) const
{
return static_cast<const T&>(*this).operator[](i);
}
operator const T&() const
{
return static_cast<const T&>(*this);
}
};
template <typename A, typename B>
struct add_expr : public expr<add_expr<A, …Run Code Online (Sandbox Code Playgroud) assembly ×5
x86 ×5
c++ ×3
avx ×2
intel ×2
intrinsics ×2
performance ×2
biginteger ×1
c ×1
c++11 ×1
gcc ×1
julia ×1
linux ×1
llvm ×1
matlab ×1
optimization ×1
processor ×1
simd ×1
x86-64 ×1