A Philosophy of Software Design

《A Philosophy of Software Design》

作者 John Ousterhout

为什么要关注软件的复杂度

从开发的视角来看,软件设计遇到的最基础的问题就是解构功能:如何将一个复杂的问题分解成能够被独立解决的小问题。这是一个熵减的过程。而从软件整体的视角来看,恰恰相反,随着加入的功能越来越多,维护的人员规模越来越庞大,是一个复杂性逐渐升高的过程。从另外一个角度说,软件开发时最大的一个限制就是如何去理解我们已经创造的这个系统。

代码写出来是给人读的。机器执行只认机器码,代码写的再好也没用。但是软件系统除了运行以外,还有一个作用是能让除了写代码的你以外的人看懂。代码复杂度越低,从另外一个角度说,系统的鲁棒性也越强。因为有越多的人能读懂你的代码,review你的代码,代码中潜在的bug也越容易被发现。所以,作为开发者,我们的工作不仅是编写我们自己维护更轻松的代码,更要是编写所有人维护更轻松的代码。

如何降低软件整体的复杂度,作者在文中提到了两种方法:

  • 让代码更加简洁明了。这个也有一个著名的KISS原则:Keep It Simple and Stupid。比如,忽略一些特殊的cases。
  • 将复杂性封装在黑盒,通过接口提供易用的功能。这种方式也被称作模块化设计。在这种情况下,局部系统的复杂性并不会显著增加整体系统的复杂性。

复杂度的表现

复杂度可以有三种表现形式:

  • Change amplification:如果一个简单的变更需要我们在多个不同的地方修改相同的代码时,这时复杂度的第一个表现;
  • Cognitive load:第二个复杂度的表现是开发者要完成一项任务,他需要了解多少前置知识。这个需要花的时间越多,潜藏忽略的信息就越多,完成的任务里存在bug的可能性越大;有时候,某种方法可能需要更多简单的代码实现,但它可能是更好的方法,因为它能降低其他开发者的心智负担。
  • Unknown unknowns:第三个表现就是完成功能需要修改某处代码,但是这部分代码十分隐蔽,看起来与要完成的功能都毫无关联。这种情况是最糟的。因为前两种情况都可以通过花费更多的精力来解决,但是Unknown unknowns在于你根本没有意识到这一点。可能直到bug出现了才发现这个问题。

一个好的系统设计是明了清晰。两种情况会显著增加系统的复杂性:

  • 依赖过多,表现在代码不能被独立地理解和修改,必须先要搞明白或者修改另外一块代码;
  • 代码晦涩难懂,出现这种情况绝大部分是因为文档的不完善。不过,优秀的设计需要的文档会更少,如果代码需要大量的文档,通常也说明设计存在一定的问题。

如何避免复杂度增高

在开发过程中,要始终秉承一个思路:==我们的首要目标不仅仅是开发迭代一个功能,而是产出一个优秀的设计,这个设计正要能满足我们的功能需求==。这是一种更高层次地,从战略地眼光去看待软件开发。如果我们仅仅关注眼下的功能,一味地追求效率,留给后来者的很可能是一个烂摊子,俗称“屎山”。这种方式是非常短视的。

要做到这一点,就需要我们在开发中不断迭代系统的设计,使系统更符合当下的需求。作者建议花费总开发时间的10%-20%去改善系统设计,一方面不会过多地影响整体进度,另一方面也对后续的开发有着非常积极的意义。

当然,对于雇主来说,最好的降低开发成本的方式是雇佣优秀的工程师,相比平庸的工程师,他们的价钱更高点,但是能极大地提高生产效率。

设计深模块

所谓的深模块指的是模块里面尽量包括更多的复杂性,然后通过简单的接口暴露给用户。 这样做的目的是减少模块之间的依赖。

image-20220319190303080

模块由两部分组成:接口和实现。接口包括了开发者想要使用这个模块所需知道的所有信息,即模块能干什么。接口不仅仅是传参,返回值,还包括了接口的注释,错误信息等等。

实现包括了模块为了实现这个承诺所有的代码,即怎么做的。

接口尽量简单,这样能减少模块给系统其他部分带来的复杂度。实现需要包括尽可能多的功能,可能有些人的想法是将每个功能都独立成一个模块,这样的缺点是整个系统的模块数增多,导致模块间的依赖也变多了,反而增加了复杂度。合理的方式应该是把相近的功能尽可能的合并到一个模块中。

