相关疑难解决方法(0)

使用进位标志进行高效的128位加法

我在我的C++代码的内部循环中使用了128位整数计数器.(不相关背景:实际应用是评估规则网格上的有限差分方程,其中涉及重复递增大整数,甚至64位也不够精确,因为小的舍入累积足以影响答案.)

我将整数表示为两个64位无符号长整数.我现在需要将这些值递增128位常数.这并不难,但你必须手动捕捉低字到高字的进位.

我有这样的工作代码:

inline void increment128(unsigned long &hiWord, unsigned long &loWord)
  {
    const unsigned long hiAdd=0x0000062DE49B5241;
    const unsigned long loAdd=0x85DC198BCDD714BA;

    loWord += loAdd;
    if (loWord < loAdd) ++hiWord; // test_and_add_carry
    hiWord += hiAdd;
  }
Run Code Online (Sandbox Code Playgroud)

这是一个紧凑而简单的代码.有用.

不幸的是,这大约是我运行时的20%.这条杀手线就是低价测试.如果我删除它,我显然得到了错误的答案,但运行时开销从20%下降到4%!因此携带测试特别昂贵!

我的问题:C++是否公开了硬件进位标志,即使是作为GCC的扩展?如果实际编译的指令使用最后一个进位指令进行添加,似乎可以在没有上面的测试和添加进位线的情况下完成添加.有没有办法重写test-and-add-carry行以使编译器使用内部操作码?

c++ assembly gcc bigint carryflag

39
推荐指数
2
解决办法
8350
查看次数

使用进位标志添加多字

GCC具有128位整数.使用这些我可以让编译器使用mul(或imul只有一个操作数)指令.例如

uint64_t x,y;
unsigned __int128 z = (unsigned __int128)x*y;
Run Code Online (Sandbox Code Playgroud)

生产mul.我用它来创建一个128x128到256的函数(在更新之前,请参阅此问题的结尾,如果您感兴趣,请参阅此代码).

现在我想要进行256位加法,ADC除了使用汇编之外,我还没有找到让编译器使用的方法.我可以使用汇编程序,但我想要内联函数以提高效率.编译器已经生成了一个有效的128x128到256函数(因为我在这个问题的开头解释了)所以我不明白为什么我应该在汇编中重写它(或者编译器已经有效实现的任何其他函数) .

这是我提出的内联汇编函数:

