Skip to content

Instantly share code, notes, and snippets.

@BruceChen7
Last active December 28, 2022 14:52
Show Gist options
  • Save BruceChen7/675b64f260068d14ae8598c3dff42008 to your computer and use it in GitHub Desktop.
Save BruceChen7/675b64f260068d14ae8598c3dff42008 to your computer and use it in GitHub Desktop.
#golang#effective#practice#go
// [Go语言高性能编程手册(万字长文)](https://mp.weixin.qq.com/s/iSxWFsnNLGgilzKIsSDBgg)
// Bad
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
BenchmarkFmtSprint-4 143 ns/op 2 allocs/op
// Good
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}
BenchmarkStrconv-4 64.2 ns/op 1 allocs/op
// 少量的重复不比反射差
// DeleteSliceElms 从切片中过滤指定元素。注意:不修改原切片。
func DeleteSliceElms(i interface{}, elms ...interface{}) interface{} {
// 构建 map set。
m := make(map[interface{}]struct{}, len(elms))
for _, v := range elms {
m[v] = struct{}{}
}
// 创建新切片,过滤掉指定元素。
v := reflect.ValueOf(i)
t := reflect.MakeSlice(reflect.TypeOf(i), 0, v.Len())
for i := 0; i < v.Len(); i++ {
if _, ok := m[v.Index(i).Interface()]; !ok {
t = reflect.Append(t, v.Index(i))
}
}
return t.Interface()
}
// DeleteU64liceElms 从 []uint64 过滤指定元素。注意:不修改原切片。
func DeleteU64liceElms(i []uint64, elms ...uint64) []uint64 {
// 构建 map set。
m := make(map[uint64]struct{}, len(elms))
for _, v := range elms {
m[v] = struct{}{}
}
// 创建新切片,过滤掉指定元素。
t := make([]uint64, 0, len(i))
for _, v := range i {
if _, ok := m[v]; !ok {
t = append(t, v)
}
}
return t
}
// 当使用反射时,请问一下自己,我真地需要它吗?
// 遍历 []struct{} 使用下标而不是 range
// 运算符 + 只能简单地完成字符串之间的拼接,非字符串类型的变量需要单独做类型转换。
// 行内拼接字符串不会产生内存分配,也不涉及类型地动态转换,所以性能上优于fmt.Sprintf()。
// Good
func BenchmarkJoinStrWithOperator(b *testing.B) {
s1, s2, s3 := "foo", "bar", "baz"
for i := 0; i < b.N; i++ {
_ = s1 + s2 + s3
}
}
// Bad
func BenchmarkJoinStrWithSprintf(b *testing.B) {
s1, s2, s3 := "foo", "bar", "baz"
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("%s%s%s", s1, s2, s3)
}
}
// 非行内拼接字符串推荐使用 strings.Builder
func BenchmarkJoinStrWithStringsJoin(b *testing.B) {
s1, s2, s3 := "foo", "bar", "baz"
for i := 0; i < b.N; i++ {
_ = strings.Join([]string{s1, s2, s3}, "")
}
}
func BenchmarkJoinStrWithStringsBuilder(b *testing.B) {
s1, s2, s3 := "foo", "bar", "baz"
for i := 0; i < b.N; i++ {
var builder strings.Builder
_, _ = builder.WriteString(s1)
_, _ = builder.WriteString(s2)
_, _ = builder.WriteString(s3)
}
}
func BenchmarkJoinStrWithBytesBuffer(b *testing.B) {
s1, s2, s3 := "foo", "bar", "baz"
for i := 0; i < b.N; i++ {
var buffer bytes.Buffer
_, _ = buffer.WriteString(s1)
_, _ = buffer.WriteString(s2)
_, _ = buffer.WriteString(s3)
}
}
func BenchmarkJoinStrWithByteSlice(b *testing.B) {
s1, s2, s3 := "foo", "bar", "baz"
for i := 0; i < b.N; i++ {
var bys []byte
bys= append(bys, s1...)
bys= append(bys, s2...)
_ = append(bys, s3...)
}
}
func BenchmarkJoinStrWithByteSlicePreAlloc(b *testing.B) {
s1, s2, s3 := "foo", "bar", "baz"
for i := 0; i < b.N; i++ {
bys:= make([]byte, 0, 9)
bys= append(bys, s1...)
bys= append(bys, s2...)
_ = append(bys, s3...)
}
}
// 综合易用性和性能,一般推荐使用strings.Builder来拼接字符串。
// string.Builder也提供了预分配内存的方式 Grow:
func BenchmarkJoinStrWithStringsBuilderPreAlloc(b *testing.B) {
s1, s2, s3 := "foo", "bar", "baz"
for i := 0; i < b.N; i++ {
var builder strings.Builder
builder.Grow(9)
_, _ = builder.WriteString(s1)
_, _ = builder.WriteString(s2)
_, _ = builder.WriteString(s3)
}
}
// 使用sync.Pool来减少gc
//
type Student struct {
Name string
Age int32
Remark [1024]byte
}
var studentPool = sync.Pool{
New: func() interface{} {
return new(Student)
},
}
stu := studentPool.Get().(*Student)
json.Unmarshal(buf, stu)
studentPool.Put(stu)
@BruceChen7
Copy link
Author

