Go编程模式 - 2.基础编码下

简介: 尽量用`time.Time`和`time.Duration`,如果必须用string,尽量用`time.RFC3339`

目录

注:切勿过早优化!

Time

这部分的内容实战项目中用得不多,大家记住耗子叔总结出来的一个原则即可:

尽量用time.Timetime.Duration,如果必须用string,尽量用time.RFC3339

然而现实情况并没有那么理想,实际项目中用得最频繁,还是自定义的2006-01-02 15:04:05

time.Now().Format("2006-01-02 15:04:05")

Performance1

Itoa性能高于Sprint

主要性能差异是由于Sprint针对的是复杂的字符串拼接,底层有个buffer,会在它的基础上进行一些字符串的拼接;

Itoa直接通过一些位操作组合出字符串。

// 170 ns/op
func Benchmark_Sprint(b *testing.B) {
   
    for i := 0; i < b.N; i++ {
   
        _ = fmt.Sprint(rand.Int())
    }
}

// 81.9 ns/op
func Benchmark_Itoa(b *testing.B) {
   
    for i := 0; i < b.N; i++ {
   
        _ = strconv.Itoa(rand.Int())
    }
}

减少string到byte的转换

主要了解go的string[]byte的转换还是比较耗性能的,但大部分情况下无法避免这种转换。

我们注意一种场景即可:从[]byte转换为string,再转换为[]byte

// 43.9 ns/op
func Benchmark_String2Bytes(b *testing.B) {
   
    data := "Hello world"
    w := ioutil.Discard
    for i := 0; i < b.N; i++ {
   
        w.Write([]byte(data))
    }
}

// 3.06 ns/op
func Benchmark_Bytes(b *testing.B) {
   
    data := []byte("Hello world")
    w := ioutil.Discard
    for i := 0; i < b.N; i++ {
   
        w.Write(data)
    }
}

切片能声明cap的,尽量初始化时声明

了解slice的扩容机制就能很容易地理解。切片越长,影响越大。

var size = 1000

// 4494 ns/op
func Benchmark_NoCap(b *testing.B) {
   
    for n := 0; n < b.N; n++ {
   
        data := make([]int, 0)
        for k := 0; k < size; k++ {
   
            data = append(data, k)
        }
    }
}

// 2086 ns/op
func Benchmark_Cap(b *testing.B) {
   
    for n := 0; n < b.N; n++ {
   
        data := make([]int, 0, size)
        for k := 0; k < size; k++ {
   
            data = append(data, k)
        }
    }
}

避免用string做大量字符串的拼接

频繁拼接字符串的场景并不多,了解即可。

var strLen = 10000

// 0.0107 ns/op
func Benchmark_StringAdd(b *testing.B) {
   
    var str string
    for n := 0; n < strLen; n++ {
   
        str += "x"
    }
}

// 0.000154 ns/op
func Benchmark_StringBuilder(b *testing.B) {
   
    var builder strings.Builder
    for n := 0; n < strLen; n++ {
   
        builder.WriteString("x")
    }
}

// 0.000118 ns/op
func Benchmark_BytesBuffer(b *testing.B) {
   
    var buffer bytes.Buffer
    for n := 0; n < strLen; n++ {
   
        buffer.WriteString("x")
    }
}

Performance2

并行操作用sync.WaitGroup控制

热点内存分配用sync.Pool

注意一下,一定要是热点,千万不要 过早优化

倾向于使用lock-free的atomic包

除了常用的CAS操作,还有atomic.ValueStoreLoad操作,这里简单地放个实例:

func main() {
   
    v := atomic.Value{
   }
    type demo struct {
   
        a int
        b string
    }

    v.Store(&demo{
   
        a: 1,
        b: "hello",
    })

    data, ok := v.Load().(*demo)
    fmt.Println(data, ok)
    // &{1 hello} true
}

复杂场景下,还是建议用mutex

对磁盘的大量读写用bufio包

bufio.NewReader()bufio.NewWriter()

对正则表达式不要重复compile

// 如果匹配的格式不会变化,全局只初始化一次即可
var compiled = regexp.MustCompile(`^[a-z]+[0-9]+$`)

func main() {
   
    fmt.Println(compiled.MatchString("test123"))
    fmt.Println(compiled.MatchString("test1234"))
}

用protobuf替换json

go项目内部通信尽量用protobuf,但如果是对外提供api,比如web前端,json格式更方便。

map的key尽量用int来代替string

var size = 1000000

// 0.0442 ns/op
func Benchmark_MapInt(b *testing.B) {
   
    var m = make(map[int]struct{
   })
    for i := 0; i < size; i++ {
   
        m[i] = struct{
   }{
   }
    }
    b.ResetTimer()
    for n := 0; n < size; n++ {
   
        _, _ = m[n]
    }
}

// 0.180 ns/op
func Benchmark_MapString(b *testing.B) {
   
    var m = make(map[string]struct{
   })
    for i := 0; i < size; i++ {
   
        m[strconv.Itoa(i)] = struct{
   }{
   }
    }
    b.ResetTimer()
    for n := 0; n < size; n++ {
   
        _, _ = m[strconv.Itoa(n)]
    }
}

示例中strconv.Itoa函数对性能多少有点影响,但可以看到stringint的差距是在数量级的。

Further

PPT中给出了8个扩展阅读,大家根据情况自行阅读。

如果说你的时间只够读一个材料的话,我推荐大家反复品读一下Effective Go

目录
相关文章
|
1月前
|
Go Windows
|
4月前
|
消息中间件 设计模式 缓存
GO 中优雅编码和降低圈复杂度
GO 中优雅编码和降低圈复杂度
|
5月前
|
监控 NoSQL Go
GO 中 ETCD 的编码案例分享
GO 中 ETCD 的编码案例分享
|
6月前
|
设计模式 Kubernetes 监控
Go编程模式 - 8-装饰、管道和访问者模式
装饰、管道和访问者模式的使用频率不高,但在特定场景下会显得很酷
21 0
|
6月前
|
Kubernetes Shell Go
Go编程模式 - 7-代码生成
良好的命名能体现出其价值。尤其是在错误码的处理上,无需再去查询错误码对应的错误内容,直接可以通过命名了解。
28 0
|
6月前
|
SQL 分布式计算 Go
Go编程模式 - 6-映射、归约与过滤
但是,我不建议大家在实际项目中直接使用这一块代码,毕竟其中大量的反射操作是比较耗时的,尤其是在延迟非常敏感的web服务器中。 如果我们多花点时间、直接编写指定类型的代码,那么就能在编译期发现错误,运行时也可以跳过反射的耗时。
29 0
|
6月前
|
Go
Go编程模式 - 5.函数式选项
编程的一大重点,就是要 `分离变化点和不变点`。这里,我们可以将必填项认为是不变点,而非必填则是变化点。
15 0
|
6月前
|
Go
Go编程模式 - 4.错误处理
如何Wrap Error,在多人协同开发、多模块开发过程中,很难统一。而一旦不统一,容易出现示例中的过度Unwrap的情况。
19 0
|
6月前
|
Go
Go编程模式 - 3.继承与嵌入
业务逻辑依赖控制逻辑,才能保证在复杂业务逻辑变化场景下,代码更健壮!
22 0
|
6月前
|
编译器 Go C++
Go编程模式 - 1.基础编码上
Program to an interface, not an implementation
17 0