5 个版本

0.0.5 2024 年 4 月 6 日
0.0.4 2024 年 4 月 1 日
0.0.3 2024 年 3 月 20 日
0.0.2 2024 年 3 月 20 日
0.0.1 2024 年 3 月 19 日

#253过程宏

Download history 3351/week @ 2024-05-04 2930/week @ 2024-05-11 2545/week @ 2024-05-18 1134/week @ 2024-05-25 899/week @ 2024-06-01 698/week @ 2024-06-08 802/week @ 2024-06-15 578/week @ 2024-06-22 1024/week @ 2024-06-29 697/week @ 2024-07-06 807/week @ 2024-07-13 519/week @ 2024-07-20 596/week @ 2024-07-27 482/week @ 2024-08-03 829/week @ 2024-08-10 813/week @ 2024-08-17

2,821 每月下载量

Apache-2.0 OR MIT

17KB
315

Latest Version Documentation

Const-currying

一个 Rust proc macro,帮助你使用柯里化技术提高函数的性能。

动机

Const generics 是 Rust 的一项功能,允许你将 const 值作为泛型参数传递。此功能非常有助于提高代码性能,通过在编译时生成同一函数的多个版本。然而,有时即使很可能是已知的 const 值,或者调用的一部分使用 const 值,你也必须使用运行时依赖的值来调用函数。此宏可以帮助你在编译时生成具有 const 或运行时值的同一函数的多个版本。

有一个令人兴奋的 crate partial_const,它已经提供了类似的功能。它完全基于 Rust 的奇妙类型系统,但是它要求调用者显式指定 const 值。我们的 crate 提供了一个基于 proc-macro 的解决方案,它对调用者的代码不引入任何侵入性更改。

用法

您可以使用 maybe_const 属性指定函数参数的潜在 const 值。

use const_currying::const_currying;

#[const_currying]
fn f1(
    #[maybe_const(dispatch = x, consts = [0, 1])] x: i32,
    #[maybe_const(dispatch = y, consts = [true, false])] y: bool,
    z: &str,
) -> i32 {
    if y {
        x
    } else {
        -x
    }
}

有两个参数 xy,它们可能以 const 值的形式传递。可选的 dispatch 属性指定生成的函数名的后缀。以下是一个示例,我们可以在这里看到生成的完整代码。

#[allow(warnings)]
fn f1_orig(x: i32, y: bool, z: &str) -> (i32, String) {
    if y { (x, z.to_string()) } else { (-x, z.chars().rev().collect()) }
}
#[allow(warnings)]
fn f1_x<const x: i32>(y: bool, z: &str) -> (i32, String) {
    if y { (x, z.to_string()) } else { (-x, z.chars().rev().collect()) }
}
#[allow(warnings)]
fn f1_y<const y: bool>(x: i32, z: &str) -> (i32, String) {
    if y { (x, z.to_string()) } else { (-x, z.chars().rev().collect()) }
}
#[allow(warnings)]
fn f1_x_y<const x: i32, const y: bool>(z: &str) -> (i32, String) {
    if y { (x, z.to_string()) } else { (-x, z.chars().rev().collect()) }
}
#[inline(always)]
fn f1(x: i32, y: bool, z: &str) -> (i32, String) {
    match (x, y) {
        (1, false) => f1_x_y::<1, false>(z),
        (1, true) => f1_x_y::<1, true>(z),
        (0, false) => f1_x_y::<0, false>(z),
        (0, true) => f1_x_y::<0, true>(z),
        (x, false) => f1_y::<false>(x, z),
        (x, true) => f1_y::<true>(x, z),
        (1, y) => f1_x::<1>(y, z),
        (0, y) => f1_x::<0>(y, z),
        (x, y) => f1_orig(x, y, z),
    }
}

原始函数 f1 被重命名为 f1_orig,并生成两个 const 参数 xy 的幂集作为不同的函数 f1_xf1_yf1_x_y。最后,原始函数 f1 被替换为一个调度函数,该函数根据 xy 的运行时值调用生成的函数。

优势

在大多数情况下,编译器的优化已经足够可靠,能够为你生成最佳代码。然而,当你的函数过于复杂以至于无法内联时,编译器可能无法获取足够的信息来优化该函数。

该宏会生成函数的多个版本,并在分发函数中显式匹配 consts 参数。这迫使编译器为每个 const 值生成最佳代码。这也是 Rust 中引入 const-generics 的原因,这个宏使得与运行时依赖值的使用变得更加容易。

"没有银弹" 是软件工程中众所周知的原则。该宏不是银弹,而且并不总是使用它的最佳选择。至少,它可能会大幅增加你的二进制文件大小。在使用该宏之前和之后,你应该始终对代码进行性能分析,并确保性能有所提升。

许可证

根据您的选择,许可协议为 Apache License, Version 2.0MIT 许可证

依赖

~1–1.5MB
~33K SLoC