#define ADD256(X1, X2, X3, X4, Y1, Y2, Y3, Y4) \
 __asm__ __volatile__ ( \
 "addq %[v1], %[u1] \n" \
 "adcq %[v2], %[u2] \n" \
 "adcq %[v3], %[u3] \n" \
 "adcq %[v4], %[u4] \n" \
 : [u1] "+&r" (X1), [u2] "+&r" (X2), [u3] "+&r" (X3), [u4] "+&r" (X4) \
 : [v1]  "r" (Y1), [v2]  "r" (Y2), [v3]  "r" (Y3), [v4] …
Run Code Online (Sandbox Code Playgroud)

c x86 assembly gcc visual-c++

13
推荐指数
1
解决办法
2124
查看次数

GCC/x86内联asm:你怎么告诉gcc内联汇编部分会修改%esp?

在试图让一些旧代码再次运行时(https://github.com/chaos4ever/chaos/blob/master/libraries/system/system_calls.h#L387,FWIW)我发现一些语义gcc似乎已经改变了在最近10到15年间,以一种非常微妙但仍然危险的方式......:P

该代码曾用于旧版本gcc,如2.95.无论如何,这是代码:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter,
    tag_type *identification)
{
    return_type return_value;

    asm volatile("pushl %2\n"
                 "pushl %3\n"
                 "pushl %4\n"
                 "lcall %5, $0"
                 : "=a" (return_value),
                   "=g" (*service_parameter)
                 : "g" (identification),
                   "g" (service_parameter),
                   "g" (protocol_name),
                   "n" (SYSTEM_CALL_SERVICE_GET << 3));

    return return_value;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码的问题是gcc(在我的情况下为4.7)将编译为以下asm代码(AT&T语法):

# 392 "../system/system_calls.h" 1
pushl 68(%esp)  # This pointer (%esp + 0x68) is valid when the inline asm is entered.
pushl %eax
pushl 48(%esp)  # ...but this …
Run Code Online (Sandbox Code Playgroud)

c x86 assembly gcc inline-assembly

11
推荐指数
1
解决办法
1096
查看次数

何时在扩展GCC内联汇编中使用earlyclobber约束?

我知道何时使用补鞋匠列表(例如列出在程序集中修改的寄存器,以便它不被选择用作输入寄存器等),但我无法绕过早期约束条件&.如果列出输出,那是否已经意味着输入不能使用所选寄存器(除了匹配数字约束)?

例如:

asm(
    "movl $1, %0;"
    "addl $3, %0;"
    "addl $4, %1;"
    "addl %1, %0;"
    : "=g"(num_out)
    : "g"(num_in)
    :
);
Run Code Online (Sandbox Code Playgroud)

&甚至需要为输出变量?编译器应该知道为输出选择的寄存器,因此知道不要将它用于输入.

c assembly gcc inline-assembly

9
推荐指数
2
解决办法
1913
查看次数

使用内联汇编在数组上循环

当使用内联汇编循环数组时,我应该使用寄存器修饰符"r"还是内存修饰符"m"?

让我们考虑其将两个浮标阵为例x,与y和结果写入z.通常我会使用内在函数这样做

for(int i=0; i<n/4; i++) {
    __m128 x4 = _mm_load_ps(&x[4*i]);
    __m128 y4 = _mm_load_ps(&y[4*i]);
    __m128 s = _mm_add_ps(x4,y4);
    _mm_store_ps(&z[4*i], s);
}
Run Code Online (Sandbox Code Playgroud)

这是我使用寄存器修饰符"r"提出的内联汇编解决方案

void add_asm1(float *x, float *y, float *z, unsigned n) {
    for(int i=0; i<n; i+=4) {
        __asm__ __volatile__ (
            "movaps   (%1,%%rax,4), %%xmm0\n"
            "addps    (%2,%%rax,4), %%xmm0\n"
            "movaps   %%xmm0, (%0,%%rax,4)\n"
            :
            : "r" (z), "r" (y), "r" (x), "a" (i)
            :
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

这会产生与GCC类似的组装.主要区别在于GCC将16添加到索引寄存器并使用1的标度,而内联汇编解决方案将4添加到索引寄存器并使用4的标度.

我无法使用通用寄存器作为迭代器.在这种情况下,我必须指定一个rax.是否有一个原因?

这是我想出的使用内存修饰符"m"的解决方案

void add_asm2(float *x, float *y, …
Run Code Online (Sandbox Code Playgroud)

c assembly gcc inline-assembly

5
推荐指数
1
解决办法
1512
查看次数

具有内在和汇编的嵌入式广播

英特尔架构指令集扩展编程参考的2.5.3节"广播"中我们学习的比AVX512(和骑士角落)有

用于编码一些加载操作指令的数据广播的位字段,即从存储器加载数据并执行一些计算或数据移动操作的指令.

例如,使用英特尔汇编语法,我们可以在存储的地址广播标量,rax然后乘以16浮点数zmm2并将结果写入zmm1这样的

vmulps zmm1, zmm2, [rax] {1to16}
Run Code Online (Sandbox Code Playgroud)

但是,没有内在的东西可以做到这一点.因此,使用内在函数,编译器应该能够折叠

__m512 bb = _mm512_set1_ps(b);
__m512 ab = _mm512_mul_ps(a,bb);
Run Code Online (Sandbox Code Playgroud)

一个指令

vmulps zmm1, zmm2, [rax] {1to16}
Run Code Online (Sandbox Code Playgroud)

但我没有观察到GCC这样做.我发现了一个GCC错误报告.

我观察到与GCC类似的FMA.例如,GCC 4.9不会崩溃_mm256_add_ps(_mm256_mul_ps(areg0,breg0) 为单个fma指令-Ofast.但是,GCC 5.1确实将它崩溃为一个单一的fma.至少有内在函数可以用FMA做到这一点,例如_mm256_fmadd_ps.但是没有例如_mm512_mulbroad_ps(vector,scalar)内在的.

海湾合作委员会可能会在某个时候解决这个问题,但在此之前,装配是唯

所以我的问题是如何在GCC中进行内联汇编?

我想我可能已经为上面的例子提出了GCC内联汇编的正确语法(但我不确定).

"vmulps        (%%rax)%{1to16}, %%zmm1, %%zmm2\n\t"
Run Code Online (Sandbox Code Playgroud)

我真的在寻找这样的功能

static inline __m512 mul_broad(__m512 a, float b) {
    return a*b;
}
Run Code Online (Sandbox Code Playgroud)

如果b在内存中指向rax它产生

vmulps        (%rax){1to16}, %zmm0, %zmm0
ret
Run Code Online (Sandbox Code Playgroud)

如果b它在xmm1它产生

vbroadcastss …
Run Code Online (Sandbox Code Playgroud)

c assembly gcc intrinsics avx512

5
推荐指数
1
解决办法
653
查看次数

标签 统计

assembly ×6

gcc ×6

c ×5

inline-assembly ×3

x86 ×2

avx512 ×1

bigint ×1

c++ ×1

carryflag ×1

intrinsics ×1

visual-c++ ×1