1. 线程ID,最好自己模拟实现。不要使用runtime库。性能损耗很大。
var buf [64]byte
n := runtime.Stack(buf[:], false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
GoroutineId, err := strconv.Atoi(idField)
if err != nil {
panic( fmt.Sprintf("cannot get goroutine id: %v", err) )
}
return GoroutineId
}
return atomic.AddUint64( &g_u64MockId, uint64(1) )
}
string与[]byte相互转换
在写程序的过程中经常遇到string与[]byte的相互转换,但是这种转换是有代价的,string与[]byte并不共享底层内存空间,所以每次转换都伴随着内存的分配与底层字节的拷贝。
我们可以借助unsafe完成指针类型转换,避开内存分配与复制,从而提升性能。属于黑魔法,尽量不要用。
/*
struct string{
uint8 *str;
int len;
}
struct []uint8{
uint8 *array;
int len;
int cap;
}
uintptr是golang的内置类型,是能存储指针的整型,uintptr的底层类型是int,它和unsafe.Pointer可相互转换。
但是转换后的string与[]byte共享底层空间,如果修改了[]byte那么string的值也会改变,就违背了string应该是只读的规范了,可能会造成难以预期的影响。
*/
func str2byte(s string) []byte {
x := (*[2]uintptr)unsafe.Pointer(&s)
h := [3]uintptr{x[0],x[1],x[1]}
return *(*[]byte)(unsafe.Pointer(&h))
}
func byte2str(b []byte) string{
return *(*string)(unsafe.Pointer(&b))
}
map使用注意事项
- 预设容量
map可以动态扩容,所以我们可以不关心map的大小,但是每次动态扩容时需要付出数据拷贝和重新哈希成本,如果我们能预先知道一个map最终的容量,那么最好在初始化时就指定。
bigMap := make(map[int]int,100000)
- 直接存储小对象值而不是指针
对于小对象,直接将数据交由 map 保存,远比用指针高效。这不但减少了堆内存分配,关键还在于垃圾回收器不会扫描非指针类型 key/value 对象。
//存储值对象
m := make(map[int]int,1000)
for i := 0 ;i<10000;i++ {
m[i]=i;
}
//存储指针对象
//如果value是个小对象,直接存储值会比较好
m := make(map[int]*int,1000)
for i := 0 ;i<10000;i++ {
value := i
m[i]=&value;
}
- 手动删除没有元素的map
map可以动态扩容,我们可以不断的往map中添加新元素,但是map并不会自动收缩空间,即使一个map中的所有元素都被删除,map依然会保留所有已分配的空间。
var dict map[int]int = make(map[int]int)
for i := 0 ;i<100000;i++ {
dict[i] = i
}
for key := range dict {
delete(key) //即使删掉所有的元素,dict的容量仍然>=100000
}
// 如果不再使用dict那么手动设置为nil
// dict=nil
//也可以把dict指向一个新创建的小map,原有的map所占用的内存空间会被回收
//dict = make(map[int]int)
了解defer
defer是提高可读性和避免资源未释放的非常有用的关键字,是使用golang写出可靠、稳定程序的利器。
defer后面的表达式会被放入一个类似于栈(stack)的结构,在当前方法返回的时候,列表中的表达式会按照后进先出的顺序执行。
defer本身会有一定的性能损失,但是和它带来的好处相比根本不值得一提,我们需要注意的关键点是defer表达式会在函数返回时被调用,意味着有些资源只能在函数结束时才被释放。
func f(){
m.lock()
defer m.unlock()
//....业务处理逻辑
//这是很常见的上锁方式,但是m.unlock()只会在函数返回时调用,如果业务处理逻辑耗时很长,那么会一直占用着锁,在高并发情况下严重影响性能。
//解决办法是找到**最小临界区**,在处理完最小临界区后及时释放掉锁。
}
func f() {
m.lock()
//...最小临界区
m.unlock()
//...继续处理
}
字符串拼接
字符串的拼接大概有以下几种方式
- fmt.Sprintf(“%s%s%d%s%s”,”hello”,”world”,2016,”come”,”on”) //这种方式效率最低,但是代码最简单,最优雅
- 使用”+”拼接字符串 “hello”+”world”+ strconv.FormatInt(2016,10) +”come”+”on” //比fmt.Sprintf()高效一些,但是代码很难看
- 使用strings.Join()
将参数组装成[]string,然后调用strings.join,效率最高的一种方式,推荐使用
strs := []string{"hello","world",strconv.FormatInt(2016,10),"come","on"}
str = strings.Join(strs,"")
为什么使用string.Join效率最好呢,来看下strings.Join的代码
func Join(a []string, sep string) string {
//计算最终字符串的长度,根据最终长度创建[]byte,避免拼接过程中内存重新分配
n := len(sep) * (len(a) - 1)
for i := 0; i < len(a); i++ {
n += len(a[i])
}
b := make([]byte, n)
//使用copy函数是最高效的
bp := copy(b, a[0])
for _, s := range a[1:] {
bp += copy(b[bp:], sep)
bp += copy(b[bp:], s)
}
return string(b)
}
- 但是有时候很难将参数拼接成[]string,这时我们可以使用byte.Buffer
var buffer bytes.Buffer
for i := 0; i < 1000; i++ {
buffer.WriteString("a")
}
reflect的性能影响
反射带来了极大的方便,但是同时也有一定的性能损失。性能要求极高的模块中应该注意反射所带来的性能损失。
JSON是一种常用的数据交换格式,但Go的encoding/json库依赖于反射来对json进行序列化和反序列化。使用ffjson,可以通过使用代码生成的方式来避免反射的使用,相比使用原生库可以提升2~3倍的性能。
channel并不是很高效
使用golang语言编程有一条很重要的思想是Don't communicate by sharing memory, share memory by communicating.
而channel则是不同goroutine之间communicate的通道。但是channel的底层实现也不是无锁的,往channel中读写数据都是需要加锁的,而且锁的力度还很大。
在一些情况下可以使用利用atomic实现无锁的结构(ring buffer)来替代channel以提高程序的性能。
而在有些情况下使用sync.Mutex、atmoi不仅比使用channel效率更高代码还更简洁明了。Use whichever is most expressive and most simple