在x86和ARM架构中,浮点数和整数的性能差异很大吗?

9
我想知道智能手机上的ARM浮点性能与x86相比如何。为此,我编写了以下代码:
#include "Linderdaum.h"
sEnvironment* Env = NULL;

volatile float af = 1.0f;
volatile float bf = 1.0f;
volatile int a = 1;
volatile int b = 1;

APPLICATION_ENTRY_POINT
{
    Env = new sEnvironment();

    Env->DeployDefaultEnvironment( "", "CommonMedia" );

    double Start = Env->GetSeconds();

    float Sum1 = 0.0f;

    for ( int i = 0; i != 200000000; i++ )    {        Sum1 += af + bf;    }

    double End = Env->GetSeconds();

    Env->Logger->Log( L_DEBUG, LStr::ToStr( Sum1, 4 ) );
    Env->Logger->Log( L_DEBUG, "Float: " + LStr::ToStr( End-Start, 5 ) );

    Start = Env->GetSeconds();

    int Sum2 = 0;

    for ( int i = 0; i != 200000000; i++ )    {       Sum2 += a + b;    }

    End = Env->GetSeconds();

    Env->Logger->Log( L_DEBUG, LStr::ToStr( Sum2, 4 ) );
    Env->Logger->Log( L_DEBUG, "Int: " + LStr::ToStr( End-Start, 5 ) );

    Env->RequestExit();

    APPLICATION_EXIT_POINT( Env );
}

APPLICATION_SHUTDOWN
{}

这是针对不同目标和编译器的结果。
1. 在 Core i7 920 上的 Windows PC。
VS 2008,调试构建,Win32/x86。
(Main):01:30:11.769   Float: 0.72119
(Main):01:30:12.347   Int: 0.57875

"floatint 慢。"
"VS 2008,调试版本,Win64/x86-64"
(Main):01:43:39.468   Float: 0.72247
(Main):01:43:40.040   Int: 0.57212

VS 2008,发布版本,Win64 / x86-64
(Main):01:39:25.844   Float: 0.21671
(Main):01:39:26.060   Int: 0.21511

VS 2008,发布版本,Win32/x86
(Main):01:33:27.603   Float: 0.70670
(Main):01:33:27.814   Int: 0.21130

1. "int"正在领先。 2. 三星Galaxy S智能手机。 3. GCC 4.3.4,armeabi-v7a,-mfpu = vfp -mfloat-abi = softfp -O3。
01-27 01:31:01.171 I/LEngine (15364): (Main):01:31:01.177   Float: 6.47994
01-27 01:31:02.257 I/LEngine (15364): (Main):01:31:02.262   Int: 1.08442

“float比int慢得多。现在让我们将循环内的加法改为乘法:”
float Sum1 = 2.0f;

for ( int i = 0; i != 200000000; i++ )
{
    Sum1 *= af * bf;
}
...
int Sum2 = 2;

for ( int i = 0; i != 200000000; i++ )
{
    Sum2 *= a * b;
}

Visual Studio 2008,调试版本,Win32/x86。
(Main):02:00:39.977   Float: 0.87484
(Main):02:00:40.559   Int: 0.58221

VS 2008,调试构建,Win64/x86-64
(Main):01:59:27.175   Float: 0.77970
(Main):01:59:27.739   Int: 0.56328

VS 2008,发布版本,Win32/x86
(Main):02:05:10.413   Float: 0.86724
(Main):02:05:10.631   Int: 0.21741

VS 2008,发布版本,Win64/x86-64
(Main):02:09:58.355   Float: 0.29311
(Main):02:09:58.571   Int: 0.21595

GCC 4.3.4,armeabi-v7a,-mfpu = vfp,-mfloat-abi = softfp,-O3
01-27 02:02:20.152 I/LEngine (15809): (Main):02:02:20.156   Float: 6.97402
01-27 02:02:22.765 I/LEngine (15809): (Main):02:02:22.769   Int: 2.61264

问题是:我错过了什么(任何编译器选项)?在ARM设备上,浮点数运算速度真的比整数慢吗?

1
更改测试顺序:先是整数,后是浮点数。然后检查它们是否相同。 - huseyin tugrul buyukisik
3
移除a/b和af/bf变量上的volatile关键字,并将它们作为参数传入,你这里测量的是内存性能而不是浮点性能(当然在这里你不能将a/b定义为常量,优化器会将它们移除并直接返回答案)。 - old_timer
我知道,那就是我说的。如果你去除内存循环并计时指令,结果会发生什么?检查所生成的代码。 - old_timer
1
从硬件角度来看,无论是加法还是乘法,在浮点数中都比定点数更加复杂。两者都使用固定点的ALU(加法器、乘法器等),但浮点数必须事先准备好值,并在后端将其规范化。理想情况下,这是一个单独的时钟周期,但并非总是如此。ARM FPU(嗯,不止一个)在后端进行同步,您可以去做其他事情,然后回来询问答案,如果尚未完成,则会停顿直到完成。我不知道x86 FPU如何工作。由于您正在添加小数,因此速度应该非常快。 - old_timer
1
同意@dwelch的观点,从a/b和af/bf中删除volatile。如果您在循环后只使用变量,则足以防止优化器删除计算。 - auselen
显示剩余7条评论
4个回答

