为什么_mm_extract_ps返回int而不是float?
float从C中的XMM寄存器读取单个数据的正确方法是什么?
或者更确切地说,一种不同的方式是:与_mm_set_ps指令相反的是什么?
nature.com 最近发表了一篇文章《使用深度强化学习发现更快的排序算法》,其中谈到了 AlphaDev 发现了一种更快的排序算法。这引起了我的兴趣,我一直在努力理解这一发现。
关于该主题的其他文章有:
这是原始 sort3 算法与 AlphaDev 发现的改进算法的伪代码。
原始伪代码
Memory [0] = A
Memory [1] = B
Memory [2] = C
mov Memory[0] P // P = A
mov Memory[1] Q // Q = B
mov Memory[2] R // R = C
mov R S
cmp P R
cmovg P R // R = max(A, C)
cmovl P S // S = min(A, C)
mov S P // P = min(A, C)
cmp …Run Code Online (Sandbox Code Playgroud) 是否有任何asm指令可以加速Core i7架构上双精度/整数向量的最小值/最大值的计算?
更新:
我没想到会有如此丰富的答案,谢谢.所以我看到max/min可以不分支.我有一个小问题:
有没有一种有效的方法来获得阵列中最大的双倍索引?
对于乘法大二进制矩阵(10Kx20K),我通常要做的是将矩阵转换为浮点数并执行浮点矩阵乘法,因为整数矩阵乘法非常慢(请看这里).
但这一次,我需要执行超过数十万次这样的乘法运算,甚至平均事情上的毫秒级性能提升.
我想要一个int或float矩阵作为结果,因为产品可能有非0或1的元素.输入矩阵元素都是0或1,因此它们可以存储为单个位.
在行向量和列向量之间的内积中(为了产生输出矩阵的一个元素),乘法简化为按位AND.添加仍然是添加,但我们可以添加具有填充计数功能的位,而不是单独循环它们.
一些其他布尔/二进制矩阵函数或位而不是计数它们,产生位矩阵结果,但这不是我需要的.
下面是一个示例代码,显示将问题形成为std::bitset, AND并且count操作比矩阵乘法更快.
#include <iostream>
using std::cout; using std::endl;
#include <vector>
using std::vector;
#include <chrono>
#include <Eigen/Dense>
using Eigen::Map; using Eigen::Matrix; using Eigen::MatrixXf;
#include <random>
using std::random_device; using std::mt19937; using std::uniform_int_distribution;
#include <bitset>
using std::bitset;
using std::floor;
const int NROW = 1000;
const int NCOL = 20000;
const float DENSITY = 0.4;
const float DENOMINATOR = 10.0 - (10*DENSITY);
void fill_random(vector<float>& vec) { …Run Code Online (Sandbox Code Playgroud) 在为AVX256,AVX512和一天AVX1024设计前瞻性算法时,考虑到大SIMD宽度的完全通用置换的潜在实现复杂性/成本,我想知道即使在AVX512中通常保持隔离128位操作是否更好?
特别是考虑到AVX有128位单元来执行256位操作.
为此,我想知道在所有512位向量中AVX512置换类型操作之间是否存在性能差异,而在 512位向量的每个4x128位子向量中是否存在置换类型操作?
我有8个AVX向量,每个向量包含8个浮点数(总共64个浮点数),我想将每个向量中的元素加在一起(基本上执行8个水平求和).
现在,我正在使用以下代码:
__m256 HorizontalSums(__m256 v0, __m256 v1, __m256 v2, __m256 v3, __m256 v4, __m256 v5, __m256 v6, __m256 v7)
{
// transpose
const __m256 t0 = _mm256_unpacklo_ps(v0, v1);
const __m256 t1 = _mm256_unpackhi_ps(v0, v1);
const __m256 t2 = _mm256_unpacklo_ps(v2, v3);
const __m256 t3 = _mm256_unpackhi_ps(v2, v3);
const __m256 t4 = _mm256_unpacklo_ps(v4, v5);
const __m256 t5 = _mm256_unpackhi_ps(v4, v5);
const __m256 t6 = _mm256_unpacklo_ps(v6, v7);
const __m256 t7 = _mm256_unpackhi_ps(v6, v7);
__m256 v = _mm256_shuffle_ps(t0, t2, 0x4E);
const __m256 tt0 = …Run Code Online (Sandbox Code Playgroud) 我得到了一个小写字符数组(最多 1.5Gb)和一个字符 c。我想使用 AVX 指令查找字符 c 出现了多少次。
unsigned long long char_count_AVX2(char * vector, int size, char c){
unsigned long long sum =0;
int i, j;
const int con=3;
__m256i ans[con];
for(i=0; i<con; i++)
ans[i]=_mm256_setzero_si256();
__m256i Zer=_mm256_setzero_si256();
__m256i C=_mm256_set1_epi8(c);
__m256i Assos=_mm256_set1_epi8(0x01);
__m256i FF=_mm256_set1_epi8(0xFF);
__m256i shield=_mm256_set1_epi8(0xFF);
__m256i temp;
int couter=0;
for(i=0; i<size; i+=32){
couter++;
shield=_mm256_xor_si256(_mm256_cmpeq_epi8(ans[0], Zer), FF);
temp=_mm256_cmpeq_epi8(C, *((__m256i*)(vector+i)));
temp=_mm256_xor_si256(temp, FF);
temp=_mm256_add_epi8(temp, Assos);
ans[0]=_mm256_add_epi8(temp, ans[0]);
for(j=1; j<con; j++){
temp=_mm256_cmpeq_epi8(ans[j-1], Zer);
shield=_mm256_and_si256(shield, temp);
temp=_mm256_xor_si256(shield, FF);
temp=_mm256_add_epi8(temp, Assos);
ans[j]=_mm256_add_epi8(temp, ans[j]);
}
}
for(j=con-1; j>=0; …Run Code Online (Sandbox Code Playgroud) 假设有一个__m128变量包含4个SP值,并且您想要最小值,是否有任何可用的内部函数,或者除了值之间的天真线性比较之外的任何其他函数?
正确知道我的解决方案如下(假设输入__m128变量是x):
x = _mm_min_ps(x, (__m128)_mm_srli_si128((__m128i)x, 4));
min = _mm_min_ss(x, (__m128)_mm_srli_si128((__m128i)x, 8))[0];
Run Code Online (Sandbox Code Playgroud)
这是非常可怕的,但它的工作(顺便说一下,有什么类似的_mm_srli_si128但是__m128类型?)
我有一个任务来计算数组中的xor-sum字节:
X = char1 XOR char2 XOR char3 ... charN;
Run Code Online (Sandbox Code Playgroud)
我正在尝试并行化它,而是使用__m128.这应该加速因子4.另外,要重新检查算法,我使用int.这应该加速因子4.测试程序是100行,我不能让它更短,但它很简单:
#include "xmmintrin.h" // simulation of the SSE instruction
#include <ctime>
#include <iostream>
using namespace std;
#include <stdlib.h> // rand
const int NIter = 100;
const int N = 40000000; // matrix size. Has to be dividable by 4.
unsigned char str[N] __attribute__ ((aligned(16)));
template< typename T >
T Sum(const T* data, const int N)
{
T sum = 0;
for ( int i = 0; i < N; ++i …Run Code Online (Sandbox Code Playgroud) 我希望这不会成为一个非常愚蠢的问题我稍后会感到尴尬,但我总是对SIMD内在函数感到困惑,以至于我发现汇编代码比内在函数更容易合理化.
所以我的主要问题是使用SIMD内部数据类型__m256.只是为了跳到这一点,我的问题是做这样的事情:
class PersistentObject
{
...
private:
std::vector<__m256, AlignedAlloc<__m256, 32>> data;
};
Run Code Online (Sandbox Code Playgroud)
在生成最有效的代码时,它是否可以接受,是否会使编译器绊倒?这就是我现在困惑的部分.我处于缺乏经验的水平,当我有一个热点并且已经筋疲力尽所有其他直接选择时,我给SIMD内在函数一个镜头,并且如果它们不能提高性能,我总是希望退出我的更改(并且我已经退出这么多与SIMD相关的变化).
但是这个关于存储SIMD内部类型的问题和困惑一直让我意识到我并不真正理解这些内在函数如何在基本编译器级别上工作.我的想法想要__m256像一个抽象的YMM寄存器(尚未分配).当我看到加载和存储指令时,它开始与我点击.我认为它们是编译器执行其寄存器分配的提示.
我以前是没有把更多的想进入这个不是,因为我一直使用SIMD类型的临时办法:_mm256_load_ps对__m256,做一些操作,结果存储回32位SPFP 256位对齐排列float[8].我想到__m256像YMM寄存器一样.
摘要YMM注册?
但是最近我正在实现一个数据结构,试图围绕SIMD处理(一个简单的代表SoA方式的一组向量),这里如果我可以主要工作__m256而不经常从浮点数组加载而变得方便之后将结果存回.在一些快速测试中,MSVC至少似乎发出了将我的内在函数映射到汇编的适当指令(以及当我从向量中访问数据时正确对齐的加载和存储).但这打破了我__m256作为抽象YMM寄存器思考的概念模型,因为存储这些东西会持续地暗示更像常规变量的东西,但在那时,负载/运动和存储是什么?
所以我对我在脑海中构建的关于如何思考所有这些内容的概念模型略微喋喋不休,我的希望是,也许经验丰富的人可以立即认识到我正在考虑这些东西的方式被破坏了什么我调查我脑子的尤里卡回答.我希望这个问题不是太愚蠢(我有一种不安的感觉,但是我试图在其他地方发现答案,但仍然发现自己很困惑).所以最终,是否可以永久地直接存储这些数据类型(这意味着我们在不使用它已经从YMM寄存器溢出之后的某个时刻重新加载内存_mm_load*),如果是这样,我的概念模型有什么问题?
如果这是一个如此愚蠢的问题,请道歉!这个东西我耳朵后面真的很湿.
更多细节
非常感谢您迄今为止提供的有用评论!我想我应该分享更多细节,以使我的问题不那么模糊.基本上我正在尝试创建一个数据结构,它只是以SoA形式存储的向量集合:
xxxxxxxx....
yyyyyyyy....
zzzzzzzz....
Run Code Online (Sandbox Code Playgroud)
...主要是为了用于热点,其中关键循环具有顺序访问模式.但与此同时,非关键执行路径可能想要以AoS形式(x/y/z)随机访问第5个3向量,此时我们不可避免地进行标量访问(如果不是这样的话,那就非常好了)如此高效,因为它们不是关键路径).
在这个特例中,我发现从实现的角度来看,只是持久存储和使用__m256而不是更方便float*.这将阻止我洒了很多的垂直糊涂的代码_mm_loads*,并_mm_stores*在此情况下(无论是在关键的执行和大部分代码的换算)SIMD内部函数实现,因为通常情况下.但是我不确定这是否只是保留__m256短暂的临时数据,一些函数本地,将一些浮点数加载到__m256,执行某些操作,并按照我通常所做的那样存储结果过去.它会更加方便,但我有点担心这种方便的实现方式可能会阻塞一些优化器(尽管我还没有发现它的情况).如果他们没有绊倒优化器,那么我一直在思考这些数据类型的方式有点过时了.
所以在这种情况下,就好像做完这些东西并且我们的优化器一直处理得非常好,然后我很困惑,因为我正在思考这些东西的方式,并认为我们需要那些明确的_mm_load和_mm_store短暂的上下文(函数的本地,即)帮助我们的优化器是错的!而这种情况让我感到不安,因为我觉得这应该没问题!:-D
答案
来自Mysticial的一些评论确实对我来说很有帮助,并且帮助我修复了我的大脑,并且给了我一些保证,我想做的事情是正确的.它是以评论的形式而不是答案的形式给出的,所以我会在这里引用它,以防任何人碰巧遇到类似的混淆.
如果它有帮助,我有大约200k LOC写得完全像这样.IOW,我将SIMD类型视为一等公民.没关系.编译器处理它们与任何其他基本类型没有区别.所以没有问题.
优化者并不那么脆弱.他们确实在C/C++标准的合理解释中保持正确性.除非您需要特殊的(未对齐,非时间,蒙版等),否则不需要加载/存储内在函数.
也就是说,请随时写下自己的答案.更多信息更好!我真的希望能够更有信心地改进对如何编写SIMD代码的基本理解,因为我正处于对所有事情犹豫不决的阶段,并且仍然在猜测自己.
反思
再次感谢大家!我现在感觉非常清楚,并且对设计围绕SIMD构建的代码更有信心.出于某种原因,我对SIMD内在函数的优化器非常怀疑,认为我必须以尽可能最低级别的方式编写代码,并在有限的函数范围内尽可能将这些加载和存储作为本地.我认为我的一些迷信来自于几乎几十年前编写的SIMD内在函数最初针对较旧的编译器,也许当时优化器可能需要更多的帮助,或者我可能一直都是非理性的迷信.我看着它有点像80年代人们如何看待C编译器,在register这里和那里提出类似的提示.
有了SIMD,我总是有非常喜欢的结果,并且有一种趋势,尽管每次在蓝色的月亮里一次又一次地使用它,总是觉得自己像个初学者,也许只是因为混合的成功让我不愿意使用它严重拖延了我的学习过程.最近我想纠正这个问题,我非常感谢所有的帮助!