2014-03-13 43 views
2

我有一个大功能,需要从一个点浮动转换为整数。没有这个转换,我的机器上的功能需要11-12 ns /循环。通过转换,它需要约400纳秒/循环。为什么我会收到这些汇编错误?

经过一番阅读,我找到了一种方法来加快使用一些内联汇编的转换。我的函数的第一次迭代如下:

inline int FISTToInt (float f) 
{ 
    int i; 
    asm("fld %1;" 
     "fistp %0;" 
     :"=r" (i) 
     :"r" (f) 
     : 
    ); 
    return i; 
} 

当我整理,我得到了以下错误:

src/calcRunner.cpp: Assembler messages: 
src/calcRunner.cpp:43: Error: operand type mismatch for `fld' 
src/calcRunner.cpp:43: Error: operand type mismatch for `fistp' 

想到了一个位所提供的答案,我忘了指令后缀,所以我改为如下功能:

inline int FISTToInt (float f) 
{ 
    int i; 
    asm("flds %1;" 
     "fistps %0;" 
     :"=r" (i) 
     :"r" (f) 
     : 
    ); 
    return i; 
} 

但是这并没有解决问题,而不是我得到这个:

src/calcRunner.cpp: Assembler messages: 
src/calcRunner.cpp:43: Error: invalid instruction suffix for `fld' 
src/calcRunner.cpp:43: Error: invalid instruction suffix for `fistp' 

这是怎么回事?

+0

它可以跨x86体系结构移植,在这种情况下,我需要的速度远远超过我需要的可移植性,尤其是考虑到它将运行的网格由x86机器组成。这个循环运行数百万亿次,我只能使用我的大学网格规则获得600个核心,因此循环需要非常快速,因此需要组装。如果您运行在一组有限的体系结构上,则可移植性无关紧要。 –

+0

我想我知道问题是什么(我也不相信它会加速很多!)。在使用fpu时,你需要使用内存加载/存储,而不是寄存器。我会测试并写出答案。 –

+0

如果你打算为速度编写程序集,你应该考虑SIMD指令:http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions –

回答

2

这工作:

int trunk(float x) 
{ 
    int i; 
    __asm__ __volatile__(
    " flds %1\n" 
    " fistpl %0\n" 
    : "=m"(i) : "m"(x)); 
    return i; 
} 

然而,这只是(可能),如果你实际使用的x87模式,它的速度更快,因为它比编译器生成的代码快不加载和存储确定舍入的FP控制字。我会回来与一对夫妇基准...

简单的基准:

#include <stdio.h> 
#include <stdlib.h> 

int trunk(float x) 
{ 
    int i; 
    __asm__ __volatile__(
    " flds %1\n" 
    " fistpl %0\n" 
    : "=m"(i) : "m"(x)); 
    return i; 
} 


int trunk2(float x) 
{ 
    return (int)x; 
} 

inline long long rdtsc() 
{ 
    unsigned long a, d; 
    __asm volatile ("rdtsc" : "=a" (a), "=d" (d) : : "ebx", "ecx"); 
    return a | ((long long)d << 32); 
} 


int main() 
{ 
    float f[1000]; 
    for(int i = 0; i < 1000; i++) 
    { 
    f[i] = rand()/(i+1); 
    } 
    long long t = rdtsc(); 
    int sum = 0; 
    for(int i = 0; i < 1000; i++) 
    { 
    sum = trunk(f[i]); 
    } 
    t = rdtsc() - t; 
    printf("Sum=%d time=%ld\n", sum, t); 

    t = rdtsc(); 
    sum = 0; 
    for(int i = 0; i < 1000; i++) 
    { 
    sum = trunk2(f[i]); 
    } 
    t = rdtsc() - t; 
    printf("Sum=%d time=%ld\n", sum, t); 

    return 0; 
} 

用gcc -02 -m64 -std = C99编译,这将产生以下结果:

Sum=1143565 time=30196 
Sum=1143565 time=15946 

在一个32位的编译器(gcc -O2 -m32 -std = C99):

Sum=1143565 time=29847 
Sum=1143565 time=107618 

换句话说,这是一个慢很多。但是,如果我们能够SSE2(并删除:gcc -m32 -msse2 -mfpmath=sse -O2,它变得更好:

Sum=1143565 time=30277 
Sum=1143565 time=11789 

注意,第一个数字是“解决方案”,其中的第二个结果是编译器的解决方案。

很明显,请对您的系统进行测量,以确保结果确实匹配。

编辑:发现我居然在循环加号,而不是仅仅走过他们把他们在sum后,我得到铛以下结果:

clang -m32 -msse2 -mfpmath=sse -O2 floatbm.c -std=c99

Sum=625049287 time=30290 
Sum=625049287 time=3663 

为什么在“让编译器完成这项工作”中更好的解释是,Clang 3.5正在生成一个展开循环,其中第二个循环具有正确的SSE simd - 它不能在第一个循环中这样做,因此每次迭代是1浮点值。

只是为了显示GCC仍然给出了同样的结果,我重新运行用gcc:

Sum=625049287 time=31612 
Sum=625049287 time=15007 

从之前唯一的区别是,我使用sum += trunk(f[i]);代替sum = ...

+0

对于时间安排,我已经走了:http://stereopsis.com/sree/fpu2006.html。他证明“我的解决方案”应该更快。 –

+0

你已经测试过了吗?自2006年以来,例如编译器就有一些奇怪的发展,今天的操作系统支持SSE2和SSE3指令,这些指令允许编译器“做得更好”。如果你的编译器是一个64位的x86编译器,那么你的“优化”的结果将是我发布的第一个结果。换句话说,比编译器生成的代码慢大约2倍。如果你在没有sse的情况下编译x86 32位,则适用中间结果,如果你使用sse2编译32位,则应该得到相同的2倍差。 –

+0

即使使用gcc -O3(版本4.5.1),编译器也会生成需要很长时间比较的代码。我自由地承认,自那时起事情可能会变得更好。我在循环中的其他地方使用了sse2,它表现良好,这是将事情降低到11-12ns。当我使用sse时,在最终商店之前,然后添加,转换为整数并简单地添加整数,循环仍然需要200ns。我正在寻找其他方法。当然,我会以此为基准,因为我拥有其他一切。但是我首先无法弄清楚什么是错的。 –

-1

如果你能做得比你的编译器更快,那么尽可能地抛出一个,并得到一个体面的。

请在这里告诉我们,所以没有人会甚至想到认真使用它。

+0

那么使用xmmintrin.h中可用的东西,就加速内部循环而言,我可以将gcc -O3(版本4.5.1)中的heck粉碎掉30倍左右。但是,我没有看到有人很快就要离开gcc。 –

1

浮点数是内存操作数,而不是寄存器。因此,你需要这样的:

inline int FISTToInt (float f) { 
    int i; 
    asm("flds %1;" 
     "fistl %0;" 
     :"=m" (i) 
     :"m" (f) 
     : 
    ); 
    return i; 
} 

注意s是16位整数,但32位单(浮动)的浮点和l是整数一个32位int,但64位双为浮点。

Live demo

This seems like a decent resource