如果考虑到有些用户对模块有定制化的需求,最好的办法也是通过默认值等方式,让最通用的case尽量简单,同时提供一些自定义修改的参数。

减少Errors的定义

在模块中,过多地返回定义地errors其实是一种懒惰地行为。因为很多时候,模块里不知道怎么处理错误,调用者也不会知道怎么处理错误。反而让调用者需要考虑到各种异常case,增大了代码的复杂度。如果模块有太多的异常需要处理,会增加接口的复杂性,也就是上面说的浅模块。

减少Errors的几种方法:

  1. 最好的方式是定义一个没有errors需要处理的API,这是最能简化软件的方式;
  2. 第二种方法是能够让errors在模块内部消化,不对外暴露;
  3. 第三种方法是聚合多个异常,抛出一个统一的异常,这样也能减少调用者的代码量;
  4. 第四种方法就是打印错误信息,直接crash程序。这些异常通常不值得处理,并且也很难处理,发生的概率也很低。

设计第二种方案

设计第二种方案是从设计的角度降低复杂度的方式。好的设计能从根本上降低软件的复杂度。软件设计很难,所以大多数时候我们的第一个想法并不一定是最好的设计。考虑多个可能性,对比他们之间的优劣,选择最好的一个,或者将他们取长补短组合起来,形成新的方案。

注释

写注释的过程,也是一个改善系统设计的过程。好的注释能够给软件质量带来很大的提升。

要不要写注释主要取决于两点:

  1. 函数是否足够简单,用户能一眼看出实现的功能;
  2. 函数名称和函数声明是否能够表达出函数的功能。

如果不能达到这两点之一,那么注释就是必要的。不能因为不想写注释而把函数拆成多个简单的接口,增加系统的复杂度。

注释能够携带一些代码内看不到的信息。这些信息可能是设计者写代码的想法,有什么考虑因素。这些是在代码中无法体现的。注释本身也是抽象的一部分,对于复杂函数的注释,也更容易让用户了解接口的功能,以及检查接口的实现是否和设计者所期望的一致。

写注释最好的时间是在写代码之前。注释是对代码的总结,而不是简单的重复。

Summary of Design Principles

Here are the most important software design principles discussed in this book:

置身事内

《置身事内:中国政府与经济发展》

作者 兰小欢

前言 从了解现状开始

所以对于常见数据,如直接来自《中国统计年鉴》或万得数据库中的数据

第一章 地方政府的权力与事务

要理解政府治理和运作的模式,首先要了解权力和资源在政府体系中的分布规则,既包括上下级政府间的纵向分布,也包括同级政府间的横向分布。

第一节 政府治理的特点

图1-1描绘了中国的五级政府管理体系:中央—省—市—县区—乡镇。

在乡以下的村落,则实行村民自治,因为行政能力毕竟有限,若村落也建制,那财政供养人口又要暴涨一个数量级。

一方面,维持大一统的国家必然要求维护中央权威和统一领导;另一方面,中国之大又决定了政治体系的日常运作要以地方政府为主。

中国共产党对政府的绝对领导是政治生活的主题。简单说来,党负责重大决策和人事任免,政府负责执行,但二者在组织上紧密交织、人员上高度重叠,很难严格区分。

大多数地方部门都要同时接受“条条”和“块块”的双重领导。

制度设计的一大任务就是要避免把过多决策推给上级,减轻上级负担,提高决策效率,所以体制内简化决策流程的原则之一,就是尽量在能达成共识的最低层级上解决问题。

若是部门事务,本部门领导就可以决定;若是经常性的跨部门事务,则设置上级“分管领导”甚至“领导小组”来协调推进。

在任何体制下,权力运作都受到两种约束:做事的能力及做事的意愿。前者取决于掌握的资源,后者取决于各方的积极性和主动性。

第二节 外部性与规模经济

影响行政区划的首要因素是“外部性”,这是个重要的经济学概念,简单来说就是人的行为影响到了别人。在公共场合抽烟,让别人吸二手烟,是负外部性;打流感疫苗,不仅自己受益,也降低了他人的感染风险,是正外部性。

一件事情该不该由地方自主决定,可以从外部性的角度来考虑。若此事只影响本地,没有外部性,就该由本地全权处理;若还影响其他地方,那上级就该出面协调。

