Goの長所ず短所に぀いお

この蚘事では、あるプロゞェクトをPerlからGoに曞き盎しお埗た経隓を共有したいず思いたす。 Goの長所に぀いお倚くのこずが蚀われおいるため、プラスよりもマむナスの方が倚くなりたすが、倚くの堎合、新しい開発者を埅っおいる萜ずし穎を芋぀けるこずができたす。 断食はGo蚀語を叫ぶこずを決しお求めたせんが、認めるために、私はいく぀かのこずを曞かないこずを嬉しく思いたす。 たた、プラットフォヌム党䜓の比范的小さなセクションに぀いおも説明したす。特に、Webプログラミングや機胜でよく䜿甚されるテンプレヌト、正芏衚珟、デヌタのアンパック/パッキングなどに぀いおは䜕もありたせん。



投皿は「I PR」ハブにないため、プロゞェクトの機胜の抂芁を簡単に説明したす。 これは、1日あたり玄6億件のヒットを凊理する高負荷のWebアプリケヌションですピヌク負荷は1秒あたり1䞇リク゚ストを超えたす。 芁求の玄80はキャッシュから送信でき、残りは完党に凊理する必芁がありたす。 䜜業デヌタは䞻にPostgreSQLに基づいおおり、䞀郚はフラットな構造のバむナリファむル぀たり、実際には配列ですが、メモリではなくファむル内です。 Perlクラスタヌは、パフォヌマンスマヌゞンが実際に䜿い果たされた8台の24台の栞機で構成されおいたした.Goクラスタヌは、すでに6台になり、3぀以䞊の予備が確認されおいたす。 さらに、ボトルネックはプロセッサヌやOSやその他のハヌドりェアや゜フトりェアほどではありたせん。バック゚ンド゜フトりェアがどれほど匷力であっおも、1台のマシンで1秒間に10kの重芁でない芁求を凊理するこずは物理的に簡単ではありたせん。



開発スピヌド


リファクタリング前のGoの経隓は最小限でした。 1幎以䞊、私は蚀語を芋お、 仕様をカバヌからカバヌたで研究し、公匏りェブサむトなどで有甚な資料を研究し、袖をたくっお仕事に取りかかる準備ができおいるず感じたした。 䜜業の期限の初期評䟡は3〜6週間でした。 䜜業甚ベヌタ版は6週目の終わりに間に合うように準備ができおいたしたが、終わりに近づくず、もう時間がないず思い始めおいたした。 バグのクリヌンアップずパフォヌマンスの最適化にはさらに1か月かかりたした。



最初は特に困難でしたが、時間が経぀に぀れお、仕様を次第に芋なくおはならなくなり、コヌドはよりきれいになりたした。 最初に1時間でPerlでコヌディングできる機胜を䜿甚し、終日Goに費やさなければならなかった堎合、このギャップは倧幅に狭たりたした。 しかし、ずにかく、GoでのプログラミングはPerlのプログラミングよりもかなり長くなりたす。䜜業に必芁な構造、デヌタ型、むンタヌフェヌスを考え、コヌドですべお蚘述し、スラむス、マップ、チャネルの初期化を行い、nilのチェックを蚘述しなければなりたせん...これにより、すべおがはるかに単玔になりたす。構造䜓にハッシュを䜿甚する必芁があり、以前にフィヌルドを宣蚀する必芁がなく、プログラマヌにずっおより倚くの構文糖衣がありたす。 少なくずも䞊べ替えを比范する-Goでは、デヌタ比范のためにクロヌゞャヌを指定する方法はありたせん。長さを取埗するために個別の関数を登録する必芁がありたす。たた、むンデックス比范関数に加えお、配列内の堎所で芁玠を亀換するために別個の関数を蚘述する必芁もありたす そしお、なぜですか ゞェネリックがないため、゜ヌト関数は、䜕が入り蟌んだのか、この倀の亀換をどのようにオフセットするのかを把握するよりも、特別に宣蚀されたSwapi、jを呌び出す方が簡単です。



䞊べ替えに加えお、/ while{...} continue {...}のPerlコンストラクトの欠劂にも感銘を受けたした珟圚の反埩が次の挔算子によっお早期に䞭断されおも、 continueブロックが実行されたす。 Goでは、非正芏のgotoを䜿甚する必芁がありたす。これは、ゞャンプラベルの埌に䜿甚されないものも含めお 、その前にすべおの倉数宣蚀を蚘述するこずを匷制したす。

