This article is long, it is recommended to click the original article at the end of the article, after the collection to view. Alternatively, connect the collector PC to ๐ tigerb.cn/php2go/
Copyright statement
- Distribution of this Manual and materially modified versions is prohibited without the express authorization of the copyright holder.
- Distribution of this work and its derivative works in standard (paper) book form is prohibited without prior authorization from the copyright owner.
- No cooperation in any form with any third party.
preface
A brief manual is compiled to help you get started with Go language efficiently. It is mainly to strengthen your understanding by comparing the differences between PHP and Go. The content is mainly divided into the following four parts:
- Linguistic level difference
- Basic grammatical differences
- Avoid pit guide
- Use the advanced
Linguistic level difference
Note: The following is based on the mainstream PHP-FPM mode of PHP.Copy the code
Compare the item | PHP | Go |
---|---|---|
String representation | Single quotation marks (PSR) | Double quotation marks |
Concatenated string | . | + |
Language version compatibility | bad | Backwards compatible |
Code style. | There are no official standards, and community standards start late | Official standards and tools have been provided since the beginning |
Scripting language | is | not |
Strongly typed language | No (PHP7 supports strict mode) | is |
Whether garbage collection is supported | is | is |
Object-oriented Language (OOP) | Spirit likeness | Partially supported, the core is composite reuse |
Whether inheritance is supported | is | No (with composite reuse) |
Whether to support interfaces | is | is |
Whether to support try… catch… | is | no |
Whether to support package management | is | is |
Cross-platform support | is | is |
Environmental construction cost | high | low |
Implement way | Cli mode, PHP-fpm mode (โ ) | binary |
Process model | Multiple processes | Single process |
Native Whether to create TCP/UDP services | Yes (poor support, production unavailable) | is |
Whether to create the HTTP service | Yes (poor support, production unavailable) | is |
Process blocking | is | no |
Whether coroutines are supported | No (2) | is |
Concurrency capability (โข) | weak | strong |
Whether to run in resident memory | Not (4) | is |
Import file mode | require orinclude Corresponding to the file |
import Import packages |
Whether to support unit testing | is | is |
Whether benchmark is supported | no | is |
Whether to support performance analysis | Support (xhprof/tideways) | Support (pprof/DLV) |
Cost of using performance analysis tools | High (high installation and expansion cost) | Very low |
Swoole coroutine (SWOole coroutine) is one of the most popular modes in the world. It is also possible to use THE SWOole coroutine framework (SWOole coroutine framework) in the WORLD. It is also possible to use THE SWOole coroutine framework (SWOole coroutine framework) in the worldCopy the code
In the initial transition from PHP to Go, the focus was on a shift in programming consciousness, especially the following:
- Strongly typed
- Resident memory operation
- Understand and use Pointers
- Concurrent security
- Timely release or return of resources
Basic grammatical differences
Note: The following is based on PHP5.4+Copy the code
Common basic type comparisons
PHP has few and simple types. Common data types in PHP include Boolean, string, int, float, array, and object.
PHP’s common data types are compared with their Go counterparts or similar types.
Language \ type | boolean | string | int | float | array | object |
---|---|---|---|---|---|---|
PHP | bool | string | int | float | Array (1, 2, 3) index array, the array (‘ 1 ‘= > 1,’ 2 ‘= > 2,’ 3 ‘= > 3) associative array | Instantiate class |
Go | bool | string | Int, int8, INT16, INT32, INT64, uint, uint8, uint16, uint32, uint64 | Float32, float64 | [length]type | More like struct |
In addition, Go supports a much richer variety of types:
type |
---|
Slice slices (PHP equivalent to indexed arrays) |
Map (equivalent to associative arrays in PHP) |
Channel (channel, shared by communication, don’t communicate by sharing) |
Pointer (Go has a pointer type for each value type) |
Byte (corresponds to uint8 alias, which can represent Ascaii code) |
Rune (corresponding to Int32, which can represent Unicode) |
, etc. |
A custom type, for exampletype userDefinedType int32 |
## Comparison of common initialization methods for basic types |
type | PHP | Go(define variable bandvar Keywords, or without using grammar sugar directly: = ) |
---|---|---|
boolean | $varStr = true; |
var varStr bool = true or var varStr = true or varStr := true |
string | $varStr = 'demo'; |
var varStr string = "" or varStr := "" (:= Writing method is omitted below) |
int32 | $varNum = 0; |
var varInt32 int32 = 0 |
int64 | Same as above | var varInt64 int64 = 0 |
float32 | $varNum = 0.01; |
var varFloat32 float32 = 0 |
float64 | Same as above | var varFloat64 float64 = 0 |
array | $varArray = array(); Or grammatical sugar $varArray = []; |
var varArray [6]int32 = [6]int32{} |
Slice (slice) | Again, PHP is called index data | var varSlice []int32 = []int32{} Slices are automatically expanded relative to the data |
map | $varMap = array('key' => 'value'); |
var varMap map[string]int32 = map[string]int32{} |
A closure (closures) | $varClosure = function() {}; |
var varClosure func() = func() {} |
channel | There is no | var varChannel chan string = make(chan string) No cache channel;var varChannelBuffer chan string = make(chan string, 6) Have a cache channel |
Instantiation of a PHP class versus initialization of a Go structure
Instantiation of the PHP class
/* Define class */
class ClassDemo {
// Private property
private $privateVar = "";
// Public property
public $publicVar = "";
// The constructor
public function __construct()
{
// Execute when the class is instantiated
}
// Private method
private function privateFun()
{}// Public method
public function publicFun()
{}}// Instantiate class ClassDemo to obtain ClassDemo objects
$varObject = new ClassDemo(); // Object (class)
Copy the code
Initialization of the Go structure
// When the package is initialized
func init(a){}type StructDemo struct{
// The lower case hump indicates private property
// Cannot be exported
privateVar string
// The uppercase hump indicates the public property
/ / can be exported
PublicVar string
}
// A lowercase hump indicates a private method
// Private method of StructDemo
func (demo *StructDemo) privateFun(a) error {
return nil
}
// The uppercase hump indicates the public property
// Public method of StructDemo
func (demo *StructDemo) PublicFun(a) error {
return nil
}
// Initialize StructDemo
// structDemo := &StructDemo{}
Copy the code
Comparison of common functions
Description of common functions | PHP | Go |
---|---|---|
The length of the array | count() | len() |
Split a string into an array | explode() | strings.Split(s string, sep string) []string |
Turn the capital | strtoupper() | strings.ToUpper(s string) string |
Turn to lowercase | strtolower() | strings.ToLower(s string) string |
Remove the blank space | trim() | strings.Trim(s, cutset string) string |
Json serialization | json_encode() | json.Marshal(v interface{}) ([]byte, error) |
Json deserialization | json_decode() | json.Unmarshal(data []byte, v interface{}) error |
Serialization (no longer recommended) | Serialize () and unserialize () | packageGithub.com/wulijun/go-… |
md5 | md5() | Package crypto/md5 |
Terminal output | Echo, var_dump, etc | fmt.Println(a … interface{}) |
Various types are interchanged | Intval (), etc | Package strconv |
Avoid pit guide
- Use globals with caution. They are not destroyed after completing a request, as they are in PHP
- Parameter is
slice
,map
Type. Note that the value can be modified globally - When resources are used up, release or recycle them
- Do not rely on the map traversal order
- Do not write maps concurrently
- Note That the pointer type is not null
nil
And then the operation - The Go language does not support inheritance, but it does have synthetic reuse
1. Use global variables with caution. Global variables are not destroyed after completing a request, as they are in PHP
package main
import (
"sync/atomic"
"github.com/gin-gonic/gin"
)
// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0
// Simulate the interface logic
func main(a) {
r := gin.Default()
r.GET("/ping".func(c *gin.Context) {
// Atom plus one
atomic.AddInt32(&GlobalVarDemo, 1)
c.JSON(200, gin.H{
"message": GlobalVarDemo,
})
})
r.Run()
}
// When we request the interface many times, it is obvious that global variables are not destroyed after completing a request, as in PHP.
// However, in PHP, global variables are automatically destroyed after completing a request.
/ / curl "127.0.0.1:8080 / ping"
// {"message":1}
/ / curl "127.0.0.1:8080 / ping"
// {"message":2} <------- value incrementing
/ / curl "127.0.0.1:8080 / ping"
// {"message":3} <------- value incrementing
Copy the code
2. The parameter isslice
,map
Type. Note that the value can be modified globally
Like PHP passing by reference, Go is all about passing values, for reasons described below.
/ / section
package main
import "fmt"
func main(a) {
paramDemo := []int32{1}
fmt.Printf("main.paramDemo 1 %v, pointer: %p \n", paramDemo, ¶mDemo)
/ / shallow copy
demo(paramDemo)
fmt.Printf("main.paramDemo 2 %v, pointer: %p \n", paramDemo, ¶mDemo)
}
func demo(paramDemo []int32) ([]int32, error) {
fmt.Printf("main.demo.paramDemo pointer: %p \n", ¶mDemo)
paramDemo[0] = 2
return paramDemo, nil
}
// main.paramDemo 1 [1], pointer: 0xc00000c048
// main.demo.paramDemo Pointer: 0xC00000C078 <------- Value copy occurred because memory addresses were different
// main.paramDemo 2 [2] <------- The original value is modified
// main.paramDemo 1 [1], pointer: 0xc0000a6030
// main.demo.paramDemo Pointer: 0xC0000A6060 <------- Value copy occurred because memory addresses were different
// main.paramDemo 2 [2], pointer: 0xc0000A6030 <------- The original value was modified
// the =========== array does not have this problem ===========
package main
import "fmt"
func main(a) {
paramDemo := [1]int32{1}
fmt.Println("main.paramDemo 1", paramDemo)
demo(paramDemo)
fmt.Println("main.paramDemo 2", paramDemo)
}
func demo(paramDemo [1]int32) ([1]int32, error) {
paramDemo[0] = 2
return paramDemo, nil
}
// [Running] go run "... /demo/main.go"
// main.paramDemo 1 [1]
// main.paramDemo 2 [1] < the value ------- has not been modified
//===========Map has the same problem ===========
package main
import "fmt"
func main(a) {
paramDemo := map[string]string{
"a": "a",
}
fmt.Println("main.paramDemo 1", paramDemo)
demo(paramDemo)
fmt.Println("main.paramDemo 2", paramDemo)
}
func demo(paramDemo map[string]string) (map[string]string, error) {
paramDemo["a"] = "b"
return paramDemo, nil
}
// [Running] go run "... /demo/main.go"
// main.paramDemo 1 map[a:a]
// main.paramDemo 2 map[a:b] <------- the value is modified
Copy the code
Why is that?
A: Go is all value passing, shallow copy, slice and Map's underlying type is a structure, and the type that actually stores the value is a pointer.Copy the code
/ / versions / 1.13.8 / SRC/runtime/slice. Go
// Slice source structure
type slice struct {
array unsafe.Pointer // The type of the actual stored value is a pointer
len int
cap int
}
/ / versions / 1.13.8 / SRC/runtime/map. Go
// Map source structure
type hmap struct {
count int
flags uint8
B uint8
noverflow uint16
hash0 uint32
buckets unsafe.Pointer // The type of the actual stored value is a pointer
oldbuckets unsafe.Pointer
nevacuate uintptr
extra *mapextra
}
Copy the code
How to do?
A: A deep copy is to create a new piece of memory, pointer to the new memory address, and copy the original value. As follows:Copy the code
package main
import "fmt"
func main(a) {
paramDemo := []int32{1}
fmt.Println("main.paramDemo 1", paramDemo)
// Initialize the new space
paramDemoCopy := make([]int32.len(paramDemo))
/ / copy
copy(paramDemoCopy, paramDemo)
demo(paramDemoCopy)
fmt.Println("main.paramDemo 2", paramDemo)
}
func demo(paramDemo []int32) ([]int32, error) {
paramDemo[0] = 2
return paramDemo, nil
}
// [Running] go run "... /demo/main.go"
// main.paramDemo 1 [1]
// main.paramDemo 2 [1]
Copy the code
3. Release or recycle resources when resources are used up
package main
import (
"github.com/gomodule/redigo/redis"
)
var RedisPool *redis.Pool
func init(a) {
RedisPool = NewRedisPool()
}
func main(a) {
redisConn := RedisPool.Get()
// Remember to defer releasing the resource
defer redisConn.Close()
}
func NewRedisPool(a) *redis.Pool {
/ / a little...
return &redis.Pool{}
}
Copy the code
Why is that?
A: Avoid resources being held inefficiently, wasting resources and increasing the number of connections to resources. Second, returning the connection pool also reduces the cost of creating new resources.Copy the code
- The number of resource connections increases linearly
- If it is always held, the resource server also has a timeout period
4. Do not rely on the map traversal order
Previously, PHP’s “Map” (associative array) had a stable order of elements no matter how many times it was traversed, as follows:
$demoMap = array(
'a'= >'a'.'b'= >'b'.'c'= >'c'.'d'= >'d'.'e'= >'e',);foreach ($demoMap as $v) {
var_dump("v {$v}");
}
// First execution
[Running] php "... /php/demo.php"
string(3) "v a"
string(3) "v b"
string(3) "v c"
string(3) "v d"
string(3) "v e"
// The NTH execution
// The sequence of traversal results is stable
[Running] php "... /php/demo.php"
string(3) "v a"
string(3) "v b"
string(3) "v c"
string(3) "v d"
string(3) "v e"
Copy the code
But in The Go language, it’s a different story.
package main
import "fmt"
func main(a) {
var demoMap map[string]string = map[string]string{
"a": "a"."b": "b"."c": "c"."d": "d"."e": "e",}for _, v := range demoMap {
fmt.Println("v", v)
}
}
// First execution
// [Running] go run "... /demo/main.go"
// v a
// v b
// v c
// v d
// v e
// Execute the second time
// The sequence of elements has changed as a result of the traverse
// [Running] go run "... /demo/main.go"
// v e
// v a
// v b
// v c
// v d
Copy the code
Why is that?
Answer: The underlying implementation is array + similar to the zipper method. The hash function is written out of order. 2. Multiples and 3. PHP then maintains the order of map elements through the extra memory space.Copy the code
5. Do not write maps concurrently
package main
import (
"testing"
)
func BenchmarkDemo(b *testing.B) {
var demoMap map[string]string = map[string]string{
"a": "a"."b": "b",}// Simulate concurrent writing to map
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
demoMap["a"] = "aa"}})}// BenchmarkDemo
// fatal error: concurrent map writes
// fatal error: concurrent map writes
Copy the code
Why is that?
A: The concurrency is unsafe and triggers panic: FATAL error: concurrent map writes.Copy the code
Go version 1.13.8 source code
// The hashWriting value is 4
ifh.flags&hashWriting ! =0 {
throw("concurrent map read and map write")}Copy the code
6. Ensure that the pointer type is not nullnil
And then the operation
package main
import (
"fmt"
"log"
"net/http"
)
func main(a) {
resp, err := http.Get("https://www.example.com")
ifresp.StatusCode ! = http.StatusOK || err ! =nil {
// Panic will be triggered when resp is nil
/ / when resp. StatusCode! = Http. StatusOK When err is nil, panic is triggered
log.Printf("err: %s", err.Error())
}
}
// [Running] go run "... /demo/main.go"
// panic: runtime error: invalid memory address or nil pointer dereference
Copy the code
package main
import (
"fmt"
"log"
"net/http"
)
func main(a) {
// Simulate the request business code
resp, err := http.Get("https://www.example.com")
fmt.Println(resp, err)
iferr ! =nil {
// Report an error and log the exception
log.Printf("err: %s", err.Error())
return
}
// The simulated service code is not the successful code
ifresp ! =nil&& resp.StatusCode ! = http.StatusOK {// Report an error and log the exception}}Copy the code
7. Go does not support inheritance, but there is synthetic reuse
abstract class AbstractClassDemo {
// Abstract method
abstract public function demoFun();
// Public method
public function publicFun()
{
$this->demoFun(); }}class ClassDemo extends AbstractClassDemo {
public function demoFun()
{
var_dump("Demo"); }} (new ClassDemo())->demoFun();
// [Running] php "... /php/demo.php"
// string(4) "Demo"
Copy the code
package main
import (
"fmt"
)
// Infrastructure
type Base struct{}/ / Base DemoFun
func (b *Base) DemoFun(a) {
fmt.Println("Base")}func (b *Base) PublicFun(a) {
b.DemoFun()
}
type Demo struct {
// Composite reuse Base
Base
}
/ / the Demo DemoFun
func (d *Demo) DemoFun(a) {
fmt.Println("Demo")}func main(a) {
/ / execution
(&Demo{}).PublicFun()
}
// [Running] go run "... /demo/main.go"
// Base <------ Note that this is a method of synthesising a reused structure
Copy the code
Use the advanced
- Hot load tool Bee
- Goroutine concurrency control
sync.WaitGroup
The use of the package - Child Goroutine timeout control
context.Context
The use of the package - A map for concurrency security
sync.Map
The use of the package - Reduce GC pressure
sync.Pool
The use of the package - Reduce cache penetration
singleflight
The use of the package - The use of the Channel
- Unit testing & benchmarking
- Performance analysis
1. Hot loading tool Bee
Function: Run Go code in hot loading mode, monitor code changes and re-run the code to improve development efficiency.
Use:
Install go get github.com/beego/bee/v2 Start the project in hot load mode SOAAGENT=10.40.24.126 bee run-main =main. go-runargs ="start"Copy the code
2.Goroutine concurrency controlsync.WaitGroup
The use of the package
Effect: A Goroutine can wait until the execution of a child of the current Goroutine is complete.
Use:
package main
import (
"fmt"
"sync"
"time"
)
func main(a) {
wg := &sync.WaitGroup{}
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Sub A starts execution.")
time.Sleep(5 * time.Second)
fmt.Println("Sub A completed.")
}(wg)
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Child B begins execution.")
time.Sleep(5 * time.Second)
fmt.Println("Subb completed.")
}(wg)
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Subc begins execution.")
time.Sleep(5 * time.Second)
fmt.Println("Completion of subC")
}(wg)
fmt.Println("The Lord waits")
wg.Wait()
fmt.Println("The Lord exits.")}// First execution
// [Running] go run "... /demo/main.go"
// Sub a starts execution
// Subc starts execution
// Subb starts execution
// Main wait <------ Note that this is not the same as the location printed below, because the current code concurrent execution is not guaranteed execution order
// Subb is finished
// Suba is finished
// The subc is finished
/ / the main exit
// First execution
// [Running] go run "... /demo/main.go"
// Main wait <------ Note that this is not the same as the position printed above, because the current code concurrent execution is not guaranteed execution order
// Sub a starts execution
// Subc starts execution
// Subb starts execution
// Subb is finished
// The subc is finished
// Suba is finished
// Primary exit <------ The primary Goroutine waits until all the child goroutines have executed
Copy the code
3. Subgoroutine timeout controlcontext.Context
The use of the package
Function: The first parameter of the Go language is usually context.Context type, 1. 2. Control the sub-goroutine timeout exit 3. Control the sub-Goroutine timed exit
Use:
package main
import (
"context"
"fmt"
"time"
)
func main(a) {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
go func(ctx context.Context) {
execResult := make(chan bool)
// Simulate the business logic
go func(execResult chan<- bool) {
// Simulate processing timeouts
time.Sleep(6 * time.Second)
execResult <- true
}(execResult)
// Wait for the result
select {
case <-ctx.Done():
fmt.Println("Timeout exit")
return
case <-execResult:
fmt.Println("Processing completed")
return
}
}(ctx)
time.Sleep(10 * time.Second)
}
// [Running] go run "... /demo/main.go"
// Exit due to timeout
Copy the code
4. Map of concurrency securitysync.Map
The use of the package
Function: A map with concurrent security and supports concurrent write. The performance of scenarios with more read and less write is good.
Use:
package main
import (
"sync"
"testing"
)
func BenchmarkDemo(b *testing.B) {
demoMap := &sync.Map{}
demoMap.Store("a"."a")
demoMap.Store("b"."b")
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
demoMap.Store("a"."aa")}}}// BenchmarkDemo
// Benchmarkdemo-4 6334993 203.8 ns/op 16 B/op 1 allocs/op
// PASS
/ / not panic
Copy the code
5. Reduce GC pressuresync.Pool
The use of the package
Functions: Reuse objects and reduce GC pressure.
Use:
5.1 Code example for Not Using Sync. Pool
package main
import (
"sync"
"testing"
)
type Country struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Province struct {
ID int `json:"id"`
Name string `json:"name"`
}
type City struct {
ID int `json:"id"`
Name string `json:"name"`
}
type County struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Street struct {
ID int `json:"id"`
Name string `json:"name"`
}
// Simulation data
// Address information object
type AddressModule struct {
Consignee string `json:"consignee"`
Email string `json:"email"`
Mobile int64 `json:"mobile"`
Country *Country `json:"country"`
Province *Province `json:"province"`
City *City `json:"city"`
County *County `json:"county"`
Street *Street `json:"street"`
DetailedAddress string `json:"detailed_address"`
PostalCode string `json:"postal_code"`
AddressID int64 `json:"address_id"`
IsDefault bool `json:"is_default"`
Label string `json:"label"`
Longitude string `json:"longitude"`
Latitude string `json:"latitude"`
}
// Sync. Pool is not used
func BenchmarkDemo_NoPool(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
// Direct initialization
addressModule := &AddressModule{}
addressModule.Consignee = ""
addressModule.Email = ""
addressModule.Mobile = 0
addressModule.Country = &Country{
ID: 0,
Name: "",
}
addressModule.Province = &Province{
ID: 0,
Name: "",
}
addressModule.City = &City{
ID: 0,
Name: "",
}
addressModule.County = &County{
ID: 0,
Name: "",
}
addressModule.Street = &Street{
ID: 0,
Name: "",
}
addressModule.DetailedAddress = ""
addressModule.PostalCode = ""
addressModule.IsDefault = false
addressModule.Label = ""
addressModule.Longitude = ""
addressModule.Latitude = ""
// The following code is meaningless just to avoid syntax errors
if addressModule == nil {
return}}})}// The result of not using sync.Pool
// goos: darwin
// goarch: amd64
// pkg: demo
// cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
// BenchmarkDemo_NoPool-4 144146564 84.62 ns/op 120 B/op 5 allocs/op
// PASS
// ok demo 21.782s
Copy the code
Perform analysis without sync.Pool: flame diagram &Top function
You can clearly see that the GC process consumes a lot of CPU.
5.2 Code Example for Using Sync. Pool
/ / use the sync. The Pool
func BenchmarkDemo_Pool(b *testing.B) {
// Use the cache Pool sync.Pool
demoPool := &sync.Pool{
// Define the anonymous function that initializes the structure
New: func(a) interface{} {
return &AddressModule{
Country: &Country{
ID: 0,
Name: "",
},
Province: &Province{
ID: 0,
Name: "",
},
City: &City{
ID: 0,
Name: "",
},
County: &County{
ID: 0,
Name: "",
},
Street: &Street{
ID: 0,
Name: "",
},
}
},
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
// Get the object from the cache pool
addressModule, _ := (demoPool.Get()).(*AddressModule)
// The following code is meaningless just to avoid syntax errors
if addressModule == nil {
return
}
// Reset the object to return the object to the cache pool
addressModule.Consignee = ""
addressModule.Email = ""
addressModule.Mobile = 0
addressModule.Country.ID = 0
addressModule.Country.Name = ""
addressModule.Province.ID = 0
addressModule.Province.Name = ""
addressModule.County.ID = 0
addressModule.County.Name = ""
addressModule.Street.ID = 0
addressModule.Street.Name = ""
addressModule.DetailedAddress = ""
addressModule.PostalCode = ""
addressModule.IsDefault = false
addressModule.Label = ""
addressModule.Longitude = ""
addressModule.Latitude = ""
// Return object to cache pool
demoPool.Put(addressModule)
}
})
}
// Use sync.Pool to execute the result
// goos: darwin
// goarch: amd64
// pkg: demo
// cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
// BenchmarkDemo_Pool-4 988550808 12.41 ns/op 0 B/op 0 allocs/op
// PASS
// ok demo 14.215s
Copy the code
Use sync.Pool to perform the analysis: flame diagram &Top function
Runtime. mallocgc is no longer visible in top
The use of the flame diagram and the Top function will be discussed below.Copy the code
6. Reduce cache penetrationsingleflight
The use of the package
Effect: Reduce the number of requests during cache penetration.
Use:
package main
import (
"io/ioutil"
"net/http"
"sync"
"testing"
"golang.org/x/sync/singleflight"
)
// No code example using SingleFlight
func TestDemo_NoSingleflight(t *testing.T) {
t.Parallel()
wg := sync.WaitGroup{}
// Simulate concurrent remote calls
for i := 0; i < 3; i++ {
wg.Add(1)
go func(a) {
defer wg.Done()
resp, err := http.Get("http://example.com")
iferr ! =nil {
t.Error(err)
return
}
_, err = ioutil.ReadAll(resp.Body)
iferr ! =nil {
t.Error(err)
return
}
t.Log("log")
}()
}
wg.Wait()
}
// Example code using SingleFlight
func TestDemo_Singleflight(t *testing.T) {
t.Parallel()
singleGroup := singleflight.Group{}
wg := sync.WaitGroup{}
// Simulate concurrent remote calls
for i := 0; i < 3; i++ {
wg.Add(1)
go func(a) {
defer wg.Done()
/ / use singleflight
res, err, shared := singleGroup.Do("cache_key".func(a) (interface{}, error) {
resp, err := http.Get("http://example.com")
iferr ! =nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
iferr ! =nil {
return nil, err
}
return body, nil
})
iferr ! =nil {
t.Error(err)
return
}
_, _ = res.([]byte)
t.Log("log", shared, err)
}()
}
wg.Wait()
}
Copy the code
To capture a request for domain example.com, run the tcpdump host example.com command
Three requests were made without Singleflight
Only one request was made using Singleflight
7. Use of Channel
Function: Do not communicate through shared memory. Communicate to achieve shared memory. It’s a pipe.
Use:
package main
import (
"fmt"
"time"
)
// Respond to a common structure
type APIBase struct {
Code int32 `json:"code"`
Message string `json:"message"`
}
// Simulate the response structure of interface A
type APIDemoA struct {
APIBase
Data APIDemoAData `json:"data"`
}
type APIDemoAData struct {
Title string `json:"title"`
}
// Simulate the response structure of interface B
type APIDemoB struct {
APIBase
Data APIDemoBData `json:"data"`
}
type APIDemoBData struct {
SkuList []int64 `json:"sku_list"`
}
// Simulate the interface logic
func main(a) {
// Create A channel for interface A to transmit the results
execAResult := make(chan APIDemoA)
// Create a channel for interface B to transmit the results
execBResult := make(chan APIDemoB)
// Call interface A concurrently
go func(execAResult chan<- APIDemoA) {
// Simulate the remote call process of interface A
time.Sleep(2 * time.Second)
execAResult <- APIDemoA{}
}(execAResult)
// Call interface B concurrently
go func(execBResult chan<- APIDemoB) {
// Simulate the remote call process of interface B
time.Sleep(1 * time.Second)
execBResult <- APIDemoB{}
}(execBResult)
var resultA APIDemoA
var resultB APIDemoB
i := 0
for {
if i >= 2 {
fmt.Println("Quit")
break
}
select {
case resultA = <-execAResult: // Wait for A response from interface A
i++
fmt.Println("resultA", resultA)
case resultB = <-execBResult: // Wait for the response from interface B
i++
fmt.Println("resultB", resultB)
}
}
}
// [Running] go run "... /demo/main.go"
// resultB {{0 } {[]}}
// resultA {{0 } {}}
/ / exit
Copy the code
8. Unit testing & benchmarking
Function: Debug code block and interface during development; Benchmark code blocks, interfaces, and analyze performance issues, including CPU usage, memory usage, etc. You can do a comparison test. The CI phase detects code quality and reduces bugs.
Use:
8.1 Unit Testing
A very simple example of a unit test:
package main
import (
"io/ioutil"
"net/http"
"testing"
)
func TestDemo(t *testing.T) {
t.Parallel()
// Simulate the call interface
resp, err := http.Get("http://example.com?user_id=121212")
iferr ! =nil {
t.Error(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
iferr ! =nil {
t.Error(err)
return
}
t.Log("body".string(body))
}
/ / execution
// go test -timeout 30s -run ^TestDemo$ demo -v -count=1
// === RUN TestDemo
// === PAUSE TestDemo
// === CONT TestDemo
/ /...
// -- PASS: TestDemo (0.45s)
// PASS
/ / ok demo 1.130 s
Copy the code
Examples of unit tests for multiple test cases:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"testing"
)
type Req struct {
UserID int64
}
func TestDemo(t *testing.T) {
t.Parallel()
tests := []struct {
TestName string
*Req
}{
{
TestName: "Test Case 1",
Req: &Req{
UserID: 12121212,
},
},
{
TestName: "Test Case 2",
Req: &Req{
UserID: 829066,,}}}for _, v := range tests {
t.Run(v.TestName, func(t *testing.T) {
// Simulate the call interface
url := fmt.Sprintf("http://example.com?user_id=%d", v.UserID)
resp, err := http.Get(url)
iferr ! =nil {
t.Error(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
iferr ! =nil {
t.Error(err)
return
}
t.Log("body".string(body), url)
})
}
}
/ / execution
// go test -timeout 30s -run ^TestDemo$ demo -v -count=1
// === RUN TestDemo
// === PAUSE TestDemo
// === CONT TestDemo
// === RUN TestDemo/ Test case 1
// ...
// === RUN TestDemo/ Test Case 2
// ...
// -- PASS: TestDemo (7.34s)
// -- PASS: TestDemo/ test case 1 (7.13s)
// -- PASS: TestDemo/ test case 2 (0.21s)
// PASS
// ok demo 7.984s
Copy the code
8.2 Benchmark Test
A simple benchmark:
package main
import (
"sync"
"testing"
)
// Stress test sync.map
func BenchmarkSyncMap(b *testing.B) {
demoMap := &sync.Map{}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
demoMap.Store("a"."a")
for i := 0; i < 1000; i++ {
demoMap.Load("a")}}})}// go test -benchmem -run=^$ -bench ^(BenchmarkSyncMap)$ demo -v -count=1 -cpuprofile=cpu.profile -memprofile=mem.profile -benchtime=10s
// goos: darwin
// goarch: amd64
// pkg: demo
// BenchmarkSyncMap
// BenchmarkSyncMap-4
// 570206 23047 ns/op 16 B/op 1 allocs/op
// PASS
// ok demo 13.623s
Copy the code
Benchmarking:
package main
import (
"sync"
"testing"
)
// Stress test sync.map
func BenchmarkSyncMap(b *testing.B) {
demoMap := &sync.Map{}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
demoMap.Store("a"."a")
for i := 0; i < 1000; i++ {
demoMap.Load("a")}}})}// Implement a concurrent map with read/write locks
type ConcurrentMap struct {
value map[string]string
mutex sync.RWMutex
}
/ / write
func (c *ConcurrentMap) Store(key string, val string) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.value == nil {
c.value = map[string]string{}
}
c.value[key] = val
}
/ / read
func (c *ConcurrentMap) Load(key string) string {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.value[key]
}
// Stress test concurrent map
func BenchmarkConcurrentMap(b *testing.B) {
demoMap := &ConcurrentMap{}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
demoMap.Store("a"."a")
for i := 0; i < 1000; i++ {
demoMap.Load("a")}}})}// go test -benchmem -run=^$ -bench . demo -v -count=1 -cpuprofile=cpu.profile -memprofile=mem.profile -benchtime=10s
// goos: darwin
// goarch: amd64
// pkg: demo
// BenchmarkSyncMap
// BenchmarkSyncMap-4 668082 15818 ns/op 16 B/op 1 allocs/op
// BenchmarkConcurrentMap
// BenchmarkConcurrentMap-4 171730 67888 ns/op 0 B/op 0 allocs/op
// PASS
// coverage: 0.0% of statements
// ok demo 23.823s
Copy the code
9. Performance analysis
Function: CPU analysis and memory analysis. Quickly locate code problems and improve code performance by visualizing call links, visualizing fire diagrams, and TOP functions.
- pprof
- trace
- dlv
Use:
9.1 Use of pprof
9.1.1 Benchmark Scenario
- First write benchmark cases and reuse the above
sync.Map
Use cases:
package main
import (
"sync"
"testing"
)
// Stress test sync.map
func BenchmarkSyncMap(b *testing.B) {
demoMap := &sync.Map{}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
demoMap.Store("a"."a")
for i := 0; i < 1000; i++ {
demoMap.Load("a")}}})}Copy the code
- Perform benchmarking and generate
cpu.profile
Files andmem.profile
File. The command is
Go test-run =^ โ benchmarksyncmap-bench ^BenchmarkSyncMap demo – v-count =1 -cpuprofile=cpu.profile -memprofile=mem.profile -benchtime=10s
Common parameters are described as follows:
-benchmem: output memory metrics -run: regular, specifying methods that need to be tested -bench: regular, specifying methods that need to be benchmarked -v: output output results and logs even if successful -count: number of times of execution -cpuprofile: -memprofile: output memory profile. -benchtime: execution time. For more parameters, see go help testflagCopy the code
- use
go tool
The built-in PProf tool analyzes test results. The command is as follows:
go tool pprof -http=:8000 cpu.profile
Common parameters are described as follows:
- HTTP: specify the IP: port, start the web service visual view analysis, the browser will automatically open the page http://localhost:8000/ui/Copy the code
Visual Options menu
Flame figure
Call link diagram
Top function
9.1.2 Web Service Scenario
- Using the above global variables, the code example is introduced
net/http/pprof
Package, and register each port separately to get pprof data.
package main
import (
"net/http"
// Introduce the pprof package
// _ indicates that only init functions within the package are executed
_ "net/http/pprof"
"github.com/gin-gonic/gin"
)
// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0
// Simulate the interface logic
func main(a) {
r := gin.Default()
r.GET("/ping".func(c *gin.Context) {
GlobalVarDemo++
c.JSON(200, gin.H{
"message": GlobalVarDemo,
})
})
// Open another port to retrieve pprof data
go func(a) {
http.ListenAndServe(": 8888".nil)
}()
// Start the Web service
r.Run()
}
Copy the code
- Access the link
http://localhost:8888/debug/pprof/
, you can see the related profiles.
- Using the pprof tool, obtain the remote service profile as follows:
go tool pprof -http=:8000 http://localhost:8888/debug/pprof/profile? seconds=5
Note: the above command, you can use the simulation flow pressure measuring tool, such as command: siege - c 50-100 "http://localhost:8080/ping" tCopy the code
Again, we get this familiar page:
9.2 Using the Trace Tool
Function: Clearly view The execution process of Goroutine in each logical processor, and you can intuitively see The blocking consumption of Goroutine, including network blocking, synchronization blocking (lock), system call blocking, scheduling wait, GC execution time, GC STW(Stop The World) time.
9.2.1 Benchmark Scenario
Use:
To generate a trace.out file: go test -benchmem -run=^$ -bench ^BenchmarkDemo_NoPool$ demo -v -count=1 -trace=trace.out go test -benchmem -run=^$ -bench ^BenchmarkDemo_Pool$bench -v -count=1 -trace=trace.out Go tool trace -http=127.0.0.1:8000 trace.outCopy the code
Don’t use the sync. The Pool
Using the sync. The Pool
9.2.2 Web Service Scenario
Use:
Also introduce the package NET/HTTP /pprof
package main
import (
"net/http"
// Introduce the pprof package
// _ indicates that only init functions within the package are executed
_ "net/http/pprof"
"github.com/gin-gonic/gin"
)
// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0
// Simulate the interface logic
func main(a) {
r := gin.Default()
r.GET("/ping".func(c *gin.Context) {
GlobalVarDemo++
c.JSON(200, gin.H{
"message": GlobalVarDemo,
})
})
// Open another port to retrieve pprof data
go func(a) {
http.ListenAndServe(": 8888".nil)
}()
// Start the Web service
r.Run()
}
Copy the code
Start the service and run the following command:
1. Generate trace. The out file command: curl http://localhost:8888/debug/pprof/trace? . 20 seconds = > trace out and the above command execution at the same time, the simulation of the request, you can also use ab: siege - 50-100 "http://localhost:8080/ping" t 2 c. To analyze the trace.out file, run the go tool trace -http=127.0.0.1:8000 trace.out shortcut key: w zoom in and e move rightCopy the code
9.3 Using the DLV tool
9.3.1 Benchmark Scenario
Function: breakpoint debugging and so on.
Installation:
go install github.com/go-delve/delve/cmd/dlv@latest
Copy the code
Use:
package main
import(_"net/http/pprof"
"github.com/gin-gonic/gin"
)
// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0
// Simulate the interface logic
func main(a) {
r := gin.Default()
r.GET("/ping".func(c *gin.Context) {
GlobalVarDemo++
c.JSON(200, gin.H{
"message": GlobalVarDemo,
})
})
r.Run()
}
Copy the code
Command line execution command:
dlv debug main.go
Enter debugging. Common debugging commands:
- (list or L: output code) : list main.go:16
- (break or B: breakpoint command) : Execute
break main.go:16
To the lineGlobalVarDemo++
Breaking point - (continue or C: to continue) : continue
- (print or p: print variable) :
print GlobalVarDemo
- (step or s: can enter the function) : step
For more commands, run help.
Simulation request: curl http://localhost:8080/pingCopy the code
9.3.2 Web Service Scenario
Again, this demo
package main
import (
"github.com/gin-gonic/gin"
)
// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0
// Simulate the interface logic
func main(a) {
r := gin.Default()
r.GET("/ping".func(c *gin.Context) {
GlobalVarDemo++
c.JSON(200, gin.H{
"message": GlobalVarDemo,
})
})
// Start the Web service
r.Run()
}
Copy the code
- Find the service process ID
lsof -i :8080
- DLV Debugging process
dlv attach 36968
- Enter debug mode and debug the code (same as above)
9.4(extended) Escape analysis
Escape analysis command: go build -gcflags “-m -l” *. Go
package main
type Demo struct{}func main(a) {
DemoFun()
}
func DemoFun(a) *Demo {
demo := &Demo{}
return demo
}
// # command-line-arguments
//./main.go:11:10: &Demo literal escapes to heap <------- Local variable memory is allocated to the heap
Copy the code
9.5(extension) Assembly code
Go run -gcflags -s main.go
# command-line-arguments "".main STEXT nosplit size=1 args=0x0 locals=0x0 0x0000 00000 (... /demo/main.go:6) TEXT "".main(SB), NOSPLIT|ABIInternal, $0-0 0x0000 00000 (... / demo/main. Go: $0, 6) FUNCDATA gclocals ยท 33 cdeccccebe80329f1fdbee7f5874cb (SB) 0 x0000 00000 (... / demo/main. Go: $1, 6) FUNCDATA gclocals ยท 33 cdeccccebe80329f1fdbee7f5874cb (SB) 0 x0000 00000 (... /demo/main.go:6) FUNCDATA $2, Gclocals ยท 33 cdeccccebe80329f1fdbee7f5874cb (SB) 0 x0000 00000 (< unknown line number >) RET 0 x0000 c3 slightly...Copy the code
GOSSAFUNC=main go build main.go
/ssa. HTML <------- This file is generated and opened on the browserCopy the code
conclusion
Finally, let’s summarize the process from PHPer to Gopher, we need to pay attention to the following points:
- Correspondence between PHP and Go commonly used code blocks
- Permanent memory
- Use of global variables
- resources
- reuse
- The release of
- return
- Pointer to the
- concurrent
- Concurrent security
- Concurrency control
- Timeout control
- Unit testing & benchmarking
- Performance analysis
This article is long, it is recommended to click the original article at the end of the article, after the collection to view.
Alternatively, connect the collector PC to ๐ tigerb.cn/php2go/