5

-mfloat-abi=softfp 明确要求使用模拟浮点数。检查您的Galaxy的规格,并尽可能使用硬件FP进行编译。

并非所有的ARM CPU都支持硬件浮点运算。NDK的ARMEABI的默认设置为模拟FP - 这应该是与无FP的机器兼容的。最好的情况是,您可以在运行时根据CPU功能进行一些分支处理。


3
不可以,softfp 允许生成使用硬件浮点指令的代码,但仍然使用软浮点调用约定。http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html - Sergey K.
2
使用硬浮点运算可以避免额外的函数调用层,但这不一定是必须的。 - old_timer

5

@Seva Alekseyev,“-mfloat-abi”标志仅控制浮点值如何传递给函数。使用“softfp”时,值使用普通寄存器进行传递。使用“hardfp”时,值使用FPU寄存器进行传递。“-mfloat-abi”标志不控制使用哪些硬件指令。

基本上,“softfp”用于保持与没有FPU的设备的向后兼容性。对于具有FPU的设备,使用“softfp”将导致一些额外的开销。

@Sergey K比较x86和ARM就像比较苹果和橙子。它们是两个非常不同的平台。 ARM的主要设计目标是低功耗而不是速度。您可以使用“hardfp”看到一些性能提升。还有一个4.6版本的编译器可用。考虑到架构差异,我认为您的结果是可信的。


“hardfp” 的缺陷有哪些? - Sergey K.
1
你的二进制文件将无法在没有FPU的设备上运行。大多数新手机都有它们。 - Frohnzie
它能在任何兼容ARMv7芯片的设备上运行吗? - Sergey K.
2
我不知道。不幸的是,使用armv7指令集的芯片组有许多变种。我的最佳猜测是NO。 - Frohnzie
这是一个问题,我无法使用“硬”模式。 - Sergey K.
1
您可以使用 android_getCpuFeatures() 函数来确定是否有浮点运算器(FPU)可用。看起来所有的 armv7 都有 FPU。从文档中可以得知:ANDROID_CPU_ARM_FEATURE_VFPv3 表示设备的 CPU 支持 VFPv3 硬件 FPU 指令集扩展。由于 'armeabi-v7a' 的定义,如果返回了 ANDROID_CPU_ARM_FEATURE_ARMv7,则始终如此。 - Frohnzie

4

这些结果是可信的。

Exynos 3 SoC中使用的Cortex-A8内核具有未流水线化的VFP实现。我记不清确切的数字,但我的记忆是,在该内核上,每8个周期的VFP加法和乘法吞吐量大约为一个操作码。

好消息是:那是一个非常老的SoC,更新的ARM SoC具有更强的VFP实现-加、减和乘法完全流水线化,吞吐量得到了显着提高。此外,一些(但不是全部)Cortex-A8 SoC支持NEON,这为您提供了完全流水线化的单精度浮点运算。


在使用NEON方面,你提出了一个好的观点。关于在NDK中使用NEON的方法,可以参考文档:docs/CPU-ARM-NEON.html。 - Frohnzie

3
请看http://github.com/dwelch67/stm32f4d,转到float03目录。本测试比较固定与浮动两个函数的性能差异。
.thumb_func
.globl add
add:
    mov r3,#0
loop:
    add r3,r0,r1
    sub r2,#1
    bne loop
    mov r0,r3
    bx lr

.thumb_func
.globl m4add
m4add:
    vmov s0,r0
    vmov s1,r1
m4loop:
    vadd.f32 s2,s0,s1
    sub r2,#1
    bne m4loop
    vmov r0,s2
    bx lr

结果并不太令人惊讶,0x4E2C是定点数,而0x4E2E是浮点数。在浮点测试函数中可能有一些额外的指令,这很可能解释了差异。请注意,保留了HTML标签。
00004E2C                                                                        
00004E2C                                                                        
00004E2E                                                                        
00004E2E                                                                        
00004E2C                                                                        
00004E2E    

在stm32f4中,fpu是vfp的单精度版本,与它的"兄弟姐妹"不同。如果你的armv7有vfp硬件,应该能够在其中执行上述测试。
当__aeabi_fadd函数被链接并且每次通过循环进行额外调用时,加上内存访问的额外计时,可能在库函数内外进行的转换(vmov)等,都会对你所看到的结果产生影响。答案当然在反汇编中。

因此,很可能__aeabi_fadd或者你的gcc使用的其他指令,将操作数放在通用寄存器中,所以除了浮点加法之外,每个操作还有三个vmov指令。因此,在比较一个加法指令和六个指令(bl、vmov、vmov、vadd、vmov、bx)时,无论浮点指令有多快,它都会比单个定点指令慢。如果你可以让编译器进行公平比较而不调用库函数,你就会看到速度匹配。 - old_timer
为了澄清我的结果,十六进制的数字是 10000 次固定点和浮点循环中的系统时钟数(可能预分频)。 固定点加法和浮点加法之间没有区别。 明显地,以 OP 的方式使用 fpu 存在几倍的指令开销,这将占执行差异的大部分比重。 上述是一个苹果与橙子测试,而上面则是一个苹果与苹果测试。 - old_timer

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