.. Kenneth Lee 版权所有 2023
:Authors: Kenneth Lee :Version: 1.1 :Date: 2023-06-02 :Status: Released
:dtag:架构设计定义
什么是架构设计2023
我隔一段时间就重建一下我的架构设计的认识,之前已经写了5个版本了:
什么是软件架构
再谈什么是软件架构
2020年又写的一个什么是架构设计的定义
什么是架构设计V3
什么是架构设计V4
我希望通过一次次的重新建模来更好描述架构设计的本质。最近我又有了一些心得,但似 乎还没有到值得重新建模的程度,所以这个版本我叫它“什么是架构设计2023”,就为了记 录最近的这些心得,但没有打算做一个面面俱到的建模。
最近某个芯片做了一个创新,修改了Memory Barrier的规则,提案在我这里评审的时候, 说完硬件做这个修改以后可以获得的收益,对于软件的用法,他们也说明了约束要求:有 些地方不能插入Barrier,有些地方插入的Barrier的约束有特殊要求。
我对这个提案的评审意见没法说可以,也没法说不可以,我只能说,我需要下来建模推演 一次,我才能反馈我的意见。
然后我基于我们现在提供的所有影响内存序的指令和一些典型应用场景,把每个原来的功 能都用新的规则推理了一次逻辑,以便确定如果我接受这些新的约束,我们未来的软件是 不是要竞争中吃亏。
我顺理成章地认为我在做的就是架构设计的工作,但事后我深入想总结一下,我在干的到 底是什么事情?为什么这个事情必须做?因此,我得到两个结论:
架构设计是把一个或者一组个例设计扩展到一个集合边界的定义。一个MB可以工作, 不表示一组MB可以工作,在一个场景中可以工作,不表示在所有场景都可以工作。架 构设计奠定整体逻辑的安全性,否则可能抓住了这个需求就丢掉其他的需求。
架构设计收缩了真正的边界。比如说,一个RISCV的fence指令,它可以保证特定类型 的内存指令不能越过它,这样如果在这个栅栏后面做特定的动作,这个动作被其他CPU 观察到了,那么它之前的那些内存指令的行为也会被观察到。但这个限定条件其实过 于粗糙的,比如我会有这样的fence要求:我先写5条没有顺序要求的指令,然后写一 条“命令”指令,我希望“命令”总是在前面5条指令被观察到以后被观察到。我并没有对 其他指令进行限制,但RISCV的fence对比这个精细的要求明显是粗糙了。这也是为什 么不久行业就发明Acquire/Release这样的fence指令了(这个定义也没有完全满足前 面的语义,因为还有个成本的问题)。
所以,我只有进行这种类型的推演,我才能真正问出硬件说的约束是什么,知道他们 说的“可以插入和不可以插入”的真实条件到底是什么。
这些都是无论如何,进行一个设计的时候,我们必须先从架构设计角度来重新思考这个问 题的原因。它的关键作用似乎是:有人在桶里放进来一个物体,我必须摇一下,判断一下 这个东西周围有多大的自由空间,让其他的东西可以和它贴得更紧,这样这个桶里面的东 西才会稳固,而且桶里才能装下更多的东西。所以总有人觉得架构是可有可无的,因为表 面来说,桶才是关键,而且初期也很容易把东西装进去了。但随随便便这么装一桶,根本 无法实战,就好像盖个房子不夯土,这个房子就是盖不起来。
设计的时候不去扩大这种逻辑边际,保证留下空间,所有的条件就是硬的,你一个都不敢 改。最终这会和上螺丝一样:我们平时上螺丝,都是先松着把螺丝都上了,然后才开始一 个个加固,这样才能把所有螺丝才拧紧,否则如果一个个螺丝单独一开始就拧紧,部分螺 丝可能就根本拧不进去。设计也一样,每个设计目的都会有一组约束,如果每个约束都是 刚刚好的,一点都不能退让的,这个设计必然最后要破坏部分约束。所以当我们拿到一个 新的约束后,第一件事是真正判断它的范围,然后才会加入到系统中,而不会无条件在现 有系统中拉紧它。
所以,最怕那种这样的需求:“我最近做一个新的总线,这是总线引脚和寄存器定义,你 们集成一下吧”,“我有个新的中断控制器方案,对于特别频繁的中断处理有优势,你们集 成一下吧”。老天,每个bit每个寄存器都要按你的方案来设计吗?那你自己就可以了,谈 什么集成呢?
前一章我们强调高层设计的信息其实比细节信息多,好像高层设计包含了细节设计的信息。 其实两者提供的是不一样的信息。这一章我们把这个问题强化一下。
比如你写了一个代码,分成三个文件:io.c, algorithm.c, app.c,其中app实现程序的 用户接口,用来实现main,接收用户输入等,algorithm.c实现提供的关键算法,io.c封 装所有的io操作。
我上面给你描述的信息,对比代码,就是一种高层设计。单独看这些信息,你知道io.c的 代码怎么写的吗?我现在盖着io.c的代码,你只看上面这个段落,你会发现这里没有io.c 怎么写的信息。
好,反过来,我现在让你看这三个文件的具体内容,其中app.c中有一个宏:::
p = port_mem(); \
sprintf(p, " | %s: " fmt, __FUNCTION__, __VA_ARGS__) \
write_pickoff(p);
请问你知道这宏实现在这里合适吗?这是个IO,但它确实是app.c用的,如果没有前面那 个控制信息,这个宏实现在这里一点问题没有。但盖掉前面的信息,仅看这一个文件,你 不知道这样实现有什么不行的。
我们这里只是类比了一个很细节的情况,但想象你有数千上万个文件,编译在多个应用和 库中,分多个子目录。你还能看得清楚这个规律吗?
所以关键问题就在于,我们人脑能罩得主多大范围内的信息。我们能罩的住的信息,也就 是一两页,两三页A4纸的内容,超过了对我们来说就是一个“恍惚”了。所以,我们控制高 层设计,只是保证我们一直清醒,而不恍惚而已。
如果有一点,AI思考发展了,它一次处理的逻辑范围比人类的脑子高,那么它写的设计文 档就会成为某种程度的天书,因为你再也无法理智地思考它了。你最多只能抛开理智根据 结果去“信任”或者“不信任”它。那个时候,AI就真的“超越”人类了。
但我觉得人的设计能力要超越自然选择,还是挺难的。所以AI想要超越人脑,也是挺难的。
前面两个表述其实还是没有表达出架构设计的特征,架构设计还有一个特征是它是一个或 者多个“逻辑闭包”。
我们前面说了,高层设计(架构设计是一种最高层次的高层设计)的目的是减少信息,让 它在人脑中“罩”得住。但如果这种罩得住,必须有一个成立条件,就是它必须把同层或者 同逻辑的特征放到一起了,我们人脑才能做出有效的判断。“我带了钱,而且我饿了,所 以我到这个店里来吃饭了”。这几个条件都是一个空间中的,你能进行思考。“我昨天理了 个发,今天正好端午,30年前的今天,李雷爱上了韩梅梅,所以我到这个店里来吃饭了”。 这每个条件都是事实,但有意义吗?
代码是一个综合体,所有细节都在里面了。我们进行代码评审,本质上也是要找一个个独 立的逻辑闭包的,比如我们会分析:收报文流程,缓冲区溢出流程,内存分配算法,线程 并行模型……我们是抽取了有限的消息来组成一个控制空间,我们才能判断一个代码对不对。 不抽取这种独立的模型,我们没法判断一个代码对不对的。
对复杂设计做这种独立的抽象,是我们应用一个设计前预判它对不对的唯一方法。
所以,我们画各种架构图,分对象关系图,类关系图,流水线图,状态变迁图,都是为了 单独突出一个问题。但不少人却把这个东西当作代码,把什么东西都往上搬,这是深怕自 己的脑子不够迷糊吗?
设计是分离每个逻辑闭包,不是把逻辑闭包组合在一起。