使用联合(封装在结构中)绕过霓虹灯数据类型的转换

Posted

技术标签:

【中文标题】使用联合(封装在结构中)绕过霓虹灯数据类型的转换【英文标题】:Using an union (encapsulated in a struct) to bypass conversions for neon data types 【发布时间】:2015-03-23 11:05:27 【问题描述】:

我的第一个方法是使用 SSE 的矢量化内在函数,其中基本上只有一个数据类型 __m128i。切换到 Neon 我发现数据类型和函数原型更加具体,例如uint8x16_t(16 个 unsigned char 的向量)、uint8x8x2_t(2 个向量,每个向量 8 个 unsigned char)、uint32x4_t(一个向量,4 个 uint32_t)等。

首先我很热情(更容易找到对所需数据类型进行操作的确切函数),然后我看到想要以不同方式处理数据时会变得多么混乱。使用specific casting operators 会带我永远。这个问题也解决了here。然后我想到了将联合封装到结构中的想法,以及一些强制转换和赋值运算符。

struct uint_128bit_t  union 
        uint8x16_t uint8x16;
        uint16x8_t uint16x8;
        uint32x4_t uint32x4;
        uint8x8x2_t uint8x8x2;
        uint8_t uint8_array[16] __attribute__ ((aligned (16) ));
        uint16_t uint16_array[8] __attribute__ ((aligned (16) ));
        uint32_t uint32_array[4] __attribute__ ((aligned (16) ));
    ;

    operator uint8x16_t& () return uint8x16;
    operator uint16x8_t& () return uint16x8;
    operator uint32x4_t& () return uint32x4;
    operator uint8x8x2_t& () return uint8x8x2;
    uint8x16_t& operator =(const uint8x16_t& in) uint8x16 = in; return uint8x16;
    uint8x8x2_t& operator =(const uint8x8x2_t& in) uint8x8x2 = in; return uint8x8x2;

;

这种方法对我有用:我可以使用 uint_128bit_t 类型的变量作为参数并使用不同的 Neon 内在函数进行输出,例如vshlq_n_u32vuzp_u8vget_low_u8(在这种情况下只是作为输入)。如果需要,我可以使用更多数据类型对其进行扩展。 注意:数组是为了方便打印变量的内容。

这是正确的处理方式吗? 有没有隐藏的缺陷? 我是否重新发明了***? (对齐属性是必须的吗?)

【问题讨论】:

看看这个问题(以及最上面的答案):***.com/questions/2310483/… @gurka 是的,谢谢,很清楚! 【参考方案1】:

根据 C++ 标准,这种数据类型几乎是无用的(当然对于您想要的目的而言也是如此)。那是因为从联合的非活动成员中读取是未定义的行为。

但是,您的编译器可能会承诺使这项工作正常进行。但是,您没有询问任何特定的编译器,因此无法进一步评论。

【讨论】:

感谢您指出这一点!我添加了标签 gcc。 “不活跃的工会成员”是什么意思?尚未“明确触及”的联合术语? 认为它声明您只能从最后写入的成员中读取。因此,如果您有一个包含两个成员的联合:foobar 并向foo 写入一些内容,那么您只能读取foo - 从bar 读取将是未定义的行为。【参考方案2】:

由于最初提出的方法有undefined behaviour in C++,我已经实现了这样的东西:

template <typename T>
struct NeonVectorType 

    private:
    T data;

    public:
    template <typename U>
    operator U () 
        BOOST_STATIC_ASSERT_MSG(sizeof(U) == sizeof(T),"Trying to convert to data type of different size");
        U u;
        memcpy( &u, &data, sizeof u );
        return u;
    

    template <typename U>
    NeonVectorType<T>& operator =(const U& in) 
        BOOST_STATIC_ASSERT_MSG(sizeof(U) == sizeof(T),"Trying to copy from data type of different size");
        memcpy( &data, &in, sizeof data );
        return *this;
    

;

然后:

typedef NeonVectorType<uint8x16_t> uint_128bit_t; //suitable for uint8x16_t, uint8x8x2_t, uint32x4_t, etc.
typedef NeonVectorType<uint8x8_t> uint_64bit_t; //suitable for uint8x8_t, uint32x2_t, etc.