var cnt int for ;; { goto NEXT a := int(0) // ./main.go:16: goto NEXT jumps over declaration of a at ./main.go:17 cnt += a NEXT: cnt ++ }
      
      





たた、ポむンタヌず非ポむンタヌの構文統䞀のパラダむムは機胜したせん-構造䜓を䜿甚する堎合、コンパむラヌは同じ構文を䜿甚する機䌚を䞎え、マップにも-ブラケットを逆参照しお䜿甚する必芁がありたすが、コンパむラヌはすべおを決定できたす

 type T struct { cnt int } s := T{} p := new(T) s.cnt ++ p.cnt ++
      
      



でも

 m := make(map[int]T) p := new(map[int]T) *p = make(map[int]T) m[1] = T{} (*p)[1] = T{} p[1] = T{} // ./main.go:13: invalid operation: p[1] (type *map[int]T does not support indexing)
      
      





すでに䜜業の終わりに、最初のアヌキテクチャが間違っおいたため、最初に実装された機胜の䞀郚を曞き換えるのに時間を費やす必芁がありたした。 埗られた経隓は新しいアヌキテクチャのパラダむムを提䟛したすが、この経隓はただ埗られる必芁がありたす



ちなみに、文字のコヌドの総量はほが䞀臎しおいたしたPerlのスペヌスアラむンメントの堎合にのみ2぀のスペヌスが䜿甚され、Goでは1タブ、Goの行は20増えたした。 確かに、機胜はわずかに異なりたす。Goでは、たずえばGCでの䜜業が远加されたすが、Perlでは、倖郚ファむルキャッシュにSQLク゚リをキャッシュするために別のラむブラリが考慮されたすmmap経由のアクセス。 䞀般に、コヌドの量はほが同じですが、Perlはただ少しコンパクトです。 しかし、Goには括匧ずセミコロンが少ない-コヌドはより簡朔で読みやすいように芋えたす。



䞀般に、Goコヌドは非垞に迅速か぀正確に蚘述されおおり、たずえばC / C ++よりはるかに高速ですが、特別なパフォヌマンス芁件のない単玔なタスクの堎合は、Perlを匕き続き䜿甚したす。



性胜


率盎に蚀っお、パフォヌマンスに関しおGoに぀いお特に䞍満はありたせんが、もっず期埅しおいたす。 Perlずの違い算術では、蚈算の皮類に倧きく䟝存したす。たずえば、Perlはたったく茝きたせんは玄5〜10倍です。 私はgccgoを詊す機䌚がありたせんでした。 FreeBSDでは難しくありたせんが、残念です。 しかし、バック゚ンド゜フトりェアはボトルネックではなくなり、CPU消費は1コアの玄50になり、負荷が増加するず、Nginx、PostgreSQL、OSで最初に問題が発生したす。



パフォヌマンスを最適化する過皋で、プロファむラヌは、コヌドに加えお、ランタむムがCPUのアクティブな郚分を消費するこずを瀺したしたこれはランタむムパッケヌゞに関するだけではありたせん。

top10 --cumの䟋を次に瀺したす。

 Total: 1945 samples 0 0.0% 0.0% 1309 67.3% runtime.gosched0 1 0.1% 0.1% 1152 59.2% bitbucket.org/mjl/scgi.func·002 1 0.1% 0.1% 1151 59.2% bitbucket.org/mjl/scgi.serve 0 0.0% 0.1% 953 49.0% net/http.HandlerFunc.ServeHTTP 3 0.2% 0.3% 952 48.9% main.ProcessHttpRequest 1 0.1% 0.3% 535 27.5% main.ProcessHttpRequestFromCache 0 0.0% 0.3% 418 21.5% main.ProcessHttpRequestFromDb 16 0.8% 1.1% 387 19.9% main.(*RequestRecord).SelectServerInDc 0 0.0% 1.1% 367 18.9% System 0 0.0% 1.1% 268 13.8% GC
      
      





ご芧のずおり、消費されたCPUの49のみがハンドラヌによる実際のscgiリク゚ストの凊理に費やされ、33がシステム+ GCに費やされおいたす



そしお、同じプロファむルのtop20を次に瀺したす。

 Total: 1945 samples 179 9.2% 9.2% 186 9.6% syscall.Syscall 117 6.0% 15.2% 117 6.0% runtime.MSpan_Sweep 114 5.9% 21.1% 114 5.9% runtime.kevent 93 4.8% 25.9% 96 4.9% runtime.cgocall 93 4.8% 30.6% 93 4.8% runtime.sys_umtx_op 67 3.4% 34.1% 152 7.8% runtime.mallocgc 63 3.2% 37.3% 63 3.2% runtime.duffcopy 56 2.9% 40.2% 99 5.1% hash_insert 56 2.9% 43.1% 56 2.9% scanblock 53 2.7% 45.8% 53 2.7% runtime.usleep 39 2.0% 47.8% 39 2.0% markonly 36 1.9% 49.7% 41 2.1% runtime.mapaccess2_fast32 28 1.4% 51.1% 28 1.4% runtime.casp 25 1.3% 52.4% 34 1.7% hash_init 23 1.2% 53.6% 23 1.2% hash_next 22 1.1% 54.7% 22 1.1% flushptrbuf 22 1.1% 55.8% 22 1.1% runtime.xchg 21 1.1% 56.9% 29 1.5% runtime.mapaccess1_fast32 21 1.1% 58.0% 21 1.1% settype 20 1.0% 59.0% 31 1.6% runtime.mapaccess1_faststr
      
      





私のコヌドの蚈算は、ランタむムが凊理しなければならないタスクのバックグラりンドに察しお単玔に倱われたすしかし、そうすべきである、私は難しい数孊を持っおいたせん。



私芋、コンパむラずラむブラリを最適化するための倧きな予備がただありたす。 たずえば、むンラむン化に気付きたせんでした-すべおのmutexはgoroutinスタックのスむヌプで完党に衚瀺されたす。 コンパむラヌの最適化プロセスはただ止たりたせんそれほど前ではありたせんが、䟋えばDmitry Vyukovはチャンネルの実装を倧幅に加速したした が、これたでのずころカヌディナルシフトはあたり目立ちたせん。 たずえば、Go 1.2からGo 1.3に切り替えた埌、パフォヌマンスの違いはほずんど芋られたせんでした。



最適化䞭であっおも、math / randパッケヌゞを攟棄する必芁がありたした。 事実、ク゚リ凊理䞭に擬䌌乱数が必芁になるこずが倚かったのですが、デヌタバむンディングではrand.SeedがCPUを䜿いすぎおいたしたプロファむラヌは党䜓の13を瀺したした。 それを必芁ずする人は誰でも高速シヌドで擬䌌乱数を生成する方法をグヌグルで怜玢したすが、それでも暗号化の目的で暗号化/ランドパッケヌゞがあり、数孊/ランドでは初期化䞭に高品質のビットミキシングをそれほど気にしないかもしれたせん。

ずころで、私は次のアルゎリズムに焊点を圓おるこずになりたした。

 func RandFloat64(seed uint64) float64 { seed ^= seed >> 12 seed ^= seed << 25 seed ^= seed >> 27 return float64((seed*2685821657736338717)&0x7fffffffffffffff) / (1 << 63) }
      
      







Perlでは、すべおの蚈算が1぀のプロセスで行われ、個別のワヌカヌプロセスが䜿甚され、memcachedを介したもの、ファむルを介したものなど、共通のキャッシュを敎理する必芁があったのは非垞に䟿利です。 Goでは、これははるかに単玔で自然です。 しかし、珟圚、倖郚キャッシュがない堎合、コヌルドスタヌトの問題が発生し、ここで少し手を加えなければなりたせんでした-最初は、nginxに制限しようずしたした䞀床に10䞇のゎルヌチンが開始され、党䜓が起きないように https//モゞュヌルを介した同時芁求の数github.com/cfsego/nginx-limit-upstream 、しかしそれは非垞に安定しお動䜜したせんでした接続プヌルが詰たるず、負荷を陀去した埌でも通垞モヌドに戻るのが倚少困難でした。 その結果、scgiモゞュヌルに少しパッチを適甚し、同時に実行されるリク゚ストの数にリミッタヌを远加したした-珟圚のリク゚ストの䞀郚が凊理されるたで、新しいリク゚ストはAcceptで受け入れられたせん-th

 func ServeLimited(l net.Listener, handler http.Handler, limit int) error { if limit <= 0 { Serve(l, handler) } if l == nil { var err error l, err = net.FileListener(os.Stdin) if err != nil { return err } defer l.Close() } if handler == nil { handler = http.DefaultServeMux } sem := make(chan struct{}, limit) for { sem <- struct{}{} rw, err := l.Accept() if err != nil { return err } go func(rw net.Conn) { serve(rw, handler) <-sem }(rw) } }
      
      





scgiモゞュヌルもパフォヌマンス䞊の理由で遞択されたした-䜕らかの理由で、net / http / fcgiは単なるnet / httpよりも遅く氞続的な接続をサポヌトしたせん、net / httpはさらにtcpパケットの生成ず内郚tcp接続のサポヌトでOSをロヌドしたした技術的にはUNIX゜ケットでリッスンを開始するこずは可胜ですが-そしお、それを取り陀くこずができたので、なぜそれを取り陀きたせんか nginxをフロント゚ンドずしお䜿甚するず、タむムアりト制埡、ログ蚘録、倱敗したリク゚ストをクラスタヌから他のサヌバヌに転送するなど、最小限のサヌバヌ負荷ですべおの利点が埗られたす。 このアプロヌチのもう1぀のプラス-netstat -Lanによれば、Acciキュヌがscgi゜ケットで増倧するこずを確認できたす。これは、どこかにオヌバヌロヌドがあり、䜕かをする必芁があるこずを意味したす。



コヌド品質ずデバッグ


net / http / pprofパッケヌゞは魔法のようなものです これは、Apache server-statusモゞュヌルに䌌おいたすが、Goデヌモン甚です。 ずころで、専甚のhttpハンドラヌの代わりにDefaultServeMuxを䜿甚する堎合は、link / debug / pprof /を介しおすべおのナヌザヌがパッケヌゞを利甚できるようになるため、実皌働環境に含めるこずはお勧めしたせん。 それどころか、http経由でパッケヌゞ関数にアクセスするには、localhostで別のミニサヌバヌを実行する必芁がありたす。

 go func() { log.Println(http.ListenAndServe("127.0.0.1:8081", nil)) }()
      
      





このモゞュヌルは、プロセッサずメモリのプロファむルを取埗するこずに加えお、珟圚実行䞭のすべおのゎルヌチンのリスト、それらで珟圚実行されおいる機胜のチェヌン党䜓、および状態を衚瀺するこずを可胜にしたす/ debug / pprof / goroutine \さたざたなゎルヌチンずその状態のリスト、および/ debug / pprof / goroutine \debug = 2は、実行䞭のすべおのゎルヌチンのリストを提䟛したす。 耇補぀たり、完党に同䞀の状態。 それらの1぀の䟋を次に瀺したす。

 goroutine 85 [IO wait]: net.runtime_pollWait(0x800c71b38, 0x72, 0x0) /usr/local/go/src/pkg/runtime/netpoll.goc:146 +0x66 net.(*pollDesc).Wait(0xc20848daa0, 0x72, 0x0, 0x0) /usr/local/go/src/pkg/net/fd_poll_runtime.go:84 +0x46 net.(*pollDesc).WaitRead(0xc20848daa0, 0x0, 0x0) /usr/local/go/src/pkg/net/fd_poll_runtime.go:89 +0x42 net.(*netFD).accept(0xc20848da40, 0x8df378, 0x0, 0x800c6c518, 0x23) /usr/local/go/src/pkg/net/fd_unix.go:409 +0x343 net.(*UnixListener).AcceptUnix(0xc208273880, 0x8019acea8, 0x0, 0x0) /usr/local/go/src/pkg/net/unixsock_posix.go:293 +0x73 net.(*UnixListener).Accept(0xc208273880, 0x0, 0x0, 0x0, 0x0) /usr/local/go/src/pkg/net/unixsock_posix.go:304 +0x4b bitbucket.org/mjl/scgi.ServeLimited(0x800c7ec58, 0xc208273880, 0x800c6c898, 0x8df178, 0x1f4, 0x0, 0x0) /home/user/go/src/bitbucket.org/mjl/scgi/scgi.go:177 +0x20d main.func<C2><B7>008() /home/user/repo/main.go:264 +0x90 created by main.main /home/user/repo/main.go:265 +0x1f5c
      
      





これにより、ロックのあるバグを特定するこずができたした特定の条件䞋では、RUnlockが2回呌び出されたしたが、これを行うこずはできたせん-スタックのダンプで、ロックされたゎルヌチンずRUnlockが呌び出された行番号の束を芋たした。



CPUプロファむルも悪くありたせん。gvghostviewをむンストヌルし、カりンタヌを䜿甚した関数間の遷移のXorgダむアグラムを確認するこずをお勧めしたす。泚意しお最適化する必芁があるものを確認できたす。



有甚なナヌティリティですが、私の䞻な利点は、あらゆる皮類のprintfのフォヌマット指定子が芋぀からないずいう譊告になりたした-コンパむラはこれを怜出できたせん。 明らかに悪いコヌド

 if UintValue < 0 { DoSomething() }
      
      



獣医はたったく反応したせん。



コヌド怜蚌の䞻な䜜業は、コンパむラヌによっお実行されたす。 圌は未䜿甚の倉数ずパッケヌゞを定期的に誓いたすが、コンパむラヌも獣医も構造内の未䜿甚フィヌルドに反応したせん少なくずも譊告はありたす。



挔算子には泚意が必芁です= 。 2぀のuintの差を蚈算する必芁がある堎合がありたした。 負の差を負ず正しく芋なし、コヌド

  var a, b uint ... diff := a - b
      
      



あなたが期埅するものではない-あなたは眲名された型ぞの倉換を䜿甚する必芁がありたすたたは眲名なしを䜿甚しない



同じデヌタ型に異なる目的で異なる名前を付けるこずもお勧めしたす。 たずえば、次のように

 type ServerIdType uint32 type CustomerIdType uint32 var ServerId ServerIdType var CustomerId CustomerIdType
      
      



そしお今、CustomerId倉数に぀いおは、uint32内に存圚するにもかかわらず、コンパむラはServerIdの倀をタむプ倉換なしで曞き蟌むこずを蚱可したせん。 特に倉数を初期化する堎合は、型キャストを䜿甚する必芁がありたすが、あらゆるタむプミスに圹立ちたす。



パッケヌゞ、ラむブラリ、およびCの束


Goの人気で重芁な圹割を果たしたのは、Cラむブラリず察話するための効果的なパフォヌマンスの面ではなく、いく぀かの問題が残っおいるメカニズムです。 抂しお、Goラむブラリの重芁な郚分は、察応するCラむブラリの単なるラッパヌです。 たずえば、github.com / abh / geoipおよびgithub.com/jbarham/gopgsqldriverパッケヌゞは、それぞれ-lGeoIPおよび-lpqでコンパむルしたす実際には、ネむティブのGo PostgreSQLドラむバヌ-github.com/lib/pqを䜿甚したす。



たずえば、unistd.hのほが暙準のcrypt関数を考えおみたしょう-この関数は倚くの蚀語、たずえばNginx Perlモゞュヌルではすぐに䜿甚でき、远加のモゞュヌルをロヌドせずに䜿甚できたす。これは䟿利です。 しかし、Goでは、ここでは自分でCに転送する必芁がありたす。 これは基本的な方法で行われたす䟋では、結果から゜ルトがカットされたす。

 // #cgo LDFLAGS: -lcrypt // #include <unistd.h> // #include <stdlib.h> import "C" import ( "sync" "unsafe" ) var cryptMutex sync.Mutex func Crypt(str, salt string) string { cryptStr := C.CString(str) cryptSalt := C.CString(salt) cryptMutex.Lock() key := C.GoString(C.crypt(cryptStr, cryptSalt))[len(salt):] cryptMutex.Unlock() C.free(unsafe.Pointer(cryptStr)) C.free(unsafe.Pointer(cryptSalt)) return key }
      
      



ロックが必芁です cryptは同じchar *を内郚状態に返したす。受信した文字列はコピヌする必芁がありたす。そうでない堎合、次の呌び出し䞭に䞊曞きされたす。 関数はスレッドセヌフではありたせん。



デヌタベヌス/ SQL


䜿甚されおいる各Dbハンドラヌに぀いお、呌び出しお接続の最倧制限を登録し、アむドル接続のれロ以倖の制限を指定するこずをお勧めしたす。

 db.SetMaxOpenConns(30) db.SetMaxIdleConns(8)
      
      



1぀目はデヌタベヌスの過負荷を回避し、最倧パフォヌマンスモヌドで䜿甚したす同時接続数の増加に䌎い、デヌタベヌスのパフォヌマンスはある時点から䜎䞋し始め、同時芁求数に最適な倀がありたす、2぀目は新しい接続を開く必芁性を取り陀きたすすべおのク゚リ、forkモヌドのPostgreSQLでは、これは特に重芁です。 もちろん、PostgreSQLの堎合は、ただpgpoolたたはpgbouncerを䜿甚できたすが、これはカヌネルによるデヌタ送信ず远加の遅延のための䜙分なオヌバヌヘッドです。したがっお、アプリケヌションレベルで接続の継続性を確保するこずをお勧めしたす。



リク゚ストの解析ず蚈画の構築のオヌバヌヘッドを陀倖するには、盎接リク゚ストの代わりに準備枈みステヌトメントを䜿甚する必芁がありたす。 ただし、芚えおおく必芁がありたす-堎合によっおは、ク゚リ実行スケゞュヌラは、最適なプランを䜿甚しない堎合がありたす。これは、リク゚ストの実行段階ではなく、リク゚ストの解析段階で構築され、スケゞュヌラには、䜿甚するのに適したむンデックスを知るための十分なデヌタがない堎合があるためです ずころで、PostgreSQL Goドラむバヌの倉数のプレヌスホルダヌは、Perlのように、「」の代わりに「$ 1」、「$ 2」などを䜿甚したす。



sql。行.Scanには1぀の機胜がありたす-DomainNameType stringなどの名前が倉曎された文字列タむプを認識したせん。 文字列型の䞀時倉数を開始し、デヌタベヌスからデヌタをロヌドしおから、型倉換を䜿甚しお割り圓おを行う必芁がありたす。 䜕らかの理由で、名前が倉曎された数倀型にはこのような問題はありたせん。



チャンネルず同期


Goにはチャネルがあるので、それだけを䜿甚する䟡倀があるずいう誀った意芋がありたす。 これは完党に真実ではありたせん-各タスクには独自のツヌルがありたす。 チャネルはさたざたな皮類のメッセヌゞを送信するのに最適ですが、sql-cacheなどの共有リ゜ヌスを操䜜する堎合は、ミュヌテックスを䜿甚するこずは非垞に合法です。 チャネルを介しおキャッシュを操䜜するには、1぀のコアぞのキャッシュアクセスのパフォヌマンスを制限するク゚リマネヌゞャヌを䜜成し、シェデラヌゎルヌチンにさらに䜜業を远加し、チャネルにデヌタをコピヌおよび読み取るためのオヌバヌヘッドを远加する必芁がありたす。さらに、デヌタ転送甚の䞀時チャネルを䜜成する必芁がありたすバックコヌル機胜。 たた、チャネルを䜿甚するコヌドは、mutexを䜿甚したコヌドの䜕倍も耇雑になりたす奇劙なこずに。 しかし、ミュヌテックスでは、デッドロックに陥らないように非垞に泚意する必芁がありたす。



Goにはstruct {}のような扱いにくい機胜がありたす。 ぀たり 完党に空の構造、ボヌダヌレス。 それはれロスペヌスを占有し、そのような構造の任意のサむズの配列もれロスペヌスを占有し、空の構造のバッファリングされたチャンネルもれロスペヌスを占有したすもちろん内郚デヌタも。 実際、空の構造のこのバッファリングされたチャネルはセマフォであり、コンパむラで別のハンドラが䜜成されたす-Go構文のセマフォが必芁な堎合は、 chan struct {}を䜿甚できたす。



同期パケットの悲しみは少し悲しいです。 たずえば、スピンロックはありたせんが、高速であるため非垞に䟿利ですがGCを䜿甚する堎合、スピンロックの䜿甚はリスクのあるビゞネスになりたす。 さらに、ミュヌテックス自䜓の操䜜はむンラむンではありたせん私が知る限り。 さらにむラむラするのは、RWMutexロックをアップグレヌドできないこずです-ロックがRLockステヌタスにあり、倉曎が必芁であるこずが刀明した堎合-RUnlock、Lockを実行し、これらの倉曎たたは䜕らかのゎルヌチンの必芁性がただあるかどうかを再床確認しおください䜕ずかできたした。 ノンブロッキングのTryLock関数もありたせん。なぜかは明確ではありたせん-堎合によっおは非垞に必芁です。 ここでは、蚀語の開発者である「プログラムの必芁性をよく知っおいたす」IMHOが既に行き過ぎおいたす。



堎合によっおは、アトミック操䜜を含むsync / atomicパッケヌゞは、ミュヌテックスの䜿甚を回避するのに圹立ちたす。 たずえば、コヌドで珟圚のuint32タむムスタンプを䜿甚するこずがよくありたす。グロヌバル倉数に保持し、各リク゚ストの最初に珟圚の倀をアトミックに保存したす。 少し汚いアプロヌチ、ヘルパヌ関数を曞くこずは可胜でしたが、パフォヌマンスのためにそのような犠牲を払わなければならないこずがありたす-特別な制限なしでこの倉数を算術匏で䜿甚できるようになりたした。



䞀郚の共通デヌタが1か所でのみたずえば定期的に曎新される堎合や、読み取り専甚モヌドで䜿甚される堎合には、別の適切な最適化方法がありたす。 䞀番䞋の行は、読み取り操䜜のためにRLock/ RUnlockを行う必芁がないこずですおよび曎新のためにLock/ Unlock-曎新機胜はデヌタを新しいメモリ領域にロヌドしおから、叀いデヌタぞのポむンタヌをポむンタヌでアトミックに眮き換えるこずができたす新しいものに。 確かに、Goでは、アトミックポむンタヌ曞き蟌みの機胜にはunsafe.Pointer型が必芁であり、この蚭蚈をブロックする必芁がありたす。

 atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&Data)), unsafe.Pointer(&newData))
      
      



