针对某些可能的参数值优化Rust函数

vc9ivgsu  于 2023-01-26  发布在  其他
关注(0)|答案(2)|浏览(149)

如果我知道某个参数的定义域可能在几个选择值中,是否可以要求编译器优化代码?
例如

// x will be within 1..10
fn foo(x: u32, y: u32) -> u32 {
   // some logic
}

上述函数应编译为

fn foo(x: u32, y: u32) -> u32 {
    match x {
        1 => foo1(y), // foo1 is generated by the compiler from foo, optimized for when x == 1
        2 => foo2(y), // foo2 is generated by the compiler from foo, optimized for when x == 2
        ...
        10 => foo10(y),
        _ => foo_default(x, y) // unoptimized foo logic 
    }
}

我希望编译器根据一些提示生成上面的重写。

k5ifujac

k5ifujac1#

您可以将逻辑放入#[inline(always)]foo_impl()中,然后使用所需的值调用它:

// x will be within 1..10
#[inline(always)]
fn foo_impl(x: u32, y: u32) -> u32 {
   // some logic
}

fn foo(x: u32, y: u32) -> u32 {
    match x {
        1 => foo_impl(1, y),
        2 => foo_impl(2, y),
        // ...
        10 => foo_impl(10, y),
        _ => foo_impl(x, y),
    }
}

因为#[inline(always)],编译器会内联所有的foo_impl()调用,然后使用常量来优化调用。没有什么是保证的,但它应该是相当可靠的(虽然还没有测试)。
确保基准:这实际上可能是由于代码膨胀而导致的回归。

m2xkgtsf

m2xkgtsf2#

让我们以这个玩具为例:

fn foo(x: u32, y: u32) -> u32 {
    x * y
}
movl    %edi, %eax
    imull   %esi, %eax
    retq

但是在你的应用程序中,你知道x很可能每次都是2,我们可以用std::intrinsics::likely把它传递给编译器:
一个二个一个一个
免责声明:我没有足够的经验来知道这是一个 * 好 * 优化与否,只是提示改变了输出。
不幸的是,虽然我认为这是最清晰的语法,但std::intrinsics并不稳定。幸运的是,我们可以使用#[cold]属性获得相同的行为,该属性在stable上可用,可以将您的愿望传达给编译器:

fn foo(x: u32, y: u32) -> u32 {
    if x == 2 {
        foo_impl(x, y)
    } else {
        foo_impl_unlikely(x, y)
    }
}

fn foo_impl(x: u32, y: u32) -> u32 {
    x * y
}

#[cold]
fn foo_impl_unlikely(x: u32, y: u32) -> u32 {
    foo_impl(x, y)
}
leal    (%rsi,%rsi), %eax
    imull   %edi, %esi
    cmpl    $2, %edi
    cmovnel %esi, %eax
    retq

我很怀疑把这个应用到你的用例中是否真的会产生你所提出的转换,我认为这对常量传播有很大的影响,甚至编译器愿意把x < 10优化成一个10个常量的分支,但是使用上面的提示会让它决定什么是最好的。
但是有时候,您知道什么比编译器更好,并且可以通过手动应用转换来强制常量传播:就像你在最初的例子中所做的那样,或者在"ChayimFriedman"的回答中用不同的方式。

相关问题