here(和here)讨论了 memcpy 的使用,并避免违反严格的别名规则。注意in general it gets optimized away。

如果您查看编辑历史记录,我已经实现了一个自定义版本,其中包含向量向量的组合运算符(例如 uint8x8x2_t)。提到了这个问题here。然而,由于这些数据类型被声明为数组(参见guide,第 12.2.2 节)并因此位于连续的内存位置,编译器必然会正确处理memcpy

最后,要打印变量的内容,可以使用a function like this。

【讨论】:

这也是未定义的行为——违反了严格的别名规则。 你完全错了。严格的别名不是关于语法,而是关于有指向你不应该做的事情的指针。您的选角技巧与 OP 的工会技巧一样糟糕且未定义。 @Antonio:实际上,所有这些。当 T 和 U 不满足高度具体的要求时,这种指针转换是完全未定义的行为,基本上只是 char 相关的。 @Antonio:您是否在寻找解释双关语类型和严格别名规则的资源时遇到困难? Stack Overflow 上有很多问题,有一些非常详细的答案。 @Antonio:您冒的风险是认为严格的混叠违规被定义为在某些情况下未被注意到的更改,从而导致使用陈旧的值。但是严格的混叠违规并没有以这种方式或任何其他方式定义。它们是未定义的行为。编译器不需要诊断所有未定义行为的实例。【参考方案3】:

如果您尝试避免通过各种数据结构骇客以明智的方式进行转换,您最终会改组内存/单词,这将扼杀您希望从 NEON 获得的任何性能。

您可以轻松地将四寄存器转换为双寄存器,但其他方式可能是不可能的。

一切都归结为这一点。在每条指令中都有几个位来索引寄存器。如果指令需要 Quad 寄存器,它将像 Q(2*n)、Q(2*n+1) 一样对寄存器进行二乘二计数,并且仅在编码指令中使用 n,(2*n+1) 将隐含在内核中.如果您的代码中的任何一点您试图将两个双精度转换为一个四边形,您可能处于一个位置,即那些不是连续的强制编译器将寄存器混洗到堆栈中并返回以获得连续的布局。

我觉得还是一样的答案https://***.com/a/13734838/1163019

NEON 指令被设计为流式传输,您从内存中大块加载、处理它,然后存储您想要返回的内容。这应该都是非常简单的机制,否则你会失去它提供的额外性能,这会让人们问你为什么一开始就尝试使用 Neon,让自己的生活变得更加艰难。

将 NEON 视为不可变的值类型和操作。

【讨论】:

在汇编程序 NEON 指令或 SSE 内在函数中,完全避免了这种转换混乱。我正在做图像处理,我正在做的转换是(目前)整数到整数,所以我很安全。在我实现的答案中,指令数据类型混合得到了很好的管理(d 寄存器对总是耦合在q 寄存器中)并且所有 memcpys 都被优化掉了,生成的程序集看起来就像它应该的那样,我的剩下的疑问是我正在做的事情对我的使用是否完全安全。 我确实有使用霓虹灯的好处,例如允许编译器自动向量化时节省 75%,使用内在函数进行手动向量化时额外节省 33%(总共节省约 85%)。即使我缺少说明,并且在最后计算了 16 个值,其中只有 4 个是有效的,我将存储在最后。 @Antonio 在 SW 世界中没有人给出“完全安全”的保证。因此,如果它对您有用,请继续做您所做的事情。对于琐碎的情况,事情可能会奏效,对于复杂的情况,您可能需要格外小心/处理。 @auselan 我希望得到保证,除了编译器和Cosmic Rays 造成的错误 :) :) 我认为检查生产的组件就足够了。

以上是关于使用联合(封装在结构中)绕过霓虹灯数据类型的转换的主要内容,如果未能解决你的问题,请参考以下文章

C89:论结构体/枚举体/联合体的使用

c语言篇 +自定义类型(枚举联合结构体)以及位段

自定义类型:结构体,枚举,联合

如何使用霓虹内在函数准确地将 uchar 转换为 float32 ,反之亦然

Java实战之01Struts2-03属性封装类型转换数据验证

错误:“double”不是类、结构或联合类型 [关闭]