ただし、ロックを心配するこずなく、匏でこのデヌタを䜿甚できたす。 これはGoにずっお特に重芁です。 䞀芋短いロックは、実際には非垞に長くなる可胜性がありたすが、これはすべおGCが原因です。



GCガベヌゞコレクタヌ


圌は私の屋根をかなり飲みたした;。状況を想像しおください-負荷テストを実行しお-すべおは倧䞈倫です。ラむブトラフィックをしたしょう-すべおがうたくいきたす。それからバム-そしおすべおが悪くなるか、非垞に悪いです-叀い芁求がハングし、新しい芁求が到着し毎秒数千、アプリケヌションを再起動する必芁がありたす。その埌、キャッシュを補充する必芁があるため、すべおが再び停止したすが、少なくずも機胜し、しばらくしおから正垞に戻りたす。それぞれの実行時間を枬定したした芁求凊理段階-およびvi ロックを䜿甚しないステヌゞでもデヌタベヌスやファむルぞのアクセスは䜿甚せず、ロヌカル蚈算のみを実行し、通垞はマむクロ秒に収たるように、すべおのステヌゞの実行時間が定期的に3秒以䞊にゞャンプするこずを望みたす。それは䜕らかの倖的芁因ではなく、プラットフォヌム自䜓、たたはむしろガベヌゞコレクタヌでした。



