函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码

简介: 本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对 Java 8 有些许了解即可。

image.png

作者 | 悬衡
来源 | 阿里技术公众号

本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对 Java 8 有些许了解即可。

一 抽象一定会导致代码性能降低?

程序员的梦想就是能写出 “高内聚,低耦合”的代码,但从经验上来看,越抽象的代码往往意味着越低的性能。机器可以直接执行的汇编性能最强,C 语言其次,Java 因为较高的抽象层次导致性能更低。业务系统也受到同样的规律制约,底层的数增删改查接口性能最高,上层业务接口,因为增加了各种业务校验,以及消息发送,导致性能较低。

对性能的顾虑,也制约程序员对于模块更加合理的抽象。

一起来看一个常见的系统抽象,“用户” 是系统中常见的一个实体,为了统一系统中的 “用户” 抽象,我们定义了一个通用领域模型 User,除了用户的 id 外,还含有部门信息,用户的主管等等,这些都是常常在系统中聚合在一起使用的属性:

public class User {// 用户 idprivate Long uid;// 用户的部门,为了保持示例简单,这里就用普通的字符串// 需要远程调用 通讯录系统 获得private String department;// 用户的主管,为了保持示例简单,这里就用一个 id 表示// 需要远程调用 通讯录系统 获得private Long supervisor;// 用户所持有的权限// 需要远程调用 权限系统 获得private Set< String> permission;
}

这看起来非常棒,“用户“常用的属性全部集中到了一个实体里,只要将这个 User 作为方法的参数,这个方法基本就不再需要查询其他用户信息了。但是一旦实施起来就会发现问题,部门和主管信息需要远程调用通讯录系统获得,权限需要远程调用权限系统获得,每次构造 User 都必须付出这两次远程调用的代价,即使有的信息没有用到。比如下面的方法就展示了这种情况(判断一个用户是否是另一个用户的主管):

public boolean isSupervisor(User u1, User u2) {return Objects.equals(u1.getSupervisor(), u2.getUid());
}

为了能在上面这个方法参数中使用通用 User 实体,必须付出额外的代价:远程调用获得完全用不到的权限信息,如果权限系统出现了问题,还会影响无关接口的稳定性。

想到这里我们可能就想要放弃通用实体的方案了,让裸露的 uid 弥漫在系统中,在系统各处散落用户信息查询代码。

其实稍作改进就可以继续使用上面的抽象,只需要将 department, supervisor 和 permission 全部变成惰性加载的字段,在需要的时候才进行外部调用获得,这样做有非常多的好处:

  • 业务建模只需要考虑贴合业务,而不需要考虑底层的性能问题,真正实现业务层和物理层的解耦
  • 业务逻辑与外部调用分离,无论外部接口如何变化,我们总是有一层适配层保证核心逻辑的稳定
  • 业务逻辑看起来就是纯粹的实体操作,易于编写单元测试,保障核心逻辑的正确性

但是在实践的过程中常会遇到一些问题,本文就结合 Java 以及函数式编程的一些技巧,一起来实现一个惰性加载工具类。

二 严格与惰性:Java 8 的 Supplier 的本质

Java 8 引入了全新的函数式接口 Supplier,从老 Java 程序员的角度理解,它不过就是一个可以获取任意值的接口而已,Lambda 不过是这种接口实现类的语法糖。这是站在语言角度而不是计算角度的理解。当你了解了严格(strict)与惰性(lazy)的区别之后,可能会有更加接近计算本质的看法。

因为 Java 和 C 都是严格的编程语言,所以我们习惯了变量在定义的地方就完成了计算。事实上,还有另外一个编程语言流派,它们是在变量使用的时候才进行计算的,比如函数式编程语言 Haskell。

image.png

所以 Supplier 的本质是在 Java 语言中引入了惰性计算的机制,为了在 Java 中实现等价的惰性计算,可以这么写:

Supplier< Integer> a = () -> 10 + 1;
int b = a.get() + 1;

三 Supplier 的进一步优化:Lazy

Supplier 还存在一个问题,就是每次通过 get 获取值时都会重新进行计算,真正的惰性计算应该在第一次 get 后把值缓存下来。只要对 Supplier 稍作包装即可:

/**
* 为了方便与标准的 Java 函数式接口交互,Lazy 也实现了 Supplier
*/
public class Lazy< T> implements Supplier< T> {private final Supplier< ? extends T> supplier;// 利用 value 属性缓存 supplier 计算后的值private T value;private Lazy(Supplier< ? extends T> supplier) {this.supplier = supplier;}public static < T> Lazy< T> of(Supplier< ? extends T> supplier) {return new Lazy< >(supplier);}public T get() {if (value == null) {T newValue = supplier.get();if (newValue == null) {throw new IllegalStateException("Lazy value can not be null!");}value = newValue;}return value;}
}

通过 Lazy 来写之前的惰性计算代码:

Lazy< Integer> a = Lazy.of(() -> 10 + 1);
int b = a.get() + 1;
// get 不会再重新计算, 直接用缓存的值
int c = a.get();

通过这个惰性加载工具类来优化我们之前的通用用户实体:

public class User {// 用户 idprivate Long uid;// 用户的部门,为了保持示例简单,这里就用普通的字符串// 需要远程调用 通讯录系统 获得private Lazy< String> department;// 用户的主管,为了保持示例简单,这里就用一个 id 表示// 需要远程调用 通讯录系统 获得private Lazy< Long> supervisor;// 用户所含有的权限// 需要远程调用 权限系统 获得private Lazy< Set< String>> permission;public Long getUid() {return uid;}public void setUid(Long uid) {this.uid = uid;}public String getDepartment() {return department.get();}/*** 因为 department 是一个惰性加载的属性,所以 set 方法必须传入计算函数,而不是具体值*/public void setDepartment(Lazy< String> department) {this.department = department;}// ... 后面类似的省略
}

一个简单的构造 User 实体的例子如下:

Long uid = 1L;
User user = new User();
user.setUid(uid);
// departmentService 是一个rpc调用
user.setDepartment(Lazy.of(() -> departmentService.getDepartment(uid)));
// ....

这看起来还不错,但当你继续深入使用时会发现一些问题:用户的两个属性部门和主管是有相关性,需要通过 rpc 接口获得用户部门,然后通过另一个 rpc 接口根据部门获得主管。代码如下:

String department = departmentService.getDepartment(uid);
Long supervisor = SupervisorService.getSupervisor(department);

但是现在 department 不再是一个计算好的值了,而是一个惰性计算的 Lazy 对象,上面的代码又应该怎么写呢?"函子" 就是用来解决这个问题的

四 Lazy 实现函子(Functor)

快速理解:类似 Java 中的 stream api 或者 Optional 中的 map 方法。函子可以理解为一个接口,而 map 可以理解为接口中的方法。

1 函子的计算对象

Java 中的 Collection< T>,Optional< T>,以及我们刚刚实现 Lazy< T>,都有一个共同特点,就是他们都有且仅有一个泛型参数,我们在这篇文章中暂且称其为盒子,记做 Box< T>,因为他们都好像一个万能的容器,可以任意类型打包进去。

image.png

2 函子的定义

函子运算可以将一个 T 映射到 S 的 function 应用到 Box< T> 上,让其成为 Box< S>,一个将 Box 中的数字转换为字符串的例子如下:

image.png

在盒子中装的是类型,而不是 1 和 "1" 的原因是,盒子中不一定是单个值,比如集合,甚至是更加复杂的多值映射关系。

需要注意的是,并不是随便定义一个签名满足 Box< S> map(Function< T,S> function) 就能让 Box< T> 成为函子的,下面就是一个反例:

// 反例,不能成为函子,因为这个方法没有在盒子中如实反映 function 的映射关系
public Box< S> map(Function< T,S> function) {return new Box< >(null);
}

所以函子是比 map 方法更加严格的定义,他还要求 map 满足如下的定律,称为 函子定律(定律的本质就是保障 map 方法能如实反映参数 function 定义的映射关系):

  • 单位元律:Box< T> 在应用了恒等函数后,值不会改变,即 box.equals(box.map(Function.identity()))始终成立(这里的 equals 只是想表达的一个数学上相等的含义)
  • 复合律:假设有两个函数 f1 和 f2,map(x -> f2(f1(x))) 和 map(f1).map(f2) 始终等价

很显然 Lazy 是满足上面两个定律的。

3 Lazy 函子

虽然介绍了这么多理论,实现却非常简单:

    public < S> Lazy< S> map(Function< ? super T, ? extends S> function) {return Lazy.of(() -> function.apply(get()));}

可以很容易地证明它是满足函子定律的。

通过 map 我们很容易解决之前遇到的难题,map 中传入的函数可以在假设部门信息已经获取到的情况下进行运算:

Lazy< String> departmentLazy = Lazy.of(() -> departmentService.getDepartment(uid));
Lazy< Long> supervisorLazy = departmentLazy.map(department -> SupervisorService.getSupervisor(department)
);

4 遇到了更加棘手的情况

我们现在不仅可以构造惰性的值,还可以用一个惰性值计算另一个惰性值,看上去很完美。但是当你进一步深入使用的时候,又发现了更加棘手的问题。

我现在需要部门和主管两个参数来调用权限系统来获得权限,而部门和主管这两个值都是惰性的值。先用嵌套 map 来试一下:

Lazy< Lazy< Set< String>>> permissions = departmentLazy.map(department ->supervisorLazy.map(supervisor -> getPermissions(department, supervisor))
);

返回值的类型好像有点奇怪,我们期待得到的是 Lazy< Set< String>>,这里得到的却多了一层变成 Lazy< Lazy< Set< String>>>。而且随着你嵌套 map 层数增加,Lazy 的泛型层次也会同样增加,三参数的例子如下:

Lazy< Long> param1Lazy = Lazy.of(() -> 2L);
Lazy< Long> param2Lazy = Lazy.of(() -> 2L);
Lazy< Long> param3Lazy = Lazy.of(() -> 2L);
Lazy< Lazy< Lazy< Long>>> result = param1Lazy.map(param1 ->param2Lazy.map(param2 ->param3Lazy.map(param3 -> param1 + param2 + param3))
);

这个就需要下面的单子运算来解决了。

五 Lazy 实现单子 (Monad)

快速理解:和 Java stream api 以及 Optional 中的 flatmap 功能类似

1 单子的定义

单子和函子的重大区别在于接收的函数,函子的函数一般返回的是原生的值,而单子的函数返回却是一个盒装的值。下图中的 function 如果用 map 而不是 flatmap 的话,就会导致结果变成一个俄罗斯套娃--两层盒子。

image.png

单子当然也有单子定律,但是比函子定律要复杂些,这里就不做阐释了,他的作用和函子定律也是类似,确保 flatmap 能够如实反映 function 的映射关系。

2 Lazy 单子

实现同样很简单:

    public < S> Lazy< S> flatMap(Function< ? super T, Lazy< ? extends S>> function) {return Lazy.of(() -> function.apply(get()).get());}

利用 flatmap 解决之前遇到的问题:

Lazy< Set< String>> permissions = departmentLazy.flatMap(department ->supervisorLazy.map(supervisor -> getPermissions(department, supervisor))
);

三参数的情况:

Lazy< Long> param1Lazy = Lazy.of(() -> 2L);
Lazy< Long> param2Lazy = Lazy.of(() -> 2L);
Lazy< Long> param3Lazy = Lazy.of(() -> 2L);
Lazy< Long> result = param1Lazy.flatMap(param1 ->param2Lazy.flatMap(param2 ->param3Lazy.map(param3 -> param1 + param2 + param3))
);

其中的规律就是,最后一次取值用 map,其他都用 flatmap。

3 题外话:函数式语言中的单子语法糖

看了上面的例子你一定会觉得惰性计算好麻烦,每次为了取里面的惰性值都要经历多次的 flatmap 与 map。这其实是 Java 没有原生支持函数式编程而做的妥协之举,Haskell 中就支持用 do 记法简化 Monad 的运算,上面三参数的例子如果用 Haskell 则写做:

doparam1 < - param1Lazyparam2 < - param2Lazyparam3 < - param3Lazy-- 注释: do 记法中 return 的含义和 Java 完全不一样-- 它表示将值打包进盒子里,-- 等价的 Java 写法是 Lazy.of(() -> param1 + param2 + param3)return param1 + param2 + param3

Java 中虽然没有语法糖,但是上帝关了一扇门,就会打开一扇窗。在 Java 中可以清晰地看出每一步在做什么,理解其中的原理,如果你读过了本文之前的内容,肯定能明白这个 do 记法就是不停地在做 flatmap 。

六 Lazy 的最终代码

目前为止,我们写的 Lazy 代码如下:

public class Lazy< T> implements Supplier< T> {private final Supplier< ? extends T> supplier;private T value;private Lazy(Supplier< ? extends T> supplier) {this.supplier = supplier;}public static < T> Lazy< T> of(Supplier< ? extends T> supplier) {return new Lazy< >(supplier);}public T get() {if (value == null) {T newValue = supplier.get();if (newValue == null) {throw new IllegalStateException("Lazy value can not be null!");}value = newValue;}return value;}public < S> Lazy< S> map(Function< ? super T, ? extends S> function) {return Lazy.of(() -> function.apply(get()));}public < S> Lazy< S> flatMap(Function< ? super T, Lazy< ? extends S>> function) {return Lazy.of(() -> function.apply(get()).get());}
}

七 构造一个能够自动优化性能的实体

利用 Lazy 我们写一个构造通用 User 实体的工厂:

@Component
public class UserFactory {// 部门服务, rpc 接口@Resourceprivate DepartmentService departmentService;// 主管服务, rpc 接口@Resourceprivate SupervisorService supervisorService;// 权限服务, rpc 接口@Resourceprivate PermissionService permissionService;public User buildUser(long uid) {Lazy< String> departmentLazy = Lazy.of(() -> departmentService.getDepartment(uid));// 通过部门获得主管// department -> supervisorLazy< Long> supervisorLazy = departmentLazy.map(department -> SupervisorService.getSupervisor(department));// 通过部门和主管获得权限// department, supervisor -> permissionLazy< Set< String>> permissionsLazy = departmentLazy.flatMap(department ->supervisorLazy.map(supervisor -> permissionService.getPermissions(department, supervisor)));User user = new User();user.setUid(uid);user.setDepartment(departmentLazy);user.setSupervisor(supervisorLazy);user.setPermissions(permissionsLazy);}
}

工厂类就是在构造一颗求值树,通过工厂类可以清晰地看出 User 各个属性间的求值依赖关系,同时 User 对象能够在运行时自动地优化性能,一旦某个节点被求值,路径上的所有属性的值都会被缓存。

image.png

八 异常处理

虽然我们通过惰性让 user.getDepartment() 仿佛是一次纯内存操作,但是他实际上还是一次远程调用,所以可能出现各种出乎意料的异常,比如超时等等。

异常处理肯定不能交给业务逻辑,这样会影响业务逻辑的纯粹性,让我们前功尽弃。比较理想的方式是交给惰性值的加载逻辑 Supplier。在 Supllier 的计算逻辑中就充分考虑各种异常情况,重试或者抛出异常。虽然抛出异常可能不是那么“函数式”,但是比较贴近 Java 的编程习惯,而且在关键的值获取不到时就应该通过异常阻断业务逻辑的运行。

九 总结

利用本文方法构造的实体,可以将业务建模上需要的属性全部放置进去,业务建模只需要考虑贴合业务,而不需要考虑底层的性能问题,真正实现业务层和物理层的解耦。

同时 UserFactory 本质上就是一个外部接口的适配层,一旦外部接口发生变化,只需要修改适配层即可,能够保护核心业务代码的稳定。

业务核心代码因为外部调用大大减少,代码更加接近纯粹的运算,因而易于书写单元测试,通过单元测试能够保证核心代码的稳定且不会出错。

十 题外话:Java 中缺失的柯里化与应用函子(Applicative)

仔细想想,刚刚做了这么多,目的就是一个,让签名为 C f(A,B) 的函数可以无需修改地应用到盒装类型 Box< A>和 Box< B> 上,并且产生一个 Box< C>,在函数式语言中有更加方便的方法,那就是应用函子。

应用函子概念上非常简单,就是将盒装的函数应用到盒装的值上,最后得到一个盒装的值,在 Lazy 中可以这么实现:

    // 注意,这里的 function 是装在 lazy 里面的public < S> Lazy< S> apply(Lazy< Function< ? super T, ? extends S>> function) {return Lazy.of(() -> function.get().apply(get()));}

不过在 Java 中实现这个并没有什么用,因为 Java 不支持柯里化。

柯里化允许我们将函数的几个参数固定下来变成一个新的函数,假如函数签名为 f(a,b),支持柯里化的语言允许直接 f(a) 进行调用,此时返回值是一个只接收 b 的函数。

在支持柯里化的情况下,只需要连续的几次应用函子,就可以将普通的函数应用在盒装类型上了,举个 Haskell 的例子如下(< *> 是 Haskell 中应用函子的语法糖, f 是个签名为 c f(a, b) 的函数,语法不完全正确,只是表达个意思):

-- 注释: 结果为 box c
box f < *> box a < *> box b

