1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00

Merge branch 'master' of github.com:chai2010/advanced-go-programming-book

This commit is contained in:
启程 2018-02-24 22:50:35 +08:00
commit a0cdd7a016
92 changed files with 2112 additions and 132 deletions

View File

@ -16,11 +16,18 @@
* [2.4. CGO内存模型](ch2-cgo/ch2-04-memory.md)
* [2.5. C++类包装](ch2-cgo/ch2-05-class.md)
* [2.6. 静态库和动态库](ch2-cgo/ch2-06-static-shared-lib.md)
* [2.7. Go实现Python模块(TODO)](ch2-cgo/ch2-07-py-module.md)
* [2.8. SWIG(TODO)](ch2-cgo/ch2-08-swig.md)
* [2.9. 补充说明(TODO)](ch2-cgo/ch2-09-faq.md)
* [第三章 汇编语言](ch3-asm/readme.md)
* [第四章 移动平台](ch4-mobile/readme.md)
* [2.7. Go实现Python模块](ch2-cgo/ch2-07-py-module.md)
* [2.8. 补充说明](ch2-cgo/ch2-08-faq.md)
* [第三章 汇编语言(TODO)](ch3-asm/readme.md)
* [3.1. 汇编基础(TODO)](ch3-asm/ch3-01-basic.md)
* [3.2. 控制流(TODO)](ch3-asm/ch3-02-control-flow.md)
* [3.3. 包变量(TODO)](ch3-asm/ch3-03-var.md)
* [3.4. 叶子函数(TODO)](ch3-asm/ch3-04-leaf-func.md)
* [3.5. 动态栈(TODO)](ch3-asm/ch3-05-more-stack.md)
* [3.6. 垃圾回收(TODO)](ch3-asm/ch3-06-gc.md)
* [3.7. 例子: 图像降采样(TODO)](ch3-asm/ch3-07-pyrdown.md)
* [3.8. 补充说明(TODO)](ch3-asm/ch3-08-faq.md)
* [第四章 移动平台(TODO)](ch4-mobile/readme.md)
* [第六章 Go和Web](ch6-web/readme.md)
* [6.1. Web开发简介](ch6-web/ch6-01-introduction.md)
* [6.2. Router请求路由](ch6-web/ch6-02-router.md)

View File