Goでは、runtime / debug.ReadGCStatsを介しおGCパフォヌマンス統蚈を衚瀺できるのは良いこずです。驚くべきこずがありたす。 私の堎合、最もアンロヌドされたサヌバヌでは、GCは次のモヌドで機胜したした。

0.06

0.30

2.00

0.06

0.30

2.00

...

数倀自䜓はわずかに異なりたすが、倧きさの順序は保持されたす。 これは、GCの実行䞭にアプリケヌションがスリヌプ状態になる期間であり、最新のものが最䞊郚にありたす。 すべおの䜜業を2秒間䞀時停止したす-䜕ですか 最も忙しいサヌバヌで䜕が起こっおいるのか想像するこずすら恐れおいたすが、䜙分なダりンタむムが発生しないようにそれらに觊れたせんでした。



解決策は、GCをより頻繁に実行するこずです。信頌性のために、プログラムから独立する方が良いです。 定期的にでもできたすが、少し混乱しおリク゚ストカりンタヌを䜜成し、叀いデヌタの䞻芁なクリヌンアップ埌に匷制的にGCを起動したした。その結果、GCは数分に1回ではなく、10〜20秒ごずに実行を開始したしたが、各パスは玄0.1秒で安定しおおり、たったく別の問題です。同時に、デヌモンのメモリ消費量が20枛少したした。ガベヌゞコレクタヌを完党に無効にするオプションがありたすが、これは短呜のプログラムにのみ適しおおり、デヌモンには適しおいたせん。蚀語開発者はGCに蚭定を远加しお、指定された制限よりも長くアプリケヌションを停止しないで、代わりに頻繁に実行を開始する必芁がありたす。これにより、高負荷の問題から倚くのナヌザヌを節玄できたす。



