什么是 CLR ?

作者:By Vance Morrison – 2007
原文链接:https://github.com/dotnet/coreclr/blob/master/Documentation/botr/intro-to-clr.md
翻译:dontpanic

什么是公共语言运行时(Common Language Runtime, CLR)?简单来说就是:

公共语言运行时(CLR)是一套完整的、高级的虚拟机,它被设计为用来支持不同的编程语言,并支持它们之间的互操作。

啊,有点绕口,同时也不太直观。不过这样的表述还是 有用的 ,它把 CLR 的特性用一种易于理解的方式分了类。由于 CLR 实在太过庞大和复杂,这是我们理解它的第一步——犹如从万米高空俯视它,我们可以了解到 CLR 的整体目标;而在这之后,我们就可以带着这种全局观念,更好地详细了解各个子模块。

CLR:一个(很少见的)完备的编程平台

每一个程序都有大量的运行时依赖。当然,一个程序需要由某种特定的编程语言编写而成,不过这只是程序员把想法变成现实的第一步。所有有意义的程序,都免不了需要与一些 运行时库 打交道,以便能够操作机器的其他资源(比如用户输入、磁盘文件、网络通讯,等等)。程序代码还需要某种变换(翻译或编译)才能够被硬件直接执行。这些依赖实在是太多了,不仅种类繁多还互相纠缠,因此编程语言的实现者通常都把这些问题交由其他标准来指定。例如,C++ 语言并没有制定一种 “C++可执行程序” 格式;相反,每个 C++ 编译器都会与特定的硬件架构(例如 x86)以及特定的操作系统(例如 Windows、Linux 或 macOS)绑定,它们会对可执行文件的格式进行描述,并规定要如何加载这些程序。因此,程序员们并不会搞出一个 “C++可执行文件”,而是 “Windows X86 可执行程序” 或 “Power PC Mac OS 可执行程序”。

通常来说,直接使用现有的硬件和操作系统标准是件好事,但它同样也会把语言规范与现有标准的抽象层次紧密捆绑起来。例如,常见的操作系统并没有支持垃圾回收的堆内存,因此我们就无法用现有的标准来描述一种能够利用垃圾回收优势的接口(例如,把一堆字符串传来传去而不用担心谁来删除它们)。同样,典型的可执行文件格式只提供了运行一个程序所需要的信息,但并没有提供足够的信息能让编译器把其他的二进制文件与这个可执行文件绑定。举例来说,C++ 程序通常都会使用标准库(在 Windows 上叫做 msvcrt.dll),它包含了大多数常用的功能(例如 printf),但只有这一个库文件是不行的。程序员如果想使用这个库,必须还要有与它相匹配的头文件(例如 stdio.h)才可以。由此可见,现有的可执行文件格式标准无法同时做到:1、满足运行程序的需求;2、提供使程序完整所必须的其他信息或二进制文件。

CLR 能够解决这些问题,因为它制定了一套非常完整的规范(已被 ECMA 标准化)。这套规范描述了一个程序的完整生命周期中所需要的所有细节,从构建、绑定一直到部署和执行。例如,CLR 制订了:

  • 一个支持 GC 的虚拟机,它拥有自己的指令集(叫做公共中间语言,Common Intermediate Langauge),用来描述程序所能执行的基本操作。这意味着 CLR 并不依赖于某种特定类型的 CPU。
  • 一种丰富的元数据表示,用来描述一个程序的声明(例如类型、字段、方法等等)。因此编译器能够利用这些信息来生成其他程序,它们能够从“外面”调用这段程序提供的功能。
  • 一种文件格式,它指定了文件中各个字节所表达的意含义。因此你可以说,一个 “CLR EXE”并没有与某个特定的操作系统或计算机硬件相捆绑。
  • 已加载程序的生命周期语义,即一种 “CLR EXE 引用其他 CLR EXE” 的机制。同时还制订了一些规则,指定了运行时要如何在执行阶段查找并引用其他文件。
  • 一套类库,它们能够利用 CLR 所支持的功能(例如垃圾回收、异常以及泛型)来向程序提供一些基本功能(例如整型、字符串、数组、列表和字典),同时也提供了一些与操作系统有关的功能(例如文件、网络、用户交互)。
Continue reading “什么是 CLR ?”

长模式与 16 位程序

这是我在知乎的回答:https://www.zhihu.com/question/310785889/answer/58670983

首先我们要把 16 位程序分成两类来看:跑在实模式下的 16 位程序和跑在保护模式下的 16 位程序。长模式是支持兼容 16 位的保护模式的(x86-64 白皮书):