BruceChen7 commented Jun 15, 2022

资料来源

常见的问题

package main

import "fmt"

func main() {
    defer_call()
}

func defer_call()  {
    defer func() {fmt.Println("打印前")}()
    defer func() {fmt.Println("打印中")}()
    defer func() {fmt.Println("打印后")}()

    panic("触发异常")
}

答:输出内容为:

打印后
打印中
打印前
panic: 触发异常

解析: defer 的执行顺序是先进后出。出现panic语句的时候,会先按照 defer 的后进先出顺序执行,最后才会执行panic。

编译是否问题

func funcMui(x, y int) (sum int, error) {
    return x + y, nil
}

解析:

在函数有多个返回值时,只要有一个返回值有命名,其他的也必须命名。如果有多个返回值必须加上括号();如果只有一个返回值且命名也需要加上括号()。这里的第一个返回值有命名sum,第二个没有命名,所以错误。

编译问题

下面这段代码能否通过编译,如果可以,输出什么?

var (
    size := 1024
    max_size = size * 2
)

func main() {
    fmt.Println(size, max_size)
}

答:不能通过

解析:

这道题的主要知识点是变量的简短模式,形如:x := 100 。但这种声明方式有限制:

必须使用显示初始化;
不能提供数据类型,编译器会自动推导;
只能在函数内部使用简短模式

编译问题

func main() {
    sn1 := struct {
        age  int
        name string
	}{age: 11, name: "qq"}
	sn2 := struct {
        age  int
        name string
	}{age: 11, name: "11"}

    if sn1 == sn2 {
        fmt.Println("sn1 == sn2")
    }

    sm1 := struct {
        age int
        m   map[string]string
    }{age: 11, m: map[string]string{"a": "1"}}
    sm2 := struct {
        age int
        m   map[string]string
    }{age: 11, m: map[string]string{"a": "1"}}

    if sm1 == sm2 {
        fmt.Println("sm1 == sm2")
    }
}
  • 结构体只能比较是否相等,但是不能比较大小;
  • 相同类型的结构体才能进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关;
  • 如果struct的所有成员都可以比较,则该struct就可以通过==或!=进行比较是否相同,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等;

那有什么是可以比较的呢?

  • 常见的有bool、数值型、字符、指针、数组等

不能比较的有

  • slice、map、函数

编译问题

func GetValue() int {
    return 1
}

func main() {
    i := GetValue()
    switch i.(type) {
    case int:
        fmt.Println("int")
    case string:
        fmt.Println("string")
    case interface{}:
        fmt.Println("interface")
    default:
        fmt.Println("unknown")
    }
}
  • interface才用来做类型转换

编译问题

func main() {  
    s := make(map[string]int)
    delete(s, "h")
    fmt.Println(s["h"])
}
  • 删除不存在的key,是不会有任何问题

@BruceChen7
Copy link
Author

BruceChen7 commented Jun 19, 2022

输出什么

func main() {  
    var i interface{}
    if i == nil {
        fmt.Println("nil")
        return
    }
    fmt.Println("not nil")
}

当且仅当接口的动态值和动态类型都为 nil 时,接口类型值才为 nil,上面的代码输出nil。

func Foo(x interface{}) {
     if x == nil {
         fmt.Println("empty interface")
         return
     }
     fmt.Println("non-empty interface")
 }
 func main() {
     var x *int = nil
    Foo(x)
}

interface 的内部结构,我们知道接口除了有静态类型,还有动态类型和动态值,当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。这里的 x 的动态类型是 *int,所以 x 不为 nil

@BruceChen7
Copy link
Author

BruceChen7 commented Jul 12, 2022

type ConfigOne struct {
    Daemon string
}
func (c *ConfigOne) String() string {
    return fmt.Sprintf("print: %v", c)
}
func main() {
    c := &ConfigOne{}
    c.String()
}

运行时错误

解析:

如果类型实现 String() 方法,当格式化输出时会自动使用 String() 方法。上面这段代码是在该类型的 String() 方法内使用格式化输出,导致递归调用,最后抛错。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment