- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
我想在宽度为 128、256 或 512 位的 CPU 寄存器(xmm、ymm 或 zmm)上执行单个位、位对和半字节(4 位)的任意排列;这应该尽可能快。为此,我正在研究 SIMD 指令。有谁知道执行此操作的方法/实现它的库?我在 Windows 上使用 MSVC,在 Linux 上使用 GCC,宿主语言是 C 或 C++。谢谢!
我得到了一个任意排列,需要打乱大量的位 vector/位 vector 对/半字节。我知道如何为 64 位值中的位执行此操作,例如using a Benes network .
或者在更宽的 SIMD 寄存器上混洗 8 位和更大的 block ,例如将 Agner Fog 的 GPLed VectorClass 库 ( https://www.agner.org/optimize/vectorclass.pdf ) 用于模板元编程函数,该函数根据 AVX2 channel 内字节洗牌和/或更大元素的 channel 交叉洗牌构建洗牌,将洗牌作为模板参数。
不过,更细粒度的排列 segmentation (分为 1、2 或 4 位 block )似乎很难跨宽 vector 实现。
我能够对排列进行预处理,例如提取位掩码,根据需要计算索引,例如对于 Benes 网络或其他任何东西 - 也很乐意用另一种高级语言来做到这一点,因此假设排列以最方便解决问题的任何格式给出;包括小型查找表。
我希望代码比做类似的事情快得多
// actually 1 bit per element, not byte. I want a 256-bit bit-shuffle
const uint8_t in[256] = get_some_vector(); // not a compile-time constant
const uint8_t perm[256] = ...; // compile-time constant
uint8_t out[256];
for (size_t i = 0; i < 256; i ++)
out[i] = in[perm[i]];
正如我所说,我有一个 <= 64 位的解决方案(即 64 位、32 位对和 16 个半字节)。对于更宽的 SIMD 寄存器上大小为 8、16、32 等的 block ,该问题也已解决。
编辑:澄清一下,排列是一个编译时常量(但不仅仅是一个特定的排列,我将对每个给定的排列编译一次程序)。
最佳答案
AVX2 256 位置换案例
我认为不可能编写一个高效的通用 SSE4/AVX2/AVX-512 算法适用于所有 vector 大小(128、256、512 位)和元素粒度(位、位对、半字节、字节)。一个问题是存在许多 AVX2 指令例如,对于字节大小的元素,双字元素不存在,反之亦然。
下面讨论 AVX2 256 位排列的情况。可以将此案例的想法用于其他案例。
想法是每步从输入 vector x
中提取 32(排列)位。在每个步骤中,从置换 vector pos
中读取 32 个字节。这些 pos
字节的位 7..3 确定需要来自 x
的哪个字节。正确的字节由模拟的 256 位宽 AVX2 channel 交叉字节选择洗牌 coded here by Ermlg 。pos
字节的位 2..0 确定要查找的位。使用 _mm256_movemask_epi8
,32 位被收集在一个 _uint32_t
中重复此步骤8次,得到所有256个置换位。
代码看起来不是很优雅。尽管如此,我还是会感到惊讶如果明显更快,比如快两倍,AVX2 方法就会存在。
/* gcc -O3 -m64 -Wall -mavx2 -march=skylake bitperm_avx2.c */
#include <immintrin.h>
#include <stdio.h>
#include <stdint.h>
inline __m256i shuf_epi8_lc(__m256i value, __m256i shuffle);
int print_epi64(__m256i a);
uint32_t get_32_bits(__m256i x, __m256i pos){
__m256i pshufb_mask = _mm256_set_epi8(0,0,0,0, 0,0,0,0, 128,64,32,16, 8,4,2,1, 0,0,0,0, 0,0,0,0, 128,64,32,16, 8,4,2,1);
__m256i byte_pos = _mm256_srli_epi32(pos, 3); /* which byte within the 32 bytes */
byte_pos = _mm256_and_si256(byte_pos, _mm256_set1_epi8(0x1F)); /* mask off the unwanted bits */
__m256i bit_pos = _mm256_and_si256(pos, _mm256_set1_epi8(0x07)); /* which bit within the byte */
__m256i bit_pos_mask = _mm256_shuffle_epi8(pshufb_mask, bit_pos); /* get bit mask */
__m256i bytes_wanted = shuf_epi8_lc(x, byte_pos); /* get the right bytes */
__m256i bits_wanted = _mm256_and_si256(bit_pos_mask, bytes_wanted); /* apply the bit mask to get rid of the unwanted bits within the byte */
__m256i bits_x8 = _mm256_cmpeq_epi8(bits_wanted, bit_pos_mask); /* check if the bit is set */
return _mm256_movemask_epi8(bits_x8);
}
__m256i get_256_bits(__m256i x, uint8_t* pos){ /* glue the 32 bit results together */
uint64_t t0 = get_32_bits(x, _mm256_loadu_si256((__m256i*)&pos[0]));
uint64_t t1 = get_32_bits(x, _mm256_loadu_si256((__m256i*)&pos[32]));
uint64_t t2 = get_32_bits(x, _mm256_loadu_si256((__m256i*)&pos[64]));
uint64_t t3 = get_32_bits(x, _mm256_loadu_si256((__m256i*)&pos[96]));
uint64_t t4 = get_32_bits(x, _mm256_loadu_si256((__m256i*)&pos[128]));
uint64_t t5 = get_32_bits(x, _mm256_loadu_si256((__m256i*)&pos[160]));
uint64_t t6 = get_32_bits(x, _mm256_loadu_si256((__m256i*)&pos[192]));
uint64_t t7 = get_32_bits(x, _mm256_loadu_si256((__m256i*)&pos[224]));
uint64_t t10 = (t1<<32)|t0;
uint64_t t32 = (t3<<32)|t2;
uint64_t t54 = (t5<<32)|t4;
uint64_t t76 = (t7<<32)|t6;
return(_mm256_set_epi64x(t76, t54, t32, t10));
}
inline __m256i shuf_epi8_lc(__m256i value, __m256i shuffle){
/* Ermlg's lane crossing byte shuffle https://stackoverflow.com/a/30669632/2439725 */
const __m256i K0 = _mm256_setr_epi8(
0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0);
const __m256i K1 = _mm256_setr_epi8(
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70);
return _mm256_or_si256(_mm256_shuffle_epi8(value, _mm256_add_epi8(shuffle, K0)),
_mm256_shuffle_epi8(_mm256_permute4x64_epi64(value, 0x4E), _mm256_add_epi8(shuffle, K1)));
}
int main(){
__m256i input = _mm256_set_epi16(0x1234,0x9876,0x7890,0xABCD, 0x3456,0x7654,0x0123,0x4567,
0x0123,0x4567,0x89AB,0xCDEF, 0xFEDC,0xBA98,0x7654,0x3210);
/* Example */
/* 240 224 208 192 176 160 144 128 112 96 80 64 48 32 16 0 */
/* input 1234 9876 7890 ABCD | 3456 7654 0123 4567 | 0123 4567 89AB CDEF | FEDC BA98 7654 3210 */
/* output 0000 0000 0012 00FF | 90AB 3210 7654 ABCD | 8712 1200 FF90 AB32 | 7654 ABCD 1087 7654 */
uint8_t permutation[256] = {16,17,18,19, 20,21,22,23, 24,25,26,27, 28,29,30,31,
28,29,30,31, 32,33,34,35, 0,1,2,3, 4,5,6,7,
72,73,74,75, 76,77,78,79, 80,81,82,83, 84,85,86,87,
160,161,162,163, 164,165,166,167, 168,169,170,171, 172,173,174,175,
8,9,10,11, 12,13,14,15, 200,201,202,203, 204,205,206,207,
208,209,210,211, 212,213,214,215, 215,215,215,215, 215,215,215,215,
1,1,1,1, 1,1,1,1, 248,249,250,251, 252,253,254,255,
248,249,250,251, 252,253,254,255, 28,29,30,31, 32,33,34,35,
72,73,74,75, 76,77,78,79, 80,81,82,83, 84,85,86,87,
160,161,162,163, 164,165,166,167, 168,169,170,171, 172,173,174,175,
0,1,2,3, 4,5,6,7, 8,9,10,11, 12,13,14,15,
200,201,202,203, 204,205,206,207, 208,209,210,211, 212,213,214,215,
215,215,215,215, 215,215,215,215, 1,1,1,1, 1,1,1,1,
248,249,250,251, 252,253,254,255, 1,1,1,1, 1,1,1,1,
1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
printf("input = \n");
print_epi64(input);
__m256i x = get_256_bits(input, permutation);
printf("permuted input = \n");
print_epi64(x);
return 0;
}
int print_epi64(__m256i a){
uint64_t v[4];
int i;
_mm256_storeu_si256((__m256i*)v,a);
for (i = 3; i>=0; i--) printf("%016lX ",v[i]);
printf("\n");
return 0;
}
示例排列的输出看起来是正确的:
$ ./a.out
input =
123498767890ABCD 3456765401234567 0123456789ABCDEF FEDCBA9876543210
permuted input =
00000000001200FF 90AB32107654ABCD 87121200FF90AB32 7654ABCD10877654
效率
如果你仔细看算法,你会发现一些操作只取决于置换 vector pos
,而不是 x
。这意味着应用使用变量 x
和固定 pos
的排列应该更有效而不是对变量 x
和 pos
应用排列。
下面的代码说明了这一点:
/* apply the same permutation several times */
int perm_array(__m256i* restrict x_in, uint8_t* restrict pos, __m256i* restrict x_out){
for (int i = 0; i<1024; i++){
x_out[i]=get_256_bits(x_in[i], pos);
}
return 0;
}
使用 clang 和 gcc 这编译为真正 nice code : Loop .L5
at line 237 only contains 16vpshufb
s 而不是 24。此外,vpaddb
s 被提升到循环之外。请注意,循环内也只有一个 vpermq
。
我不知道 MSVC 是否会在循环外提升这么多指令。如果没有,它可能是可能的通过手动修改代码来提高循环的性能。这应该这样做仅依赖于 pos
而不是 x
的操作被提升到循环之外。
关于 Intel Skylake 的性能:此循环的吞吐量可能受限于每个循环迭代大约 32 个端口 5 微操作。这意味着吞吐量在诸如 perm_array
之类的循环上下文中,每 32 个 CPU 周期大约有 256 个置换位,或每个 CPU 周期大约 8 个置换位。
使用 AVX2 指令的 128 位排列
此代码与 256 位排列的情况非常相似。虽然只排列了 128 位,但 AVX2 的完整 256 位宽度寄存器用于实现最佳性能。这里没有模拟字节洗牌。这是因为存在进行字节改组的有效单条指令在 128 位 channel 内:vpshufb
。
函数 perm_array_128
测试位排列的性能对于固定排列和可变输入 x
。汇编循环包含大约 11 个端口 5 (p5) 微操作,如果我们假设一个 Intel Skylake CPU。这 11 个 p5 微操作至少需要 11 个 CPU 周期(吞吐量)。因此,在最好的情况下,我们的吞吐量约为每个周期 12 个置换位,大约是 256 位置换情况的 1.5 倍。
/* gcc -O3 -m64 -Wall -mavx2 -march=skylake bitperm128_avx2.c */
#include <immintrin.h>
#include <stdio.h>
#include <stdint.h>
int print128_epi64(__m128i a);
uint32_t get_32_128_bits(__m256i x, __m256i pos){ /* extract 32 permuted bits out from 2x128 bits */
__m256i pshufb_mask = _mm256_set_epi8(0,0,0,0, 0,0,0,0, 128,64,32,16, 8,4,2,1, 0,0,0,0, 0,0,0,0, 128,64,32,16, 8,4,2,1);
__m256i byte_pos = _mm256_srli_epi32(pos, 3); /* which byte do we need within the 16 byte lanes. bits 6,5,4,3 select the right byte */
byte_pos = _mm256_and_si256(byte_pos, _mm256_set1_epi8(0xF)); /* mask off the unwanted bits (unnecessary if _mm256_srli_epi8 would have existed */
__m256i bit_pos = _mm256_and_si256(pos, _mm256_set1_epi8(0x07)); /* which bit within the byte */
__m256i bit_pos_mask = _mm256_shuffle_epi8(pshufb_mask, bit_pos); /* get bit mask */
__m256i bytes_wanted = _mm256_shuffle_epi8(x, byte_pos); /* get the right bytes */
__m256i bits_wanted = _mm256_and_si256(bit_pos_mask, bytes_wanted); /* apply the bit mask to get rid of the unwanted bits within the byte */
__m256i bits_x8 = _mm256_cmpeq_epi8(bits_wanted, bit_pos_mask); /* set all bits if the wanted bit is set */
return _mm256_movemask_epi8(bits_x8); /* move most significant bit of each byte to 32 bit register */
}
__m128i permute_128_bits(__m128i x, uint8_t* pos){ /* get bit permutations in 32 bit pieces and glue them together */
__m256i x2 = _mm256_broadcastsi128_si256(x); /* broadcast x to the hi and lo lane */
uint64_t t0 = get_32_128_bits(x2, _mm256_loadu_si256((__m256i*)&pos[0]));
uint64_t t1 = get_32_128_bits(x2, _mm256_loadu_si256((__m256i*)&pos[32]));
uint64_t t2 = get_32_128_bits(x2, _mm256_loadu_si256((__m256i*)&pos[64]));
uint64_t t3 = get_32_128_bits(x2, _mm256_loadu_si256((__m256i*)&pos[96]));
uint64_t t10 = (t1<<32)|t0;
uint64_t t32 = (t3<<32)|t2;
return(_mm_set_epi64x(t32, t10));
}
/* Test loop performance with the following loop (see assembly) -> 11 port5 uops inside the critical loop */
/* Use gcc -O3 -m64 -Wall -mavx2 -march=skylake -S bitperm128_avx2.c to generate the assembly */
int perm_array_128(__m128i* restrict x_in, uint8_t* restrict pos, __m128i* restrict x_out){
for (int i = 0; i<1024; i++){
x_out[i]=permute_128_bits(x_in[i], pos);
}
return 0;
}
int main(){
__m128i input = _mm_set_epi16(0x0123,0x4567,0xFEDC,0xBA98, 0x7654,0x3210,0x89AB,0xCDEF);
/* Example */
/* 112 96 80 64 48 32 16 0 */
/* input 0123 4567 FEDC BA98 7654 3210 89AB CDEF */
/* output 8FFF CDEF DCBA 08EF CDFF DCBA EFF0 89AB */
uint8_t permutation[128] = {16,17,18,19, 20,21,22,23, 24,25,26,27, 28,29,30,31,
32,32,32,32, 36,36,36,36, 0,1,2,3, 4,5,6,7,
72,73,74,75, 76,77,78,79, 80,81,82,83, 84,85,86,87,
0,0,0,0, 0,0,0,0, 8,9,10,11, 12,13,14,15,
0,1,2,3, 4,5,6,7, 28,29,30,31, 32,33,34,35,
72,73,74,75, 76,77,78,79, 80,81,82,83, 84,85,86,87,
0,1,2,3, 4,5,6,7, 8,9,10,11, 12,13,14,15,
1,1,1,1, 1,1,1,1, 1,1,1,1, 32,32,32,1};
printf("input = \n");
print128_epi64(input);
__m128i x = permute_128_bits(input, permutation);
printf("permuted input = \n");
print128_epi64(x);
return 0;
}
int print128_epi64(__m128i a){
uint64_t v[2];
int i;
_mm_storeu_si128((__m128i*)v,a);
for (i = 1; i>=0; i--) printf("%016lX ",v[i]);
printf("\n");
return 0;
}
一些任意排列的示例输出:
$ ./a.out
input =
01234567FEDCBA98 7654321089ABCDEF
permuted input =
8FFFCDEFDCBA08EF CDFFDCBAEFF089AB
关于c++ - 使用 SIMD 指令执行任意 128/256/512 位排列的最快方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54408726/
我正在尝试在现有指令的基础上构建一个新指令,但我在我的过程中停止了。加载页面时,我面临以下错误: Multiple directives [directive#1, directive#2] aski
我是 angularjs 世界的新手,我只需要在数字类型的输入中输入从 1 到 10 的数字。不使用 HTML5 的 min 和 max 属性 我在 Jquery 中找到了一个示例,能否帮我将其转换为
我想使用 ionic与 Material 设计。我被困在使用带有自定义 CSS 的 ionic 指令和 angular-material 之间。 我读过使用 ionic 指令我们得到了很多高效的特性,
我创建了以下代码: var node = document.getElementById('TreeList'); var keys = Object.keys(model[0]); var trac
在 AngularJs 中没有提供 ng-enabled 指令。是否有任何适当的理由不在框架中提供该指令,因为当您可以使用 ng- 时,我们同时拥有 ng-show 和 ng-hide隐藏来实现我们的
我最近制作的程序有问题。基本上,它是 John Conway 人生游戏的简单版本,但它运行不正常。问题出在读取单元格及其邻居的状态并决定该单元格的 future 状态的代码中。这是代码的一部分(有点长
Dockerfile reference关于 FROM 指令的内容如下: FROM can appear multiple times within a single Dockerfile in or
我一直在尝试理解指令中孤立作用域和继承作用域之间的区别。这是我准备让自己理解的一个例子: HTML Inside isolated scope directive: {{m
知道如何从指令内部访问属性值吗? angular.module('portal.directives', []) .directive('languageFlag', ['$r
我正在通过将 c 程序与其等价的汇编程序进行比较来学习汇编。 这是代码。 .file "ex3.c" .section .rodata .LC0: .string "I am %d
我正在尝试写一个 Jenkinsfile并行执行一系列步骤。目标是拥有两个 agents (又名。 nodes )。一个应该进行 Windows 构建,另一个应该进行 linux 构建。但是,我不希望
我想知道为什么指令 FYL2XP1在 x86 架构上精确计算数学公式 y · log2(x + 1)。 这个公式有什么特别之处? 最佳答案 y操作数通常是编译时常量,暂时忘记 x + 1 . 自 lo
这个问题已经有答案了: Parameterize an SQL IN clause (41 个回答) 已关闭 8 年前。 第一个声明: Select GroupMember FROM Group 结果
我从 this question fork 并编辑了一个 plunker 我想做的是在数据加载后更新/填充 SELECT 元素(组合框),但有些事情不对劲。我检索数据,它位于 SELECT 元素的范围
我想创建一个简单的 markdown 指令,它接受元素中的一些内容,解析它并用 html 替换它。 所以这样: #Heading 或这个(其中 $scope.heading = '#Heading';
我对 Ansible 还很陌生,对于我对 local_action 指令的理解有一个简单的问题。 这是否意味着该命令完全在本地执行?假设你有这样的东西: local_action: command w
我有以下 HTML: ... ... 以及以下指令: myApp.directive('specialInput', ['$timeout', function($timeout)
如何在 .htaccess 中创建 Apache 指令强制文件 .mp4和 .pdf去下载?目前它们出现在浏览器窗口中。相反,我希望出现一个下载文件对话框。 最佳答案 将以下内容添加到 .htacce
我的问题是关于 C 中的 fork() 指令。我有以下程序: void main(){ int result, status; result = fork(); if(result=
我想要一个类似于 ng-model 的属性指令。我只想另外将一个输入字段值绑定(bind)到一个范围变量(只是在一个方向输入字段 ->范围变量)。所以我刚刚尝试了这个指令,但无论如何我都无法调用该指令
我是一名优秀的程序员,十分优秀!