learn.lianglianglee.com/专栏/22 讲通关 Go 语言-完/19 性能优化:Go 语言如何进行代码检查和优化?.md.html
2022-08-14 03:40:33 +08:00

457 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>19 性能优化Go 语言如何进行代码检查和优化?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/22 讲通关 Go 语言-完/00 开篇词 Go 为开发者的需求设计,带你实现高效工作.md.html">00 开篇词 Go 为开发者的需求设计,带你实现高效工作</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/01 基础入门:编写你的第一个 Go 语言程序.md.html">01 基础入门:编写你的第一个 Go 语言程序</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/02 数据类型:你必须掌握的数据类型有哪些?.md.html">02 数据类型:你必须掌握的数据类型有哪些?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/03 控制结构if、for、switch 逻辑语句的那些事儿.md.html">03 控制结构if、for、switch 逻辑语句的那些事儿</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/04 集合类型:如何正确使用 array、slice 和 map.md.html">04 集合类型:如何正确使用 array、slice 和 map</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/05 函数和方法Go 语言中的函数和方法到底有什么不同?.md.html">05 函数和方法Go 语言中的函数和方法到底有什么不同?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/06 struct 和 interface结构体与接口都实现了哪些功能.md.html">06 struct 和 interface结构体与接口都实现了哪些功能</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/07 错误处理:如何通过 error、deferred、panic 等处理错误?.md.html">07 错误处理:如何通过 error、deferred、panic 等处理错误?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/08 并发基础Goroutines 和 Channels 的声明与使用.md.html">08 并发基础Goroutines 和 Channels 的声明与使用</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/09 同步原语sync 包让你对并发控制得心应手.md.html">09 同步原语sync 包让你对并发控制得心应手</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/10 Context你必须掌握的多线程并发控制神器.md.html">10 Context你必须掌握的多线程并发控制神器</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/11 并发模式Go 语言中即学即用的高效并发模式.md.html">11 并发模式Go 语言中即学即用的高效并发模式</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/12 指针详解:在什么情况下应该使用指针?.md.html">12 指针详解:在什么情况下应该使用指针?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/13 参数传递:值、引用及指针之间的区别?.md.html">13 参数传递:值、引用及指针之间的区别?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/14 内存分配new 还是 make什么情况下该用谁.md.html">14 内存分配new 还是 make什么情况下该用谁</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/15 运行时反射:字符串和结构体之间如何转换?.md.html">15 运行时反射:字符串和结构体之间如何转换?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/16 非类型安全:让你既爱又恨的 unsafe.md.html">16 非类型安全:让你既爱又恨的 unsafe</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/17 SliceHeaderslice 如何高效处理数据?.md.html">17 SliceHeaderslice 如何高效处理数据?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/18 质量保证Go 语言如何通过测试保证质量?.md.html">18 质量保证Go 语言如何通过测试保证质量?</a>
</li>
<li>
<a class="current-tab" href="/专栏/22 讲通关 Go 语言-完/19 性能优化Go 语言如何进行代码检查和优化?.md.html">19 性能优化Go 语言如何进行代码检查和优化?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/20 协作开发:模块化管理为什么能够提升研发效能?.md.html">20 协作开发:模块化管理为什么能够提升研发效能?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/21 网络编程Go 语言如何玩转 RESTful API 服务?.md.html">21 网络编程Go 语言如何玩转 RESTful API 服务?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/22 网络编程Go 语言如何通过 RPC 实现跨平台服务?.md.html">22 网络编程Go 语言如何通过 RPC 实现跨平台服务?</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/23 结束语 你的 Go 语言成长之路.md.html">23 结束语 你的 Go 语言成长之路</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>19 性能优化Go 语言如何进行代码检查和优化?</h1>
<p>在上节课中,我为你留了一个小作业:在运行 go test 命令时,使用 -benchmem 这个 Flag 进行内存统计。该作业的答案比较简单,命令如下所示:</p>
<pre><code>➜ go test -bench=. -benchmem ./ch18
</code></pre>
<p>运行这一命令就可以查看内存统计的结果了。这种通过 -benchmem 查看内存的方法<strong>适用于所有的基准测试用例</strong></p>
<p>今天要讲的内容是 Go 语言的代码检查和优化,下面我们开始本讲内容的讲解。</p>
<p>在项目开发中,<strong>保证代码质量和性能的手段不只有单元测试和基准测试</strong><strong>还有代码规范检查和性能优化</strong></p>
<ul>
<li><strong>代码规范检查</strong>是对单元测试的一种补充,它可以从非业务的层面检查你的代码是否还有优化的空间,比如变量是否被使用、是否是死代码等等。</li>
<li><strong>性能优化</strong>是通过基准测试来衡量的,这样我们才知道优化部分是否真的提升了程序的性能。</li>
</ul>
<h3>代码规范检查</h3>
<h4>什么是代码规范检查</h4>
<p>代码规范检查,顾名思义,是从 Go 语言层面出发,依据 Go 语言的规范,对你写的代码进行的<strong>静态扫描检查</strong>,这种检查和你的业务无关。</p>
<p>比如你定义了个常量,从未使用过,虽然对代码运行并没有造成什么影响,但是这个常量是可以删除的,代码如下所示:</p>
<p><em>ch19/main.go</em></p>
<pre><code>const name = &quot;飞雪无情&quot;
func main() {
}
</code></pre>
<p>示例中的常量 name 其实并没有使用,所以为了节省内存你可以删除它,这种<strong>未使用常量</strong>的情况就可以通过代码规范检查检测出来。</p>
<p>再比如,你调用了一个函数,该函数返回了一个 error但是你并没有对该 error 做判断,这种情况下,程序也可以正常编译运行。但是代码写得不严谨,因为返回的 error 被我们忽略了。代码如下所示:</p>
<p><em>ch19/main.go</em></p>
<pre><code>func main() {
os.Mkdir(&quot;tmp&quot;,0666)
}
</code></pre>
<p>示例代码中Mkdir 函数是有返回 error 的,但是你并没有对返回的 error 做判断,这种情况下,哪怕创建目录失败,你也不知道,因为错误被你忽略了。如果你使用代码规范检查,这类潜在的问题也会被检测出来。</p>
<p>以上两个例子可以帮你理解什么是代码规范检查、它有什么用。除了这两种情况,还有拼写问题、死代码、代码简化检测、命名中带下划线、冗余代码等,都可以使用代码规范检查检测出来。</p>
<h4>golangci-lint</h4>
<p>要想对代码进行检查,则需要对代码进行扫描,静态分析写的代码是否存在规范问题。</p>
<blockquote>
<p>小提示:静态代码分析是不会运行代码的。</p>
</blockquote>
<p>可用于 Go 语言代码分析的工具有很多,比如 golint、gofmt、misspell 等,如果一一引用配置,就会比较烦琐,所以通常我们不会单独地使用它们,而是使用 golangci-lint。</p>
<p><a href="https://github.com/golangci/golangci-lint">golangci-lint</a> 是一个<strong>集成工具</strong>,它集成了很多静态代码分析工具,便于我们使用。通过配置这一工具,我们可以很灵活地启用需要的代码规范检查。</p>
<p>如果要使用 golangci-lint首先需要安装。因为 golangci-lint 本身就是 Go 语言编写的,所以我们可以从源代码安装它,打开终端,输入如下命令即可安装。</p>
<pre><code>➜ go get github.com/golangci/golangci-lint/cmd/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="70171f1c111e1713195d1c191e043006415e43425e42">[email&#160;protected]</a>
</code></pre>
<p>使用这一命令安装的是 v1.32.2 版本的 golangci-lint安装完成后在终端输入如下命令检测是否安装成功。</p>
<pre><code>➜ golangci-lint version
golangci-lint has version v1.32.2
</code></pre>
<blockquote>
<p>小提示:在 MacOS 下也可以使用 brew 来安装 golangci-lint。</p>
</blockquote>
<p>好了,安装成功 golangci-lint 后,就可以使用它进行代码检查了,我以上面示例中的常量 name 和 Mkdir 函数为例,演示 golangci-lint 的使用。在终端输入如下命令回车:</p>
<pre><code>➜ golangci-lint run ch19/
</code></pre>
<p>这一示例表示要检测目录中 ch19 下的代码,运行后可以看到如下输出结果。</p>
<pre><code>ch19/main.go:5:7: `name` is unused (deadcode)
const name = &quot;飞雪无情&quot;
^
ch19/main.go:8:10: Error return value of `os.Mkdir` is not checked (errcheck)
os.Mkdir(&quot;tmp&quot;,0666)
</code></pre>
<p>通过代码检测结果可以看到,我上一小节提到的两个代码规范问题都被检测出来了。检测出问题后,你就可以修复它们,让代码更加符合规范。</p>
<h4>golangci-lint 配置</h4>
<p>golangci-lint 的配置比较灵活,比如你可以自定义要启用哪些 linter。golangci-lint 默认启用的 linter包括这些</p>
<pre><code>deadcode - 死代码检查
errcheck - 返回错误是否使用检查
gosimple - 检查代码是否可以简化
govet - 代码可疑检查,比如格式化字符串和类型不一致
ineffassign - 检查是否有未使用的代码
staticcheck - 静态分析检查
structcheck - 查找未使用的结构体字段
typecheck - 类型检查
unused - 未使用代码检查
varcheck - 未使用的全局变量和常量检查
</code></pre>
<blockquote>
<p>小提示golangci-lint 支持的更多 linter可以在终端中输入 golangci-lint linters 命令查看,并且可以看到每个 linter 的说明。</p>
</blockquote>
<p>如果要修改默认启用的 linter就需要对 golangci-lint 进行配置。即在项目根目录下新建一个名字为 .golangci.yml 的文件,这就是 golangci-lint 的配置文件。在运行代码规范检查的时候golangci-lint 会自动使用它。假设我只启用 unused 检查,可以这样配置:</p>
<p><em>.golangci.yml</em></p>
<pre><code>linters:
disable-all: true
enable:
- unused
</code></pre>
<p>在团队多人协作开发中,有一个固定的 golangci-lint 版本是非常重要的,这样大家就可以<strong>基于同样的标准检查代码</strong>。要配置 golangci-lint 使用的版本也比较简单,在配置文件中添加如下代码即可:</p>
<pre><code>service:
golangci-lint-version: 1.32.2 # use the fixed version to not introduce new linters unexpectedly
</code></pre>
<p>此外,你还可以针对每个启用的 linter 进行配置,比如要设置拼写检测的语言为 US可以使用如下代码设置</p>
<pre><code>linters-settings:
misspell:
locale: US
</code></pre>
<p>golangci-lint 的配置比较多,你自己可以灵活配置。关于 golangci-lint 的更多配置可以参考<a href="https://golangci-lint.run/usage/configuration/">官方文档</a>,这里我给出一个常用的配置,代码如下:</p>
<p><em>.golangci.yml</em></p>
<pre><code>linters-settings:
golint:
min-confidence: 0
misspell:
locale: US
linters:
disable-all: true
enable:
- typecheck
- goimports
- misspell
- govet
- golint
- ineffassign
- gosimple
- deadcode
- structcheck
- unused
- errcheck
service:
golangci-lint-version: 1.32.2 # use the fixed version to not introduce new linters unexpectedly
</code></pre>
<h4>集成 golangci-lint 到 CI</h4>
<p><strong>代码检查一定要集成到 CI 流程中</strong>效果才会更好这样开发者提交代码的时候CI 就会自动检查代码,及时发现问题并进行修正。</p>
<p>不管你是使用 Jenkins还是 Gitlab CI或者 Github Action都可以通过<strong>Makefile</strong>的方式运行 golangci-lint。现在我在项目根目录下创建一个 Makefile 文件,并添加如下代码:</p>
<p><em>Makefile</em></p>
<pre><code>getdeps:
@mkdir -p ${GOPATH}/bin
@which golangci-lint 1&gt;/dev/null || (echo &quot;Installing golangci-lint&quot; &amp;&amp; go get github.com/golangci/golangci-lint/cmd/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c1a6aeada0afa6a2a8ecada8afb581b7f0eff2f3eff3">[email&#160;protected]</a>)
lint:
@echo &quot;Running <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="664226">[email&#160;protected]</a> check&quot;
@GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
verifiers: getdeps lint
</code></pre>
<blockquote>
<p>小提示:关于 Makefile 的知识可以网上搜索学习一下,比较简单,这里不再进行讲述。</p>
</blockquote>
<p>好了,现在你就可以把如下命令添加到你的 CI 中了,它可以帮你自动安装 golangci-lint并检查你的代码。</p>
<pre><code>make verifiers
</code></pre>
<h3>性能优化</h3>
<p>性能优化的目的是让程序更好、更快地运行,但是它不是必要的,这一点一定要记住。所以在程序开始的时候,你不必刻意追求性能优化,先大胆地写你的代码就好了,<strong>写正确的代码是性能优化的前提</strong>
<img src="assets/CgpVE1_sUrmATX3ZAAU9yqs6HjM626.png" alt="19金句.png" /></p>
<h4>堆分配还是栈</h4>
<p>在比较古老的 C 语言中,内存分配是手动申请的,内存释放也需要手动完成。</p>
<ul>
<li>手动控制有一个很大的<strong>好处</strong>就是你需要多少就申请多少,可以最大化地<strong>利用内存</strong></li>
<li>但是这种方式也有一个明显的<strong>缺点</strong>,就是如果忘记释放内存,就会导致<strong>内存泄漏</strong></li>
</ul>
<p>所以为了让程序员更好地专注于业务代码的实现Go 语言增加了垃圾回收机制,自动地回收不再使用的内存。</p>
<p>Go 语言有两部分内存空间:<strong>栈内存</strong><strong>堆内存</strong></p>
<ul>
<li><strong>栈内存</strong>由编译器自动分配和释放,开发者无法控制。栈内存一般存储函数中的局部变量、参数等,函数创建的时候,这些内存会被自动创建;函数返回的时候,这些内存会被自动释放。</li>
<li><strong>堆内存</strong>的生命周期比栈内存要长,如果函数返回的值还会在其他地方使用,那么这个值就会被编译器自动分配到堆上。堆内存相比栈内存来说,不能自动被编译器释放,只能通过垃圾回收器才能释放,所以栈内存效率会很高。</li>
</ul>
<h4>逃逸分析</h4>
<p>既然栈内存的效率更高,肯定是优先使用栈内存。那么 Go 语言是如何判断一个变量应该分配到堆上还是栈上的呢?这就需要<strong>逃逸分析</strong>了。下面我通过一个示例来讲解逃逸分析,代码如下:</p>
<p><em>ch19/main.go</em></p>
<pre><code>func newString() *string{
s:=new(string)
*s = &quot;飞雪无情&quot;
return s
}
</code></pre>
<p>在这个示例中:</p>
<ul>
<li>通过 new 函数申请了一块内存;</li>
<li>然后把它赋值给了指针变量 s</li>
<li>最后通过 return 关键字返回。</li>
</ul>
<blockquote>
<p>小提示:以上 newString 函数是没有意义的,这里只是为了方便演示。</p>
</blockquote>
<p>现在我通过逃逸分析来看下是否发生了逃逸,命令如下:</p>
<pre><code>➜ go build -gcflags=&quot;-m -l&quot; ./ch19/main.go
# command-line-arguments
ch19/main.go:16:8: new(string) escapes to heap
</code></pre>
<p>在这一命令中,-m 表示打印出逃逸分析信息,-l 表示禁止内联,可以更好地观察逃逸。从以上输出结果可以看到,发生了逃逸,<strong>也就是说指针作为函数返回值的时候</strong><strong>一定会发生逃逸</strong></p>
<p>逃逸到堆内存的变量不能马上被回收,只能通过垃圾回收标记清除,增加了垃圾回收的压力,所以要尽可能地避免逃逸,让变量分配在栈内存上,这样函数返回时就可以回收资源,提升效率。</p>
<p>下面我对 newString 函数进行了避免逃逸的优化,优化后的函数代码如下:</p>
<p><em>ch19/main.go</em></p>
<pre><code>func newString() string{
s:=new(string)
*s = &quot;飞雪无情&quot;
return *s
}
</code></pre>
<p>再次通过命令查看以上代码的逃逸分析,命令如下:</p>
<pre><code>➜ go build -gcflags=&quot;-m -l&quot; ./ch19/main.go
# command-line-arguments
ch19/main.go:14:8: new(string) does not escape
</code></pre>
<p>通过分析结果可以看到,虽然还是声明了指针变量 s但是函数返回的并不是指针所以没有发生逃逸。</p>
<p>这就是关于指针作为函数返回逃逸的例子,那么是不是不使用指针就不会发生逃逸了呢?下面看个例子,代码如下:</p>
<pre><code>fmt.Println(&quot;飞雪无情&quot;)
</code></pre>
<p>同样运行逃逸分析,你会看到如下结果:</p>
<pre><code>➜ go build -gcflags=&quot;-m -l&quot; ./ch19/main.go
# command-line-arguments
ch19/main.go:13:13: ... argument does not escape
ch19/main.go:13:14: &quot;飞雪无情&quot; escapes to heap
ch19/main.go:17:8: new(string) does not escape
</code></pre>
<p>观察这一结果,你会发现「飞雪无情」这个字符串逃逸到了堆上,这是因为「飞雪无情」这个字符串被已经逃逸的指针变量引用,所以它也跟着逃逸了,引用代码如下:</p>
<pre><code>func (p *pp) printArg(arg interface{}, verb rune) {
p.arg = arg
//省略其他无关代码
}
</code></pre>
<p>所以<strong>被已经逃逸的指针引用的变量也会发生逃逸</strong></p>
<p>Go 语言中有 3 个比较特殊的类型,它们是 slice、map 和 chan被这三种类型引用的指针也会发生逃逸看个这样的例子</p>
<p><em>ch19/main.go</em></p>
<pre><code>func main() {
m:=map[int]*string{}
s:=&quot;飞雪无情&quot;
m[0] = &amp;s
}
</code></pre>
<p>同样运行逃逸分析,你看到的结果是:</p>
<pre><code>➜ gotour go build -gcflags=&quot;-m -l&quot; ./ch19/main.go
# command-line-arguments
ch19/main.go:16:2: moved to heap: s
ch19/main.go:15:20: map[int]*string literal does not escape
</code></pre>
<p>从这一结果可以看到,变量 m 没有逃逸,反而被变量 m 引用的变量 s 逃逸到了堆上。<strong>所以被map</strong><strong>slice 和 chan 这三种类型引用的指针一定会发生逃逸的</strong></p>
<p>逃逸分析是判断变量是分配在堆上还是栈上的一种方法,在实际的项目中要尽可能避免逃逸,这样就不会被 GC 拖慢速度,从而提升效率。</p>
<blockquote>
<p>小技巧:从逃逸分析来看,指针虽然可以减少内存的拷贝,但它同样会引起逃逸,所以要根据实际情况选择是否使用指针。</p>
</blockquote>
<h4>优化技巧</h4>
<p>通过前面小节的介绍,相信你已经了解了栈内存和堆内存,以及变量什么时候会逃逸,那么在优化的时候思路就比较清晰了,因为都是基于以上原理进行的。下面我总结几个优化的小技巧:</p>
<p><strong>第 1 个</strong>需要介绍的技巧是尽可能避免逃逸,因为栈内存效率更高,还不用 GC。比如小对象的传参array 要比 slice 效果好。</p>
<p>如果避免不了逃逸,还是在堆上分配了内存,那么对于频繁的内存申请操作,我们要学会重用内存,比如使用 sync.Pool这是<strong>第 2 个</strong>技巧。</p>
<p><strong>第 3 个</strong>技巧就是选用合适的算法,达到高性能的目的,比如空间换时间。</p>
<blockquote>
<p>小提示:性能优化的时候,要结合基准测试,来验证自己的优化是否有提升。</p>
</blockquote>
<p>以上是基于 GO 语言的内存管理机制总结出的 3 个方向的技巧,基于这 3 个大方向基本上可以优化出你想要的效果。除此之外,还有一些小技巧,比如要尽可能避免使用锁、并发加锁的范围要尽可能小、使用 StringBuilder 做 string 和 [ ] byte 之间的转换、defer 嵌套不要太多等等。</p>
<p>最后推荐一个 Go 语言自带的性能剖析的工具 pprof通过它你可以查看 CPU 分析、内存分析、阻塞分析、互斥锁分析,它的使用不是太复杂,你可以搜索下它的使用教程,这里就不展开介绍。</p>
<h3>总结</h3>
<p>这节课主要介绍了代码规范检查和性能优化两部分内容,其中代码规范检查是从工具使用的角度讲解,而性能优化可能涉及的点太多,所以是从原理的角度讲解,你明白了原理,就能更好地优化你的代码。</p>
<p>我认为是否进行性能优化取决于两点:<strong>业务需求和自我驱动</strong>。所以不要刻意地去做性能优化,尤其是不要提前做,先保证代码正确并上线,然后再根据业务需要,决定是否进行优化以及花多少时间优化。自我驱动其实是一种编码能力的体现,比如有经验的开发者在编码的时候,潜意识地就避免了逃逸,减少了内存拷贝,在高并发的场景中设计了低延迟的架构。</p>
<p>最后给你留个作业,把 golangci-lint 引入自己的项目吧,相信你的付出会有回报的。</p>
<p>下一讲我将介绍“协作开发:模块化管理为什么能够提升研发效能”,记得来听课!</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/22 讲通关 Go 语言-完/18 质量保证Go 语言如何通过测试保证质量?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/22 讲通关 Go 语言-完/20 协作开发:模块化管理为什么能够提升研发效能?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996dcfd8f33d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>