6个版本

0.3.1 2022年5月2日
0.3.0 2022年4月12日
0.2.1 2022年1月25日
0.1.1 2021年12月31日

#1112 in 数据结构

每月24次下载

Apache-2.0

220KB
1.5K SLoC

静态线性代数系统

Crates.io GitHub Workflow Status Coverage Status Docs Donate on paypal

一个专注于性能、静态分配、静态形状数据和写时复制(又称cow)行为的线性代数系统。同时也提供对blas/blis的安全和快速绑定。

使命

slas的目标是在编译时知道尽可能多的信息的情况下,提供最佳的性能。这主要包含代数对象的形状和大小、目标架构和可用硬件特性/设备。

请注意,slas专门针对在相同系统上编译和执行二进制文件的情况,因此主要针对本地编译。

注意:如果不使用本地编译,slas可能仍然非常不完善。

通过模块化后端系统尝试在硬件和使用案例上进行专业化,该系统将支持未来的自定义分配器。

什么是BLAS?

示例

可以使用StaticCowVecmoo宏以及cow_vec宏来创建。它们具有相同的语法,但cow_vec宏对于认真的程序员来说是一个很好的选择。有关moo宏的更好文档可以在此处找到。

use slas::prelude::*;
use slas::cow_vec;

let a = moo![f32: 1, 2, 3.2];
let b = cow_vec![f32: 3, 0.4, 5];

println!("Dot product of {a:?} and {b:?} is {:?}", a.dot(&b));
println!("{a:?} + {b:?} = {:?}", a.add(&b));

默认情况下,slas会尝试为您选择后端,您也可以自己选择静态后端。(有关后端究竟是什么以及如何配置它的更多信息,请参阅此处。)

use slas::prelude::*;
let a = moo![on slas_backend::Rust:f32: 1, 2, 3.2];
// This will only use rust code for all operations on a
use slas::prelude::*;
let a = moo![on slas_backend::Blas:f32: 1, 2, 3.2];
// This will always use blas for all operations on a

默认情况下,slas将选择在构建过程中设置环境选项时假定最快的后端。(有关更多信息的说明,请参阅此处。)

StaticCowVec解引用到StaticVecUnion,它反过来又解引用到[T; LEN],因此可以对[T;LEN]实现的任何方法也应用于StaticCowVecStaticVecUnion

更多示例代码请见此处。

什么是cow,何时有用?

写时复制(Copy-on-Write,简称COW)功能灵感来源于 std::borrow::cow。其基本思路是通过在运行时而非编译时确定何时复制,从而节省分配(和时间)。在某些情况下,这可能会导致内存效率低下(因为枚举的大小是其最大字段大小加标签大小),这就是为什么你可以选择使用 StaticVecUnionStaticVec 的原因。你可以在实现 StaticVec 的任何类型上调用 moomoo_refmut_moo_ref 来将其转换为适用于其用例的适当类型,且无任何开销。

moo_ref 返回一个 StaticVecRef,这是一个指向 StaticVecUnion 的引用的类型别名。当你知道不需要对向量进行可变访问或所有权时,这最有效。

mut_moo_ref 返回一个 MutStaticVecRef。这和 moo_ref 很相似,但当你想在原地修改数据时(例如规范化一个向量),它非常有用。你应该只在需要具有向量可变访问并且有副作用的情况下使用它。

moo 返回一个引用 selfStaticCowVec。如果你不知道是否需要向量的可变访问并且不希望有副作用,这很有用。如果你想将数据复制到 StaticCowVec,则需要使用 StaticCowVec::from

moo_owned 只会返回一个 StaticVecUnion。当你真正只需要一个 [T; LEN],但你需要只有 StaticVecUnion 实现的方法时,这很有用。

写时复制行为示例

use slas::prelude::*;

let source: Vec<f32> = vec![1., 2., 3.];
let mut v = source.moo();

// Here we mutate v,
// so the content of source will be copied into v before the mutation occours.
v[0] = 0.;

assert_eq!(**v, [0., 2., 3.]);
assert_eq!(source, vec![1., 2., 3.]);

在创建 v 后,借用检查器不会允许修改 source,因为不允许对借用值进行赋值。在某些情况下,这可能会成为一个问题。

use slas::prelude::*;

let mut source: Vec<f32> = vec![1., 2., 3.];
let mut v = unsafe { StaticCowVec::<f32, 3>::from_ptr(source.as_ptr()) };

// Here we can mutate source, because v was created from a raw pointer.
source[1] = 3.;
v[0] = 0.;
source[2] = 4.;

assert_eq!(**v, [0., 3., 3.]);
assert_eq!(source, vec![1., 3., 4.]);

