还在下雨

早上闹钟还没响,就收到学弟从美东发来的微信消息,高管在加拿大被扣留,感觉刚释放的贸易战缓和信号瞬间消失无影。接着看到同学发送的新闻,张首晟教授去世,又给学弟把这则消息转了过去。

到了公司也没获得更多信息。一天工作下来,解决了几个问题,心情是近来最平静的一天。雨一直下个不停,沿着玻璃幕墙下来,汇成一条条水线流下。

各类信息媒介上也是消息攒动,同样没获得更多有效信息。前段时间看到一个问题:为什么感觉今年逝去的名人特别多?我想,也许是他们的时代逝去了,也许是我们的时代逝去了。

小学、初中、高中的时候喜欢数学和诗歌。现在对一些人向往的“诗意的栖居”这种描述不怎么向往了。在这个世界,不可能所有人都能到达那种美好的彼岸。现实还是要战斗,在枪炮中让玫瑰绽放。

有观点说外面的世界再乱,只要自己的小环境能保持美好就够了。我也不知道要怎么选择,一是我不知道什么叫好的小环境,而是我可能也没能力隔绝外界的干扰,也就无所谓选择。

晚上从公司回来,雨还是在下,风吹在手上冻得不行。

寒冷 / 困顿 / 饥饿
树影 / 摇曳 / 零落
混乱 / 猜测 / 迷惑
启程 / 归来 / 此刻

Escape from escape analysis

1. 逃逸分析背景

Go 语言采用了并发的(Concurrent)、非移动的(Non-Movable)、非分代的(Non-Generational)、基于三色(Tri-color)标记的垃圾回收(Garbage Collection)算法,只在 特定阶段开启写屏障(write barrier)。
特点是全局停顿时间比较少,在一些场景下是十微秒级别的。

垃圾回收算法针对的是堆(heap)中的内存。
为了减少垃圾回收的时间消耗,Go 语言在编译阶段通过静态分析算法对程序的结构进行分析,尽可能讲对象分配在栈上(如果这个对象的生命周期在它定义的函数返回时就结束的话)。
这一算法也利用了 Go 语言在函数传递参数时总是传递参数的值这一个语言特性。

而静态分析不总是完备的,会有一些本来可以分配在栈上的对象被 Go 的编译器分配在了堆上。
如这篇文章《Golang escape analysis》所描述的一些例子一样,有些对象本来可以避免逃逸(Escape,指的是对象被分配在堆上)。

对于某些场景,我们确定一个对象肯定可以(也应当)被分配在栈上,但是它却逃逸了。
这样在某些关键路径上的逃逸的对象会造成大量的分配和垃圾回收。

2. Go 版本

使用的 Go 版本为今晚刚从 master 分支上 pull 下的源码直接构建。

1
2
ThinkPad-X1-Carbon:bin bef0rewind$ ./go version
go version devel +42e8b9c3a4 Fri Nov 30 15:17:34 2018 +0000 darwin/amd64

3. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// file: escape.go
package main

import "fmt"

type BigTempObject struct {
/// ...
field1 int
}

func causeEscape(i interface{}) {
switch i.(type) {
case *BigTempObject:
println(i)
default:
fmt.Println(i)
}
}

func main() {
obj := BigTempObject{}
addrObj := &obj

causeEscape(addrObj)
}

使用 go run -gcflags="-m -m" escape.go 可以在运行时输出逃逸分析的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
./escape.go:10: cannot inline causeEscape: unhandled op TYPESW
./escape.go:19: cannot inline main: non-leaf function
./escape.go:10: leaking param: i
./escape.go:10: from ... argument (arg to ...) at ./escape.go:15
./escape.go:10: from *(... argument) (indirection) at ./escape.go:15
./escape.go:10: from ... argument (passed to call[argument content escapes]) at ./escape.go:15
./escape.go:15: causeEscape ... argument does not escape
./escape.go:23: addrObj escapes to heap
./escape.go:23: from addrObj (passed to call[argument escapes]) at ./escape.go:23
./escape.go:21: &obj escapes to heap
./escape.go:21: from addrObj (assigned) at ./escape.go:21
./escape.go:21: from addrObj (interface-converted) at ./escape.go:23
./escape.go:21: from addrObj (passed to call[argument escapes]) at ./escape.go:23
./escape.go:20: moved to heap: obj
(0x10904e0,0xc420080050)

obj 可以分配在栈上,因为在 main 函数返回时(栈退出),这个变量占用的空间就可以安全被用在其他地方了。
但是 “./escape.go:20: moved to heap: obj” 说明 obj 被分配在了堆上。

4. 小技巧

如何改变这个分析结果,需要一点小技巧。

关键词是 uintptr 类型。
Go 语言中对 uintptr 是这样描述的:

uintptr is an integer type that is large enough to hold the bit pattern of any pointer.

比如在 64-bit Linux 系统上 uintptr 被定义成为了 uint64
Go 中合法的类型转换为:normal pointerunsafe.Pointeruintptr
因此我们可以把上面的程序中的 addrObj 转换为 uintptr
这样 Go 编译器不再认为 addrObj 同后面函数 causeEscape 使用的参数 i 存在引用关系,从而绕过 Escape Analysis Algorithm 。
为了防止垃圾回收过程中 obj 被回收,可以使用 obj.field1 = 0 来保持 obj 活跃。

修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"unsafe"
)

type BigTempObject struct {
/// ...
field1 int
}

func causeEscape(i interface{}) {
switch i.(type) {
case *BigTempObject:
println(i)
default:
fmt.Println(i)
}
}

func main() {
obj := BigTempObject{}
addrObj := &obj
intAddr := uintptr(unsafe.Pointer(addrObj))
causeEscape((*BigTempObject)(unsafe.Pointer(intAddr)))
obj.field1 = 0
}

使用 go run -gcflags="-m -m" escape.go 运行结果:

1
2
3
4
5
6
7
8
9
10
11
./escape.go:13: cannot inline causeEscape: unhandled op TYPESW
./escape.go:22: cannot inline main: non-leaf function
./escape.go:13: leaking param: i
./escape.go:13: from ... argument (arg to ...) at ./escape.go:18
./escape.go:13: from *(... argument) (indirection) at ./escape.go:18
./escape.go:13: from ... argument (passed to call[argument content escapes]) at ./escape.go:18
./escape.go:18: causeEscape ... argument does not escape
./escape.go:26: (*BigTempObject)(unsafe.Pointer(intAddr)) escapes to heap
./escape.go:26: from (*BigTempObject)(unsafe.Pointer(intAddr)) (passed to call[argument escapes]) at ./escape.go:26
./escape.go:24: main &obj does not escape
(0x10904e0,0xc42003bf70)

可以看到 obj 不再逃逸,主要是 intAddr 中断了逃逸分析算法构建的指针依赖关系(表示为一个有向图)。

5. 一点感想

我们可以做到不代表一定去做,有风险也不代表禁区,采取什么样的行动是个人权衡后的选择。
什么原因导致了人们做了不同的选择,而人们不同的选择又导致了什么结果?
多样性是这个世界的现状,黑暗面与光明面同在。
May the force be with you.

Golang Receiver Type 探索

1. 参考

在 Go 的官方 spec 中有以下涉及到类型和方法的章节,如果需要了解具体的细节,可以参考阅读。

核心的概念是 method sets:

A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type T is the set of all methods declared with receiver T or T (that is, it also contains the method set of T). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.

The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.

下面的一些细节基本上都和这段描述相关。

2. Duck typing 与方法调用

在很多面向对象的语言中,一个对象都可以“拥有”一些方法,使用例如 obj.f(a, b, c) 的形式进行调用。结合语言的类型系统,通过“扩展”、“继承”、“实现”等术语,我们可以将不同的类组织起来。在 Go 语言中采用的是 “duck typing”,没有显式的类型关系定义关键字。当一个类型实现了一个接口的全部方法时,那这个类型就被视为实现了这个接口。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type Duck interface {
Bark()
}

type A struct {}

type B struct {}

func (*A) Bark() {}

func main() {
var iA interface{} = &A{}
if _, ok := (iA).(Duck); ok {
fmt.Println("&A{} is Duck")
} else {
fmt.Println("&A{} is not Duck")
}

var iB interface{} = &B{}
if _, ok := (iB).(Duck); ok {
fmt.Println("&B{} is Duck")
} else {
fmt.Println("&B{} is not Duck")
}
}
1
2
&A{} is Duck
&B{} is not Duck

我们可以用原始的类型去调用一个方法,也可以使用一个接口去调用方法。这里就涉及到方法调用者的问题:什么样的对象是一个合法的方法调用者?

至少 A{} 不是,因为我们实现 Duck 接口的时候,使用的是 func (*A) Bark() 进行的定义,而非 func (A) Bark()。这样就导致了只有 A 类型对象的指针类型才能作为方法调用者去调用 Bark 方法。

3. 成员函数的参数

在实现中,调用某个类型的成员方法,第一个参数其实是这个方法的实现对象自身,即如果是一个指针的方法,就是这个指针的值,如果是一个对象,就是这个对象的值。

下面使用 Go 1.8.3 展示,因为当前最新的 Go 编译器在打印 stack trace 的时候不再打印函数的参数(这个例子中)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

type R1 struct {
n int
}

func (r *R1) f(n int) {
println("received", n)
panic("just a panic")
}

func main() {
r := &R1{}
a := 1
println(r)
r.f(a)
}
1
2
3
4
5
6
7
8
9
0xc420039f70
received 1
panic: just a panic

goroutine 1 [running]:
main.(*R1).f(0xc420039f70, 0x1)
/Users/bef0rewind/Projects/net example/src/main/receiver_type.go:9 +0xa3
main.main()
/Users/bef0rewind/Projects/net example/src/main/receiver_type.go:16 +0x5a

Stack trace 中函数 f 第一个值是指针 r 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

type R1 struct {
n int
m int
}

func (r *R1) f(n int) {
println("received", n)
panic("just a panic")
}

func (r R1) g(n int) {
println("received", n)
panic("just a panic")
}

func main() {
r := R1{7, 9}
a := 1
println(r.n)
(r).g(a)
}
1
2
3
4
5
6
7
8
9
7
received 1
panic: just a panic

goroutine 1 [running]:
main.R1.g(0x7, 0x9, 0x1)
/Users/bef0rewind/Projects/net example/src/receiver_type/main/args.go:15 +0xa3
main.main()
/Users/bef0rewind/Projects/net example/src/receiver_type/main/args.go:22 +0x58

Stack trace 中函数 g 第一个值是 r 的值 79