参考资料

  • 在 Java 函数式类库 VAVR 中提供了类似的 Lazy 实现,不过如果只是为了用这个一个类的话,引入整个库还是有些重,可以利用本文的思路直接自己实现
  • 函数式编程进阶:应用函子 前端角度的函数式编程文章,本文一定程度上参考了里面盒子的类比方法:掘金
  • 《Haskell函数式编程基础》
  • 《Java函数式编程》

原文链接
本文为阿里云原创内容,未经允许不得转载。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512097.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WorkManager从入门到实践,有这一篇就够了

作者 | Eason来源 | 程序员巴士前言一般情况下&#xff0c;我们大部分的操作都是在app打开的时候进行的&#xff0c;但是在某些情况下&#xff0c;即使app关闭了&#xff0c;我们也可能需要执行必要的动作&#xff0c;或者会采取一个动作&#xff0c;而不是让用户等待加载&…

终端卡顿优化的全记录

简介&#xff1a; 目前手机SOC的性能越来越少&#xff0c;很多程序员在终端程序的开发过程中也不太注意性能方面的优化&#xff0c;尤其是不注意对齐和分支优化&#xff0c;但是这两种问题一旦出现所引发的问题&#xff0c;是非常非常隐蔽难查的&#xff0c;不过好在项目中用到…

brew安装指定版本mysql,Mac 系统为 Valet 开发环境安装指定版本 MySQL

Mac 系统为 Valet 开发环境安装指定版本 MySQL由 学院君 创建于1年前, 最后更新于 5个月前版本号 #31547 views1 likes0 collects在 Mac 系统下使用 Valet 作为 Laravel 本地开发环境的话&#xff0c;需要自行安装 MySQL 数据库&#xff0c;我们通过 Homebrew 来安装。如果之前…

系统架构面临的三大挑战,看 Kubernetes 监控如何解决?

简介&#xff1a; 随着 Kubernetes 的不断实践落地&#xff0c;我们经常会遇到负载均衡、集群调度、水平扩展等问题。归根到底&#xff0c;这些问题背后都暴露出流量分布不均的问题。那么&#xff0c;我们该如何发现资源使用&#xff0c;解决流量分布不均问题呢&#xff1f;今天…

JavaScript 数组你都掰扯不明白,还敢说精通 JavaScript ?| 赠书

作者 | 哪吒来源 | CSDN博客最近小编在看文章的时候&#xff0c;总有很多刚刚入门的小白说精通这个&#xff0c;精通那个技术&#xff0c;更有意思的是&#xff0c;最近看到一则简历上说精通 JavaScript &#xff0c;聊一聊发现数组还不明白&#xff0c;就对外说精通~所以今天小…

基于消息队列 RocketMQ 的大型分布式应用上云实践

简介&#xff1a; Apache RocketMQ 作为阿里巴巴开源的支撑万亿级数据洪峰的分布式消息中间件&#xff0c;在众多行业广泛应用。在选型过程中&#xff0c;开发者一定会关注开源版与商业版的业务价值对比。 那么&#xff0c;今天就围绕着商业版本的消息队列 RocketMQ和开源版本 …

Gartner发布2022年政府行业主要技术趋势:XaaS、数字化、超自动化等

作者 | Gartner研究副总裁 Bettina Tratz-Ryan Gartner杰出研究副总裁John Kost Gartner高级研究总监 相斌斌 供稿 | Gartner 政府领导人和民选官员在2022年不仅要面对巨大的挑战&#xff0c;还要把握疫情与经济复苏应对措施、不断变化的政治需求和持续数字化变革所带来的机遇…

RedShift到MaxCompute迁移实践指导

简介&#xff1a; 本文主要介绍Amazon Redshift如何迁移到MaxCompute&#xff0c;主要从语法对比和数据迁移两方面介绍&#xff0c;由于Amazon Redshift和MaxCompute存在语法差异&#xff0c;这篇文章讲解了一下语法差异 1.概要 本文档详细介绍了Redshift和MaxCompute之间SQL…

数字农业WMS库存操作重构及思考

简介&#xff1a; 数字农业库存管理系统在2020年时&#xff0c;部门对产地仓生鲜水果生产加工数字化的背景下应运而生。项目一期的数农WMS中的各类库存操作均为单独编写。而伴随着后续的不断迭代&#xff0c;这些库存操作间慢慢积累了大量的共性逻辑&#xff1a;如参数校验、幂…

数字营销行业大数据平台云原生升级实战

