阅读更多

2顶
0踩

编程语言

转载新闻 Go语言黑魔法

2015-04-28 10:31 by 副主编 mengyidan1988 评论(1) 有10642人浏览
go c
今天我要教大家一些无用技能,也可以叫它奇技淫巧或者黑魔法。用得好可以提升性能,用得不好就会招来恶魔,嘿嘿。

黑魔法导论

为了让大家在学习了基础黑魔法之后能有所悟,在必要的时候能创造出本文传授之外的属于自己的魔法,这里需要先给大家打好基础。

学习Go语言黑魔法之前,需要先看清Go世界的本质,你才能获得像Neo一样的能力。

在Go语言中,Slice本质是什么呢?是一个reflect.SliceHeader结构体和这个结构体中Data字段所指向的内存。String本质是什么呢?是一个reflect.StringHeader结构体和这个结构体所指向的内存。

在Go语言中,指针的本质是什么呢?是unsafe.Pointer和uintptr。

当你清楚了它们的本质之后,你就可以随意的玩弄它们,嘿嘿嘿。
第一式 - 获得Slice和String的内存数据

让我小试身手,你有一个CGO接口要调用,需要你把一个字符串数据或者字节数组数据从Go这边传递到C那边,比如像这个:mysql/conn.go at master · funny/mysql · GitHub

查了各种教程和文档,它们都告诉你要用C.GoString或C.GoBytes来转换数据。

但是,当你调用这两个函数的时候,发生了什么事情呢?这时候Go复制了一份数据,然后再把新数据的地址传给C,因为Go不想冒任何风险。

你的C程序只是想一次性的用一下这些数据,也不得不做一次数据复制,这对于一个性能癖来说是多麽可怕的一个事实!

这时候我们就需要一个黑魔法,来做到不拷贝数据又能把指针地址传递给C。
// returns &s[0], which is not allowed in go
func stringPointer(s string) unsafe.Pointer {
	p := (*reflect.StringHeader)(unsafe.Pointer(&s))
	return unsafe.Pointer(p.Data)
}

// returns &b[0], which is not allowed in go
func bytePointer(b []byte) unsafe.Pointer {
	p := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	return unsafe.Pointer(p.Data)
}

以上就是黑魔法第一式,我们先去到Go字符串的指针,它本质上是一个*reflect.StringHeader,但是Go告诉我们这是一个*string,我们告诉Go它同时也是一个unsafe.Pointer,Go说好吧它是,于是你得到了unsafe.Pointer,接着你就躲过了Go的监视,偷偷的把unsafe.Pointer转成了*reflect.StringHeader。

有了*reflect.StringHeader,你很快就取到了Data字段指向的内存地址,它就是Go保护着不想给你看到的隐秘所在,你把这个地址偷偷告诉给了C,于是C就愉快的偷看了Go的隐私。

第二式 - 把[]byte转成string

你肯定要笑,要把[]byte转成string还不简单?Go语言初学者都会的类型转换语法:[]byte(str)。

但是你知道这么做的代价吗?既然我们能随意的玩弄SliceHeader和StringHeader,为什么我们不能造个string给Go呢?Go的内部会不会就是这么做的呢?

先上个实验吧:
package labs28

import "testing"
import "unsafe"

func Test_ByteString(t *testing.T) {
	var x = []byte("Hello World!")
	var y = *(*string)(unsafe.Pointer(&x))
	var z = string(x)

	if y != z {
		t.Fail()
	}
}

func Benchmark_Normal(b *testing.B) {
	var x = []byte("Hello World!")
	for i := 0; i < b.N; i ++ {
		_ = string(x)
	}
}

func Benchmark_ByteString(b *testing.B) {
	var x = []byte("Hello World!")
	for i := 0; i < b.N; i ++ {
		_ = *(*string)(unsafe.Pointer(&x))
	}
}

这个实验先证明了我们可以用[]byte的数据造个string给Go。接着做了两组Benchmark,分别测试了普通的类型转换和伪造string的效率。

结果如下:
$ go test -bench="."
PASS
Benchmark_Normal    20000000            63.4 ns/op
Benchmark_ByteString    2000000000           0.55 ns/op
ok      github.com/idada/go-labs/labs28 2.486s

哟西,显然Go这次又为了稳定性做了些复制数据之类的事情了!这让性能癖怎么能忍受!

我现在手头有个[]byte,但是我想用strconv.Atoi()把它转成字面含义对应的整数值,竟然需要发生一次数据拷贝把它转成string,比如像这样:mysql/types.go at master · funny/mysql · GitHub,这实在不能忍啊!
出招:
// convert b to string without copy
func byteString(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

我们取到[]byte的指针,这次Go又告诉你它是*byte不是*string,你告诉它滚犊子这是unsafe.Pointer,Go这下又老实了,接着你很自在的把*byte转成了*string,因为你知道reflect.StringHeader和reflect.SliceHeader的结构体只相差末尾一个字段,两者的内存是对其的,没必要再取Data字段了,直接转吧。

于是,世界终于安宁了,嘿嘿。

第三式 - 结构体和[]byte互转

有一天,你想把一个简单的结构体转成二进制数据保存起来,这时候你想到了encoding/gob和encoding/json,做了一下性能测试,你想到效率有没有可能更高点?

于是你又试了encoding/binady,性能也还可以,但是你还不满意。但是瓶颈在哪里呢?你恍然大悟,最高效的办法就是完全不解析数据也不产生数据啊!

怎么做?是时候使用这个黑魔法了:
type MyStruct struct {
	A int
	B int
}

var sizeOfMyStruct = int(unsafe.Sizeof(MyStruct{}))

func MyStructToBytes(s *MyStruct) []byte {
	var x reflect.SliceHeader
	x.Len = sizeOfMyStruct
	x.Cap = sizeOfMyStruct
	x.Data = uintptr(unsafe.Pointer(s))
	return *(*[]byte)(unsafe.Pointer(&x))
}

func BytesToMyStruct(b []byte) *MyStruct {
	return (*MyStruct)(unsafe.Pointer(
		(*reflect.SliceHeader)(unsafe.Pointer(&b)).Data,
	))
}

这是个曲折但又熟悉的故事。你造了一个SliceHeader,想把它的Data字段指向你的结构体,但是Go又告诉你不可以,你像往常那样把Go提到一边,你得到了unsafe.Pointer,但是这次Go有不死心,它告诉你Data是uintptr,unsafe.Pointer不是uintptr,你大脚把它踢开,怒吼道:unsafe.Pointer就是uintptr,你少拿这些概念糊弄我,Go屁颠屁颠的跑开了,现在你一马平川的来到了函数的出口,Go竟然已经在哪里等着你了!你上前三下五除二把它踢得远远的,顺利的把手头的SliceHeader转成了[]byte。

过了一阵子,你拿到了一个[]byte,你知道需要把它转成MyStruct来读取其中的数据。Go这时候已经完全不是你的对手了,它已经洗好屁股在函数入口等你,你一行代码就解决了它。

第四式 - 用CGO优化GC

你已经是Go世界的Neo,Go跟本没办法拿你怎么样。但是有一天Go的GC突然抽风了,原来这货是不管对象怎么用的,每次GC都给来一遍人口普查,导致系统暂停时间很长。

可是你是个性能癖,你把一堆数据都放在内存里方便快速访问,你这时候很想再踢Go的屁股,但是你没办法,毕竟你还在Go的世界里,你现在得替它擦屁股了,你似乎看到Go躲在一旁偷笑。
你想到你手头有CGO,可以轻易的用C申请到Go世界外的内存,Go的GC不会扫描这部分内存。

你还想到你可以用unsafe.Pointer将C的指针转成Go的结构体指针。于是一大批常驻内存对象被你用这种方式转成了Go世界的黑户,Go的GC一下子轻松了下来。

但是你手头还有很多Slice,于是你就利用C申请内存给SliceHeader来构造自己的Slice,于是你旗下的Slice纷纷转成了Go世界的黑户,Go的GC终于平静了。

但好景总是不长久,有一天Go世界突然崩溃了,只留下一句话:Segmentation Fault。你一下怂了,怎么段错误了?

经过一个通宵排查,你发现你管辖的黑户对象竟然偷偷的跟Go世界的其它合法居民搞在一起,当Go世界以为某个居民已经消亡时,用GC回收了它的住所,但是你的地下世界却认为它还活着,还继续访问它。

于是你废了一番功夫斩断了所有关联,世界暂时宁静了下来。

但是你已经很累了,这时候你想起一句话:

引用
为无为,则无不治


本文来自:知乎-达达
来自: 知乎-达达
2
0
评论 共 1 条 请登录后发表评论
1 楼 lyl_pages 2015-04-29 16:20
你想用指针,就得为指针负责,忍受Segmentation Fault;如果想用gc带来的轻松,就得忍受性能损失。

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Go语言黑魔法中的问题修正

    是时候使用这个黑魔法了: type MyStruct struct { A int B int } var sizeOfMyStruct = int(unsafe.Sizeof(MyStruct{})) func MyStructToBytes(s *MyStruct) []byte { var x reflect.SliceHeader x.Len = ...

  • 来了!Go的2个黑魔法技巧

    导语|最近,在写Go代码的时候,我发现了其特别有意思的两个奇技淫巧或者黑魔法,若使用得好可以提升性能,用得不好就会招来恶魔,嘿嘿,于是写下这篇文章和大家分享一下。一、魔法:调用runtime中的私有函数按照Go的...

  • 必撸系列!Go另外几个黑魔法技巧汇总

    Go的2个黑魔法技巧》中,笔者分享了go中两个有意思的技巧。而最近一段时间,笔者重新梳理了一下go知识点,并深入地看看了它的源码,在实践中又有了新的沉淀,于是写下这篇文章和大家分享一下。一、魔法:最小化运行...

  • 我为什么放弃Go语言

    我为什么放弃Go语言?有好几次,当我想起来的时候,总是会问自己:这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题。开门见山地说,我当初放弃Go语言,就是因为两个“不爽”:第一,对Go语言...

  • 【Go语言入门教程】Go语言简介

    文章目录Go语言简介Go语言创始人1) Ken Thompson2) Rob Pike3) Robert GriesemerGo 是编译型语言为什么要学习Go语言Go语言吉祥物Go语言的特性有哪些?语法简单并发模型内存分配垃圾回收静态链接标准库工具链Go语言为...

  • 【Golang】一篇文章带你快速了解Go语言&为什么你要学习Go语言

    Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。Go语言是编程语言设计的又一...

  • Go黑魔法之导出私有函数与私有变量

    Go黑魔法之导出私有函数与私有变量 在Go语言中, package中包含函数与变量通过identifier的首字母是否大写来决定它是否可以被其它package所访问。当一个函数或变量名称为小写字母时,默认是无法被其他package引用的...

  • go语言介绍

    go语言介绍

  • go语言学习笔记

    学习go语言前瞻 为什么你应该学习Go语言? 终于等到你!Go语言——让你用写Python代码的开发效率编写C语言代码。 为什么互联网世界需要Go语言 世界上已经有太多太多的编程语言了,为什么又出来一个Go语言? 硬件限制...

  • Go黑魔法之导出API供C调用

    Calling Go functions From C go build 包含一个选项-buildmode可通过配置c-archive &amp;amp;amp;amp; c-shared两种模式分别生成可供C调用的静态 &amp;amp;amp;amp; 动态库. 具体详情可通过go help buildmode查看...

  • 为什么 Go 语言能在中国这么火?

    1. 因为 "golang并没有颠覆性解决问题",只是, 简单, 学习简单 1.1 太阳底下没有新鲜事, 历史总是在"重复" 当年 写 CGI , php 打败了 perl , 无他, 在 web 的 CGI 时代, php 学习成本低. 同样 , 2018年 vueJS 与 ...

  • 必须要掌握的几个 Go 语言技巧

    点击上方蓝色“飞雪无情”关注我,设个星标,第一时间看文章Go是一种非常不错的编程语言。它是一种让你真正的关注自己的业务,不必为程序本身操心太多的语言,因此您可以尽快编写应用程序。比如它有比...

  • 一文吃透 Go 语言解密之接口 interface

    大家好,我是煎鱼。自古流传着一个传言...在 Go 语言面试的时候必有人会问接口(interface)的实现原理。这又是为什么?为何对接口如此执着?实际上,Go 语言的接口设计在整体扮演...

  • Go和C++通用性能优化黑魔法——PGO!

    ????导读我们在进行性能优化的时候,往往会应用各种花式的优化手段:优化算法复杂度(从 O(N) 优化到 O(logN) ),优化锁的粒度或者无锁化,应用各种池化技术:内存池、...本文会介绍 PGO 的原理,以及 Go/C++ 语...

  • Github 上 1.6W 星的「黑魔法」,非常实用啊

    德墨忒尔定律 得墨忒耳定律又称最少知识原则,是一条与面向对象语言有关的软件设计原则。 该定律表明,软件的一个单元应该只与其直接合作者交谈。 比如对象 A 引用了对象 B,对象 B 引用了对象 C,则 A 可以直接...

  • Go语言三个高效编程的技巧

    点击上方蓝色“飞雪无情”关注我,设个星标,第一时间看文章Go是一种非常不错的编程语言。它是一种让你真正的关注自己的业务,不必为程序本身操心太多的语言,因此您可以尽快编写应用程序。比如它有比较完整的生态...

  • 江西师范大学科学技术学院在四川2020-2024各专业最低录取分数及位次表.pdf

    那些年,与你同分同位次的同学都去了哪里?全国各大学在四川2020-2024年各专业最低录取分数及录取位次数据,高考志愿必备参考数据

  • 麒麟win10双系统重新安装win10后麒麟启动菜单看不到解决方法

    麒麟win10双系统重新安装win10后麒麟启动菜单看不到解决方法

  • 多邻国Duolingo v6.0.3 高级版.apk

    多邻国Duolingo v6.0.3 高级版.apk

Global site tag (gtag.js) - Google Analytics