• 2021-07-23

    复杂软件系统开发的第一原则: KISS

    Views: 373 | No Comments

    俗话说:

    Keep It Simple, Stupid!

    由于大部分新手程序员在从学生转换成为工程师之前, 都经过所谓的"算法"编程训练, 特别是不少人还主动进行大量的"刷题"行为, 因此, 对"性能"的追求被潜移默化地植入了所有程序员的基因, 这就造成了程序员往往把细节上的所谓性能优化放到第一优先的位置.

    这种片面追求细节性能, 从而缺少大局观的思维, 其实是非常错误的. 比如 C++ 程序员, 几乎把性能优化等同于减少内存拷贝和无锁(lock free), 认为内存拷贝等于性能差, 认为加锁等于阻塞, 因此, C++ 程序员的代码主要在语法和语句层面上做优化, 逻辑非常别扭. 这种观念是非常落后的, 没有大局观, 这种程序员的技术视野极其狭窄.

    根据我多年的软件开发经验, 我想针对那些认为"性能最重要"的程序员, 表明一个事实: 所有的代码层面性能优化, 必然以增加逻辑分支, 增加更多的 Indirection 作为代价.

    什么意思呢? 也就是说, 所谓的性能优化, 必然会在代码里增加 if 分支, 增加新的类, 从而造成代码行数膨胀, 类的数量变多, 类的交叉调用关系变得更加复杂混乱. 这些后果都是非常负面的, 甚至无法抵消减少1%内存拷贝带来的性能提升.

    Continue reading »

    Posted by ideawu at 2021-07-23 21:44:28
  • 2021-06-26

    并发编程两原则

    Views: 700 | No Comments

    之前写过一篇文章, 并发编程的核心技术 – 多版本(Multi Versioning), 本文继续对并发编程做一次更全面的总结, 这样的总结并非具体的编程指导, 而概括性的理论, 是笔记性质的.

    根据经验总结, 并发编程的指导思想可以总结为两个原则, 也即并发编程两原则:

    1. Sharding
    2. Leveling

    Continue reading »

    Posted by ideawu at 2021-06-26 20:17:54
  • 2021-06-06

    可靠通信的三条基本定理(可靠通信三原则)

    Views: 1391 | 2 Comments

    "通信"是一个广义的概念, 不仅限于计算机网络通信, 任何抽象或者具体的对象间的交互行为, 都是一种通信. 对象间一旦进行通信, 便有可靠通信的需求. 可靠通信至少包括三项要求:

    1. 不丢包
    2. 不重复
    3. 完整性(原子性)

    为了达到这三项要求, 对应有三条定理(可靠通信三原则):

    • 定理一(重传定理): 确认和重传是解决丢包问题的唯一正确方法
    • 定理二(去重定理): 排队(串行化)是解决去重问题的唯一正确方法
    • 定理三(原子定理): 单点标记或者循环自校验是实现完整性(原子性)的唯一正确方法

    有些同学可能对排队理论有怀疑, 表示搞一个全局位图(标记集合)也能解决去重问题. 如果深究, 判断标记和修改标记是独立的两步操作, 这两步操作就遇到去重问题, 也即, 对标记集合的操作本身也需要排队. 当然, CAS 是一种解决方案, 但 CAS 的实现内部本质也是排队.
    Continue reading »

    Posted by ideawu at 2021-06-06 13:50:46
  • 2021-04-24

    操作的先后顺序的确定

    Views: 1055 | No Comments

    在计算机领域, 两个操作的先后顺序的确定, 是一个非常严肃的科学问题, 不能仅凭人的直觉来判定.

    有两种方式可以规划两个操作的先后顺序:

    • 通信协调
    • 绝对时间规划

    通信协调是指, 一个操作 (B) 在明确知道另一个操作 (A) 已经结束的前提下才启动, 那么便可以判定 (B) 在 (A) 之后. 这里说的"明确知道", 包含主观上的知道, 也包括客观上的知道. 例如, 对于顺序(串行)操作, 先后顺序是明确的客观的, 即使后一个操作不关心(或者说不知道)前一个操作是否已经完成, 它也应当知道.

    绝对时间规划不存在, 即使是原子钟也不是绝对时间. 因此, 绝对时间规划需要假设时间差异(gap), 让两个操作的时间区间有大于 gap 的距离.

    相关讨论: 数据库的并发操作与一致性

    并发操作对于程序员来说, 这个概念很常见, 但是, 很多人对它的理解未必那么准确.

    这个理论有什么用?

    有很多业务逻辑是严格依赖操作的先后顺序的, 先做什么, 后做什么, 做什么之前必须先做什么, 这些是业务逻辑的本质. 对于并发编程(多线程, 多进程, 分布式), 如果没有做通信协调, 那么系统必然有 BUG, 系统就是错的.

    Posted by ideawu at 2021-04-24 10:14:44
  • 2021-04-17

    并发编程的核心技术 – 多版本(Multi Version)

    Views: 2385 | No Comments

    在单机编程时代, 每一项数据只有唯一的一份, 对数据的修改也是 in-place 的. 但是, 在并发编程领域, 包括分布式系统, 数据多版本(Multi Version, Versioning)是核心.

    我们先从单机编程的内存操作出发. 对于内存的操作, 都是原地(in-place)更新的. 对象和内存空间强绑定, 当更新对象时, 是将对象的内存空间擦除然后用新数据写覆盖. 到了多线程编程时代, 就引入了锁机制, 因为擦除和写操作过程不是原子性的, 可能擦除到一半时, 就被其它线程读取了, 因此要加锁.

    单机的硬盘操作, 基本也是借鉴内存操作, 也是对象和硬盘空间强绑定. 至少大部分程序员的思想是这样的, 这样比较直观. 跟内存操作一样, in-place 也遇到了操作的原子性挑战. 内存本来就是易失的(掉电后丢失), 但硬盘不一样, 数据需要持久化(掉电不丢失), 即使靠加锁解决了访问原子性问题, 但解决不了数据丢失问题. 所以, 硬盘操作是最先引入多版本技术的. 当需要修改某个对象时, 在另外的地方保存对象的新数据, 然后在另外的地方原子性地修改指向新数据的"指针". 事实上, 指针的修改也是多版本的, 不是 in-place 的, 后面会细说.
    Continue reading »

    Posted by ideawu at 2021-04-17 18:20:34
  • 2020-04-05

    接口与实现分离

    Views: 3086 | No Comments

    我在遇到"接口与实现分离"这个编程领域的概念时, 感到非常模糊. 随着编程经验的积累, 才明白了"接口与实现分离". 用 Java 的程序员应该天天用到 interfalce 和 class, 不过, 即使是 Java 程序员, 可能偶尔也会违反广义的分离原则.

    我最近接触到的一个违反"接口与实现分离"原则的例子, 可以分享一下.

    RTT(round trip time)是一个非常重要的时间概念, 这会让程序变得很"慢". 例如:

    func(1);
    func(2);
    func(3);
    

    如果每一次函数调用要花 100ms 的话, 那么做完 3 件事要花 300ms. 有经验的程序员立即就做了代码"优化":

    func([1,2,3]);
    

    把函数参数改成数组, 一次传入 3 个任务, 利用了 batch 机制, 做完 3 件事也仅需要 100 ms. 看起来完美解决问题. 但是, 工程上这样做带来了缺点, 那就是改变了接口(interface). 这是一个经典的违反"接口与实现分离"原则的例子, 接口因实现而被迫改变.

    如果函数执行是放在网络服务器上面, 而调用者是所谓的客户端, 一般的网络编程都是可以并发处理的, 例如在 3 个线程中调用函数. 这时, 接口改变之后, 反而没有用. 因为任务是不同的客户端发起的, 除非你增加一层抽象来积累请求. 这个优化看起来美好, 但是却增加了使用者的成本.

    所以, 工程上应该由 func() 函数的实现者来做请求积累, 在函数内部把并发的请求合并成一个 batch, 减少 RTT.

    Posted by ideawu at 2020-04-05 12:15:41
|<<<123456789>>>| 1/12 Pages, 72 Results.