从这个实现方式中我们可以推断以下几点:

  • Go 语言采用参数传值的方式进行函数调用,因此如果对象很大,使用的对象本身调用函数会带来大量的复制
  • 不可能在函数调用中改变函数外的调用者,因为传到函数内部的只是调用者的副本

4. 使用接口调用函数

基于这样的成员函数实现方式,我们可以尝试另外一种调用方式:使用接口类型调用一个函数。
这里不是将一个对象转换成特定的接口然后去调用函数,而是使用接口类型本身去进行函数调用。
这种方式在 Go 1.9 中开始支持,在 Go 1.10 开始写入 Go 的 specs。这个例子使用的是 Go master 分支的版本,可能是 Go 1.11。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

type M struct {

}

func (*M) f(n int) {
println("I;m M, with", n)
}

type IM interface {
f(n int)
}

func main() {
m := &M{}
IM.f(m, 7)
}
1
I;m M, with 7

此外还能使用匿名接口类型去调用函数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

type M struct {

}

func (*M) f(n int) {
println("I;m M, with", n)
}

func main() {
m := &M{}
interface{f(n int)}.f(m, 7)
}

运行结果与上面的一段采用 IM 接口定义的例子是一样的。

5. 注入依赖

有时候一个对象在实例化的时候,它的一些成员方法的行为可能还没有确定,需要依赖外界注入。此时我们可以在对象类型定义中内嵌一个接口,然后在后期传入一个接口的实例来确定其行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

type BinaryOp interface {
Compute(a, b int) int
}

type ComputeNode struct {
x, y int
BinaryOp
}

func (node *ComputeNode) Result() int {
return node.BinaryOp.Compute(node.x, node.y)
}

type Add struct {}

func (*Add) Compute(a, b int) int {
return a + b
}

type Multi struct {}

func (*Multi) Compute(a, b int) int {
return a * b
}

func main() {
node := &ComputeNode{x:2, y:3}

node.BinaryOp = &Add{}
println(node.Result())

node.BinaryOp = &Multi{}
println(node.Result())
}
1
2
5
6

注意一定要记得传入接口的实例,在这个例子中如果不给 node 传入一个 BinaryOp 接口实例,那 node.BinaryOpnil,在调用 Compute 方法的时候就会发生异常。例如将上面的 main 函数稍作修改:

1
2
3
4
5
6
func main() {
node := &ComputeNode{x:2, y:3}

//node.BinaryOp = &Add{}
println(node.Result())
}
1
2
3
4
5
6
7
8
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x104d8d7]

goroutine 1 [running]:
main.(*ComputeNode).Result(...)
/Users/bef0rewind/Projects/net example/src/receiver_type/main/injection.go:13
main.main()
/Users/bef0rewind/Projects/net example/src/receiver_type/main/injection.go:32 +0x47

6. 内部机制

内部机制有一些细节。大体就是一个接口 i 包含两部分内容(指针),一个是接口代表的方法的集合,一个实现这个接口的具体对象;而一个对象 obj,它包含了自己的内存中的值,也能通过其类型获取到 obj 实现的方法集合。

将这两个概念记住,在实现一些模式的时候就会少很多心智负担。

7. 总结

Go 语言的这套基于 “duck typing” 的机制好不好,争论有很多。不过我一向对这些争论没有特别的倾向,至少理解其机制之后按照其设计思路来用还可以正常使用,而且里面没有复杂的概念和例外情形。

也许我的理解有偏差,但现在还没有发现什么矛盾的地方。

defer, panic and recover in Golang

1. 什么是异常处理