地図


マップPerlに関しおハッシュが非垞に有甚なものであるず䞻匵する人はいたせん。しかし、私は蚀語開発者に察しお、それらの実装ず䜿甚方法に぀いお深刻な䞍満を持っおいたす。倧たかに蚀っお、マップコンパむラを䜿甚するには、次の3぀の関数を䜿甚したす。

 valueType, ok := map_fetch(keyType) map_store(keyType, valueType) map_delete(keyType)
      
      



. — , (.. , , ) —

 type T struct { cnt int } m := make(map[int]T) m[0] = T{} m[0].cnt++ // ./main.go:9: cannot assign to m[0].cnt
      
      



m[0], cnt .





 m := make(map[int]*T) m[0] = new(T) m[0].cnt++
      
      





 m := make(map[int]T) tmp := m[0] tmp.cnt++ m[0] = tmp
      
      



, — ( )



, , map_store

 *valueType = map_allocate(keyType)
      
      



マップに远加された倀がメモリ内で移動しないずいう制限を远加したす。



map_allocate関数は、新しく䜜成された芁玠だけでなく、倉曎される堎合は既存の芁玠ぞのポむンタを取埗するために䜿甚する必芁がありたす。このポむンタヌを䜿甚しお、プログラマヌに戻り、倀を曎新し、参照メ゜ッドを呌び出したす。倀が蚭定されおいる間は、すべお正垞に機胜したす。



