百科问答小站 logo
百科问答小站 font logo



.NET 6 的预览特性"new generic math" 对.net 数值计算 能带来什么影响? 第1页

  

user avatar   hez2010 网友的相关建议: 
      

泻药。

Generic Math 此前在 F# 中有在编译器层面的实现,即通过 inline 为 call site 处的类型生成代码,例如:

       let inline add x y = x + y     

这段代码的类型签名是:

       x: ^a -> y: ^b -> ^c     when (^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)     

这是 F# 的静态解析类型,和 C++ 的 template 原理一致。这里将 xy 的类型分别视为 ^a^b,要求任意其一具有静态成员 +,且该 + 成员是一个 binary operator。然后调用代码时,例如 let x: int = add 5 6, 将这里的 int 类型代入 add 方法在编译期生成一个 int -> int -> intadd 方法。

但这么做显然是有问题的,最大的问题就在于不跨 ABI:定义的 inline 方法只有 F# 编译器能识别,而通过 ABI 暴露给 C# 或者其他语言使用的话,编译器则并不会知道这是个 inline 方法,也不具备在 call site 处为具体类型生成代码的行为,因此离开了 F# 之后这个方法就无法使用了。

然而,得益于 .NET 6 在类型系统上引入的 virtual static 支持,C# 10 可以通过 abstract static interface method(后续简称 SIM)这一特性在接口上定义抽象静态方法,F# 同样也不再需要上面这样的黑魔法。下面就举回我们熟悉的 C# 作为例子。

.NET 中的运算符都是以静态方法来定义的,例如 C# 的 + 运算符,在编译后的代码中表示为 static TResult op_Addition(TLeft, TRight),因此在具备 SIM 前无法对这类方法进行抽象。

但是现在如果要定义一个可以 + 的接口(这里假设操作数和结果类型都相同),则只需要简单的:

       interface IAddable<TSelf> where TSelf : IAddable<TSelf> {     abstract static TSelf operator+(TSelf left, TSelf right); }      

熟悉 C++ 或者 Rust 的同学大概一眼就能看出来这其实是一个 CRTP trait,而 Rust 只是将 TSelf 隐含成了 self 类型。

那么我们对这个接口进行实现之后,就能定义各种泛型方法用于加法运算,例如求和:

       T Sum(params T[] values) where T : IAddable<T> {     return values.Length == 1 ? values[0] : values[0] + Sum(values[1..]); }      

然后对于任意实现了 IAddable<T>T,我们都可以调用 Sum 方法来进行求和了。

于是标准库中自然也就提供了大量相关的预定义接口用来做这件事情,并且为所有的基础类型(例如 intfloat 等等)都实现了相应的接口,例如:

       interface IAdditionOperators<TSelf, TOther, TResult>     where TSelf : IAdditionOperators<TSelf, TOther, TResult> {     static abstract TResult operator +(TSelf left, TOther right);     static abstract TResult checked operator +(TSelf left, TOther right); }      

目前尚处于早期阶段,API 未定型,可以通过引入 System.Runtime.Experimental 包来引入这些接口和实现。

不过由于 .NET 泛型约束目前尚不支持“或”关系,只支持“且”关系,因此仍然无法表达 F# 那样的 (^a or ^b) : static member (+) 约束。但是总比什么都没有强,这个可以以后再加。

组合一下加减乘除运算符:

       interface INumber<T> :      IAdditionOperators<T, T, T>,      ISubtractionOperators<T, T, T>,      IMultiplyOperators<T, T, T>,     IDivisionOperators<T, T, T> { }      

即可定义出来一个可以支持四则运算的类型 INumber<T>

此外,我们还能借助 parametric polymorphism 表达积类型:

       T SomeMethodNeedAdditionAndMultiply<T>(T a, T b, T c)      where T : IAdditionOperators<T, T, T>, IMultiplyOperators<T, T, T> {     return a + b * c; }      

这些全都是支持跨 ABI 调用的,意味着 .NET 上所有的语言都能受益,并且支持高效的互操作。

唯一比较遗憾的点就是前面所说的不支持和类型:泛型约束不属于方法签名的一部分,因此无法用于重载,无法表达类型的“或”关系,这就要等后续的 .NET 版本更新了。

再提一点大家可能会关注的性能问题,这个自然不必担心,.NET 的泛型会为所有的值类型特化一份实现,因此用起来是没有任何的额外开销的。而对于引用类型,尽管有 Shared Generics 机制,然而在 .NET 6 的 PGO 优化加持下也同样能做到和特化类型实现同样的高效。

因此结论是什么?

结论就是借助这一新的运行时特性和语言特性,各种运算库的作者们都可以轻而易举的实现高效的、可扩展的通用计算库,而完全不需要管具体的类型是什么,也不需要对不同类型写一大堆的重载,这将大大简化实现并减少所需要的精力。

不过需要注意的是,该特性在 .NET 6 中是 preview 特性,因此如果以后设计有所变动或者做了更好的实现方式的话,这个特性哪怕被大改甚至完全被删掉也是可能的。




  

相关话题

  .NET 6 的预览特性"new generic math" 对.net 数值计算 能带来什么影响? 
  C#的dynamic使用中有什么需要注意的地方,以免滥用? 
  如何正确理解.NET 4.5和C# 5.0中的async/await异步编程模式? 
  .NET Core/.NET Framework 上的第三方常用类库/框架实现平均质量真的很差吗? 
  如何看待即将发布的 C# 8.0? 
  为什么大部分程序员都喜欢用黑色界面? 
  为什么使用C#开发软件的公司和程序员都很少? 
  面向对象中,平行继承体系是否尽量完全抛弃? 
  微软开源了 Core CLR,作为 .NET 开发人员,是否需要花精力去研究Core CLR 源代码?为什么? 
  如何对 0,0,0,0 进行运算得到 24? 

前一个讨论
为什么.NET 团队在支持AOT上这么拉胯?
下一个讨论
未来有没有可能使用HTML完全替代Word,Latex等文档格式?





© 2024-12-26 - tinynew.org. All Rights Reserved.
© 2024-12-26 - tinynew.org. 保留所有权利