Go高性能编程技巧

1. 线程ID,最好自己模拟实现。不要使用runtime库。性能损耗很大。


最好别这么做。
func GoID() int {

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
}


应该这样做,模拟实现
var g_u64MockId uint64
func GoIDEx() uint64 {
return atomic.AddUint64( &g_u64MockId, uint64(1) )
}



string与[]byte相互转换

在写程序的过程中经常遇到string与[]byte的相互转换,但是这种转换是有代价的,string与[]byte并不共享底层内存空间,所以每次转换都伴随着内存的分配与底层字节的拷贝。 
我们可以借助unsafe完成指针类型转换,避开内存分配与复制,从而提升性能。属于黑魔法,尽量不要用。

 
  
  1. /*
  2. struct string{
  3. uint8 *str;
  4. int len;
  5. }
  6. struct []uint8{
  7. uint8 *array;
  8. int len;
  9. int cap;
  10. }
  11. uintptr是golang的内置类型,是能存储指针的整型,uintptr的底层类型是int,它和unsafe.Pointer可相互转换。
  12. 但是转换后的string与[]byte共享底层空间,如果修改了[]byte那么string的值也会改变,就违背了string应该是只读的规范了,可能会造成难以预期的影响。
  13. */
  14. func str2byte(s string) []byte {
  15. x := (*[2]uintptr)unsafe.Pointer(&s)
  16. h := [3]uintptr{x[0],x[1],x[1]}
  17. return *(*[]byte)(unsafe.Pointer(&h))
  18. }
  19. func byte2str(b []byte) string{
  20. return *(*string)(unsafe.Pointer(&b))
  21. }

map使用注意事项

  • 预设容量 
    map可以动态扩容,所以我们可以不关心map的大小,但是每次动态扩容时需要付出数据拷贝和重新哈希成本,如果我们能预先知道一个map最终的容量,那么最好在初始化时就指定。
 
  
  1. bigMap := make(map[int]int,100000)
  • 直接存储小对象值而不是指针 
    对于小对象,直接将数据交由 map 保存,远比用指针高效。这不但减少了堆内存分配,关键还在于垃圾回收器不会扫描非指针类型 key/value 对象。
 
  
  1. //存储值对象
  2. m := make(map[int]int,1000)
  3. for i := 0 ;i<10000;i++ {
  4. m[i]=i;
  5. }
  6. //存储指针对象
  7. //如果value是个小对象,直接存储值会比较好
  8. m := make(map[int]*int,1000)
  9. for i := 0 ;i<10000;i++ {
  10. value := i
  11. m[i]=&value;
  12. }
  • 手动删除没有元素的map 
    map可以动态扩容,我们可以不断的往map中添加新元素,但是map并不会自动收缩空间,即使一个map中的所有元素都被删除,map依然会保留所有已分配的空间。
 
  
  1. var dict map[int]int = make(map[int]int)
  2. for i := 0 ;i<100000;i++ {
  3. dict[i] = i
  4. }
  5. for key := range dict {
  6. delete(key) //即使删掉所有的元素,dict的容量仍然>=100000
  7. }
  8. // 如果不再使用dict那么手动设置为nil
  9. // dict=nil
  10. //也可以把dict指向一个新创建的小map,原有的map所占用的内存空间会被回收
  11. //dict = make(map[int]int)

了解defer

defer是提高可读性和避免资源未释放的非常有用的关键字,是使用golang写出可靠、稳定程序的利器。 
defer后面的表达式会被放入一个类似于栈(stack)的结构,在当前方法返回的时候,列表中的表达式会按照后进先出的顺序执行。 
defer本身会有一定的性能损失,但是和它带来的好处相比根本不值得一提,我们需要注意的关键点是defer表达式会在函数返回时被调用,意味着有些资源只能在函数结束时才被释放。

 
  
  1. func f(){
  2. m.lock()
  3. defer m.unlock()
  4. //....业务处理逻辑
  5. //这是很常见的上锁方式,但是m.unlock()只会在函数返回时调用,如果业务处理逻辑耗时很长,那么会一直占用着锁,在高并发情况下严重影响性能。
  6. //解决办法是找到**最小临界区**,在处理完最小临界区后及时释放掉锁。
  7. }
  8. func f() {
  9. m.lock()
  10. //...最小临界区
  11. m.unlock()
  12. //...继续处理
  13. }

字符串拼接

字符串的拼接大概有以下几种方式

  • 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,效率最高的一种方式,推荐使用
 
  
  1. strs := []string{"hello","world",strconv.FormatInt(2016,10),"come","on"}
  2. str = strings.Join(strs,"")

为什么使用string.Join效率最好呢,来看下strings.Join的代码

 
  
  1. func Join(a []string, sep string) string {
  2. //计算最终字符串的长度,根据最终长度创建[]byte,避免拼接过程中内存重新分配
  3. n := len(sep) * (len(a) - 1)
  4. for i := 0; i < len(a); i++ {
  5. n += len(a[i])
  6. }
  7. b := make([]byte, n)
  8. //使用copy函数是最高效的
  9. bp := copy(b, a[0])
  10. for _, s := range a[1:] {
  11. bp += copy(b[bp:], sep)
  12. bp += copy(b[bp:], s)
  13. }
  14. return string(b)
  15. }
  • 但是有时候很难将参数拼接成[]string,这时我们可以使用byte.Buffer
 
  
  1. var buffer bytes.Buffer
  2. for i := 0; i < 1000; i++ {
  3. buffer.WriteString("a")
  4. }

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

你可能感兴趣的:(golang)