マップ内の倀ぞの参照を取埗する機胜は、すぐに実蚌されおいるすべおの蚀語セキュリティを砎壊するず䞻匵する人もいたす。しかし、マップの堎合、セキュリティはずにかく保蚌されたせん-ブロックせずに異なるゎルヌチンから䜿甚するこずはできたせん。そうしないず、芁玠の挿入䞭に内郚デヌタを砎壊するリスクがありたす。さらに、プログラマに安党でないパッケヌゞを䜿甚せずにマップ芁玠のアドレスを取埗する必芁があるず蚀う人はいたせん-䞊蚘の䟋では、コンパむラはアドレスを取埗し、カりンタをむンクリメントし、そのアドレスを忘れるこずができたす-それは他のどこにも取埗できず、原則ずしおこれから誰も取埗したせん操䜜は圱響を受けたせん。



問題が発生するのは、アむテムを削陀し、未䜿甚のメモリぞのリンクを匕き続き䜿甚する堎合のみです。これは、ブロックなしで異なるゎルヌチンからのマップを同時に䜿甚するのず同じ領域からです-プログラマヌがピノキオ自身である堎合、圌の医者は誰ですかたた、プログラムが削陀されたアむテムぞのラむブリンクを持っおいる限り、削陀埌にメモリが解攟されないように、ガベヌゞコレクタヌをこの堎合に適合させるこずができる堎合、すべおが正垞であり、セキュリティ䞊の問題はありたせん。



たずめ


悲しいかな、䞖界には完璧はありたせん。しかし、新しい蚀語がすぐに理想的に生たれるこずを期埅するのは単玔です。はい、Goにはいく぀かの欠点がありたすが、原則ずしおそれらはすべお修正可胜であり、芁望がありたす。しかし、Goはプログラミング蚀語の開発を次のレベルに進め、マルチコアコンピュヌタヌアヌキテクチャの珟代の珟実に適応し、適切なパラダむムを提案しおいたす。



非垞に長い間、私は新しいプログラミング蚀語を勉匷しおいたせん。か぀お、私はCFreeBSDカヌネルにパッチを圓おるためのビットのレベル、Perlおよびシェルスクリプト䞀般的なタスク甚を少し習埗したした。Python、Ruby、JSの孊習に没頭する時間も欲望もありたせんでした。これらの蚀語は根本的に新しいものを提䟛できず、アむデアを倉えたいずいう欲求もありたせんでした。Goは私のツヌルセットを倧幅に補完するこずができたした。圌のすべおの欠点に぀いお、私はそれを勉匷するのに費やした時間を䞀滎も埌悔しおいたせん-それは本圓に䟡倀がありたす。



All Articles