但由于在长模式下移除了虚拟 8086 模式,无法运行实模式的(直接访问硬件的)16位程序。

至于为什么移除了虚拟 8086 模式,只能认为是不想再多背包袱了。已经有的包袱甩不掉,但至少别给自己再找新麻烦…

其实 Intel 在 80286 添加保护模式时就不想带这个包袱了,它没有提供直接的方法从保护模式转换回实模式(即无法支持实模式 16 位程序) 。也许是大家怨声载道,而后在 386 中 Intel 添加了虚拟 8086 模式,允许直接在保护模式下虚拟一个实模式出来。这样在 32 位操作系统中,实模式的 16 位程序才能运行。所以到了 64 位,这样的包袱不再扔一次怎么行呢,实在不行再把它加回来就好了,毕竟这事 Intel 已经干过一回了 🤣

总而言之,长模式是可以运行 16 位程序的,但只兼容保护模式。不过 Windows 也无法运行 16 位保护模式下的程序,这是 Windows 本身不再支持了的原因,不是长模式的限制。

错误模型 – The Error Model

  • 原文链接:http://joeduffyblog.com/2016/02/07/the-error-model/
  • 作者:Joe Duffy
  • 翻译:dontpanic

译注:下文中所有的“我”、“我们”均指代原作者 Joe Duffy 或其所在团队。作者总结了目前主流编程语言中常见的错误模型的优缺点,同时分别给出了自己的针对不可恢复错误(Unrecoverable Error)和可恢复错误(Recoverable Error)的处理方案。由于本文有一些惯用语和专业词语,受限于我的水平,可能出现翻译错误、或措辞与主流方案不同等问题,欢迎指出以便修正。本文较长,请做好长时间阅读的准备。

译注:前情提要:原作者 Joe Duffy 曾在微软参与开发一款操作系统 Midori,这是一款研究型/孵化型项目。这款操作系统主要由一种 C# 的变种语言(有人称作 M# 语言)编写。

The Error Model

Midori 使用了一种基于 C# 的、支持 AOT 编译、类型安全的语言。除了我们的微内核,整个系统都是使用这种语言编写的,包括驱动程序、域内核(Domain Kernel),以及全部的用户代码。我在这段时间里收获了很多,现在是时候总结一下了。整个语言涵盖的东西太多了,我会分成几篇文章来阐述,就先从错误模型开始。错误(Errors)的传递与处理在任何编程语言中都是非常基础的部分,而对于用来编写高可靠操作系统的语言来说更是如此。就像 Midori 项目的其他部分一样,任何修改一部分都应该从全局的角度来考量,并进行不断地迭代。我经常从原来的同事那里听到说,错误模型是他们在 Midori 上开发程序时最怀念的部分。我也很怀念这部分。那么废话少说,我们这就开始。

错误模型简介

错误模型需要回答的最基本的问题是:“错误”应该如何传达给程序员和用户?这问题似乎很简单。

要回答这个问题,最大的阻碍之一是:如何定义什么是“错误”。很多语言把 Bug 和可恢复错误归为一类,用同样的机制来处理,比如把空指针引用错误、数组访问越界错误与网络连接错误、语法分析错误混为一谈。这种一致性初看起来很美好,但它有着深层次的问题——这种一致性会造成很大误解,并且通常会导致程序员写出不可靠的代码。

总的来说,我们的解决方法是同时提供两套错误模型。一方面,对于程序的 Bug,我们提供了快速失败(Fail-Fast)模型,在 Midori 中我们称其为放弃(Abandonment);另一方面,对于可恢复的错误,我们也提供了静态受检查异常(Statically Checked Exception)。这两种错误模型从编程模式到背后的机制都截然不同。放弃会无条件地立即终止整个进程,不会再运行任何用户代码(需要说明的是:一个典型的 Midori 程序会由很多个小的、轻量级的进程组成);异常则允许恢复用户代码的执行,在这个过程中类型系统会起到重要的检查和验证的作用。

我们的旅程漫长而曲折。为了讲好这段故事,这篇文章分为以下6个主要部分:

  • 野心和经验
  • Bugs 不是可恢复错误!
  • 可靠性、容错性和隔离性
  • Bugs:放弃(Abandonment)、断言(Assertions)和合约(Contracts)
  • 可恢复错误:类型导向的异常
  • 回顾与总结

现在看来,有些结论似乎很明显,尤其是在更现代的系统语言
(比如 Go 和 Rust)出现之后。但是一些结论还是让我们很惊讶。我会尽量省略废话,但也会提供足够的背景故事。我们走过很多弯路,但我觉得这些弯路甚至要比最终的结论更有趣。