按照经典经济学的看法,政府的核心职能是提供公共物品和公共服务,比如国防和公园。这类物品一旦生产出来,大家都能用,用的人越多就越划算——因为建造和维护成本也分摊得越薄,这就是“规模经济”。但绝大部分公共物品只能服务有限人群。

但教材内容却不受物理条件限制,而且外部性极强。

第一个重要因素是人口密度。

第二个重要因素是地理条件。

第三个重要因素是语言文化差异。

理解了方言和文化的多样性,也就理解了推广普通话和共同的文化历史教育对维护国家统一的重要性。

另一个曾长期困扰边界公共治理的问题是环境污染,尤其是跨省的大江、大河、大湖,比如淮河、黄河、太湖等流域的污染。这是典型的跨区域外部性问题

下级之间一旦出现了互相影响、难以单独决断的事务,就要诉诸上级决策。反过来看,各级政府的权力都是由上级赋予的,而下放哪些权力也和外部性有关。在外部性较小的事务上,下级一般会有更大决策权。虽然从原则上说,上级可以干预下级的所有事务,但在现实工作中,干预与否、干预到什么程度、能否达到干预效果,都受制于公共事务的外部性大小、规模经济、跨地区协调的难度等。

撤县设区扩张了城市面积,整合了本地人口,将县城很多农民转化为了市民,有利于充分利用已有的公共服务,发挥规模收益。很多撤县设区的城市还吸引了更多外来人口。(21)这些新增人口扩大了市场规模,刺激了经济发展。

第三节 复杂信息

所以有信息优势的一方,或者说能以更低代价获取信息的一方,自然就有决策优势。

所以上级虽然名义上有最终决定权,拥有“形式权威”,但由于信息复杂、不易处理,下级实际上自主性很大,拥有“实际权威”。维护两类权威的平衡是政府有效运作的关键。若下级有明显信息优势,且承担主要后果,那就该自主决策。若下级虽有信息优势,但决策后果对上级很重要,上级就可能多干预。但上级干预可能会降低下级的工作积极性,结果不一定对上级更有利。

下级通常有信息优势,所以如果下级想办某件事,只要上级不明确反对,一般都能办,即使上级反对也可以变通着干,所谓“县官不如现管”;如果下级不想办某事,就可以拖一拖,或者干脆把皮球踢给上级,频繁请示,让没有信息优势的上级来面对决策的困难和风险,最终很可能就不了了之。即使是上级明确交代的事情,如果下级不想办,那办事的效果也会有很大的弹性,所谓“上有政策,下有对策”。

信息优势始终是权力运作的关键要素。

汉弗莱爵士YYDS

衙门就像车,来办事就像坐车,当官的是骡子,我们才是车把式,决定车的方向。

获取和传递信息需要花费大量时间精力,上级要不断向下传达,下级要不断向上汇报,平级要不断沟通,所以体制内工作的一大特点就是“文山会海”。作为信息载体的文件和会议也成了权力的载体之一,而一套复杂的文件和会议制度就成了权力运作不可或缺的部分。

私企从理论上讲信息传递效率要比体制内的工作要高,但现在很多大公司部门间各种扯皮,反而信息传递效率低下

获取和传递信息需要花费大量时间精力,上级要不断向下传达,下级要不断向上汇报,平级要不断沟通,所以体制内工作的一大特点就是“文山会海”。作为信息载体的文件和会议也成了权力的载体之一,而一套复杂的文件和会议制度就成了权力运作不可或缺的部分。

所谓权力,实质就是在说不清楚的情况下由谁来拍板决策的问题。

第四节 激励相容

如果一方想做的事,另一方既有意愿也有能力做好,就叫激励相容。

所有面临双重领导的部门,都有一个根本的激励机制设计问题:到底谁是主要领导?工作应该向谁负责?

对于更宏观的工作,比如发展经济,涉及方方面面,需要地方调动各种资源。激励相容原则要求给地方放权:不仅要让地方负责,也要与地方分享发展成果;不仅要能激励地方努力做好,还要能约束地方不要搞砸,也不要努力过头。做任何事都有代价,最优的结果是让效果和代价匹配,而不是不计代价地达成目标。

激励相容原则首先要求明确地方的权利和责任。

其次是权力和资源的配置要制度化,不能朝令夕改。无论对上级还是对下级,制度都要可信,才能形成明确的预期。

第五节 招商引资

