使用gcc进行uint32_t * uint32_t = uint64_t向量乘法

6

我正在尝试将uint32_t向量相乘,生成一个uint64_t向量,使用gcc编译时会出现问题。 我期望的结果是gcc生成单个的VPMULUDQ指令。但是,gcc输出的代码是对源向量中每个uint32_t进行可怕的洗牌,并进行完整的64 * 64 = 64乘法运算。以下是我的尝试:

#include <stdint.h>

typedef uint32_t v8lu __attribute__ ((vector_size (32)));
typedef uint64_t v4llu __attribute__ ((vector_size (32)));

v4llu mul(v8lu x, v8lu y) {
    x[1] = 0; x[3] = 0; x[5] = 0; x[7] = 0;
    y[1] = 0; y[3] = 0; y[5] = 0; y[7] = 0;
    return (v4llu)x * (v4llu)y;
}

首先需要对 uint32_t 向量进行屏蔽以排除不需要的部分,希望 GCC 能够优化 64*64=64 乘法中不必要的部分,然后发现屏蔽也是无意义的。但很遗憾,这并没有发生。

v4llu mul2(v8lu x, v8lu y) {
    v4llu tx = {x[0], x[2], x[4], x[6]};
    v4llu ty = {y[0], y[2], y[4], y[6]};
    return tx * ty;
}

在这里,我尝试从头开始创建一个uint64_t向量,并仅设置已使用的部分。同样,gcc应该看到每个uint64_t的前32位都为0,不会执行完整的64*64=64乘法。相反,会发生大量的提取和值的重新插入,以及64*64=64乘法。

v4llu mul3(v8lu x, v8lu y) {
    v4llu t = {x[0] * (uint64_t)y[0], x[2] * (uint64_t)y[2], x[4] * (uint64_t)y[4], x[6] * (uint64_t)y[6]};
    return t;
}

我们通过乘法将各部分构建为结果向量。或许gcc会看到可以使用VPMULUDQ来实现这一点,但不走运的是,它退回到4个IMUL操作码。

有没有办法告诉gcc我想要它做什么(32*32=64乘法,所有东西都完美地放置)?

注意:内联汇编或内置函数不是答案。手动编写操作码显然可行。但那样就需要针对许多目标体系结构和特性集编写不同版本的代码。我希望gcc能够理解问题并从单个源代码生成正确的解决方案。


7
如果你只是想知道如何让GCC做你想要的事情,为什么不使用@Ben提出的内置函数呢?依靠创建某种代码模式,使你正在使用的版本的GCC能够识别并发出你想要的代码,这种方法似乎很容易出错。如果你想确保它能正常工作,就使用显式指定意图的内置函数。 - Jason R
2
@GoswinvonBrederlow:为什么不使用内在函数?如果它能够满足你的需求,为什么不用呢? - Eric Postpischil
2
mulmul2在使用clang进行优化后表现良好:https://godbolt.org/z/d3MAay,但是`mul3`不等效,因为它需要将结果截断为32位。我猜你的选择是:a)使用clang,b)使用内置函数,c)提供一个补丁给gcc以正确地优化它(或者提交一个错误报告并希望有人修复它)。 - chtz
3
@Benпјҡж ҮеҮҶзҡ„еҸҜ移жӨҚеӣәжңүеҮҪж•°жҳҜ_mm256_mul_epu32пјҢеңЁimmintrin.hдёӯе®ҡд№үгҖӮ - Peter Cordes
2
@GoswinvonBrederlow:“这不是我想要的”和“如果我想使用内置函数,我早就这么做了”不是合理的理由。“因为我们需要支持许多不同的目标架构,并且为每个编写单独的代码成本太高”是可以接受的。根据实际项目需求编辑问题,而不是基于“想要”的需求来说明您的完整要求。 - Eric Postpischil
显示剩余8条评论
1个回答

2

正如chtz在评论中指出的那样,mul1和mul2都已经被clang进行了优化。类似于mul3但使用for循环的代码也将被优化(但不会像mul1和mul2一样好)。

因此,在我看来,语法正确地表达了代码应该做什么,而gcc目前缺乏足够的智能来正确地进行优化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接