C++ 我可以在不影响性能的情况下将此宏更改为内联函数吗?

C++ 我可以在不影响性能的情况下将此宏更改为内联函数吗?,c++,performance,macros,inline,sqrt,C++,Performance,Macros,Inline,Sqrt,(编辑:让我们把它命名为“测量如何出错的经验教训”。不过,我还没有弄清楚到底是什么导致了这种差异。) 我发现了一个由Mark Crowne编写的非常快的整数平方根函数。至少在我的机器上有了GCC,它显然是我测试过的最快的整数平方根函数(包括Hacker's Delight中的函数和标准库中的floor(sqrt()) 在稍微清理格式化、重命名变量并使用固定宽度类型后,如下所示: static uint32_t mcrowne_isqrt(uint32_t val) { uint32_t

(编辑:让我们把它命名为“测量如何出错的经验教训”。不过,我还没有弄清楚到底是什么导致了这种差异。)

我发现了一个由Mark Crowne编写的非常快的整数平方根函数。至少在我的机器上有了GCC,它显然是我测试过的最快的整数平方根函数(包括Hacker's Delight中的函数和标准库中的floor(sqrt())

在稍微清理格式化、重命名变量并使用固定宽度类型后,如下所示:

static uint32_t mcrowne_isqrt(uint32_t val)
{
    uint32_t temp, root = 0;

    if (val >= 0x40000000)
    {
        root = 0x8000;
        val -= 0x40000000;
    }

    #define INNER_ISQRT(s)                              \
    do                                                  \
    {                                                   \
        temp = (root << (s)) + (1 << ((s) * 2 - 2));    \
        if (val >= temp)                                \
        {                                               \
            root += 1 << ((s)-1);                       \
            val -= temp;                                \
        }                                               \
    } while(0)

    INNER_ISQRT(15);
    INNER_ISQRT(14);
    INNER_ISQRT(13);
    INNER_ISQRT(12);
    INNER_ISQRT(11);
    INNER_ISQRT(10);
    INNER_ISQRT( 9);
    INNER_ISQRT( 8);
    INNER_ISQRT( 7);
    INNER_ISQRT( 6);
    INNER_ISQRT( 5);
    INNER_ISQRT( 4);
    INNER_ISQRT( 3);
    INNER_ISQRT( 2);

    #undef INNER_ISQRT

    temp = root + root + 1;
    if (val >= temp)
        root++;
    return root;
}

我用GCC4.5.3尝试了你的代码。 我修改了你的第二个版本的代码来匹配第一个版本, 例如:

(1 << ((s) * 2 - 2)
我可以使用“-S”而不是“-c”来编写汇编程序,但我希望在没有其他信息的情况下查看汇编程序

你知道吗?
汇编程序完全相同, 在第一次和第二次验证中。
因此,我认为您的时间测量值是错误的。

只是一个小点,但是
temp
应该在两个函数中声明
const
inline
不能保证编译器将其内联。编译器仍然可以选择不执行。您可以强制某些编译器将其与VC++上的
\uuu forceinline
或gccAs中的
\uuu attribute\uuuu((始终\u inline))
进行内联,您可以看到,我实际上在声明中使用了u attribute\uuuuu((始终\u inline)),以防万一但我仍然不确定编译器是否在试图与我抗争,这就是为什么我在检查程序集时做了一次微弱的尝试(没有结果)。“内部的ISQRT宏不是太邪恶,因为它是本地的,并且在不再需要它后立即未定义。”#定义的范围不确定…#定义的范围可能不确定,但在这种情况下#unde似乎有效地模拟了局部范围。由于它的存在,我无法从定义它的函数外部访问Internal_ISQRT。我可能遗漏了一些微妙的例外,但无论如何,这是一个切线。毕竟,我的问题的重点仍然是首先要找到一种避免宏的方法(没有任何性能影响)。我一定是匆匆忙忙做了这件事,但你说的代码完全错了,这是对的。固定的!我将匹配这两个函数的类型只是为了确定,但到目前为止,我得到的测量值与使用Linux的高分辨率计时器之前相同(超过2^32-1次迭代)。好的,我将原始函数的类型修改为uint32\t(而不是使内联函数与原始函数匹配),因为这段代码只适用于32位输入。64位sqrt需要一些更改,因此从技术上讲,不明确的“unsigned long”类型对于64位机器来说是错误的。不管怎样,我和以前一样测量它们。我将在上面发布我的度量代码,并将原始函数的类型修改为boot.Oops,小更正:我也测试了整个范围,但我给出的次数是(2^28-1)次迭代,而不是(2^32-1)。不管怎样,我修复了函数并在上面发布了我的计时代码,但是我仍然得到了相同的计时差异。我将尝试使用您的构建选项,因为您得到的是相同的汇编代码输出。我只是使用您的选项在一个单独的文件中编译了每个函数(顺便说一句,谢谢您的支持;我是GCC的noob,通常使用IDE中的构建链),而且……您是对的。这两个函数编译为完全相同的代码。这与将它们作为程序的一部分一起编译是完全不同的。我最好的猜测是,我的测试程序在主函数的循环中内联了mcrowne_isqrt()调用,但它没有内联mcrown_inline_isqrt()调用。还有其他的可能性吗?你使用什么版本的gcc?
#include <iostream>
#include <time.h>      //  Linux high-resolution timer
#include <stdint.h>

/*  Functions go here */

timespec timespecdiff(const timespec& start, const timespec& end)
{
    timespec elapsed;
    timespec endmod = end;
    if(endmod.tv_nsec < start.tv_nsec)
    {
        endmod.tv_sec -= 1;
        endmod.tv_nsec += 1000000000;
    }

    elapsed.tv_sec = endmod.tv_sec - start.tv_sec;
    elapsed.tv_nsec = endmod.tv_nsec - start.tv_nsec;
    return elapsed;
}


int main()
{
    uint64_t inputlimit = 4294967295;
    //  Test a wide range of values
    uint64_t widestep = 16;

    timespec start, end;

    //  Time macro version:
    uint32_t sum = 0;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start);
    for(uint64_t num = (widestep - 1); num <= inputlimit; num += widestep)
    {
        sum += mcrowne_isqrt(uint32_t(num));
    }
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);
    timespec markcrowntime = timespecdiff(start, end);
    std::cout << "Done timing Mark Crowne's sqrt variant.  Sum of results = " << sum << " (to avoid over-optimization)." << std::endl;


    //  Time inline version:
    sum = 0;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start);
    for(uint64_t num = (widestep - 1); num <= inputlimit; num += widestep)
    {
        sum += mcrowne_inline_isqrt(uint32_t(num));
    }
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);
    timespec markcrowninlinetime = timespecdiff(start, end);
    std::cout << "Done timing Mark Crowne's inline sqrt variant.  Sum of results = " << sum << " (to avoid over-optimization)." << std::endl;

    //  Results:
    std::cout << "Mark Crowne sqrt variant time:\t" << markcrowntime.tv_sec << "s, " << markcrowntime.tv_nsec << "ns" << std::endl;
    std::cout << "Mark Crowne inline sqrt variant time:\t" << markcrowninlinetime.tv_sec << "s, " << markcrowninlinetime.tv_nsec << "ns" << std::endl;
    std::cout << std::endl;
}
uint32_t sse_sqrt(uint64_t num)
{
    //  Uses 64-bit input, because SSE conversion functions treat all
    //  integers as signed (so conversion from a 32-bit value >= 2^31
    //  will be interpreted as negative).  As it stands, this function
    //  will similarly fail for values >= 2^63.
    //  It can also probably be made faster, since it generates a strange/
    //  useless movsd %xmm0,%xmm0 instruction before the sqrtsd.  It clears
    //  xmm0 first too with xorpd (seems unnecessary, but I could be wrong).
    __m128d result;
    __m128d num_as_sse_double = _mm_cvtsi64_sd(result, num);
    result = _mm_sqrt_sd(num_as_sse_double, num_as_sse_double);
    return _mm_cvttsd_si32(result);
}
(1 << ((s) * 2 - 2)
(1 << ((s << 1) - 1)
g++ -ggdb -O2 -march=native -c -pipe inline.cpp
g++ -ggdb -O2 -march=native -c -pipe macros.cpp
objdump -d inline.o > inline.s
objdump -d macros.o > macros.s