简介&#xff1a; 加和科技CTO 王可攀&#xff1a;技术是为业务价值而服务 王可攀 加和科技CTO 本文将基于加和科技大数据平台升级过程中面临的问题和挑战、如何调整数据平台架构以及调整后的变化&#xff0c;为大家介绍数字营销行业大数据平台云原生升级实战经验。主要分为以…

场景模型驱动自动化测试在盒马的探索及实践

简介&#xff1a; 盒马业务有如下几个特点&#xff1a;线上线下一体化、仓储配送一体化、超市餐饮一体化、经营作业一体化、多业态与平台化。在以上的种种原因&#xff0c;生鲜及物流体验是盒马的特点&#xff0c;但仓储配送一体化作业中&#xff0c;如何能更高效的提升测试效率…

基于 KubeVela 的 GitOps 交付

简介&#xff1a; KubeVela 是一个简单、易用、且高可扩展的云原生应用管理和交付平台&#xff0c;KubeVela 背后的 OAM 模型天然解决了应用构建过程中对复杂资源的组合、编排等管理问题&#xff0c;同时也将后期的运维策略模型化&#xff0c;这意味着 KubeVela 可以结合 GitOp…

BCS2022大会将提前至5月 网络安全产业空间扩容将成热门话题

年度网络安全的盛会即将开启。 2022年3月30日&#xff0c;2022年北京网络安全大会&#xff08;BCS2022&#xff09;新闻发布会在北京奇安信安全中心召开&#xff0c;宣布2022年北京网络安全大会“提档”至5月24日至26日&#xff0c;并与北辰集团国家会议中心达成战略合作&#…

基于 Istio 的全链路灰度方案探索和实践

简介&#xff1a; 本文介绍的基于“流量打标”和“按标路由” 能力是一个通用方案&#xff0c;基于此可以较好地解决测试环境治理、线上全链路灰度发布等相关问题&#xff0c;基于服务网格技术做到与开发语言无关。同时&#xff0c;该方案适应于不同的7层协议&#xff0c;当前已…

图像检索在高德地图POI数据生产中的应用

简介&#xff1a; 高德通过自有海量的图像源&#xff0c;来保证现实世界的每一个新增的POI及时制作成数据。在较短时间间隔内&#xff08;小于月度&#xff09;&#xff0c;同一个地方的POI 的变化量是很低的。 作者 | 灵笼、怀迩 来源 | 阿里技术公众号 一 背景 POI 是 Poin…

Redis HyperLogLog 是什么?这些场景使用它~

作者 | 就是码哥呀来源 | 码哥字节在移动互联网的业务场景中&#xff0c;数据量很大&#xff0c;我们需要保存这样的信息&#xff1a;一个 key 关联了一个数据集合&#xff0c;同时对这个数据集合做统计。统计一个 APP 的日活、月活数&#xff1b;统计一个页面的每天被多少个不…

matlab三角形分割,MATLAB 2014b及以上版本中带有画家渲染器的三角形拆分补丁

在解决实际问题之前,这是一个值得怀疑的解决方法&#xff1a;对角线只是三角形之间的空白区域,所以我们看到的是补丁后面的白色空间.愚蠢的想法&#xff1a;让我们用匹配的颜色填充该空间而不是白色.为此,我们将复制所有对象,并通过一个tiiiiny位来抵消新对象.码&#xff1a;hi…

网易云音乐音视频算法的 Serverless 探索之路

简介&#xff1a; 网易云音乐最初的音视频技术大多都应用在曲库的数据处理上&#xff0c;基于音视频算法服务化的经验&#xff0c;云音乐曲库团队与音视频算法团队一起协作&#xff0c;一起共建了网易云音乐音视频算法处理平台&#xff0c;为整个云音乐提供统一的音视频算法处理…

小小的 likely 背后却大有玄机!

作者 | 张彦飞allen来源 | 开发内功修炼今天我给大家分享一个内核中常用的提升性能的小技巧。理解了它对你一定大有好处。在内核中很多地方都充斥着 likely、unlikely 这一对儿函数的使用。随便揪两处&#xff0c;比如在 TCP 连接建立的过程中的这两个函数。//file: net/ipv4/t…

阿里云马涛:因云进化的基础软件

简介&#xff1a; 基础软件的云原生化。 编者按&#xff1a;2021 年10 月20 日&#xff0c;在2021 云栖大会云计算产业升级峰会上&#xff0c;阿里云“因云而生”云原生心智大图正式发布&#xff0c;包含弹性计算、云网络、基础产品、基础设施、操作系统、云安全、开放平台等7个…