程序在执行过程中有可能出现异常状态,比如获取一个不再有效指针指向的内容、除零等。
一般语言都提供了异常处理机制来应对这些情形,例如 Java 的 try/catch/finally 机制(https://docs.oracle.com/javase/tutorial/essential/exceptions/catch.html)、
Python 的 try/raise/except/finally 机制(https://docs.python.org/3/tutorial/errors.html)等。

2. Go 语言中的异常处理机制

Go 语言中使用的是 defer/panic/recover 机制来处理异常。Go 语言官方博客的《Defer, Panic, and Recover》讲述了这个机制的具体应用方式。

还有一些其他教程对这个机制的使用方法、适用场景进行了进一步阐述:

如果搜索 “golang 异常处理”,类似的教程有很多。里面的核心思想大体就是:用 defer + recover 处理一个 panicdefer 结构要在 panic 触发之前被定义而且 recover 要直接在在 defer 结构定义的函数中被调用(而不是被直接调用或者在函数内部的其他函数中被调用)。

3. defer 语法糖的部分原理

在讲述 defer 机制的文章中,都会提到一个函数中多个 defer 结构执行的顺序和定义顺序是相反的,即后定义的 defer 结构总是先被执行。为什么会出现这样的情况?例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
func g(n int) {
println(n)
}

func h(str string) {
println(str)
}

func f() {
defer g(0)
defer h("h")
}

调用 f 输出为:

1
2
h
0

常见的函数调用流程为:

  • 将函数使用的参数压入栈
  • 执行函数指令
  • 函数执行结束返回到调用点

如果 defer 相关的代码也是这么执行的话,那么为什么不是: 0 入栈 - 执行 g - g 返回 - "h" 入栈 - 执行 h - h 返回 这个顺序呢?
按照这个顺序执行,调用 f 输出应该是 0h 前面符合预期。是不是 Go 语言中执行 defer 时采用了特殊的处理流程?

是,也不是。

太阳底下无新鲜事,defer 不过是一个语法糖,用来对一个函数 deferproc 进行包装。

1
2
3
4
// Create a new deferred function fn with siz bytes of arguments.
// The compiler turns a defer statement into a call to this.
//go:nosplit
func deferproc(siz int32, fn *funcval)

deferproc 创建一个延迟调用的函数,其参数为 siz (延迟调用的函数的参数占用的字节数量)和 fn(被延迟调用的函数本身)。
当 Go 程序的编译器遇到 defer f(),会将这条语句翻译为一条 deferproc 和一条 deferreturn
其中 deferproc 把被调用的函数及其参数挂载在 goroutine (Go 中的并发单元,协程)结构的一个链表上;
deferreturn 从链表上取下一个挂载的被延迟执行的函数,执行它。

如何使用技巧绕过 defer 关键字,模拟类似效果?
可以使用 linkname 方法来把 Go 语言运行时的一些关键函数导出,从而进行某些不常见的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
_ "runtime"
"unsafe"
)

type Eface struct {
_type uintptr
Data unsafe.Pointer
}

func EfaceOf(ep *interface{}) *Eface {
return (*Eface)(unsafe.Pointer(ep))
}

type Funcval struct {
fn uintptr
// variable-size, fn-specific data here
}

//go:linkname Deferproc runtime.deferproc
func Deferproc(siz int32, fn *Funcval)

//go:linkname Deferreturn runtime.deferreturn
func Deferreturn(arg0 uintptr)

func main() {
var f = func() {
println("hacked defer")
}
var fI interface{} = f

// Attach a defer struct to the current goroutine struct
Deferproc(0, (*Funcval)(EfaceOf(&fI).Data))

defer func() {
println("original defer")
}()

// Run a deferred function if there is one
Deferreturn(0)
}

这段代码会输出:

1
2
original defer
hacked defer

当然,如果是使用 defer 关键字,Go 语言的编译器会选择合适的位置插入 deferreturn 语句,而不是像上述代码中一样手动放在结束位置处。

4. recover 生效位置的设计原因推测

言归正传,panic 发生后,会根据函数调用顺序逐层上报,直到最后一层被抛出到系统导致崩溃或者被 recover 机制处理。
那么如果被 recover 处理,这个过程是怎么生效的?

很多教程中都提到 recover 一定要在 defer 声明的函数里面(既不是这个函数本身也不能是函数里面的其他函数里面)才能正确处理当前的 panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// case 1, not work
defer recover()

// case 2, not work
defer func() {
func() {
recover()
}
}()

// case 3, work
defer func() {
recover()
}()

为什么呢?

先不考虑实现,先从理念上分析一下。

  1. defer 直接作用于 recover():无法根据 recover() 的返回值来进行不同类型的 panic 处理
  2. 在被 defer 作用的函数内部的函数 g 中使用 recover():如果 g 是一个第三方库的函数,无法保证其中没有未知的 recover 意外处理了系统中的 panic

因此事实上也只能通过这样的约束来使这个异常处理机制看上去直观易处理一些。当然通过对 Go 编译器进行修改,还是有办法使得上面三种情况下 recover 都可以中断 panic 向上层传递过程的。

此外,由于被 defer 处理的函数被挂载在 goroutine 结构的一个链表上,因此当 panic 发生时,可以直接从这个链表上取下被延迟执行的函数一个个执行。
这也是 recover 要放在 deferred function 中的原因,因为这些函数是肯定可以执行到的。

5. 总结

不能说 Go 中这个异常处理机制有多高明,基本上属于现代语言标配。了解更多背后的原理,在使用时可以更坚定一些。

此外,最近看到一本书《最好的告别》(https://book.douban.com/subject/26576861/)。

Being Mortal

豆瓣上的介绍:

当独立、自助的生活不能再维持时,我们该怎么办?在生命临近终点的时刻,我们该和医生谈些什么?应该如何优雅地跨越生命的终点?对于这些问题,大多数人缺少清晰的观念,而只是把命运交由医学、技术和陌生人来掌控。影响世界的医生阿图•葛文德结合其多年的外科医生经验与流畅的文笔,讲述了一个个伤感而发人深省的故事,对在21世纪变老意味着什么进行了清醒、深入的探索。

defer / finally 这些关键字让我们可以控制函数退出时的行为,但是我们自身呢?也许考虑这些问题可以让我们自身活得有意义一些。

推荐大家看一下。

Useful Commands

Convert images to a video

1
ffmpeg -r 30 -start_number 3455 -i _IMG%d.jpg -s 960X600 -pix_fmt yuv420p 30fps-960.mov
  • -r 30: 30 frames per second
  • -s 960X600: resolution
  • -pix_fmt yuv420p: for OsX

youtube-dl video and extract audio file

youtube-dl --proxy socks5://127.0.0.1:1080 -x --audio-format mp3 youtube-url

virtualenvwrapper

  • WORKON_HOME: which directory your environments are created in
  • /usr/local/bin/virtualenvwrapper.sh: default location for its configuration file
  • mkvirtualenv test --python=python3: make a virtual environment ‘test’ with python3
  • rmvirtualenv test: remove a virtual environment ‘test’
  • workon test3 or lsvirtualenv -b test3: activate a virtual environment ‘test’
  • deactivate: exit current environment
  • more details: search engine
    • how to avoid globa packages
    • how to copy an environment

node && npm

npm complains: Error: Cannot find module 'process-nextick-args'

Uninstall node, brew uninstall node, then by this stackoverflow post:

1
2
3
sudo rm -rf /usr/local/bin/npm /usr/local/share/man/man1/node* /usr/local/lib/dtrace/node.d ~/.npm ~/.node-gyp 
sudo rm -rf /opt/local/bin/node /opt/local/include/node /opt/local/lib/node_modules
sudo rm -rf /usr/local/bin/npm /usr/local/share/man/man1/node.1 /usr/local/lib/dtrace/node.d

Just delete something, then brew install npm.

delve (dlv) tips

  • funcs [regexp] : get function list
  • call : call a function (in a newer a go version, dlv should be installed in the newer go version too)

电池人生

电池,一般狭义上的定义是将本身储存的化学能转成电能的装置,广义的定义为将预先储存起的能量转化为可供外用电能的装置。因此,像太阳能电池只有转化而无储存功能的装置不算是电池。其他名称有电瓶、电芯,而中文池及瓶也有储存作用之意。

《维基百科》

5 月份上海校友会来杭州参观,我带队去云栖景区玩,后来大家一起去了阿里西溪园区。园区很漂亮,中间有一块地被高墙围起,据说马云在那里开董事会。后面有个讨论环节,那时我已经昏昏欲睡了,只记得那个阿里 P11 说今后公司要拓展的方向是娱乐和健康。

娱乐和健康都是人生活的必需品。

最近认识一些朋友去今日头条工作了。这个公司的应用通过算法构建消息流推送到人们的移动终端。在信息泛滥的今天,可以永远向下滑动手指,看到无尽的“新”闻。可算法知道,这些新闻的类型是你喜欢的,同样的主题是你的老朋友了。

当然这样的应用通常不需要你付费,因为你就是它们的商品。一次次的滑动中除了那些通过视网膜输入的新闻还有各式各样的广告,都是基于你的“兴趣”进行筛选后的结果。它们攫取你的时间卖给了肯花钱展示商品的人,赚到了差价。

以前看黑客帝国的时候,总是不明白 Matrix 那么发达,却还是需要将人关在营养仓里当做电池使用。人是消耗能量的啊!最近才逐渐有所领悟,人体不一定可以拿来产生能量。人作为进化的产物,智能依赖生物结构,Matrix 可以将一个个的人作为基础的计算组件,用来支撑虚拟世界中的各种计算。现在我们使用人工智能也是如此,用来识别人脸、用来预测天气 ……

《海伯利安》中有一个设定,机器智能帮助人类构建了可以在宇宙各处穿梭的跃迁节点,而这种技术远远超出了人类的理解范围。人们以为这是机器智能回馈给它们的“造物主”的礼物,但实际上这是一个陷阱。人穿梭过跃迁节点从另一侧出来并不是瞬间完成的。在节点中度过的无法感知到的时间中,人的大脑的计算能力被机器智能利用,完成了自身的不断进化,来追求它们的终极目标。

在阿西莫夫的经典作品《我,机器人》改编的电影中,威尔•史密斯扮演的黑人警探戴尔•斯普纳不相信人类能和机器人和平共处,故事的各种冲突也从此展开。这部作品的底线是机器人还遵守“机器人三定律”,虽然是被以某种人类意想不到的方式施行的。现实中,我们能对机器人有所期待吗?我们能对创造机器人的人有所期待吗?

总是有人对新的技术保持怀疑 —— 有些人因为未知而恐惧,有些人因为远见而忧虑,只有中间的人享受着生活。

有时候想人的一生好短暂,也就一百年。昨天看到淡豹的一篇文章《近日新闻有感》,中间有一大段对日常种种琐事的描述,读的过程中仿佛头被浸在水中。究竟是什么(强大的)力量在驱使我们生活?

在我看来这种力量里又加入了一方势力,有些人在尽最大努力使用着智能算法来侵占人的生命。以前他们也这样做,通过编辑人们喜欢的花边新闻和轶事来吸引人们注意,不过那种方式毕竟低效。现在算法可以让人们通过滑动手指,自己戴上镣铐,出卖生命。

小的时候,有段时间我很喜欢四驱车,当然是在看了《四驱小子》之后。终于有了一辆(无影剑)后,我发现了一个现实的问题:这车跑起来很费电池,于是又攒钱买充电电池。最终电池的能量变成了车在赛道上前进的动力以及我的回忆。

我对四驱车的欲望肯定是来自动画片,后来我弟弟那时候买的就是悠悠球了。人的大脑是可以被外界信息影响的,现代科技也会以某种方式对人的大脑进行编程。现在人脑运行的机制还不清楚,如果可行的话,那些广告商肯定想通过某种方式对你的大脑进行编程,让你看一遍广告就下单购物。

我前段时间在想:如果总是接受这样被算法(你的手指)选择过的信息,会变傻吗?至少我体会过那种无效信息过载的时刻,就是大脑没什么感觉了。

在这个过程中我们到底付出了什么呢?我们的时间,我们的未来。

随着时间的推移,人的未来就逐渐坍缩,直到生命终点,也就没有未来了。每一秒过去,我们的未来就少了几分可能性。

如果明天不值得期待,就继续燃烧自己吧。

Upgrade DigitalOcean's Ubuntu 17.04 to LTS version in 2018-06

What happened?

I found my proxy for accessing some websites stopped working today, so I had to change my VPS’s IP address.
After some trials, everything seemed OK and I started watching a skiing video made by NorthFace on Youtube.

Emmmm, I noticed 12 packages needed to be updated.
Well, I typed sudo apt-get update and got messages like this (I didn’t save the error messages then):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  404  Not Found [IP: 91.189.91.23 80]
Ign:10 http://us.archive.ubuntu.com/ubuntu zesty-backports InRelease
Err:11 http://us.archive.ubuntu.com/ubuntu zesty Release
404 Not Found [IP: 91.189.91.26 80]
Err:12 http://us.archive.ubuntu.com/ubuntu zesty-updates Release
404 Not Found [IP: 91.189.91.26 80]
Err:13 http://us.archive.ubuntu.com/ubuntu zesty-backports Release
404 Not Found [IP: 91.189.91.26 80]
Reading package lists... Done
E: The repository 'http://security.ubuntu.com/ubuntu zesty-security Release' does no longer have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
E: The repository 'http://us.archive.ubuntu.com/ubuntu zesty Release' does no longer have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
E: The repository 'http://us.archive.ubuntu.com/ubuntu zesty-updates Release' does no longer have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
E: The repository 'http://us.archive.ubuntu.com/ubuntu zesty-backports Release' does no longer have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

Wow, It was the first time I encountered such things.
After some digging, I realized it was because Ubuntu 17.04 (zesty) was not supported any longer.

According to https://wiki.ubuntu.com/ZestyZapus/ReleaseNotes

Ubuntu 17.04 will be supported for 9 months until January 2018. If you need Long Term Support, it is recommended you use Ubuntu 16.04 LTS instead.

It means my current release version (Ubuntu 17.04) won’t receive any updates in the future.

release-end-of-life-date

How to upgrade

I was struggled to reveal some steps to upgrade my Ubuntu 17.04 (End-of-Life) to 18.04 (LTS version).

The official guide to upgrade from an Ubuntu release which reaches its “end of life” is: https://help.ubuntu.com/community/EOLUpgrades

1. Update sources.list

The path of sources.list is /etc/apt/sources.list.

1
2
3
4
5
6
7
8
## EOL upgrade sources.list
# Required
deb http://old-releases.ubuntu.com/ubuntu/ CODENAME main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ CODENAME-updates main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ CODENAME-security main restricted universe multiverse

# Optional
#deb http://old-releases.ubuntu.com/ubuntu/ CODENAME-backports main restricted universe multiverse

In my case, CODENAME should be replaced by zesty.

2. Upgrade

Try apt-get upgrade, and I got:

1
2
3
dpkg: dependency problems prevent configuration of ubuntu-standard:
ubuntu-standard depends on libpam-systemd; however:
Package libpam-systemd:amd64 is not configured yet.

By some method suggested by stackoverflowers, I finally got over it by these commands:

1
2
sudo dpkg --force-all -P libpam-systemd
sudo apt-get -f install libpam-systemd

3. Dist Upgrade

These are some normal steps for an upgrade.

1
2
sudo apt-get dist-upgrade
sudo do-release-upgrade

If you’re unlucky like me, you will not upgrade to a newer release completely.
You may see:

1
Package grub-efi-amd64 is not configured yet.

According to a question asked by someone, you can try this:

1
2
sudo dpkg --force-all -P grub-efi-amd64
sudo dpkg --force-all -P grub-efi-amd64-signed

Execute sudo apt-get update and sudo apt-get upgrade.

4. Thoughts

I first upgraded to Ubuntu 17.10 then Ubuntu 18.04.

To see if I have done the right things, I check the release version by sudo lsb_release -a:

1
2
3
4
Distributor ID:    Ubuntu
Description: Ubuntu 18.04 LTS
Release: 18.04
Codename: bionic

Is it the DigitalOcean’s customized release makes the upgrade so complex for a non-expert programmer or the Ubuntu release itself?

But apparently, I’m not the only one who is confused by the error messages and asks questions on the web. Luckily I’ve found helping answers I need.

The commands with words like force or -f make me feel anxious.

These violent delights have violent ends.
— by Shakespeare

Dolores

Anyway, it works.

理解 C++ Traits

C++ 是我学习的第一种编程语言,当时是 2007 年。现在 C++ 标准委员会已经在讨论 2020 年要发布的特性了,我连 2011 年发布的 Traits 都还不知道是啥。今天下定决心了解总结一下。

C++ 之父 Bjarne Stroustrup 说:

Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine “policy” or “implementation details”.

嗯,这段描述也比较抽象。我的理解是,trait 把不同类型的特定信息进行打包,用在算法或者其他对象中。这样算法或者其他对象的设计可以做到更加通用,通过 trait 打包的信息进行策略选择或者实现细节。

trait1

这段代码判断两个类型的关系。第 4 行的模板以两个类型作为参数,通过结构体 type_relation 的成员 relation 获取比较结果。当前代码输出:

1
2
type_relation<bool, int>: different
type_relation<int, int>: different

这段代码并未实现预期功能,因为比较 boolint,以及 intint 时,都输出 "different" 。下面继续完善这个 trait 实现。

trait2

第 11 行的类型参数种使用 <T, T> (即两个相同的类型)进行模式匹配。这段代码输出:

1
2
type_relation<bool, int>: different
type_relation<int, int>: equal

输出结果正确完成了类型比较的功能。


实现

trait 的实现基于 C++ 的模板引擎(template engine),能够在编译时期(compile time)将模板(基于最匹配的模式)展开。如果要在 Golang 或者 Java 种实现类似功能,可以使用反射或者 interface 之类的方法,而这些方法都是在运行时(runtime)进行判定,不可避免带来性能开销。


进阶

除了可以比较两个类型是否相等,还可以在类型参数上添加其他约束,例如:*[] 等。下面是一个稍微复杂一些的版本:

trait3

1
2
3
4
5
type_relation<bool, int>: different
type_relation<int, int>: equal
type_relation<int*, int>: is pointer of
type_relation<int[], int>: is array of
type_relation<int[4], int>: is array (length 4) of

当然这个“进阶”仅仅是比上面的例子复杂了一些,trait 还有更多能力。
C++ 的模板引擎为 C++ 提供了强大的元编程(meta programming)能力,也使得现代的 C++ 代码看上去比较奇怪。
事实上模板引擎是图灵完备的,如果使用好的话,能够将 C++ 的易用性和优雅性提升到一个高度(从那些看不到模板实现细节的用户的视角)。


感想

每次 C++ 标准更新的新闻我都看到了,从 C++0X、C++11、C++14 到 C++17,新出的特性一直在关注,可却从来没有真正编写一段程序去试试这些特性。这让我很伤感。

看到新的语言就想去尝试,OCaml、Clojure、Haskell …… 这些语言都很有特色,OCaml 让我体会到模式匹配的爽快,Clojure 让我体会到 S-表达式的自由和 Lisp 宏的强大,Haskell 让我体会到抽象的威力以及自己脑容量的有限。可是工作中用的最多的还是 Python、Java 和 C,当然还有 PHP (这个不能忘)。

想我和 C++ 的关系,最初的时候还是看的 Bjarne Stroustrup 的《The C++ Programming Language》。后来怎么就越走越远了呢?虽然现在工作中暂时依旧用不到,还是希望以此为契机,了解更多模板引擎可以做的事情。

追逐过的其他语言,各有各的契机,或许是缘分,或许是喜欢,或许是虚荣。现在都不敢说自己会 C++ 了。
如果当时能够更专注一些,自己目前的职业轨迹会不会有所区别?

想起最初自己理解 Haskell 里 Monad 概念的时候,一头雾水。后来懂了,就是懂了。为什么偏爱某一种语言呢?我也搞不清楚自己。

也许爱情也是如此,回忆当初做的不同结果是否能够更好?也许。记住过往,抗拒住回忆的诱惑,很难,但不得不做。喜欢的,就喜欢着吧。有些事情就是不行,正如有些事情莫名其妙地发生。

Just live with it.

2016 十一塞罕坝行动

这是很久之前应该出现的一篇日志,当时忙于处理照片、毕业论文之类的事情,加上强烈的懈怠情绪,一直没写。


2016 年暑假过后开学不久,政委喊大家出去骑车,这注定会很有趣。目的地是承德的塞罕坝,不是张家口的坝上草原。
主题是骑行、赏秋景。大家确认好时间后,大家定了火车票,提前去西直门北京北站托运了自行车。剩下的就是等待出行了。

塞罕坝国家森林公园位于承德北部,毗邻内蒙古(上面),是清代皇家猎苑一部分,集满蒙汉三民族文化。
景区内有七星湖、太阳湖、泰丰湖、白桦林、月亮湖等较大的景点和多个小景点,自然风光和人文遗迹于一体。
(注:这些湖我们一个也没去,人文遗迹也没看到)
另外,还珠格格就是在这个地方被他皇阿玛一箭射中的。

整体路线规划为:

  • 北京北 - 四合永镇 (火车)
  • 四合永镇 - 围场县 (骑车、住宿)
  • 围场县 - 机械林场 (骑车、住宿)
  • 机械林场 - 半截塔镇 (骑车、住宿)
  • 半截塔镇 - 隆化县 (骑车)
  • 隆化县 - 北京北 (火车)

Day 0

火车上的旅行总是欢快的,小帅哥 YL 拿了很多桌游,大家玩啊玩。
在玩阿瓦隆的时候,某个女博士一脸愁容,明显就是脑细胞不够用的表现。
路上路过了很多听过但是没去过的地方,比如古北口。

到了四合永镇,取车合影。

Gubeikou

到围场县很近,我们很快就骑到了,安顿好了住宿。肚子饿的呱呱叫,出去觅食,集体决定吃火锅(作为吃饭不挑剔的我当然也给不出什么建设性意见)。
一行人吃得热火朝天,风生水起,生龙活虎。最后算账,人均三十。

Hot Pot

然后去附近的超市准备接下来几天的食粮。水果、面包、零食、饮料,各种东西都来点。
逛超市比较有意思的是看到各种东西分门别类堆在一起,那么多,又都很光鲜。虽然不能都买了,但总有种莫名的愉悦感。

Supermarket

晚上还是玩玩玩,时刻压制住情绪,以免吵到其他人。

Day 1

第二天按时起床,旁边小店吃了早饭,准备出发。
说到这个出发,还是有些字面意义上的周折。小城在修路,大家凭着感觉和导航终于骑了出去。

骑车嘛,还是为了拍照。一路上大家各种互拍、自拍,对着天空、河流、群山拍。

骑车嘛,总会爆胎,尤其是漂亮的那种车,比如鲜红的挑战者 300。一路上补过胎的,没经验的,也基本都学会了。

scene1

scene2

scene4

这样的景色大家拍来拍去,怎么也拍不厌。骑在这样的路上,也感觉不累(假的,毕竟在爬大坡)。

接近傍晚,天空的颜色变得多彩,而眼前原野的景色淡下去了。

scene5

scene3

剩下的就是赶路了,还要走一段公路到机械林场。虽然路上的景色也很美,但是看不到,只能通过黑黢黢的树影摇曳想象了。路上发生了一起意外,美女 GY 摔车了。
简单处理了一下,也只能继续骑车。到了机械林场后买了创可贴和碘酒暂时顶一下。

together4

晚上十点多大家吃了羊腿饭,景区物价高,不过仍不及五道口。住宿的话,还停暖和的,三个大男生挤一挤了,另外的妹子们怎么睡都随意了,虽然大家都是在一间屋子里吧。

Day 2

together5

第二天早上在店家吃了早餐,馒头、稀饭、饼、鸡蛋、炒小菜,典型的住店组合套餐(甚至在西藏也是这种组合……)。
出去后,开始下小雪,然后是小雨。大家都披上了雨衣。不过这注定是狼狈的、凄惨的、励志的一天 ……
我不说别人了,就说我自己吧。首先我的鞋子是登山鞋,防水的那种,可以避免水从外面进来,也可以防止水从里面出去。于是我就相当于穿着一个便携泡脚盆在骑车。
总之就是一种说不清道不明的酸楚与傻乐呵在心中交织。

最大的收获是,雨中的山林颜色更加润了。眼前的雨虽然冷,但是远处的雾气很没啊,我们是不畏风雨的骑车人。

scene6

scene7

scene8

遇到一个便利店,大家进去买了各种包裹隔离物品(塑料袋),试图把自己的脚和腿保护起来。
中午在一个小饭馆吃了面,面对有点凶狠的狗去上了厕所。
吃完饭接着骑车,然后大家的车纷纷爆了胎,前面爆完后面爆胎。
忘记提的一点是,我带的补胎片失效了。
而备胎也用光了,于是各种打气筒接力打气艰难前行。
直到政委看到了一个给汽车补胎的地方,我们借用先进装备磨胎,强力胶水补胎,颇有种鸟枪换炮的感觉。

我最为先锋官,到了半截塔镇,发现了一个问题:有人办婚礼,没旅馆可以住了。
大家到了后,决定一拨人先买点东西吃饭,政委向来时方向去找住的地方,我向前面骑,去看看镇子里有没有住的地方。
我本来看到一家说可以的,但是后来店主说很抱歉,他现在在外地,只有自己孩子在家不放心。
幸好政委找到了一家汽车旅馆,住的地方有着落了,安心吃饭喽。

这天路上还遇到了交通事故,别人的。车堵了好几公里,我们骑车经过拥堵路段,骑在没车的路上,好爽啊。

这个汽车旅馆洗漱都要在屋子外面的,水冰凉。
睡之前大家把坏掉的车胎都换了。大家不停吐槽,车胎上面粘的泥里一股马粪味儿。
我和政委一屋,拿出了宝具 - 吹风机,开始吹白天被灌湿的登山鞋。
效果吗?还是超群的。累了,就睡得好了。

Day 3

因为今天要赶车,大家还是早起,吃了一点行动食品就出发了。
到了一个村子里,发现了卖包子的地方,于是纷纷买包子吃。
发现骑车的时候,怎么吃都吃不饱啊。
今天的天气,好极了,阳光灿烂。

scene9

scene10

大家嫌拍照片不过瘾,开始各种拍视频、拍延时,不亦乐乎。

我在前面骑,突然感觉内心迸发出无比力量,蹭蹭蹭。一路冲到了隆化县城火车站。
后来听闻某美女骑车走神,冲到了别人的电动三轮车车斗里面 ……

selfie1

[我一个人吃果粒爽,真是太好吃了 ……]

县城汇合后吃了羊蝎子,味道赞赞赞,食材很给力。让我的冲锋衣回去后携带的羊油气息经久不散 ……

吃完大家就去坐车了。火车上的时候我就开始傲娇了,伙伴们换了座位玩桌游,我却不想去,自己一个人坐在一个车厢里。
现在想想,很多时候情绪就不受自己控制了,当时有什么好的处理方法吗?

Day 4

回学校了。


一起骑车的小伙伴,有的还要继续在学校几年,有的已经要毕业了。想起你们,还是有些伤感的,在杭州有些孤单。

祝你们顺利。

‘How Does Monero Work?' 笔记

Monero 门罗币的一个介绍视频

我刚买到 1070Ti 显卡时曾经想要挖过这个币,后来放弃了,觉得没有意义。不过了解这个技术的一些原理还是有价值的。


Siraj Raval 的免责声明:


Monero 是一种不可追踪的加密货币,网络上很多非法买卖东西的人会使用它。他介绍这个技术的原因是:你应当对你的数据有控制权,而很多数据是交易数据(transactional data)。目前很多公司可以从中免费挖掘信息,用来预测你的购买行为,以便向你展示广告。理想情况下他们应当为你的数据付费,而他们付费的唯一可能是你控制着数据,而唯一控制数据的方式为数据是匿名的。为了查阅你的交易历史,