这种“混合经济”体系,不是主流经济学教科书中所说的政府和市场的简单分工模式,即政府负责提供公共物品、市场主导其他资源配置;也不是简单的“政府搭台企业唱戏”模式。而是政府及其各类附属机构(国企、事业单位、大银行等)深度参与大多数生产和分配环节的模式。在我国,想脱离政府来了解经济,是不可能的。

第一节 分税制改革

公众所接触的信息和看到的现象,大都已经是博弈后的结果,而缺少社会阅历的学生容易把博弈结果错当成博弈过程。

成功的政策背后是成功的协商和妥协,而不是机械的命令与执行,所以理解利益冲突,理解协调和解决机制,是理解政策的基础。

分税制是20世纪90年代推行的根本性改革之一,也是最为成功的改革之一。改革扭转了“两个比重”不断下滑的趋势(图2-2):中央占全国预算收入的比重从改革前的22%一跃变成55%,并长期稳定在这一水平;国家预算收入占GDP的比重也从改革前的11%逐渐增加到了20%以上。改革大大增强了中央政府的宏观调控能力,为之后应付一系列重大冲击(1997年亚洲金融危机、2008年全球金融危机和汶川地震等)奠定了基础,也保障了一系列重大改革(如国企改革和国防现代化建设)和国家重点建设项目的顺利实施。分税制也从根本上改变了地方政府发展经济的模式。

第二节 土地财政

这部分税是谁交呢?土地使用者?意思是房地产开发商在缴纳了土地使用权转让费用后,还需要承担其他的税务费用么?一类是直接和土地相关的税收,主要是土地增值税、城镇土地使用税、耕地占用税和契税,其收入百分之百归属地方政府。

土地转让虽然能带来收入,但地方政府也要负担相关支出,包括征地拆迁补偿和“七通一平”等基础性土地开发支出。

地方政府本来也不是靠卖地赚钱,它真正要的是土地开发之后吸引来的工商业经济活动。

2001年所得税改革后,中央财政进一步集权,拿走了企业所得税的六成。从那以后,地方政府发展经济的方式就从之前的“工业化”变成了“工业化与城市化”两手抓:一方面继续低价供应大量工业用地,招商引资;另一方面限制商住用地供给,从不断攀升的地价中赚取土地垄断收益。这些年出让的城市土地中,工业用地面积约占一半,但出让价格极低:2000年每平方米是444元,2018年是820元,只涨了85%。而商业用地价格增长了4.6倍,住宅用地价格更是猛增了7.4倍

这就是内卷

如果竞争不能让资源转移到效率更高的地方,那这种竞争就和市场竞争不同,无法长久地提高整体效率。

第三章 政府投融资与债务

土地资本化的魔力,在于可以挣脱物理属性,在抽象的意义上交易承诺和希望,将过去的储蓄、现在的收入、未来的前途,统统汇聚和封存在一小片土地上,使其价值暴增。

KAIST-CS431: Lock

  • Pros & cons: simple & possibly inefficient

Low-Level Lock API

常用的low-level锁的API有:

  • Lock.acquire(): 阻塞,直到获取到锁
  • Lock.try_acquire(): 返回锁是否已经被占用了,如果是,返回false,否,占用锁并返回true。不阻塞
  • Lock.release(): 释放锁
L.acquire();
r1 = X;
X = r1 + 1;
L.release();

L.acquire();
r2 = X;
X = r2 + 1;
L.release();

但是,这些API给用户在使用时造成了很多挑战(心智负担):

  • Relating lock and resource: 用户只有在拿到锁时才能访问被保护的变量;
  • Matching acquire/release: 用户只能释放已经拿到的锁。

如果锁没有得到正确的处理,会造成很多潜在的问题,并且,这些问题很难发现。因此,在并发编程时,low-level的锁API存在如下问题:

  • High cost:程序员需要始终关注API的使用;
  • Potential bugs:不正确的使用容易造成很多潜在的bugs。

High-Level Lock API

  • 想要一个易用地,始终能保证安全地high-level API。
    • Acquire/release自动匹配;
    • Lock和resource显式关联。

C++中,这种API被称作RAIIResource Acquisition Is Initialization

#include <string>
#include <mutex>
#include <iostream>
#include <fstream>
#include <stdexcept>

