assembly 像PEXT这样的装配指令实际上是用来做什么的?

nvbavucw  于 2022-11-30  发布在  其他
关注(0)|答案(3)|浏览(307)

我在youtube上看了一个关于Top 10 Craziest Assembly Language Instructions的视频,其中一些指令对我来说没有明显的应用。像PEXT这样的指令有什么意义呢?它只从第二个参数中获取与第一个参数中1的索引相匹配的位?编译器如何知道何时使用此指令?关于无进位乘法的相同/相似问题。
免责声明:我对汇编语言知之甚少,甚至一无所知。也许我应该多读一些这方面的书!
我希望这个问题适合堆栈溢出。

bybem2ql

bybem2ql1#

您可以在本文中找到有关PDEP/PEXT硬件单元的一些应用
有许多新兴的应用,例如密码学、成像和生物测定学,需要更高级的位操作操作。虽然这些操作可以从更简单的逻辑和移位操作构建,但如果处理器能够支持更强大的位操作指令,则使用这些高级位操作操作的应用将显著加速。这些操作包括任意位置换、并行执行多个位字段提取操作,以及并行执行多个位字段存款操作。我们分别将这些操作称为置换(perm)、并行提取(pex)或位聚集,以及并行存放(pdep)或位分散操作。
Performing Advanced Bit Manipulations Efficiently in General-Purpose Processors
位置换在bitboards中非常常见,例如反向字节/字或镜像位数组。其中有lots of algorithms需要大量的位操作,在PEXT/PDEP时代之前人们不得不使用get creative to do that。后来许多纸牌游戏引擎也使用这种技术来处理单个游戏集,只需一个或几个寄存器
PDEP/PEXT还用于大幅提高位交织性能,这在Morton code等算法中很常见。

为位板发明的乘法技术也常用于Bit Twiddling Hacks中的许多算法,例如用64位乘法交错位。当PDEP/PEXT可用时,不再需要该技术
您可以在位排列和Hacker's Delight中找到更多详细信息
PDEP/PEXT的另一个用途是提取/合并位不在相邻位置的字段,例如将其中immediates scatter around分解为make hardware design simpler的RISC-V指令,但也使其在没有PDEP/PEXT的软件上工作时有点混乱
其他一些应用:
我认为pext / pdep指令对4-着色问题、3-SAT、约束求解器等有巨大的影响。更多的研究人员可能应该研究这两个指令。
只要看看二元决策图和其他类似的组合数据结构,就可以清楚地看到PEXT / PDEP的潜在用途。
https://news.ycombinator.com/item?id=19137260
编译器如何知道何时使用此指令?
编译器可以识别常见模式并优化指令序列,但对于像这样的高级操作,程序员通常需要从高级代码中显式调用内部函数

ghhkc1vu

ghhkc1vu2#

PDEP(Parallel Deposit,并行存放)和PEXT(Parallel Extract,并行提取)是一种提取和存放位字段的便捷方式,我敢打赌它们有很好的底层用例。
对于实际应用-我写了一个数独解算器,它在两个函数中使用PEXT来提取位值。多亏了PEXT,我能够在一个指令中提取4个元素(而普通方法只提取1个)。这真的很方便。如果你真的需要,我可以在编译器资源管理器上放一个代码片段来显示区别。

wgxvkvu9

wgxvkvu93#

以下内容与PDEP / PEXT的使用没有直接关系,因为它与性能有关,但它会影响其使用是否有意义。我在Windows 11下使用Zen 2 Ryzen Threadripper 3990 X CPU,并在Windows下使用MSVC和Intel C的内部函数以及在Linux下使用clang和g测试了PDEP和PEXT的吞吐量。以下是代码:

#include <iostream>
#include <vector>
#include <chrono>
#include <random>
#include <cstdint>
#include <atomic>
#if defined(_MSC_VER)
    #include <intrin.h>
#elif defined(__GNUC__) || defined(__llvm__)
    #include <immintrin.h>
#endif

using namespace std;
using namespace chrono;

atomic_uint64_t aSum( 0 );

int main()
{
    constexpr size_t
        N = 0x1000,
        ROUNDS = 10'000;
    vector<uint64_t> data( N, 0 );
    mt19937_64 mt;
    uniform_int_distribution<uint64_t> uid( 0, -1 );
    for( uint64_t &d : data )
        d = uid( mt );
    auto pdep = []( uint64_t data, uint64_t mask ) -> uint64_t { return _pdep_u64( data, mask ); };
    auto pext = []( uint64_t data, uint64_t mask ) -> uint64_t { return _pext_u64( data, mask ); };
    auto bench = [&]<typename Permute>( Permute permute ) -> double
    {
        uint64_t sum = 0;
        auto start = high_resolution_clock::now();
        constexpr uint64_t MASK = 0x5555555555555555u;
        for( size_t r = ROUNDS; r--; )
            for( uint64_t d : data )
                sum += permute( d, MASK );
        double ns = (double)(int64_t)duration_cast<nanoseconds>( high_resolution_clock::now() - start ).count() / ((double)N * ROUNDS);
        ::aSum = sum;
        return ns;
    };
    cout << bench( pdep ) << endl;
    cout << bench( pext ) << endl;
}

根据www.example.com上的数据agner.orgPDEP / PEXT在我的Zen 2 CPU上的延迟和吞吐量应该略低于20个时钟周期。在英特尔上,自Haswell CPU以来,延迟只有3个时钟周期,吞吐量高达一个时钟周期。
但是根据我的测量,每条指令大约需要35 ns,也就是说,在我的CPU上大约需要150个时钟周期。没有测量错误,而且我检查的反汇编与您在汇编中编写的内容相匹配。所以我对其他CPU的数据很好奇。也许您会在这里报告它。这将有助于评估PDEP或PEXT的使用是否有意义。

相关问题