Continue reading “错误模型 – The Error Model”

WebApp for Desktop: 请不要滥用手型指针

这是一篇吐槽。最近想用Electron做点东西,大致浏览了几个UI库,又想起一些用Electron做的App的糟糕体验,实在是想吐槽一番。也不知道大家是不是也有类似的感觉,还是只是我个人吹毛求疵。如果是我的问题,还请打醒我。

首先,这里的WebApp指的是用基于Web的技术制作的客户端程序,比如VSCode、Microsoft Teams、Github Desktop等等。我在使用VSCode和Microsoft Teams时,在用户体验上会跟NativeApp有严重的割裂感。除了渲染性能这种客观问题之外,最主要的问题是,手型指针被滥用了

到处都是手型指针!

举例来说,在VSCode中,把鼠标放在一切能够点击的东西上,几乎都会变成手型,比如文件列表、文件Tabs、各种按钮等等:

然而,在主流的Windows/Gnome/KDE/macOS上,这些都不应该触发手型指针:

为什么在WebApp里面不应该大量使用手型指针?

因为滥用手型指针违背了各种Native UX设计指南——即,这就不是Native App的Feel。例如,在微软的Windows Desktop UxGuide中,明确说明了普通指针和手型指针的适用情况:

Normal Select – Used for most objects.
Link select – Used for text and graphics links because of their weak affordance.

在苹果的Human Interface Guidelines中,同样明确说明了普通指针和手型指针的适用情况:

Arrow – This is the standard pointer that’s used to select and interact with content and interface elements.
Pointing hand – The content beneath the pointer is a URL link to a webpage, document, or other item.

总结一下就是:只有在文本图片链接等情况下,才会推荐使用手型指针。所有一般情况,都应该使用普通的指针。

虽然手型指针为用户提供了额外的提示,表示这个元素可以被鼠标操作,但是在Native App中,很多时候不需要、也不应该依靠手型指针来增强操作提示。在微软的Windows App UxGuide中,有这样一段话:

Well-designed user interface (UI) objects are said to have affordance, which are visual and behavioral properties of an object that suggest how it is used.

也就是说,UI元素应当使用一些视觉和行为属性来表示它支持的操作——例如按钮应当做成看起来就可以被按下的样子、Slider应该有个槽槽来表示它可以被滑动,等等——而不是使用手型指针来提示这些操作。例子就像上面给出的Windows 资源管理器,以及QtCreator的侧边栏。

但为什么我不反感在普通网页中大量使用手型指针?

这里我也没有想的很清楚,可能的原因有:①在使用浏览器浏览网页时,我不期待网页会有Native的Look’n’Feel;②习惯了!

不过,我觉得主要的原因还是由于网页与客户端程序存在区别:

网页的本质是一篇文档。当我浏览一篇网页时,跟看一本杂志、看一本书很像。因此,网页上的交互组件应该优先与文档的整体风格保持一致,而不是优先显得“affordable”(不知道怎么翻译,可操作性?)。一个看起来就能够按下的按钮,且不说风格问题,更有可能喧宾夺主。所以我们可以弱化这些元素的affordance,而使用手型指针来增强操作提示。(说实话,很多Web UI Component的按钮,我就没什么按下去的欲望。)

然而客户端程序不是文档,尽管我们依然使用Web的技术来构建它,但它不是一篇文档。它的功能性更加重要,各类UI元素就是界面的主体。所以应该把各类UI元素在视觉上就设计得足够affordable,而不是去借助手型指针。上面贴出的VSCode中的各种button,有的甚至连hover效果都没有!

正例:Github Desktop

Github Desktop是我想举出的正例之一。它的下拉菜单、按钮、列表等等,全部使用普通鼠标指针,使用起来非常愉快:

结尾

其实除了手型指针这个问题之外,有些App还有一些小地方不够Native,比如Microsoft Teams中的一些图标存在延迟加载问题。在用Web技术做移动App时,大家都在往Native Look’n’Feel 靠拢;为什么到了Desktop,却不在意这些体验呢?

最后如果大家知道哪个UI库不滥用手型指针的,请推荐一个……

附:可以参考的其他讨论

https://ux.stackexchange.com/questions/105024/why-dont-button-html-elements-have-a-css-cursor-pointer-by-default

在 Medium.com 上查看

写出形似QML的C++代码

最开始想出的标题是《Declarative C++ GUI库》,但太标题党了。只写了两行代码,连Demo都算不上,怎么能叫库呢……后来想换掉“库”这个字,但始终找不到合适词来替换。最后还是起了个low一点的名字,贱名好养活啊!