void write_to_file(const std::string & message)
{
    // 创建关于文件的互斥锁
    static std::mutex mutex;

    // 在访问文件前进行加锁
    std::lock_guard<std::mutex> lock(mutex);

    // 尝试打开文件
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("unable to open file");

    // 输出文件内容
    file << message << std::endl;

    // 当离开作用域时,文件句柄会被首先析构 (不管是否抛出了异常)
    // 互斥锁也会被析构 (同样地,不管是否抛出了异常)
}

RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。

枪炮、病菌与钢铁

《枪炮、病菌与钢铁》

作者 贾雷德·戴蒙德

致我的中国读者

物理学家、化学家和分子生物学家告诉我们,唯一严谨的科学研究方法是进行可操纵的实验室实验

开场白 亚力的问题

几乎所有的儿童发展都强调:童年的刺激和活动有助于心智发展,不可逆的心智障碍与童年时的刺激不足有关。

各族群的历史循着不同的轨迹开展,那是环境而非生物差异造成的。

世界技术发展的共通模式反倒更容易理解。食物生产手段让农民生产出食物盈余,因此农业社会可以供养全职的技术专家,他们不用亲自耕作,只要专注于发展技术。

第一部分 从伊甸园到卡哈马卡

第一批走出非洲的人类祖先是直立人,其化石证据是在东南亚的爪哇岛发现的“爪哇人”(Java man)。

今天西伯利亚与阿拉斯加之间浅浅的白令海峡在冰期由于海面的升降,有时是海峡,有时则是宽广的洲际陆桥。

第2章 历史的自然实验

一般而言,政治单元越大,人口密度越高,技术和组织就越繁复

人口密度与政治单元大小的差异对波利尼西亚各地的社会有何影响?经济生活仍非常简单的岛屿要么人口密度低(如查塔姆群岛上的狩猎—采集社群),要么人口数量少(小环礁上的社群),要么二者皆是。在这种社会,家家户户自给自足,经济上几乎不需要专业分工。经济专业化是在面积较大、人口密度较高的岛屿发展出来的

第3章 卡哈马卡的冲突

阿塔瓦尔帕的被俘是现代历史上最大冲突中的决定性时刻,因此令人玩味不已。但这一事件之所以能引起更广泛的关注,是因为使得皮萨罗虏获阿塔瓦尔帕的种种因素,也在现代世界中许多殖民者和土著的冲突中起了作用。可以说,阿塔瓦尔帕被俘为我们了解世界史打开了一扇窗。

皮萨罗的武力优势在于西班牙人的枪炮、刀剑和马匹。而阿塔瓦尔帕的军队作战时没有骑乘任何动物,武器也只有石头、铜器、木棒、狼牙棒、斧头,加上弹弓和其他拼凑起来的武器。这种悬殊决定了美洲土著等族群与欧洲人交锋时的命运。

阿塔瓦尔帕之后成为印加皇帝的曼科(Manco),其麾下最骁勇善战的是尤潘基(Quizo Yupanqui)。1536年,尤潘基率军在利马发动突袭攻打西班牙人,然而对方只派出两支西班牙骑兵就把他们打得落花流水,尤潘基和手下的将领都被杀,全军覆没。另一支26名骑兵组成的队伍则在库斯科击败了曼科亲率的精锐。

较有免疫力的入侵族群把传染病带给其他没有免疫力的族群。天花、麻疹、流感、斑疹伤寒、腺鼠疫等已在欧洲蔓延的传染病,反倒成了欧洲人征服世界各地族群的助力。

皮萨罗成功的直接原因包括基于枪炮、钢铁武器和马匹的军事技术,来自欧亚大陆的传染病,欧洲的海事技术,中央集权的政治组织,以及文字。

第二部分 食物生产的兴起与扩散

食物生产对枪炮、病菌和钢铁的发展而言,是间接的前提条件。因此,从各大洲族群从事农牧的地理条件可看出日后的命运。

通过驯化动植物比狩猎—采集方式产出更多食物,从而增加人口密度,这是作物和家畜对人口直接的影响。间接一些的影响,是此类食物生产方法需要人们定居下来,这种生活方式促成了人口密度的增加。

作物和牲畜的有无,从根本上解释了为何帝国、文字、钢铁武器最早在欧亚大陆出现,而在其他地方较晚甚至没有出现。

第5章 历史上的有与无

所有生物体内的碳元素中,都含有固定比例的放射性碳14原子。生物死亡后,就不再从外界吸收碳元素,体内已有的放射性碳14原子仍继续衰变,半衰期约5 700年,生物死后大约4万年,其体内碳14的量就低到难以测量,或者难以和晚近时期混入的少量含碳14的遗存分辨了。