@ -50,7 +50,7 @@ func main() {
将以上代码保存到`hello.go`文件中。因为代码中有非ASCII的中文字符我们需要将文件的编码显式指定为无BOM的UTF8编码格式源文件采用UTF8编码是Go语言规范所要求的。然后进入命令行并切换到`hello.go`文件所在的目录。目前我们可以将Go语言当作脚本语言在命令行中直接输入`go run hello.go`来运行程序。如果一切正常的话。应该可以在命令行看到输出"你好, 世界!"的结果。
现在让我们简单介绍一下程序。所有的Go程序都是由最基本的函数和变量构成函数和变量被组织到一个个单独的Go源文件中这些源文件再按照作者的意图组织成合适的package最终这些package再有机地组成一个完的Go语言程序。其中函数用于包含一系列的语句(指明要执行的操作序列)以及执行操作时存放数据的变量。我们这个程序中函数的名字是main。虽然Go语言中函数的名字没有太多的限制但是main包中的main函数默认是每一个可执行程序的入口。而package则用于包装和组织相关的函数、变量和常量。在使用一个package之前我们需要使用import语句导入包。例如我们这个程序中导入了fmt包fmt是format单词的缩写表示格式化相关的包然后我们才可以使用fmt包中的Println函数。
现在让我们简单介绍一下程序。所有的Go程序都是由最基本的函数和变量构成函数和变量被组织到一个个单独的Go源文件中这些源文件再按照作者的意图组织成合适的package最终这些package再有机地组成一个完的Go语言程序。其中函数用于包含一系列的语句(指明要执行的操作序列)以及执行操作时存放数据的变量。我们这个程序中函数的名字是main。虽然Go语言中函数的名字没有太多的限制但是main包中的main函数默认是每一个可执行程序的入口。而package则用于包装和组织相关的函数、变量和常量。在使用一个package之前我们需要使用import语句导入包。例如我们这个程序中导入了fmt包fmt是format单词的缩写表示格式化相关的包然后我们才可以使用fmt包中的Println函数。
而双引号包含的“你好, 世界!”则是Go语言的字符串面值常量。和C语言中的字符串不同Go语言中的字符串内容是不可变更的。在以字符串作为参数传递给fmt.Println函数时字符串的内容并没有被复制——传递的仅仅是字符串的地址和长度字符串的结构在`reflect.StringHeader`中定义。在Go语言中函数参数都是以复制的方式(不支持以引用的方式)传递比较特殊的是Go语言闭包函数对外部变量是以引用的方式使用

View File

@ -30,7 +30,7 @@ func main() {
}
```
因为`mu.Lock()``mu.Unock()`并不在同一个Goroutine中所以也就不满足顺序一致性内存模型。同时它们也没有其它的同步事件可以参考这两个事件不可排序也就是可以并发的。因为可能是并发的事件所以`main`函数中的`mu.Unock()`很有可能先发生,而这个时刻`mu`互斥对象还处于未加锁的状态,从而会导致运行时异常。
因为`mu.Lock()``mu.Unlock()`并不在同一个Goroutine中所以也就不满足顺序一致性内存模型。同时它们也没有其它的同步事件可以参考这两个事件不可排序也就是可以并发的。因为可能是并发的事件所以`main`函数中的`mu.Unlock()`很有可能先发生,而这个时刻`mu`互斥对象还处于未加锁的状态,从而会导致运行时异常。
下面是修复后的代码:
@ -41,14 +41,14 @@ func main() {
mu.Lock()
go func(){
fmt.Println("你好, 世界")
mu.Unock()
mu.Unlock()
}()
mu.Lock()
}
```
修复的方式是在`main`函数所在线程中执行两次`mu.Lock()`,当第二次加锁时会因为锁已经被占用(不是递归锁)而阻塞,`main`函数的阻塞状态驱动后台线程继续向前执行。当后台线程执行到`mu.Unock()`时解锁,此时打印工作已经完成了,解锁会导致`main`函数中的第二个`mu.Lock()`阻塞状态取消,此时后台线程和主线程再没有其它的同步事件参考,它们退出的事件将是并发的:在`main`函数退出导致程序退出时,后台线程可能已经退出了,也可能没有退出。虽然无法确定两个线程退出的时间,但是打印工作是可以正确完成的。
修复的方式是在`main`函数所在线程中执行两次`mu.Lock()`,当第二次加锁时会因为锁已经被占用(不是递归锁)而阻塞,`main`函数的阻塞状态驱动后台线程继续向前执行。当后台线程执行到`mu.Unlock()`时解锁,此时打印工作已经完成了,解锁会导致`main`函数中的第二个`mu.Lock()`阻塞状态取消,此时后台线程和主线程再没有其它的同步事件参考,它们退出的事件将是并发的:在`main`函数退出导致程序退出时,后台线程可能已经退出了,也可能没有退出。虽然无法确定两个线程退出的时间,但是打印工作是可以正确完成的。
使用`sync.Mutex`互斥锁同步是比较低级的做法。我们现在改用无缓存的管道来实现同步:

View File

@ -127,6 +127,32 @@ func SayHello(s *C.char) {
}
```
现在中国版本的CGO代码中C语言代码的比例已经很少了但是我们依然可以进一步以Go语言的思维来提炼我们的CGO代码。通过分析可以发现`SayHello`函数的参数如果可以直接使用Go字符串是最直接的。在Go1.10中CGO新增加了一个`_GoString_`预定义的C语言类型用来表示Go语言字符串。下面是改进后的代码
```go
// +build go1.10
package main
//void SayHello(_GoString_ s);
import "C"
import (
"fmt"
)
func main() {
C.SayHello("Hello, World\n")
}
//export SayHello
func SayHello(s string) {
fmt.Print(s)
}
```
虽然看起来全部是Go语言代码但是执行的时候是先从Go语言的`main`函数到CGO自动生成的C语言版本`SayHello`桥接函数最后又回到了Go语言环境的`SayHello`函数。虽然看起来有点绕但CGO确实是这样运行的。
需要注意的是CGO导出Go语言函数时函数参数中不再支持C语言中`const`修饰符。

View File

@ -90,7 +90,22 @@ extern void helloString(GoString p0);
extern void helloSlice(GoSlice p0);
```
不过需要注意的是如果使用了GoString类型则会对`_cgo_export.h`头文件产生依赖而这个头文件是动态输出的。更严谨的做法是为C语言函数接口定义严格的头文件然后基于稳定的头文件实现代码。
不过需要注意的是如果使用了GoString类型则会对`_cgo_export.h`头文件产生依赖,而这个头文件是动态输出的。
Go1.10针对Go字符串增加了一个`_GoString_`预定义类型可以降低在cgo代码中可能对`_cgo_export.h`头文件产生的循环依赖的风险。我们可以调整helloString函数的C语言声明为
```c
extern void helloString(_GoString_ p0);
```
因为`_GoString_`是预定义类型我们无法通过此类型直接访问字符串的长度和指针等信息。Go1.10同时也增加了以下两个函数用于获取字符串结构中的长度和指针信息:
```c
size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);
```
更严谨的做法是为C语言函数接口定义严格的头文件然后基于稳定的头文件实现代码。
## 结构体、联合、枚举类型
@ -341,17 +356,23 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
在C语言中可以通过`GoString``GoSlice`来访问Go语言的字符串和切片。如果是Go语言中数组类型可以将数组转为切片后再行转换。如果字符串或切片对应的底层内存空间由Go语言的运行时管理那么在C语言中不能长时间保存Go内存对象。
<!--
```go
// 例子
// 字符串和切片
// 作为参数或返回值 和 C 函数通信
```
-->
关于CGO内存模型的细节在稍后章节中会详细讨论。
## 指针和切片
## 指针间的转换
C语言和Go语言指针的转换可以看做是两种不同类型的指针之间的转换。在Go语言中我们无法在不同类型之间做转换因此不同类型的指针之间也存在此限制。由于任意类型的指针均可和`unsafe.Pointer`相互转换,所以我们可以以`unsafe.Pointer`作为中间桥接类型实现不同类型指针之间的转换。
在C语言中不同类型的指针是可以显式或隐式转换的如果是隐式只是会在编译时给出一些警告信息。但是Go语言对于不同类型的转换非常严格任何C语言中可能出现的警告信息在Go语言中都可能是错误指针是C语言的灵魂指针间的自由转换也是cgo代码中经常要解决的第一个重要的问题。
在Go语言中两个指针的类型完全一致则不需要转换可以直接通用。如果一个指针类型是用type命令在另一个指针类型基础之上构建的换言之两个指针底层是相同完全结构的指针那么我我们可以通过直接强制转换语法进行指针间的转换。但是cgo经常要面对的是2个完全不同类型的指针间的转换原则上这种操作在纯Go语言代码是严格禁止的。
cgo存在的一个目的就是打破Go语言的禁制恢复C语言应有的指针的自由转换和指针运算。以下代码演示了如何将X类型的指针转化为Y类型的指针
```go
var p *X
@ -361,7 +382,31 @@ q = (*Y)(unsafe.Pointer(p)) // *X => *Y
p = (*X)(unsafe.Pointer(q)) // *Y => *X
```
再结合`reflect.SliceHeader`类型,我们可以实现`[]X``[]Y`类型的切片转换:
为了实现X类型指针到Y类型指针的转换我们需要借助`unsafe.Pointer`作为中间桥接类型实现不同类型指针之间的转换。`unsafe.Pointer`指针类型类似C语言中的`void*`类型的指针。
下面是指针间的转换流程的示意图:
![](../images/ch2-x-ptr-to-y-ptr.uml.png)
任何类型的指针都可以通过强制转换为`unsafe.Pointer`指针类型去掉原有的类型信息,然后再重新赋予新的指针类型而达到指针间的转换的目的。
## 数值和指针的转换
不同类型指针间的转换看似复杂但是在cgo中已经算是比较简单的了。在C语言中经常遇到用普通数值表示指针的场景也就是说如何实现数值和指针的转换也是cgo需要面对的一个问题。
为了严格控制指针的使用Go语言禁止将数值类型直接转为指针类型不过Go语言针对`unsafe.Pointr`指针类型特别定义了一个uintptr类型。我们可以uintptr为中介实现数值类型到`unsafe.Pointr`指针类型到转换。再结合前面提到的方法,就可以实现数值和指针的转换了。
下面流程图演示了如何实现int32类型到C语言的`char*`字符串指针类型的相互转换:
![](../images/ch2-int32-to-char-ptr.uml.png)
转换分为几个阶段在每个阶段实现一个小目标首先是int32到uintptr类型然后是uintptr到`unsafe.Pointr`指针类型,最后是`unsafe.Pointr`指针类型到`*C.char`类型。
## 切片间的转换
在C语言中数组也一种指针因此两个不同类型数组之间到转换和指针间转换基本类似。但是在Go语言中数组或数组对应到切片都不再是指针类型因为我们也就无法直接实现不同类型到切片之间的转换。
不过Go语言的reflect包提供了切片类型到底层结构再结合前面讨论到不同类型之间到指针转换技术就可以实现`[]X``[]Y`类型的切片转换:
```go
var p []X
@ -375,6 +420,10 @@ pHdr.Len = qHdr.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
pHdr.Cap = qHdr.Cap * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
```
如果X和Y类型的大小不同需要重新设置Len和Cap属性。需要注意的是如果X或Y是空类型上述代码中可能导致除0错误实际代码需要根据情况酌情处理。
不同切片类型之间转换到思路是先为构造一个空的目标切片然后用原有的切片底层数据填充目标切片。如果X和Y类型的大小不同需要重新设置Len和Cap属性。需要注意的是如果X或Y是空类型上述代码中可能导致除0错误实际代码需要根据情况酌情处理。
下面演示了切片间的转换的具体流程:
![](../images/ch2-x-slice-to-y-slice.uml.png)
针对CGO中常用的功能作者封装了 "github.com/chai2010/cgo" 包,提供基本的转换功能,具体的细节可以参考实现代码。

View File

@ -169,3 +169,13 @@ go build -tags="windows,debug"
```
其中`linux,386`中linux和386用逗号链接表示AND的意思`linux,386``darwin,!cgo`之间通过空白分割来表示OR的意思。
## CGO生成的中间文件
要了解CGO技术的底层秘密首先需要了解CGO生成了哪些中间文件。我们可以在构建一个cgo包时增加一个`-work`输出中间生成文件所在的目录并且在构建完成时保留中间文件。如果是比较简单的cgo代码我们也可以直接通过手工调用`go tool cgo`命令来查看生成的中间文件。
在一个Go源文件中如果出现了`import "C"`指令则表示将调用cgo命令生成对应的中间文件。下图是cgo生成的中间文件的简单示意图
![](../images/ch2-cgo-generated-files.dot.png)
包中有4个Go文件其中nocgo开头的文件中没有`import "C"`指令其它的2个文件则包含了cgo代码。cgo命令会为每个包含了cgo代码的Go文件创建2个中间文件比如 main.go 会分别创建 main.cgo1.go 和 main.cgo2.c 两个中间文件。然后会为整个包创建一个 `_cgo_gotypes.go` Go文件其中包含Go语言部分辅助代码。此外还会创建一个 `_cgo_export.h``_cgo_export.c` 文件对应Go语言导出到C语言的类型和函数。

View File

@ -1,10 +1,10 @@
# 2.6. 静态库和动态库
CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在`import "C"`之前的注释部分包含C代码或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似都是通过在LDFLAGS选项指定要链接的库方式链接。本节我们主要关注CGO中如何处理静态库和动态库相关的问题。
CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在`import "C"`之前的注释部分包含C代码或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似都是通过在LDFLAGS选项指定要链接的库方式链接。本节我们主要关注在CGO中如何使用静态库和动态库相关的问题。
## 使用C静态库
如果CGO中引入的C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式。很多时候我们并没有源代码或者从C/C++源代码的构建过程异常复杂这种时候使用C静态库也是一个不错的选择。静态库因为是静态链接最终的目标程序并不会产生额外的运行时依赖而且也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求:静态库一般包含了全部的代码,里面会有大量的符号,如果不同静态库之间出现符号冲突会导致链接的失败。
如果CGO中引入的C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式但很多时候我们并没有源代码或者从C/C++源代码开始构建的过程异常复杂这种时候使用C静态库也是一个不错的选择。静态库因为是静态链接最终的目标程序并不会产生额外的运行时依赖也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求静态库一般包含了全部的代码里面会有大量的符号如果不同静态库之间出现符号冲突会导致链接的失败。
我们先用纯C语言构造一个简单的静态库。我们要构造的静态库名叫number库中只有一个number_add_mod函数用于表示数论中的模加法运算。number库的文件都在number目录下。
@ -26,7 +26,7 @@ int number_add_mod(int a, int b, int mod) {
因为CGO使用的是GCC命令来编译和链接C和Go桥接的代码。因此静态库也必须是GCC兼容的格式。
通过以下命令可以生成一个libnumber.a的静态库
通过以下命令可以生成一个libnumber.a的静态库
```
$ cd ./number
@ -53,11 +53,11 @@ func main() {
}
```
其中有两个#cgo命令分别是编译和链接参数。CFLAGS通过`-I./number`将number库对应头文件所在的目录加入头文件检索路径。LDFLAGS通过`-L${SRCDIR}/number`将编译后number静态库所在目录加为链接库检索路径`-lnumber`表示链接libnumber.a静态库。需要注意的是在链接部分的检索路径不能使用相对路径这是由于C/C++代码的链接程序所限制我们必须通过cgo特有的`${SRCDIR}`变量将源文件对应的当前目录路径展开为绝对路径因此在windows平台中绝对路径不能有空白符号
其中有两个#cgo命令分别是编译和链接参数。CFLAGS通过`-I./number`将number库对应头文件所在的目录加入头文件检索路径。LDFLAGS通过`-L${SRCDIR}/number`将编译后number静态库所在目录加为链接库检索路径`-lnumber`表示链接libnumber.a静态库。需要注意的是在链接部分的检索路径不能使用相对路径C/C++代码的链接程序所限制我们必须通过cgo特有的`${SRCDIR}`变量将源文件对应的当前目录路径展开为绝对路径因此在windows平台中绝对路径不能有空白符号
因为我们有number库的全部代码我们可以用go generate工具来生成静态库或者是通过Makefile来构建静态库。因此发布CGO源码包时我们并不需要提前构建C静态库。
因为我们有number库的全部代码所以我们可以用go generate工具来生成静态库或者是通过Makefile来构建静态库。因此发布CGO源码包时我们并不需要提前构建C静态库。
因为多了一个静态库的构建步骤这种使用了自定义静态库并已经包含了静态库全部代码的Go包无法直接用go get安装。不过我们依然可以通过go get下载然后用go generate触发静态库构建最后才是go install安装来完成。
因为多了一个静态库的构建步骤这种使用了自定义静态库并已经包含了静态库全部代码的Go包无法直接用go get安装。不过我们依然可以通过go get下载然后用go generate触发静态库构建最后才是go install来完成安装
为了支持go get命令直接下载并安装我们C语言的`#include`语法可以将number库的源文件链接到当前的包。
@ -71,11 +71,11 @@ func main() {
如果使用的是第三方的静态库,我们需要先下载安装静态库到合适的位置。然后在#cgo命令中通过CFLAGS和LDFLAGS来指定头文件和库的位置。对于不同的操作系统甚至同一种操作系统的不同版本来说,这些库的安装路径可能都是不同的,那么如何在代码中指定这些可能变化的参数呢?
在Linux环境一个pkg-config命令可以查询要使用某个静态库或动态库时的编译和链接参数。我们可以在#cgo命令中直接使用pkg-config命令来生成编译和链接参数。而且还可以通过PKG_CONFIG环境变量订制pkg-config命令。因为不同的操作系统对pkg-config命令的支持不尽相同通过该方式很难兼容不同的操作系统下的构建参数。不过对于Linux等特定的系统pkg-config命令确实可以简化构建参数的管理。关于pkg-config的使用细节我们不深入展开,大家可以自行参考相关文档。
在Linux环境有一个pkg-config命令可以查询要使用某个静态库或动态库时的编译和链接参数。我们可以在#cgo命令中直接使用pkg-config命令来生成编译和链接参数。而且还可以通过PKG_CONFIG环境变量订制pkg-config命令。因为不同的操作系统对pkg-config命令的支持不尽相同通过该方式很难兼容不同的操作系统下的构建参数。不过对于Linux等特定的系统pkg-config命令确实可以简化构建参数的管理。关于pkg-config的使用细节在此我们不深入展开,大家可以自行参考相关文档。
## 使用C动态库
动态库出现的初衷是对于相同的库,多个进程可以共享一个动态库以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,动态库能节省的空间已经微不足道了,那么动态库还有哪些存在的价值呢从库开发角度来说动态库可以隔离不同动态库之间的关系减少链接时出现符号冲突的风险。而且对于windows等平台动态库是跨越VC和GCC不编译器平台的唯一的可行方式。
动态库出现的初衷是对于相同的库,多个进程可以共享一个,以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,这两个作用已经显得微不足道了,那么除此之外动态库还有哪些存在的价值呢从库开发角度来说动态库可以隔离不同动态库之间的关系减少链接时出现符号冲突的风险。而且对于windows等平台动态库是跨越VC和GCC不编译器平台的唯一的可行方式。
对于CGO来说使用动态库和静态库是一样的因为动态库也必须要有一个小的静态导出库用于链接动态库Linux下可以直接链接so文件但是在Windows下必须为dll创建一个`.a`文件用于链接。我们还是以前面的number库为例来说明如何以动态库方式使用。
@ -118,7 +118,7 @@ number_add_mod
其中第一行的LIBRARY指明动态库的文件名然后的EXPORTS语句之后是要导出的符号名列表。
现在我们可以用以下命令来创建动态库需要进入VC对的x64命令行环境
现在我们可以用以下命令来创建动态库需要进入VC对的x64命令行环境
```
$ cl /c number.c
@ -139,7 +139,7 @@ $ dlltool -dllname number.dll --def number.def --output-lib libnumber.a
## 导出C静态库
CGO不仅可以使用C静态库也可以将Go实现的函数导出为C静态库。我们现在用Go实现前面的number库的加法函数。
CGO不仅可以使用C静态库也可以将Go实现的函数导出为C静态库。我们现在用Go实现前面的number库的加法函数。
创建number.go内容如下
@ -180,7 +180,7 @@ extern int number_add_mod(int p0, int p1, int p2);
其中`extern "C"`部分的语法是为了同时适配C和C++两种语言。核心内容是声明了要导出的number_add_mod函数。
然后我们创建一个`_test_main.c`的C文件用于测试生成的C静态库用下划线作为前著名是让go build构建C静态库时忽略这个文件
然后我们创建一个`_test_main.c`的C文件用于测试生成的C静态库用下划线作为前缀名是让为了让go build构建C静态库时忽略这个文件
```c
#include "number.h"
@ -241,11 +241,11 @@ $ ./a.out
Requires exactly one main package to be listed.
```
文档说明导出的C函数必须是在main包导出然后才能在生成的头文件包含声明的语句。但是很多时候我们可能更希望将不类型的导出函数组织到不同的Go包中然后统一导出为一个静态库或动态库。
文档说明导出的C函数必须是在main包导出然后才能在生成的头文件包含声明的语句。但是很多时候我们可能更希望将不类型的导出函数组织到不同的Go包中然后统一导出为一个静态库或动态库。
要实现从是从非main包导出C函数或者是多个包导出C函数因为只能有一个main包我们需要自己提供导出C函数对应的头文件因为CGO无法为非main包的导出函数生成头文件
假设我们先创建一个number子包用于提供加法函数:
假设我们先创建一个number子包用于提供加法函数:
```go
package number

View File

@ -1,11 +1,158 @@
# 2.7 Go实现Python模块(TODO)
# 2.7 Go实现Python模块
TODO
前面章节我们已经讲述了如何通过CGO来引用和创建C动态库和静态库。实现了对C动态库和静态库的支持理论上就可以应用到动态库的绝大部分场景。Python语言作为当下最红的语言本节我们将演示如何通过Go语言来为Python脚本语言编写扩展模块。
<!--
静态库和动态库
## 基于ctypes
导出C接口分布在不同的钩包
Python内置了非常丰富的模块其中ctypes支持直接从C动态库调用函数。为了演示如何基于ctypes技术来扩展模块我们需要先用Go语言创建一个C动态库。
实战: py 模块
-->
我们使用的是之前出现过的例子:
```go
// main.go
package main
import "C"
import "fmt"
func main() {}
//export SayHello
func SayHello(name *C.char) {
fmt.Printf("hello %s!\n", C.GoString(name))
}
```
其中只导出了一个SayHello函数用于打印字符串。通过以下命令基于上述Go代码创建say-hello.so动态库
```
go build -buildmode=c-shared -o say-hello.so main.go
```
现在我们就可以通过ctypes模块调用say-hello.so动态库中的SayHello函数了
```python
// hello.py
import ctypes
libso = ctypes.CDLL("./say-hello.so")
SayHello = libso.SayHello
SayHello.argtypes = [ctypes.c_char_p]
SayHello.restype = None
SayHello(ctypes.c_char_p(b"hello"))
```
我们首先通过ctypes.CDLL加载动态库到libso并通过libso.SayHello来获取SayHello函数。获取到SayHello函数之后设置函数的输入参数为一个C语言类型的字符串该函数没有返回值。然后我们通过`ctypes.c_char_p(b"hello")`将Python字节串转为C语言格式的字符串作为参数调用SayHello。如果一切正常的话就可以输出字符串了。
从这个例子可以看出给予ctypes构造Python扩展模块非常简单本质上只是在构建一个纯C语言规格的动态库。比较复杂的部分在ctypes的具体使用关于ctypes的具体细节就不详细展开的用户可以自行参考Python自带的官方文档。
## 基于Python C接口创建
在前面的例子中通过ctypes创建的模块必须要用Python再包装一层否则就要直接面对C语言风格的接口。如果基于基于Python C接口我们可以完全再Go和C语言层面创建灵活强大的模块重点是不再需要在Python中重新包装。
基于Python C接口创建模块和使用C语言的静态库的流程类似
```
package main
/*
// macOS:
#cgo darwin pkg-config: python3
// linux
#cgo linux pkg-config: python3
// windows
// should generate libpython3.a from python3.lib
#define Py_LIMITED_API
#include <Python.h>
extern PyObject* PyInit_gopkg();
extern PyObject* Py_gopkg_sum(PyObject *, PyObject *);
static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) {
return PyArg_ParseTuple(arg, "ii", a, b);
}
static PyObject* cgo_PyInit_gopkg(void) {
static PyMethodDef methods[] = {
{"sum", Py_gopkg_sum, METH_VARARGS, "Add two numbers."},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, methods,
};
return PyModule_Create(&module);
}
*/
import "C"
func main() {}
//export PyInit_gopkg
func PyInit_gopkg() *C.PyObject {
return C.cgo_PyInit_gopkg()
}
//export Py_gopkg_sum
func Py_gopkg_sum(self, args *C.PyObject) *C.PyObject {
var a, b C.int
if C.cgo_PyArg_ParseTuple_ii(args, &a, &b) == 0 {
return nil
}
return C.PyLong_FromLong(C.long(a + b))
}
```
因为Python的链接参数要复杂了很多我们借助pkg-config工具来获取编译参数和链接参数。然后我们在Go语言中分别导出了PyInit_gopkg和Py_gopkg_sum函数其中PyInit_gopkg函数用于初始化名为gopkg的Python模块而Py_gopkg_sum函数则是模块中sum方法的实现。
因此PyArg_ParseTuple是可变参数类型CGO中无法使用可变参数的C函数因此我们通过增加一个cgo_PyArg_ParseTuple_ii辅助函数小消除可变参数的影响。同样模块的方法列表必须在C语言内存空间创建因为CGO是禁止将Go语言内存直接返回到C语言空间的。
然后通过以下命令创建gopkg.so动态库
```
go build -buildmode=c-shared -o gopkg.so main.go
```
这里需要注意几个出现gopkg名字的地方。gopkg是我们创建的Python模块的名字因此它对应一个gopkg.so动态库。再gopkg.so动态库中必须有一个PyInit_gopkg函数该函数是模块的初始化函数。在PyInit_gopkg函数初始化模块时同样需要指定模块的名字时gopkg。模块中的方法函数是通过函数指针访问具体的名字没有影响。
### macOS环境构建
因为在macOS中pkg-config不支持Python3版本。不过macOS有一个python3-config的命令可以实现pkg-config类似的功能。不过python3-config生成的编译参数无法直接用于CGO编译选项因为GCC不能识别部分参数会导致错误构建
我们在python3-config的基础只是又包装了一个工具在通过python3-config获取到编译参数之后将GCC不支持的参数剔除掉。
创建py3-config.go文件
```go
func main() {
for _, s := range os.Args {
if s == "--cflags" {
out, _ := exec.Command("python3-config", "--cflags").CombinedOutput()
out = bytes.Replace(out, []byte("-arch"), []byte{}, -1)
out = bytes.Replace(out, []byte("i386"), []byte{}, -1)
out = bytes.Replace(out, []byte("x86_64"), []byte{}, -1)
fmt.Print(string(out))
return
}
if s == "--libs" {
out, _ := exec.Command("python3-config", "--ldflags").CombinedOutput()
fmt.Print(string(out))
return
}
}
}
```
cgo中的pkg-config只需要两个参数`--cflags``--libs`。其中`--libs`选项的输出我们采用的是`python3-config --ldflags`的输出,因为`--libs`选项没有包含库的检索路径,而`--ldflags`选项则是在指定链接库参数的基础上增加了库的检索路径。
基于py3-config.go可以创建一个py3-config命令。然后通过PKG_CONFIG环境变量将cgo使用的pkg-config命令指定为我们订制的命令
```
PKG_CONFIG=./py3-config go build -buildmode=c-shared -o gopkg.so main.go
```
对于不支持pkg-config的平台我们都可以基于类似的方法处理。

6
ch2-cgo/ch2-08-faq.md Normal file
View File

@ -0,0 +1,6 @@
# 2.8. 补充说明
为何要话费巨大的精力学习CGO是一个问题。任何技术和语言都有它自身的优点和不足Go语言不是银弹它无法解决全部问题。而通过CGO可以继承C/C++将近半个世纪的软件遗产通过CGO可以用Go给其它系统写C接口的共享库通过CGO技术可以让Go语言编写的代码可以很好地融入现有的软件生态——而现在的软件正式建立在C/C++语言之上的。因此说CGO是一个保底的后备技术它是Go的一个重量级的替补技术值得任何一个严肃的Go语言开发人员学习。
本章讨论了CGO的一些常见用法并给出相关的例子。关于CGO有几点补充如果有纯Go的解决方法就不要使用CGOCGO中涉及的C和C++构建问题非常繁琐CGO有一定的限制无法实现解决全部的问题不要试图越过CGO的一些限制。而且CGO只是一种官方提供并推荐的Go语言和C/C++交互的方法。如果是使用的gccgo的版本可以通过gccgo的方式实现Go和C/C++的交互。同时SWIG也是一种选择并对C++诸多特性提供了支持。

View File

@ -1,7 +0,0 @@
# 2.8. SWIG(TODO)
TODO
<!--
swig 简单说明
-->

View File

@ -1,11 +0,0 @@
# 2.9. 补充说明(TODO)
TODO
<!--
cgo的风险和不足
不要用c模拟Go字符串
不要试图越过Go运行时的边界
有些事情是不可为的,比如 bsearch 类似接口完全没有继承的价值(代价太高)
-->

3
ch3-asm/ch3-01-basic.md Normal file
View File

@ -0,0 +1,3 @@
# 3.1. 汇编基础(TODO)
TODO

View File

@ -0,0 +1,3 @@
# 3.2. 控制流(TODO)
TODO

3
ch3-asm/ch3-03-var.md Normal file
View File

@ -0,0 +1,3 @@
# 3.3. 包变量(TODO)
TODO

View File

@ -0,0 +1,3 @@
# 3.4. 叶子函数(TODO)
TODO

View File

@ -0,0 +1,3 @@
# 3.5. 动态栈(TODO)
TODO

3
ch3-asm/ch3-06-gc.md Normal file
View File

@ -0,0 +1,3 @@
# 3.6. 垃圾回收(TODO)
TODO

View File

@ -0,0 +1,3 @@
# 3.7. 例子: 图像降采样(TODO)
TODO

3
ch3-asm/ch3-08-faq.md Normal file
View File

@ -0,0 +1,3 @@
# 3.8. 补充说明(TODO)
TODO

View File

@ -1,3 +1,3 @@
# 第三章 Go汇编语言
# 第三章 Go汇编语言(TODO)
TODO

View File

@ -153,12 +153,26 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
func (ResponseWriter, *Request)
```
那么这个 handler 就是一个 HandlerFunc 类型了,也就相当于实现了 http.Handler 这个接口,这要归功于 golang 的 duck typing 式的类型系统。在 http 库需要调用你的 handler 函数来处理 http 请求时,会调用 HandlerFunc 的 ServeHTTP 函数,可见一个请求的基本调用链是这样的:
那么这个 handler 和 http.HandlerFunc 就有了一致的函数签名,可以将该 handler 函数进行类型转换,转为 http.HandlerFunc。而 http.HandlerFunc 实现了 http.Handler 这个接口。在 http 库需要调用你的 handler 函数来处理 http 请求时,会调用 HandlerFunc 的 ServeHTTP 函数,可见一个请求的基本调用链是这样的:
```go
h = getHandler() => h.ServeHTTP(w, r) => h(w, r)
```
上面提到的把自定义 handler 转换为 http.HandlerFunc 这个过程是必须的,因为我们的 handler 没有直接实现 ServeHTTP 这个接口。上面的代码中我们看到的 HandleFunc(注意 HandlerFunc 和 HandleFunc 的区别)里也可以看到这个强制转换过程:
```go
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// 调用
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
```
知道 handler 是怎么一回事,我们的中间件通过包装 handler再返回一个新的 handler 就好理解了。
总结一下,我们的中间件要做的事情就是通过一个或多个函数对 handler 进行包装,返回一个包括了各个中间件逻辑的函数链。我们把上面的包装再做得复杂一些:

View File

@ -0,0 +1,24 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build go1.10
package main
//void SayHello(_GoString_ s);
import "C"
import (
"fmt"
)
func main() {
C.SayHello("Hello, World\n")
C.SayHello("")
}
//export SayHello
func SayHello(s string) {
fmt.Println(int(C._GoStringLen(s)))
fmt.Print(s)
}

View File

@ -2,14 +2,10 @@
# License: https://creativecommons.org/licenses/by-nc-sa/4.0/
default:
go build -buildmode=c-shared -o gopkg.so main.go
python3 -c 'import gopkg; print(gopkg.system("time"))'
python3 -c 'import gopkg; print(gopkg.sum(2, 3))'
go build -o py3-config.out py3-config.go
PKG_CONFIG=./py3-config.out go build -buildmode=c-shared -o gopkg.so main.go
-rm py3-config.out
python3 -c 'import gopkg; print(gopkg.sum(1, 2))'
clean:
-rm *.so
# PKG_CONFIG=python3-config go build -buildmode=c-shared -o say-hello.so main.go
# python3-config --ldflags
# python3-config --include

View File

@ -5,12 +5,9 @@
/* Start of preamble from import "C" comments. */
#line 3 "/Users/chai/go/src/github.com/chai2010/advanced-go-programming-book/examples/ch2-07/hello-py/main.go"
#line 6 "/Users/chai/go/src/github.com/chai2010/advanced-go-programming-book/examples/ch2-07/hello-py/main.go"
// macOS:
// python3-config --cflags
// python3-config --ldflags
// linux
@ -22,30 +19,22 @@
#define Py_LIMITED_API
#include <Python.h>
static PyObject *
spam_system(PyObject *self, PyObject *args) {
const char *command;
if (!PyArg_ParseTuple(args, "s", &command)) {
return NULL;
extern PyObject* PyInit_gopkg();
extern PyObject* Py_gopkg_sum(PyObject *, PyObject *);
static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) {
return PyArg_ParseTuple(arg, "ii", a, b);
}
int status = system(command);
return PyLong_FromLong(status);
}
extern PyObject *sum(PyObject *self, PyObject *args);
static PyMethodDef modMethods[] = {
{"system", spam_system, METH_VARARGS, "Execute a shell command."},
{"sum", sum, METH_VARARGS, "Execute a shell command."},
{NULL, NULL, 0, NULL}
static PyObject* cgo_PyInit_gopkg(void) {
static PyMethodDef methods[] = {
{"sum", Py_gopkg_sum, METH_VARARGS, "Add two numbers."},
{NULL, NULL, 0, NULL},
};
static PyObject* PyInit_gopkg_(void) {
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, modMethods,
PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, methods,
};
return (void*)PyModule_Create(&module);
return PyModule_Create(&module);
}
#line 1 "cgo-generated-wrapper"
@ -97,10 +86,10 @@ extern "C" {
#endif
extern PyObject* sum(PyObject* p0, PyObject* p1);
extern PyObject* PyInit_gopkg();
extern PyObject* Py_gopkg_sum(PyObject* p0, PyObject* p1);
#ifdef __cplusplus
}
#endif

View File

@ -5,10 +5,7 @@ package main
/*
// macOS:
// python3-config --cflags
// python3-config --ldflags
#cgo darwin CFLAGS: -I/Library/Frameworks/Python.framework/Versions/3.6/include/python3.6m -I/Library/Frameworks/Python.framework/Versions/3.6/include/python3.6m -fno-strict-aliasing -Wsign-compare -fno-common -dynamic -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -g
#cgo darwin LDFLAGS: -L/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/config-3.6m-darwin -lpython3.6m -ldl -framework CoreFoundation
#cgo darwin pkg-config: python3
// linux
#cgo linux pkg-config: python3
@ -19,51 +16,38 @@ package main
#define Py_LIMITED_API
#include <Python.h>
static PyObject *
spam_system(PyObject *self, PyObject *args) {
const char *command;
if (!PyArg_ParseTuple(args, "s", &command)) {
return NULL;
extern PyObject* PyInit_gopkg();
extern PyObject* Py_gopkg_sum(PyObject *, PyObject *);
static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) {
return PyArg_ParseTuple(arg, "ii", a, b);
}
int status = system(command);
return PyLong_FromLong(status);
}
extern PyObject *sum(PyObject *self, PyObject *args);
static PyMethodDef modMethods[] = {
{"system", spam_system, METH_VARARGS, "Execute a shell command."},
{"sum", sum, METH_VARARGS, "Execute a shell command."},
{NULL, NULL, 0, NULL}
static PyObject* cgo_PyInit_gopkg(void) {
static PyMethodDef methods[] = {
{"sum", Py_gopkg_sum, METH_VARARGS, "Add two numbers."},
{NULL, NULL, 0, NULL},
};
static PyObject* PyInit_gopkg_(void) {
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, modMethods,
PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, methods,
};
return (void*)PyModule_Create(&module);
return PyModule_Create(&module);
}
*/
import "C"
import (
"fmt"
)
func main() {}
// export SayHello
func SayHello(name *C.char) {
fmt.Printf("hello %s!\n", C.GoString(name))
}
//export sum
func sum(self, args *C.PyObject) *C.PyObject {
return C.PyLong_FromLongLong(9527) // TODO
}
//export PyInit_gopkg
func PyInit_gopkg() *C.PyObject {
return C.PyInit_gopkg_()
return C.cgo_PyInit_gopkg()
}
//export Py_gopkg_sum
func Py_gopkg_sum(self, args *C.PyObject) *C.PyObject {
var a, b C.int
if C.cgo_PyArg_ParseTuple_ii(args, &a, &b) == 0 {
return nil
}
return C.PyLong_FromLong(C.long(a + b))
}

View File

@ -0,0 +1,34 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build ignore
// +build darwin
// fix python3-config for cgo build
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
)
func main() {
for _, s := range os.Args {
if s == "--cflags" {
out, _ := exec.Command("python3-config", "--cflags").CombinedOutput()
out = bytes.Replace(out, []byte("-arch"), []byte{}, -1)
out = bytes.Replace(out, []byte("i386"), []byte{}, -1)
out = bytes.Replace(out, []byte("x86_64"), []byte{}, -1)
fmt.Print(string(out))
return
}
if s == "--libs" {
out, _ := exec.Command("python3-config", "--ldflags").CombinedOutput()
fmt.Print(string(out))
return
}
}
}

View File

@ -1,12 +1,12 @@
# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
# License: https://creativecommons.org/licenses/by-nc-sa/4.0/
from ctypes import *
import ctypes
libso = CDLL("./say-hello.so")
libso = ctypes.CDLL("./say-hello.so")
SayHello = libso.SayHello
SayHello.argtypes = [c_char_p]
SayHello.argtypes = [ctypes.c_char_p]
SayHello.restype = None
SayHello(c_char_p(b"hello"))
SayHello(ctypes.c_char_p(b"hello"))

View File

@ -0,0 +1,17 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Go版本, 支持内联优化
package add
func Add(a, b int) int {
return a + b
}
func AddSlice(dst, a, b []int) {
for i := 0; i < len(dst) && i < len(a) && i < len(b); i++ {
dst[i] = a[i] + b[i]
}
return
}

View File

@ -0,0 +1,16 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build amd64
// 汇编版本, 不支持内联优化
package add
func AsmAdd(a, b int) int
func AsmAddSlice(dst, a, b []int) {
AddSlice(dst, a, b)
}
func AsmAddSlice__todo(dst, a, b []int)

View File

@ -0,0 +1,23 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "textflag.h"
// func AsmAdd(a, b int) int
TEXT ·AsmAdd(SB), NOSPLIT, $0-24
MOVQ a+0(FP), AX // a
MOVQ b+8(FP), BX // b
ADDQ AX, BX // a+b
MOVQ BX, ret+16(FP) // return a+b
RET
// func AsmAddSlice(dst, a, b []int)
TEXT ·AsmAddSlice__todo(SB), NOSPLIT, $0-72
MOVQ dst+0(FP), AX // AX: dst
MOVQ a+24(FP), BX // BX: &a
MOVQ b+48(FP), CX // CX: &b
MOVQ dst_len+8(FP), DX // DX: len(dst)
MOVQ a_len+32(FP), R8 // R8: len(a)
MOVQ b_len+56(FP), R9 // R9: len(b)
// TODO: DX = min(DX,R8,R9)
RET

View File

@ -0,0 +1,16 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build !amd64
// 对于没有汇编实现的环境, 临时采用Go版本代替
package add
func AsmAdd(a, b int) int {
return Add(a, b)
}
func AsmAddSlice(dst, a, b []int) {
AddSlice(dst, a, b)
}

View File

@ -0,0 +1,97 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// go test -bench=.
package add
import (
"testing"
)
func TestAdd(t *testing.T) {
t.Run("go", func(t *testing.T) {
if x := Add(1, 2); x != 3 {
t.Fatalf("expect = %d, got = %d", 3, x)
}
})
t.Run("asm", func(t *testing.T) {
if x := AsmAdd(1, 2); x != 3 {
t.Fatalf("expect = %d, got = %d", 3, x)
}
})
}
func TestAddSlice(t *testing.T) {
a := []int{1, 2, 3, 4, 5}
b := []int{10, 20, 30, 40, 50, 60}
t.Run("go", func(t *testing.T) {
x := make([]int, len(a))
AddSlice(x, a, b)
for i := 0; i < len(x) && i < len(a) && i < len(b); i++ {
if x[i] != a[i]+b[i] {
t.Fatalf("expect = %d, got = %d", x[i], a[i]+b[i])
}
}
})
t.Run("asm", func(t *testing.T) {
x := make([]int, len(a))
AsmAddSlice(x, a, b)
for i := 0; i < len(x) && i < len(a) && i < len(b); i++ {
if x[i] != a[i]+b[i] {
t.Fatalf("expect = %d, got = %d", x[i], a[i]+b[i])
}
}
})
}
func BenchmarkAdd(b *testing.B) {
b.Run("go", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
})
b.Run("asm", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmAdd(1, 2)
}
})
}
func BenchmarkAddSlice(b *testing.B) {
s0 := make([]int, 10<<10)
s1 := make([]int, 10<<10)
dst := make([]int, 10<<10)
b.Run("len=10", func(b *testing.B) {
dst := dst[:10]
for i := 0; i < b.N; i++ {
AddSlice(dst, s0, s1)
}
})
b.Run("len=50", func(b *testing.B) {
dst := dst[:50]
for i := 0; i < b.N; i++ {
AddSlice(dst, s0, s1)
_ = dst
}
})
b.Run("len=100", func(b *testing.B) {
dst := dst[:100]
for i := 0; i < b.N; i++ {
AddSlice(dst, s0, s1)
_ = dst
}
})
b.Run("len=1000", func(b *testing.B) {
dst := dst[:1000]
for i := 0; i < b.N; i++ {
AddSlice(dst, s0, s1)
_ = dst
}
})
}

View File

@ -0,0 +1,14 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build ignore
package main
import (
. "."
)
func main() {
println("Add(1+2) =", Add(1, 2))
}

View File

@ -0,0 +1,6 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package bsearch
func BinarySearch(arr []int, num int) bool

View File

@ -0,0 +1,45 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
TEXT ·BinarySearch+0(SB),$0
start:
MOVQ arr+0(FP), CX
MOVQ len+8(FP), AX
JMP find_index
find_index:
XORQ DX, DX
MOVQ $2, BX
IDIVQ BX
JMP comp
comp:
LEAQ (AX * 8), BX
ADDQ BX, CX
MOVQ num+24(FP), DX
CMPQ DX, (CX)
JE found
JG right
JL left
JMP not_found
left:
CMPQ len+8(FP), $1
JE not_found
MOVQ AX, len+8(FP)
JMP start
right:
CMPQ len+8(FP), $1
JE not_found
MOVQ CX, arr+0(FP)
JMP start
not_found:
MOVQ $0, ret+32(FP)
RET
found:
MOVQ $1, ret+32(FP)
RET

View File

@ -0,0 +1,26 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package bsearch
import "testing"
func TestBinarySearch(t *testing.T) {
data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
if result := BinarySearch(data, 8); result != true {
t.Errorf("Expected true value for binary search.")
}
if result := BinarySearch(data, 1); result != true {
t.Errorf("Expected true value for binary search.")
}
if result := BinarySearch(data, 10); result != true {
t.Errorf("Expected true value for binary search.")
}
if result := BinarySearch(data, 12); result != false {
t.Errorf("Expected false value for binary search.")
}
}

View File

@ -0,0 +1,26 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "textflag.h"
// func GetPkgValue() int
TEXT ·GetPkgValue(SB), NOSPLIT, $0-8
MOVQ ·gopkgValue(SB), AX
MOVQ AX, ret+0(FP)
RET
// func GetPkgInfo() PkgInfo
TEXT ·GetPkgInfo(SB), NOSPLIT, $0-24
MOVBLZX ·gInfo+0(SB), AX // .V0 byte
MOVQ AX, ret+0(FP)
MOVWLZX ·gInfo+2(SB), AX // .V1 uint16
MOVQ AX, ret+2(FP)
MOVLQZX ·gInfo+4(SB), AX // .V2 int32
MOVQ AX, ret+4(FP)
MOVQ ·gInfo+8(SB), AX // .V3 int32
MOVQ AX, ret+8(FP)
MOVBLZX ·gInfo+(16+0)(SB), AX // .V4 bool
MOVQ AX, ret+(16+0)(FP)
MOVBLZX ·gInfo+(16+1)(SB), AX // .V5 bool
MOVQ AX, ret+(16+1)(FP)
RET

View File

@ -0,0 +1,32 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// 汇编中访问Go中定义的全局变量
package globalvar
var gopkgValue int = 42
type PkgInfo struct {
V0 byte
V1 uint16
V2 int32
V3 int64
V4 bool
V5 bool
}
var gInfo PkgInfo
func init() {
gInfo.V0 = 101
gInfo.V1 = 102
gInfo.V2 = 103
gInfo.V3 = 104
gInfo.V4 = true
gInfo.V5 = false
}
func GetPkgValue() int
func GetPkgInfo() PkgInfo

View File

@ -0,0 +1,17 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build ignore
package main
import (
"fmt"
. "."
)
func main() {
fmt.Println(GetPkgValue())
fmt.Println(GetPkgInfo())
}

View File

@ -0,0 +1,10 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package hello
var text = "你好, 世界, 包变量\n"
func PrintHelloWorld()
func PrintHelloWorld_zh()
func PrintHelloWorld_var()

View File

@ -0,0 +1,51 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "textflag.h"
#include "funcdata.h"
// "Hello World!\n"
DATA text<>+0(SB)/8,$"Hello Wo"
DATA text<>+8(SB)/8,$"rld!\n"
GLOBL text<>(SB),NOPTR,$16
// utf8: ", !\n"
// hex: e4bda0e5a5bd2c20 e4b896e7958c210a
// len: 16
DATA text_zh<>+0(SB)/8,$"\xe4\xbd\xa0\xe5\xa5\xbd\x2c\x20"
DATA text_zh<>+8(SB)/8,$"\xe4\xb8\x96\xe7\x95\x8c\x21\x0a"
GLOBL text_zh<>(SB),NOPTR,$16
// func PrintHelloWorld_var()
TEXT ·PrintHelloWorld_var(SB), $16-0
NO_LOCAL_POINTERS
CALL runtime·printlock(SB)
MOVQ ·text+0(SB), AX
MOVQ AX, (SP)
MOVQ ·text+8(SB), AX
MOVQ AX, 8(SP)
CALL runtime·printstring(SB)
CALL runtime·printunlock(SB)
RET
// func PrintHelloWorld()
TEXT ·PrintHelloWorld(SB), $16-0
NO_LOCAL_POINTERS
CALL runtime·printlock(SB)
MOVQ $text<>+0(SB), AX
MOVQ AX, (SP)
MOVQ $16, 8(SP)
CALL runtime·printstring(SB)
CALL runtime·printunlock(SB)
RET
// func PrintHelloWorld_zh()
TEXT ·PrintHelloWorld_zh(SB), $16-0
NO_LOCAL_POINTERS
CALL runtime·printlock(SB)
MOVQ $text_zh<>+0(SB), AX
MOVQ AX, (SP)
MOVQ $16, 8(SP)
CALL runtime·printstring(SB)
CALL runtime·printunlock(SB)
RET

View File

@ -0,0 +1,20 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build ignore
package main
import (
"fmt"
. "."
)
func main() {
s := "你好, 世界!\n"
fmt.Printf("%d: %x\n", len(s), s)
PrintHelloWorld()
PrintHelloWorld_zh()
PrintHelloWorld_var()
}

View File

@ -0,0 +1,15 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Go版本, 支持内联优化
package ifelse
func If(ok bool, a, b int) int {
if ok {
return a
}
return b
}
func AsmIf(ok bool, a, b int) int

View File

@ -0,0 +1,31 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "textflag.h"
//
// https://github.com/golang/go/issues/14288
//
// from rsc:
// But expanding what I said yesterday just a bit:
// never use MOVB or MOVW with a register destination,
// since it's inefficient (it's a read-modify-write on the target register).
// Instead use MOVL for reg->reg and use MOVBLZX or MOVWLZX for mem->reg;
// those are pure writes on the target register.
//
// , bool, 使 MOVBLZX.
// 使 MOVB , go test ,
// go run runme.go .
//
// func AsmIf(ok bool, a, b int) int
TEXT ·AsmIf(SB), NOSPLIT, $0-32
MOVBQZX ok+0(FP), AX // ok
MOVQ a+8(FP), BX // a
MOVQ b+16(FP), CX // b
CMPQ AX, $0 // test ok
JEQ 3(PC) // if !ok, skip 2 line
MOVQ BX, ret+24(FP) // return a
RET
MOVQ CX, ret+24(FP) // return b
RET

View File

@ -0,0 +1,32 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// go test -bench=.
package ifelse
import (
"testing"
)
func TestMin(t *testing.T) {
t.Run("go", func(t *testing.T) {
if x := If(true, 1, 2); x != 1 {
t.Fatalf("expect = %d, got = %d", 1, x)
}
if x := If(false, 1, 2); x != 2 {
t.Fatalf("expect = %d, got = %d", 2, x)
}
})
t.Run("asm", func(t *testing.T) {
if x := AsmIf(true, 1, 2); x != 1 {
t.Fatalf("expect = %d, got = %d", 1, x)
}
if x := AsmIf(false, 1, 2); x != 2 {
t.Fatalf("expect = %d, got = %d", 2, x)
}
if x := AsmIf(false, 2, 1); x != 1 {
t.Fatalf("expect = %d, got = %d", 1, x)
}
})
}

View File

@ -0,0 +1,18 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build ignore
package main
import (
. "."
)
func main() {
println("If(true, 1, 2) =", If(true, 1, 2))
println("If(false, 1, 2) =", If(false, 1, 2))
println("AsmIf(true, 1, 2) =", AsmIf(true, 1, 2))
println("AsmIf(false, 1, 2) =", AsmIf(false, 1, 2))
println("AsmIf(false, 2, 1) =", AsmIf(false, 2, 1))
}

View File

@ -0,0 +1,22 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package instr
import "testing"
var g int64
func BenchmarkSum(b *testing.B) {
ns := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for i := 0; i < b.N; i++ {
g = Sum(ns)
}
}
func BenchmarkSum2(b *testing.B) {
ns := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for i := 0; i < b.N; i++ {
g = Sum2(ns)
}
}

26
examples/ch3-xx/instr/instr.go Executable file
View File

@ -0,0 +1,26 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package instr
func Add(n, m int64) int64 {
return n + m
}
func Add2(n, m int64) int64
// BSF returns the index of the least significant set bit,
// or -1 if the input contains no set bits.
func BSF(n int64) int
func BSF32(n int32) int32
func Sum(s []int64) int64 {
var ss int64
for _, n := range s {
ss += n
}
return ss
}
func Sum2(s []int64) int64

View File

@ -0,0 +1,57 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "textflag.h"
// func Add2(n, m int64) int32
TEXT ·Add2(SB), NOSPLIT, $0-24
MOVQ n+0(FP), AX
MOVQ m+8(FP), BX
ADDQ AX, BX
MOVQ BX, ret+16(FP)
RET
// func BSF(n int64) int
TEXT ·BSF(SB), NOSPLIT, $0
BSFQ n+0(FP), AX
JEQ allZero
MOVQ AX, ret+8(FP)
RET
allZero:
MOVQ $-1, ret+8(FP)
RET
// func BSF32(n int32) int32
TEXT ·BSF32(SB), NOSPLIT, $0
BSFL n+0(FP), AX
JEQ allZero32
MOVL AX, ret+8(FP)
RET
allZero32:
MOVL $-1, ret+8(FP)
RET
// func Sum2(s []int64) int64
TEXT ·Sum2(SB), NOSPLIT, $0
MOVQ $0, DX
MOVQ s_base+0(FP), AX
MOVQ s_len+8(FP), DI
MOVQ $0, CX
CMPQ CX, DI
JGE Sum2End
Sum2Loop:
MOVQ (AX), BP
ADDQ BP, DX
ADDQ $8, AX
INCQ CX
CMPQ CX, DI
JL Sum2Loop
Sum2End:
MOVQ DX, ret+24(FP)
RET
// vim: set ft=txt:

View File

@ -0,0 +1,16 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Go版本, 支持内联优化
package loop
func LoopAdd(cnt, v0, step int) int {
result := v0
for i := 0; i < cnt; i++ {
result += step
}
return result
}
func AsmLoopAdd(cnt, v0, step int) int

View File

@ -0,0 +1,21 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "textflag.h"
// func AsmLoopAdd(cnt, v0, step int) int
TEXT ·AsmLoopAdd(SB), NOSPLIT, $0-32
MOVQ cnt+0(FP), AX // cnt
MOVQ v0+8(FP), BX // v0
MOVQ step+16(FP), CX // step
loop:
CMPQ AX, $0 // compare cnt,0
JLE end // if cnt <= 0: go end
DECQ AX // cnt--
ADDQ CX, BX // v0 += step
JMP loop // goto loop
end:
MOVQ BX, ret+24(FP) // return v0
RET

View File

@ -0,0 +1,54 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// go test -bench=.
package loop
import (
"testing"
)
func TestLoopAdd(t *testing.T) {
t.Run("go", func(t *testing.T) {
if x := LoopAdd(100, 0, 1); x != 100 {
t.Fatalf("expect = %d, got = %d", 100, x)
}
if x := LoopAdd(100, 0, 2); x != 200 {
t.Fatalf("expect = %d, got = %d", 200, x)
}
if x := LoopAdd(100, 0, -1); x != -100 {
t.Fatalf("expect = %d, got = %d", -100, x)
}
if x := LoopAdd(100, 50, 1); x != 150 {
t.Fatalf("expect = %d, got = %d", 150, x)
}
})
t.Run("asm", func(t *testing.T) {
if x := AsmLoopAdd(100, 0, 1); x != 100 {
t.Fatalf("expect = %d, got = %d", 100, x)
}
if x := AsmLoopAdd(100, 0, 2); x != 200 {
t.Fatalf("expect = %d, got = %d", 200, x)
}
if x := AsmLoopAdd(100, 0, -1); x != -100 {
t.Fatalf("expect = %d, got = %d", -100, x)
}
if x := AsmLoopAdd(100, 50, 1); x != 150 {
t.Fatalf("expect = %d, got = %d", 150, x)
}
})
}
func BenchmarkLoopAdd(b *testing.B) {
b.Run("go", func(b *testing.B) {
for i := 0; i < b.N; i++ {
LoopAdd(1000, 0, 1)
}
})
b.Run("asm", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmLoopAdd(1000, 0, 1)
}
})
}

View File

@ -0,0 +1,17 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build ignore
package main
import (
. "."
)
func main() {
println("LoopAdd(100,0,1) =", LoopAdd(100, 0, 1))
println("LoopAdd(100,0,2) =", LoopAdd(100, 0, 2))
println("LoopAdd(100,200,-1) =", LoopAdd(100, 200, -1))
println("LoopAdd(100,0,-1) =", LoopAdd(100, 0, -1))
}

View File

@ -0,0 +1,31 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Go版本, 支持内联优化
package min
func Min(a, b int) int {
if a < b {
return a
}
return b
}
//go:noinline
func MinNoInline(a, b int) int {
if a < b {
return a
}
return b
}
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func AsmMin(a, b int) int
func AsmMax(a, b int) int

View File

@ -0,0 +1,26 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "textflag.h"
// func AsmMin(a, b int) int
TEXT ·AsmMin(SB), NOSPLIT, $0-24
MOVQ a+0(FP), AX // a
MOVQ b+8(FP), BX // b
CMPQ AX, BX // compare a, b
JGT 3(PC) // if a>b, skip 2 line
MOVQ AX, ret+16(FP) // return a
RET
MOVQ BX, ret+16(FP) // return b
RET
// func AsmMax(a, b int) int
TEXT ·AsmMax(SB), NOSPLIT, $0-24
MOVQ a+0(FP), AX // a
MOVQ b+8(FP), BX // b
CMPQ AX, BX // compare a, b
JLT 3(PC) // if a<b, skip 2 line
MOVQ AX, ret+16(FP) // return a
RET
MOVQ BX, ret+16(FP) // return b
RET

View File

@ -0,0 +1,65 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// go test -bench=.
package min
import (
"testing"
)
func TestMin(t *testing.T) {
t.Run("go", func(t *testing.T) {
if x := Min(1, 2); x != 1 {
t.Fatalf("expect = %d, got = %d", 1, x)
}
if x := Min(2, 1); x != 1 {
t.Fatalf("expect = %d, got = %d", 1, x)
}
})
t.Run("asm", func(t *testing.T) {
if x := AsmMin(1, 2); x != 1 {
t.Fatalf("expect = %d, got = %d", 1, x)
}
if x := AsmMin(2, 1); x != 1 {
t.Fatalf("expect = %d, got = %d", 1, x)
}
})
}
func TestMax(t *testing.T) {
t.Run("go", func(t *testing.T) {
if x := Max(1, 2); x != 2 {
t.Fatalf("expect = %d, got = %d", 2, x)
}
if x := Max(2, 1); x != 2 {
t.Fatalf("expect = %d, got = %d", 2, x)
}
})
t.Run("asm", func(t *testing.T) {
if x := AsmMax(1, 2); x != 2 {
t.Fatalf("expect = %d, got = %d", 2, x)
}
if x := AsmMax(2, 1); x != 2 {
t.Fatalf("expect = %d, got = %d", 2, x)
}
})
}
func BenchmarkMin(b *testing.B) {
b.Run("go", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Min(1, 2)
}
})
b.Run("go.noinline", func(b *testing.B) {
for i := 0; i < b.N; i++ {
MinNoInline(1, 2)
}
})
b.Run("asm", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmMin(1, 2)
}
})
}

View File

@ -0,0 +1,14 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build ignore
package main
import (
. "."
)
func main() {
println("Min(1,2) =", Min(1, 2))
}

View File

@ -0,0 +1,15 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// +build ignore
package main
import (
. "."
)
func main() {
println("SumIntSlice([]int{1,2,3}) =", SumIntSlice([]int{1, 2, 3}))
println("AsmSumIntSlice([]int{1,2,3}) =", AsmSumIntSlice([]int{1, 2, 3}))
}

View File

@ -0,0 +1,35 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Go版本, 支持内联优化
package slice
func SumIntSlice(s []int) int {
var sum int
for _, v := range s {
sum += v
}
return sum
}
func SumFloat32Slice(s []float32) float32 {
var sum float32
for _, v := range s {
sum += v
}
return sum
}
func SumFloat64Slice(s []float64) float64 {
var sum float64
for _, v := range s {
sum += v
}
return sum
}
func AsmSumInt16Slice(v []int16) int16
func AsmSumIntSlice(s []int) int
func AsmSumIntSliceV2(s []int) int

View File

@ -0,0 +1,59 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "textflag.h"
// func AsmSumInt16Slice(v []int16) int16
TEXT ·AsmSumInt16Slice(SB), NOSPLIT, $0-26
MOVQ v_base+0(FP), R8
MOVQ v_len+8(FP), R9
SHLQ $1, R9
ADDQ R8, R9
MOVQ $0, R10
loop:
CMPQ R8, R9
JE end
ADDW (R8), R10
ADDQ $2, R8
JMP loop
end:
MOVW R10, ret+24(FP)
RET
// func AsmSumIntSlice(s []int) int
TEXT ·AsmSumIntSlice(SB), NOSPLIT, $0-32
MOVQ s+0(FP), AX // &s[0]
MOVQ s_len+8(FP), BX // len(s)
MOVQ $0, CX // sum = 0
loop:
CMPQ BX, $0 // compare cnt,0
JLE end // if cnt <= 0: goto end
DECQ BX // cnt--
ADDQ (AX), CX // sum += s[i]
ADDQ $8, AX // i++
JMP loop // goto loop
end:
MOVQ CX, ret+24(FP) // return sum
RET
// func AsmSumIntSliceV2(s []int) int
TEXT ·AsmSumIntSliceV2(SB), NOSPLIT, $0-32
MOVQ s+0(FP), AX // p := &s[0]
MOVQ s_len+8(FP), BX
LEAQ 0(AX)(BX*8), BX // p_end := &s[len(s)]
MOVQ $0, CX // sum = 0
loop:
CMPQ AX, BX // compare p,p_end
JGE end // if p >= p_end: goto end
ADDQ (AX), CX // sum += s[i]
ADDQ $8, AX // p++
JMP loop // goto loop
end:
MOVQ CX, ret+24(FP) // return sum
RET

View File

@ -0,0 +1,82 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// go test -bench=.
package slice
import (
"testing"
)
func TestLoopAdd(t *testing.T) {
t.Run("go", func(t *testing.T) {
if x := SumIntSlice([]int{1, 2, 3}); x != 6 {
t.Fatalf("expect = %d, got = %d", 6, x)
}
})
t.Run("asm", func(t *testing.T) {
if x := AsmSumIntSlice([]int{1, 2, 3}); x != 6 {
t.Fatalf("expect = %d, got = %d", 6, x)
}
})
t.Run("asm.v2", func(t *testing.T) {
if x := AsmSumIntSliceV2([]int{1, 2, 3}); x != 6 {
t.Fatalf("expect = %d, got = %d", 6, x)
}
})
}
func BenchmarkLoopAdd(b *testing.B) {
s10 := make([]int, 10)
s100 := make([]int, 100)
s1000 := make([]int, 1000)
b.Run("go/len=10", func(b *testing.B) {
for i := 0; i < b.N; i++ {
SumIntSlice(s10)
}
})
b.Run("asm/len=10", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmSumIntSlice(s10)
}
})
b.Run("asm.v2/len=10", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmSumIntSliceV2(s10)
}
})
b.Run("go/len=100", func(b *testing.B) {
for i := 0; i < b.N; i++ {
SumIntSlice(s100)
}
})
b.Run("asm/len=100", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmSumIntSlice(s100)
}
})
b.Run("asm.v2/len=100", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmSumIntSliceV2(s100)
}
})
b.Run("go/len=1000", func(b *testing.B) {
for i := 0; i < b.N; i++ {
SumIntSlice(s1000)
}
})
b.Run("asm/len=1000", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmSumIntSlice(s1000)
}
})
b.Run("asm.v2/len=1000", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AsmSumIntSliceV2(s1000)
}
})
}

View File

@ -0,0 +1,25 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package stackmap
func X(b []byte) []byte
//func X(b []byte) []byte {
// if len(b) == cap(b) {
// b = growSlice(b)
// }
// b = b[:len(b)+1]
// b[len(b)-1] = 3
// return b
//}
func growSlice(b []byte) []byte {
newCap := 10
if cap(b) > 5 {
newCap = cap(b) * 2
}
b1 := make([]byte, len(b), newCap)
copy(b1, b)
return b1
}

View File

@ -0,0 +1,44 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
#include "funcdata.h"
#include "textflag.h"
// func X(b []byte) []byte
TEXT ·X(SB), $48-48
MOVQ b_base+0(FP), BX
MOVQ b_len+8(FP), CX
MOVQ b_cap+16(FP), DX
CMPQ CX, DX
JL afterGrow
// Set up the growSlice call.
MOVQ BX, gs_base-48(SP)
MOVQ CX, gs_len-40(SP)
MOVQ DX, gs_cap-32(SP)
CALL ·growSlice(SB)
MOVQ gs_base-24(SP), BX
MOVQ gs_len-16(SP), CX
MOVQ gs_cap-8(SP), DX
afterGrow:
// At this point, we have adequate capacity to increase len + 1 and the
// following register scheme:
// BX - b_base
// CX - b_len
// DX - b_cap
// Write base/cap results.
MOVQ BX, ret_base+24(FP)
MOVQ DX, ret_cap+40(FP)
// Write new element to b and increment the length.
LEAQ (BX)(CX*1), BX
MOVB $3, (BX)
ADDQ $1, CX
MOVQ CX, ret_len+32(FP)
RET

View File

@ -0,0 +1,47 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package stackmap
import (
"bytes"
"testing"
)
func TestX(t *testing.T) {
b := make([]byte, 0, 3)
for _, want := range [][]byte{
mkSlice(3, 3),
mkSlice(3, 3, 3),
mkSlice(3, 3, 3, 3),
mkSlice(10, 3, 3, 3, 3),
mkSlice(10, 3, 3, 3, 3, 3),
mkSlice(10, 3, 3, 3, 3, 3, 3),
mkSlice(10, 3, 3, 3, 3, 3, 3, 3),
mkSlice(10, 3, 3, 3, 3, 3, 3, 3, 3),
mkSlice(10, 3, 3, 3, 3, 3, 3, 3, 3, 3),
mkSlice(10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3),
mkSlice(20, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3),
} {
b = X(b)
if !slicesEqual(b, want) {
t.Fatalf("got %v[cap=%d]; want %v[cap=%d]",
b, cap(b), want, cap(want))
}
}
}
func mkSlice(cap int, vs ...byte) []byte {
b1 := make([]byte, 0, cap)
for _, v := range vs {
b1 = append(b1, v)
}
return b1
}
func slicesEqual(b0, b1 []byte) bool {
if cap(b0) != cap(b1) {
return false
}
return bytes.Equal(b0, b1)
}

View File

@ -0,0 +1,6 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package sum
func Sum(a int, b int) int

View File

@ -0,0 +1,9 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
TEXT ·Sum+0(SB),$0
MOVQ a+0(FP), BX
MOVQ b+8(FP), BP
ADDQ BP, BX
MOVQ BX, return+16(FP)
RET

View File

@ -0,0 +1,14 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package sum
import "testing"
func TestSum(t *testing.T) {
result := Sum(1, 1)
if result != 2 {
t.Errorf("%d does not equal 2", result)
}
}

View File

@ -0,0 +1,11 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
TEXT ·SumVec+0(SB), $0
MOVQ vec1+0(FP), BX // Move the first vector into BX
MOVQ vec2+24(FP), CX // Move the second vector into BX
MOVUPS (BX), X0
MOVUPS (CX), X1
ADDPS X0, X1
MOVUPS X1, result+48(FP)
RET

View File

@ -0,0 +1,8 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package vector
func Find(vec []int, num int) bool
func SumVec(vec1 []int32, vec2 []int32) [4]int32

View File

@ -0,0 +1,28 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
TEXT ·Find+0(SB),$0
MOVQ $0, SI // zero the iterator
MOVQ vec+0(FP), BX // BX = &vec[0]
MOVQ vec+8(FP), CX // len(vec)
MOVQ num+24(FP), DX
start:
CMPQ SI, CX
JG notfound
CMPQ (BX), DX
JNE notequal
JE found
found:
MOVQ $1, return+32(FP)
RET
notequal:
INCQ SI
LEAQ +8(BX), BX
JMP start
notfound:
MOVQ $0, return+32(FP)
RET

View File

@ -0,0 +1,40 @@
// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package vector
import "testing"
func TestFind(t *testing.T) {
vec := []int{1, 2, 3, 4, 5, 6, 7, 8}
if result := Find(vec, 5); result != true {
t.Errorf("Could not find number in vector, got: %v", result)
}
if result := Find(vec, 10); result != false {
t.Errorf("Returned true when false was expected")
}
}
func TestSum(t *testing.T) {
vec1 := []int32{1, 2, 3, 5}
vec2 := []int32{1, 2, 3, 5}
result := SumVec(vec1, vec2)
if result[0] != 2 {
t.Errorf("Expected 2, got %v, result was: %v", result[0], result)
}
if result[1] != 4 {
t.Errorf("Expected 4, got %v, result was: %v", result[0], result)
}
if result[2] != 6 {
t.Errorf("Expected 6, got %v, result was: %v", result[0], result)
}
if result[3] != 10 {
t.Errorf("Expected 10, got %v, result was: %v", result[0], result)
}
}

31
images/Makefile Normal file
View File

@ -0,0 +1,31 @@
# Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
# Use of this source code is governed by a Apache
# license that can be found in the LICENSE file.
UML_FILES=$(wildcard ./*.plantuml)
UML_SVG_FILES=$(patsubst %.plantuml,%.uml.svg,$(UML_FILES))
UML_PNG_FILES=$(patsubst %.plantuml,%.uml.png,$(UML_FILES))
DOT_FILES=$(wildcard ./*.dot)
DOT_PNG_FILES=$(patsubst %.dot,%.dot.png,$(DOT_FILES))
default: $(UML_PNG_FILES) $(DOT_PNG_FILES)
@echo "ok"
logo:
convert logo-2018-01.png -resize 300x65 logo.png
@echo "ok"
clean:
-rm *.dot.png
-rm *.uml.png
-rm *.uml.svg
%.uml.svg: %.plantuml
cat $< | docker run --rm -i chai2010/ibox:plantuml > $@
%.uml.png: %.plantuml
cat $< | docker run --rm -i chai2010/ibox:plantuml -tpng > $@
%.dot.png: %.dot
dot -Tpng -o $@ $<

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,46 @@
' Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
' Use of this source code is governed by a Apache
' license that can be found in the LICENSE file.
@startuml
title C.sum(2, 3)
|main.go|
:func main() {
C.sum(2, 3))
};
|#AntiqueWhite|main.cgo1.go|
:func main() {
_Cfunc_sum(2, 3))
};
|#AntiqueWhite|_cgo_types.go|
:func _Cfunc_sum(2, 3) double {
_cgo_runtime_cgocall(...)
return
};
|runtime.cgocall|
:_cgo_runtime_cgocall(...);
fork
|#AntiqueWhite|main.cgo2.c|
: double _cgo_xxx_Cfunc_sum(2, 3) {
return sum(2, 3)
};
|#AntiqueWhite|_cgo_export.c|
:sum(2, 3);
endfork
|runtime.cgocall|
:_cgo_runtime_cgocall(...);
|main.go|
stop
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,63 @@
' Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
' Use of this source code is governed by a Apache
' license that can be found in the LICENSE file.
@startuml
title c call go func
|_testmain.c|
:int main() {
extern int sum(int a, int b)
sum(1, 2)
return 0
};
-[#green,dashed]->
fork
|#AntiqueWhite|_cgo_export.c|
:int sum(int p0, int p1) {
struct { int p0, p1, r0; } a
_cgo_ctxt = _cgo_wait_runtime_init_done()
crosscall2(_cgoexp_xxx_sum, &a, 16, _cgo_ctxt)
_cgo_release_context(_cgo_ctxt)
return a.r0
};
|runtime/cgo/*.asm|
:TEXT crosscall2(SB),NOSPLIT,$0;
fork
|#AntiqueWhite|_cgo_types.go|
:func _cgoexp_xxx_sum(a unsafe.Pointer, n int32, ctxt uintptr) {
fn := _cgoexpwrap_xxx_sum
_cgo_runtime_cgocallback(
_cgoexpwrap_xxx_sum,
...
)
};
|#AntiqueWhite|_cgo_types.go|
:func _cgoexpwrap_xxx_sum(p0, p1) r0 {
return sum(p0, p1)
};
|main.go|
://export sum
func sum(a, b C.int) C.int {
return a + b
};
endfork
|runtime/cgo/*.asm|
:TEXT crosscall2(SB),NOSPLIT,$0;
endfork
|_testmain.c|
stop
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,37 @@
// Copyright 2018 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
digraph G {
subgraph cluster_cgo_package_main {
label = "package main";
nocgo_x[label="nocgo_x.go", style=filled, color=gray];
hello_go[label="hello.go", style=filled, color=orangered4];
main_go[label="main.go", style=filled, color=darkgreen];
nocgo_1[label="nocgo_1.go", style=filled, color=gray];
}
subgraph cluster_cgo_generated_files {
label = "cgo: generated fileds";
main_cgo2_c[label="main.cgo2.c", style=filled, color=green];
main_cgo1_go[label="main.cgo1.go", style=filled, color=green];
hello_cgo2_c[label="hello.cgo2.c", style=filled, color=red];
hello_cgo1_go[label="hello.cgo1.go", style=filled, color=red];
_cgo_gotypes_go[label="_cgo_gotypes.go", shape=box, style=filled, color=dodgerblue];
_cgo_export_h[label="_cgo_export.h", shape=box, style=filled, color=dodgerblue];
_cgo_export_c[label="_cgo_export.c", shape=box, style=filled, color=dodgerblue];
main_go -> main_cgo1_go -> _cgo_gotypes_go;
main_go -> main_cgo2_c -> _cgo_gotypes_go;
hello_go -> hello_cgo1_go -> _cgo_gotypes_go;
hello_go -> hello_cgo2_c -> _cgo_gotypes_go;
_cgo_gotypes_go -> _cgo_export_h;
_cgo_gotypes_go -> _cgo_export_c;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,29 @@
' Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
' Use of this source code is governed by a Apache
' license that can be found in the LICENSE file.
@startuml
title int32 <=> *C.char
participant int32
participant uintptr
participant unsafe.Pointer as unsafe_Pointer
participant "~*C.char" as c_char_ptr
== Go ==
int32 -> uintptr: int32 to uintptr
uintptr -> unsafe_Pointer: uintptr to unsafe.Pointer
== CGO ==
unsafe_Pointer -> c_char_ptr: unsafe.Pointer to *C.char
c_char_ptr -> unsafe_Pointer: ~*C.char to unsafe.Pointer
== Go ==
unsafe_Pointer -> uintptr: unsafe.Pointer to uintptr
uintptr -> int32: uintptr to int32
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,88 @@
' Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
' Use of this source code is governed by a Apache
' license that can be found in the LICENSE file.
@startuml
title qsort
participant go
participant c
[--> go: qsort
activate go
go -> go
activate go #DarkSalmon
note left
go_qsort_compare_info.elemsize = sv.Type().Elem().Size()
go_qsort_compare_info.fn = fn
end note
deactivate go
go -> c: C.qsort_proxy(cmp=go_qsort_compare)
activate c
note right
void qsort_proxy(
void* base, size_t num, size_t size,
int (*compare)(const void* a, const void* b)
) {
go_qsort_compare_save_base(base);
qsort(base, num, size, compare);
}
end note
' begin
c -> c: C.go_qsort_compare_save_base
activate c #DarkSalmon
note right: callback go func
go <- c: go_qsort_compare_save_base
activate go #DarkSalmon
note left
//export go_qsort_compare_save_base
func go_qsort_compare_save_base(base unsafe.Pointer) {
go_qsort_compare_info.base = uintptr(base)
}
var go_qsort_compare_info struct {
base uintptr
elemsize uintptr
fn func(a, b int) int
sync.RWMutex
}
end note
go -> c: go_qsort_compare_save_base done
deactivate go
deactivate c
' end
loop
c -> c: C.go_qsort_compare
activate c #DarkSalmon
note right: callback go func
c -> go: go_qsort_compare
activate go #DarkSalmon
note left
//export go_qsort_compare_save_base
func go_qsort_compare_save_base(base unsafe.Pointer) {
go_qsort_compare_info.base = uintptr(base)
}
end note
go -> c: go_qsort_compare done
deactivate go
deactivate c
end
go <- c: C.qsort_proxy done
deactivate c
[<-- go: done
deactivate go
@enduml

BIN
images/ch2-qsort-v2.uml.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,23 @@
' Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
' Use of this source code is governed by a Apache
' license that can be found in the LICENSE file.
@startuml
title *X <=> *Y
participant "~*X" as x_ptr
participant unsafe.Pointer as unsafe_Pointer
participant "~*Y" as y_ptr
== *X => *Y ==
x_ptr -> unsafe_Pointer: ~*X to unsafe.Pointer
unsafe_Pointer -> y_ptr: unsafe.Pointer to *Y
== *Y => *X ==
y_ptr -> unsafe_Pointer: ~*Y to unsafe.Pointer
unsafe_Pointer -> x_ptr: unsafe.Pointer to *X
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,36 @@
' Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
' Use of this source code is governed by a Apache
' license that can be found in the LICENSE file.
'var p []X
'var q []Y // q = p
'pHdr := (*reflect.SliceHeader)(unsafe.Pointer(&p))
'qHdr := (*reflect.SliceHeader)(unsafe.Pointer(&q))
'pHdr.Data = qHdr.Data
'pHdr.Len = qHdr.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
'pHdr.Cap = qHdr.Cap * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
@startuml
title []X <=> []Y
participant "var x []X\nvar y []Y" as slice
participant "var px *SliceHeader\nvar py *SliceHeader" as slice_header
== []X and []Y to *reflect.SliceHeader ==
slice -> slice_header: px := (*reflect.SliceHeader)(unsafe.Pointer(&x))\npy := (*reflect.SliceHeader)(unsafe.Pointer(&y))
== copy *px to *py ==
slice_header -> slice_header: ~*py = *px
== y changed by *py ==
slice -> slice: y = x
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -19,6 +19,7 @@ import (
var (
flagRootDir = flag.String("dir", ".", "root dir")
flagHttpAddr = flag.String("http", ":8080", "HTTP service address")
flagNoCache = flag.Bool("no-cache", true, "disable browser cache")
flagOpenBrowser = flag.Bool("openbrowser", true, "open browser automatically")
)
@ -47,7 +48,11 @@ func main() {
log.Printf("Hit CTRL-C to stop the server\n")
}()
log.Fatal(http.ListenAndServe(httpAddr, http.FileServer(http.Dir(*flagRootDir))))
mainHandler := http.FileServer(http.Dir(*flagRootDir))
if *flagNoCache {
mainHandler = NoCache(mainHandler)
}
log.Fatal(http.ListenAndServe(httpAddr, mainHandler))
}
// waitServer waits some time for the http Server to start
@ -97,3 +102,41 @@ func startBrowser(url string) bool {
cmd := exec.Command(args[0], append(args[1:], url)...)
return cmd.Start() == nil
}
var epoch = time.Unix(0, 0).Format(time.RFC1123)
var noCacheHeaders = map[string]string{
"Expires": epoch,
"Cache-Control": "no-cache, private, max-age=0",
"Pragma": "no-cache",
"X-Accel-Expires": "0",
}
var etagHeaders = []string{
"ETag",
"If-Modified-Since",
"If-Match",
"If-None-Match",
"If-Range",
"If-Unmodified-Since",
}
func NoCache(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// Delete any ETag headers that may have been set
for _, v := range etagHeaders {
if r.Header.Get(v) != "" {
r.Header.Del(v)
}
}
// Set our NoCache headers
for k, v := range noCacheHeaders {
w.Header().Set(k, v)
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}

1
talks/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
!chai2010-cgo-talk-sz-20110207.pdf

4
talks/README.md Normal file
View File

@ -0,0 +1,4 @@
# 相关 报告
- [Go集成C/C++代码(PDF)](chai2010-cgo-talk-sz-20110207.pdf) - chai2010, 珠三角技术沙龙深圳(2011.02.27)
- [深入CGO编程](https://mp.weixin.qq.com/s/1_v8DsvinZWfh0-HXks88Q) - by chai2010, GopherChina 2018 中国上海(2018.04.14~15) (内容将在大会前一天公开, 先占个坑)

Binary file not shown.

View File

@ -0,0 +1,3 @@
# GopherChina 2018 报告
- [深入CGO编程](https://mp.weixin.qq.com/s/1_v8DsvinZWfh0-HXks88Q) - by chai2010 (内容将在大会前一天公开, 先占个坑)