这篇文章的目的是介绍如何用C++写出带有Declarative风格的代码。有一些GUI库需要额外的预处理过程(比如qt),还有一些也支持XML格式的GUI声明,但需要运行时Parse那个XML(比如wxWidgets)。能不能只用一个C++编译器、不要运行时Parse新语言来搞定这个问题?

直观地看上去,QML语法跟C++好像还有几分像,就选择QML进行借(chao)鉴(xi)吧。
最终的代码放在了 https://github.com/dontpanic92/yz ,代码与文章一同食用味道更佳。

Continue reading “写出形似QML的C++代码”

编辑器背后的数据结构

大约刚上大二的时候,想做一个编辑器控件。不是一个用Scintilla套上外壳的编辑器,而是一个能被套上外壳的控件。当然它最后也成为了我众多流产了的练手项目中的一员,不过人人黑历史里还留存着当时的一张截图

那段时间也对编辑器所使用的数据结构非常感兴趣。我们需要一种数据结构,能够支持字符串高效地索引、遍历、插入和删除。当时找的一些论文和书还躺在硬盘里一直没删,如今拿出来再嚼一嚼。下面介绍几种在编辑器中常见的数据结构。

Continue reading “编辑器背后的数据结构”

贝叶斯分类器

这是3月25日我在TM组机器学习讨论会上的分享。

Content


  1. 贝叶斯决策论
  2. 朴素贝叶斯分类器
  3. 半朴素贝叶斯分类器
  4. 贝叶斯网络

1. 贝叶斯决策论


贝叶斯决策论是一种基于概率的决策理论。当所有相关的概率都已知的理想情况下,贝叶斯决策论考虑如何基于这些概率和误判损失来选择最优的类别标记。

Example

哈工大与哈师大的同学举办大型联♂谊♀会,两个学校分别有500人参与。在联谊会上随机找到一个同学,请猜测他是那个学校的学生?

如果我们一点额外信息都不知道的话,只能随机猜测给出答案。如果我们能够提前知道一点点信息的话,就能够更大程度地猜中正确答案。比如,性别信息:

 

如此的话,假若这个同学是男生,我们肯定会猜测他是哈工大的学生。而从贝叶斯决策论的角度来看,我们需要比较以下两个概率大小:

  • P(工大学生=是 | 性别 = X)
  • P(师大学生=是 | 性别 = X)

上述两个概率被称作后验概率。后验概率往往难以直接获得,我们需要采用一定的手段进行计算。一些算法采用直接对后验概率进行建模的方法,例如SVM、决策树等,这些模型称为判别式模型。而先对联合概率进行建模、进而计算后验概率的模型,称为生成式模型

\(P(c|\boldsymbol{x})=\frac{P(\boldsymbol{x}, c)}{P(\boldsymbol{x})}=\frac{P(c)P(\boldsymbol{x}|c)}{P(\boldsymbol{x})}\)

由此可以计算得到,P(工大学生=是 | 性别 = 男)为4/5,P(师大学生=是 | 性别 = 男)为1/5.

在上面的例子中,我们直接使用了后验概率对类别进行估计。实际问题中,如果将某一类估计错误的代价比较大的话,可以选择在后验概率前乘以一个系数,变为期望损失。分类也从最小化分类错误率变为最小化期望损失。

在上面的式子中,\(P(c)\)代表的是类先验概率。在样本足够大的情况下,直接使用频率即可作为这一概率;\(P(\boldsymbol{x}|c)\)叫做类条件概率,它跟属性x的联合概率有关。上面的例子中,x只有一维,而在实际问题中,往往会选择很多个Feature。此时他们的联合概率就变得难以计算,因此我们需要一些手段对它们进行估计。

Continue reading “贝叶斯分类器”

站在镜像源背后的男人

@雨翌 说想知道镜像源是如何工作的,我们就来说一说这个。其实要做一个镜像源,需要搞定的有两件事:一是如何向广大用户提供下载服务,一条httpd start就搞定了;二是如何与上游同步,然而这种东西也一条rsync就搞定了:joy:

关于rsync

rsync提供了一种快速的文件同步的机制。一般的diff算法需要分别遍历两个文件,然而这种功能并不适用于远程传输:如果我能够同时获得需要同步的两个文件,再diff他们就没什么意义了。rsync使用的算法并不是特别复杂,可以Google The rsync algorithm 搜到Andrew Tridgell在1996年的论文,man rsync可以得到使用说明。

Continue reading “站在镜像源背后的男人”