在上面的示例中,你可以看到 v 在第一次修改 source 时改变了值,但在第二次没有改变。这是因为当它被修改时,v 被复制了。

矩阵示例

use slas::prelude::*;
use slas_backend::*;

let a = moo![f32: 1..=6].matrix::<Blas, 2, 3>();
let b = moo![f32: 1..=6].matrix::<Blas, 3, 2>();
let c = a.matrix_mul(&b);
let d = b.vector_mul(&[1., 2.]);

assert_eq!(c, [22., 28., 49., 64.]);
assert_eq!(d, [5., 11., 17.]);

println!("{a:.0?} * {b:.0?} = {:.0?}", c.matrix::<Blas, 2, 2>());

在 slas 中有一个 Matrix 类型和一个 Tensor 类型。对于大多数操作,可以使用二维张量代替矩阵。矩阵可以解引用为一个二维张量,二维张量实现了 Into<Matrix>,这两个操作都没有开销,因为它们只更改类型信息。矩阵类型有一些额外的可选泛型参数,包括 IS_TRANS,如果在编译时已经懒惰转置,则该参数为真。如果不需要这些信息进行矩阵操作,则应将其实现为二维张量。当索引二维张量时,将使用 [usize; 2],它首先选择列,而使用 (usize, usize) 对矩阵进行索引时,首先选择行。

use slas::prelude::*;
use slas_backend::*;

let a = moo![f32: 1..=6].matrix::<Blas, 2, 3>();

assert_eq!((*a)[[0, 1]], a[(1, 0)]);

张量示例

目前,张量(因此也是矩阵)能做的事情不多。

注意:张量(以及因此矩阵)始终需要关联的后端。

use slas::prelude::*;
let t = moo![f32: 0..27].reshape([3, 3, 3], slas_backend::Rust);
assert_eq!(t[[0, 0, 1]], 9.);

let mut s = t.index_slice(1).matrix();

assert_eq!(s[(0, 0)], 9.);
assert_eq!(s.transpose()[(1, 0)], 10.);

到目前为止,就这些了...

为什么不直接使用 ndarray(或类似工具)呢?

在某些特定用例中,例如需要进行大量分配或在使用向量化操作中的引用数据时,Slas 可能比 ndarray 更快。此外,Slas 应始终至少与 ndarray 一样快,所以这不会造成伤害。

Ndarray 将始终使用你在 Cargo.toml 中选择的后端。使用 Slas,你可以选择代码中的后端,甚至可以创建符合你需求的自定义后端。

静态分配以及 Slas 与借用检查器协同工作的方式,还意味着你可能会在编译时捕获许多错误,而 ndarray 大多数时候会让你轻易地绕过去。例如,对大小不同的两个向量求点积,将在 ndarray 中引发恐慌,在 Slas 中引发编译时错误。

安装

默认情况下,Slas 会假设你的系统已安装了 blis。你可以通过禁用默认功能并启用 blis-static 功能,轻松地静态链接和编译 blis。如果你想选择自己的 blas 提供商,请将 dependencies.slas.default-features = false 设置在你的 Cargo.toml 中,并参考 blas-src 获取进一步说明。记住,如果你使用 blas-src 作为 blas 提供商,请添加 extern crate blas_src;

在 slas 的 crates.io 版本(v0.1.0 和 0.1.1)中,blis 将自动编译。

目前,如果你想使用 Slas 的最新版本,你需要在你的系统上安装 blis/blas。

  • 在 Arch Linux 上,来自 AUR 的 blis-cblas v0.7.0 已经过测试并且工作正常。
  • 在 Debian 上,你可以简单地运行 apt install libblis-dev
  • 在 Windows 上,已测试 openblas-src。这意味着你需要禁用 Slas 的默认功能,遵循 openblas 中的安装说明,并将 extern crate openblas_src; 添加到你的主文件中。

环境变量

当未指定后端时,所选择的后端取决于环境变量。

例如,SLAS_BLAS_IN_DOT_IF_LEN_GE=50 将在默认情况下使用 blas,对于在大于或等于 50 个元素的向量上执行的任何点积操作。可以在 slas::config::BLAS_IN_DOT_IF_LEN_GE 中找到 SLAS_BLAS_IN_DOT_IF_LEN_GE 常量。

同样,这仅适用于未为向量指定后端的情况(例如 moo![f32: 1, 2].dot(moo![2, 1]))。

变量和默认值

SLAS_BLAS_IN_DOT_IF_LEN_GE = 750

其他

待办事项

进度和待办事项在 trello 上!

许可证:Apache-2.0

依赖项

~0–4.5MB
~85K SLoC