利用碳14测年法的第二个问题,就是大气中碳14与碳12的比例实际上并不恒定,而是在不同的时间段内有波动,因此基于比例恒定假设的计算结果必然存在系统性的小误差,必须加以校正。

在有些地区,生产食物的手段完全是独立发展出来的。这些地区在外来作物与动物输入之前,自行驯化了很多本土的作物与动物。这种地区目前只有5个我们有翔实的证据:西南亚(或称近东或肥沃新月地带)、中国、中美洲(墨西哥中部、南部以及邻近的中美洲地区)、南美洲的安第斯山脉(或许还包括邻近的亚马孙盆地)和美国东部

食物生产方面取得先机的族群,在迈向枪炮、病菌和钢铁的路途上,领先群雄。其结果就是历史上一连串“有”与“无”的冲突。

第6章 下田好,还是打猎好?

人类最近1万年的历史彰彰在目的事实,就是人类生计的变迁,主流是从狩猎—采集转变成食物生产。因此我们必须问的是:哪些因素让食物生产显得有利,使其他的生计类型都失色了?

第一个因素是可获得的野生食物越来越少。

第二个因素是,随着可驯化的野生植物变多,驯化植物的回报越来越多

影响狩猎—采集和农业消长的第三个因素,是生产食物的技术(例如采收、处理和储藏)不断改进。

第四个因素是人口密度上升与食物生产兴起的双向关联。

接受食物生产是一种自催化的过程,也就是在一个正回馈循环中不断自我催化的过程,这个过程开启后就会加速。

明白了食物生产与人口密度的这种双向关联,就可以解释这样一种矛盾状况:食物生产增加了每英亩土地上可食用卡路里的数量,但食物生产者的营养状况不如被他们取代的狩猎—采集者。之所以有时会出现这种状况,是因为食物增产的速度稍稍落后于人口增加的速度。

食物生产者社群的人口密度很高,凭数量优势就足以驱逐或消灭狩猎—采集者,更别提其他优势了(包括技术、病菌和职业军人)。

只有当地理或生态屏障将食物生产者拦在外面,或使得适用于当地的食物生产技术难以传入的时候,狩猎—采集者才有可能一直到近现代都在适于农牧的土地上保持原有的生活方式。

第7章 杏仁的前世今生

这些作物多半是自花传粉,直接把有利的基因传给下一代,不必和其他较没有价值的植物杂交,坏了自己的种。

谷物和豆类相比,这些作物的缺点是至少得种植3年才可能有收成,盛产期则必须等待10年之久。因此,只有在一地长住的人才有可能种植这些作物。这些最早的果树和坚果树还容易栽种,插枝甚至撒下种子就长出来了,晚期才被驯化的树木可没这么简单。插枝还有个好处,也就是保证后代和亲代一模一样。

谷物的优点有长得快、碳水化合物含量高,每公顷可收获多达1吨的食物。因此,谷物在今天人类摄入的卡路里中占了超过半数。今日世界的12种主要作物中,谷物就有5种:小麦、玉米、稻米、大麦和高粱。很多谷物蛋白质含量低,但这种缺陷可由豆类来补足。豆类的25%是蛋白质,黄豆更高达38%,谷物和豆类是均衡饮食不可或缺的。

第8章 是苹果的问题,还是印第安人的问题?

现代世界作物年产量的80%是由十几种植物贡献的。这十几种“重量级”的作物如下:谷物有小麦、玉米、稻米、大麦和高粱,豆类有黄豆,块根或块茎类为马铃薯、木薯、甘薯,糖分的来源则是甘蔗、甜菜,水果如香蕉。

小麦、大麦的天生优势和墨西哥类蜀黍的明显劣势,大概就是欧亚社会和新大陆社会发展差异的要因。

肥沃新月地带的第三大优势,就是雌雄同株自花传粉的植物比例很高,这些植物偶尔也行异花传粉。

大多数的野生植物是雌雄同株异花传粉,或是雌雄异株——这种生殖生物学的现象给早期农民添了很多麻烦,因为他们选择一种突变的植物栽种后,其子代往往因和其他植株杂交而失去原来的特色。因此,大部分的作物来自野生植物中小部分行雌雄同株自花传粉者,或经无性生殖产生者(如用根来种植以复制亲代基因)。

