旨在提供高性能数字运算的 CPU 最终会采用某种向量指令集。基本上有两种:
SIMD。这在概念上很简单,例如,您不仅拥有一组 64 位寄存器及其上的操作,还拥有第二组 128 位寄存器,并且可以同时对两个 64 位值的短向量进行操作。它在实现中变得复杂,因为您还希望可以选择对四个 32 位值进行操作,然后新一代 CPU 提供 256 位向量,这需要一套全新的指令等。
较旧的 Cray 风格向量指令,其中向量一开始很大,例如 4096 位,但同时操作的元素数量是透明的,并且要在给定操作中使用的元素数量是指令参数。这个想法是,你预先减少一点复杂性,以避免以后出现复杂性。
有人认为选项 2 更好,并且这些论点似乎有道理,例如https://www.sigarch.org/simd-instructions-considered-harmful/
至少乍一看,选项 2 似乎可以完成选项 1 可以做的所有事情,而且更容易,而且总体上更好。
是否存在相反情况的工作负载?SIMD 指令在哪里可以完成 Cray 式向量无法完成的任务,或者可以更快或使用更少的代码完成任务?
我最近被介绍了向量指令(理论上)并且对如何使用它们来加速我的应用程序感到兴奋。
我想改进的一个方面是一个非常热的循环:
__declspec(noinline) void pleaseVectorize(int* arr, int* someGlobalArray, int* output)
{
for (int i = 0; i < 16; ++i)
{
auto someIndex = arr[i];
output[i] = someGlobalArray[someIndex];
}
for (int i = 0; i < 16; ++i)
{
if (output[i] == 1)
{
return i;
}
}
return -1;
}
Run Code Online (Sandbox Code Playgroud)
但是,当然,所有 3 个主要编译器(msvc、gcc、clang)都拒绝对此进行矢量化。我可以理解为什么,但我想得到确认。
如果我必须手动矢量化它,它将是:
(1) VectorLoad "arr", 这带来了 16 个 4 字节整数,让我们说到 zmm0
(2) 16个内存从zmm0[0..3]指向的地址加载到zmm1[0..3],从zmm0[4..7]指向的地址加载到zmm1[4..7]所以等等
(3)比较zmm0和zmm1
(4) 向量 popcnt 到输出中找出最高有效位并基本上除以 8 得到匹配的索引
首先,向量指令可以做这些事情吗?就像他们可以执行这种“收集”操作,即从指向 zmm0 的地址加载?
以下是 clang 生成的内容:
0000000000400530 …
Run Code Online (Sandbox Code Playgroud) 输入是存储在连续存储器中的比特阵列,每1比特存储器具有1比特的比特阵列.
输出是比特阵列的设定位索引的数组.
例:
bitarray: 0000 1111 0101 1010
setA: {4,5,6,7,9,11,12,14}
setB: {2,4,5,7,9,10,11,12}
Run Code Online (Sandbox Code Playgroud)
获得A组或B组都可以.该集存储为uint32_t数组,因此该集的每个元素都是数组中的无符号32位整数.
如何在单个cpu核心上快5倍左右?
当前代码:
#include <iostream>
#include <vector>
#include <time.h>
using namespace std;
template <typename T>
uint32_t bitarray2set(T& v, uint32_t * ptr_set){
uint32_t i;
uint32_t base = 0;
uint32_t * ptr_set_new = ptr_set;
uint32_t size = v.capacity();
for(i = 0; i < size; i++){
find_set_bit(v[i], ptr_set_new, base);
base += 8*sizeof(uint32_t);
}
return (ptr_set_new - ptr_set);
}
inline void find_set_bit(uint32_t n, uint32_t*& ptr_set, uint32_t base){
// Find the set bits …
Run Code Online (Sandbox Code Playgroud) 我想围绕 x86 指令PDEP(并行位存储)和PEXT(并行位提取)创建一个包装器。在这些不可用的架构上(并且相应的内在函数也不可用),我需要一个快速的回退实现。
32 位整数的朴素算法如下所示:
constexpr std::uint32_t bit_deposit(std::uint32_t src, std::uint32_t mask) {
std::uint32_t result = 0;
for (std::uint32_t src_pos = 0, mask_pos = 0; mask_pos != 32; ++mask_pos) {
if (mask >> mask_pos & 1) {
result |= (src >> src_pos++ & 1) << mask_pos;
}
}
return result;
}
static_assert(bit_deposit(0b000, 0b000000) == 0b000000);
static_assert(bit_deposit(0b101, 0b101010) == 0b100010);
static_assert(bit_deposit(0b111, 0b101010) == 0b101010);
Run Code Online (Sandbox Code Playgroud)
constexpr std::uint32_t bit_extract(std::uint32_t src, std::uint32_t mask) {
std::uint32_t result = 0;
for (std::uint32_t src_pos = …
Run Code Online (Sandbox Code Playgroud) 是否有一个内在函数可以在输入数组中的所有位置设置单个值,其中相应位置在提供的 BitMask 中具有 1 位?
10101010 是位掩码
值为 121
它将设置位置 0,2,4,6 值为 121
我最近对理解低级计算很感兴趣.据我所知,今天广泛使用的计算机遵循x86/x86-64架构.
据我所知,架构,更具体地说,指令集架构(ISA)是程序员能够向CPU发出的指令集.
第一个问题,ISA是不断发展还是保持不变?
我认为它不断发展(意味着新指令不断被添加/先前的指令被修改?)但是旧的处理器如何能够执行用新指令编写的代码?(它不知道新的指令,但应该能够执行代码,因为它具有x86架构).编译器是处理这个东西还是处理器?基本上,相同的指令集如何能够在所有处理器上运行,无论是旧的还是新的?
最后,除了微体系结构,这不是程序员的关注(如果我错了,请纠正我),程序员在处理新处理器时会看到哪些变化?由于微体系结构的变化,旧的指令可能因为有效的实现而快速运行.但是,是否引入了新的指令以允许以前无法完成的操作?或者之前可以用一堆指令做什么,但现在可以通过硬件的变化来完成一个?新的寄存器?还要别的吗?
它是否完成了 - 如果处理器支持这个新的强大指令以加快执行速度,那么使用新指令,否则回退到较慢的旧指令.如果是,谁实现了这个if - else子句?编译器?如果不是,那会发生什么?