野生单粒小麦、二粒小麦和大麦这三种的蛋白质含量很高,为8%~14%,相形之下,东亚的首要作物稻米和新大陆的玉米则蛋白质含量少,营养问题比较严重。

农业最早在肥沃新月地带发端,主要是“八大始祖作物”之功,它们是属于谷物的二粒小麦、野生单粒小麦、大麦,属于豆类的兵豆、豌豆、鹰嘴豆、苦野豌豆,属于纤维作物的亚麻。在这八种作物之中,只有亚麻和大麦的野生种分布超出了肥沃新月地带和安纳托利亚地区;有两种始祖作物的分布范围很窄,鹰嘴豆只在土耳其东南部,二粒小麦的分布范围则限于肥沃新月地带。因此,肥沃新月地带驯化当地现成的野生植物就足以发展农业,无须仰赖外地引入的作物包。有两种始祖作物无法在肥沃新月地带以外的地方被驯化,主要原因是它们只在肥沃新月地带有。

在食物生产兴起前,人类大抵以野生物种为食,因此掌握的关于野生动植物的知识特别丰富。最早的农民承袭了这样的知识——几千年来和大自然亲密生活、观察而累积下来的经验。因此,有价值的物种似乎不大可能逃过早期农民的眼睛。

其他领域也是一样的道理

能优先获得引进的新作物和家畜(或者在文化上乐意接受新作物和家畜)的族群就可扩张版图,没有门路或意愿接受的族群自然遭到淘汰。

肥沃新月地带登峰造极,新几内亚和美国东部则乏善可陈。肥沃新月地带野生动植物的驯养可谓轻而易举,不但驯化了许多物种,其中品种多产而优良者比比皆是,而且种类繁多。结果得以发展出精密的粮食生产业,促使人口更加稠密,进而迈入有先进技术和复杂政治组织的现代世界,同时携带可以消灭其他族群的传染病。

事实上,这个地表的各大洲上有成百上千互相竞争的社会,有些比较开放,很能接受新事物,有些则趋向保守。接受新的作物、家畜和技术的,就能日益精进,领袖群伦,人口数目远远超过那些不愿接受新事物的族群,接着便向后者大举入侵,甚至将之连根拔起。

北美土著不能驯化苹果,问题在于北美整个野生动植物的组合。这个组合的发展潜力有限,也是北美食物生产起步晚的主因。

Linux 进程调度

总体来说,调度主要解决两类问题:

  • 什么时候调度
  • 调度的目标是谁 而一个优秀的调度系统,可以从如下几个指标判断:
  • fast process response time
  • good throughput for background jobs
  • avoidance of process starvation
  • reconciliation of the needs of low- and high- priority processes

Linux调度基于分时(Time-Sharing):多个进程共享同一个CPU,CPU运行时切分成多个时间片。分时技术依赖于timer interrupt,对进程来说是透明的。

Linux进程是可抢占的。当进程切换到TASK_RUNNING状态,内核检查其动态优先级是否比当前运行的进程的优先级高。如果更高,当前执行的进程中断掉,调度器切入,选择另外一个进程运行(通常是刚刚切换成runnable的进程)。当进程的时间片消耗完毕时,进程也是可被抢占的。当前进程struct thread_info中的TIF_NEED_RESCHED被设置,timer interrupt handler终止后,scheduler切入。

一个被抢占的进程并不是被暂停,它还维持在TASK_RUNNING状态,只不过不再使用CPU。

调度算法

Linux 2.6版本之前的调度算法很简单,进程切换时,内核扫描runnable进程列表,计算它们的优先级,选择“最优”的进程运行。这种算法的问题是切换时的时间开销会随着进程数目的增多呈线性增长。

调度策略:

  • SCHED_FIFO
  • SCHED_RR
  • SCHED_NORMAL

O(1) Scheduler

基于Linux-2.6.12版本。

优先级

Linux 2.6.12 内核里,优先级表示分为用户态和内核态。用户态优先级就是我们常用的nice值。取值范围[-20, 19]。内核态优先级被称作静态优先级(static priority),取值范围是[0, 139]。其中,[0, 99]是Real-Time Process,[100, 139]对应用户态nice值。

在静态优先级外,真正发挥作用的是动态优先级(dynamic priority)。动态优先级实际上是静态优先级加上了一个运行时补偿(bonus): $$ dynamic_priority = max(100, min((static_priority - 5 + bonus), 139)), bonus \in [0, 10] $$