- 投稿日:2020-07-12T19:39:44+09:00
Goでスタックを実装
はじめに
データ構造とアルゴリズム、Goの勉強のためにスタックを実装しました。
個人的なアウトプットです。スタック
スタックは後に格納されたデータほど先に取り出される。
LIFO(Last In First Out)
コード
package main import "fmt" const StackSize = 10 type Stack struct { top int data [StackSize]int } func (s *Stack) Push(d int) error { if s.top == len(s.data) { return fmt.Errorf("Stack Overflow") } s.data[s.top] = d s.top++ return nil } func (s *Stack) Pop() (int, error) { if s.top == 0 { return 0, fmt.Errorf("Stack Underflow") } d := s.data[s.top-1] s.top-- return d, nil } func (s *Stack) Get() []int { return s.data[:s.top] }
- 投稿日:2020-07-12T18:59:55+09:00
Go のロギングライブラリ "go-logging"
ロギングについて
プログラムを書いていると、内部の処理で値がどのように変化しているかログを出して確かめたい時があると思います。しかし、 全ての情報をログに出力しようとするのはあまりにも煩雑になってしまい、本来得たい情報が読み取りづらくなる恐れがあります。
上記の問題を解決したいため、ログに出力する情報を制御できる仕組みが欲しいです。
Go 言語には go-logging というロギングライブラリがあり、レベル毎のログ出力の制御や出力情報のカスタマイズなどができます。今回はこれについてご紹介したいと思います。
使い方紹介
サンプルコード
main.gopackage main import ( "os" "github.com/op/go-logging" ) var log = logging.MustGetLogger("example") // ログの出力フォーマットを定義 var logFmt = logging.MustStringFormatter( `%{color}%{time:15:04:05.000} PID=%{pid} MOD=%{module} PKG=%{shortpkg} %{shortfile} FUNC=%{shortfunc} ▶ %{level:.4s} %{id:03x} %{color:reset} %{message}`, ) func Backend(format logging.Formatter, level logging.Level) logging.LeveledBackend { // ロギングの初期設定 // ログの出力先は os.Stderr(標準エラー)に設定する。 backend := logging.NewLogBackend(os.Stderr, "", 0) // ログのフォーマットを適用する backendFormatter := logging.NewBackendFormatter(backend, format) // 出力するログの種類(レベル)を設定 backendLeveled := logging.AddModuleLevel(backendFormatter) backendLeveled.SetLevel(level, "") return backendLeveled } func main() { backend := Backend(logFmt, logging.DEBUG) logging.SetBackend(backend) log.Debug("debug") log.Info("info") log.Notice("notice") log.Warning("warning") log.Error("err") log.Critical("crit") }実行結果
$ go run main.go 15:25:21.133 PID=15179 MOD=example PKG=main main.go:33 FUNC=main ▶ DEBU 001 debug 15:25:21.133 PID=15179 MOD=example PKG=main main.go:34 FUNC=main ▶ INFO 002 info 15:25:21.133 PID=15179 MOD=example PKG=main main.go:35 FUNC=main ▶ NOTI 003 notice 15:25:21.133 PID=15179 MOD=example PKG=main main.go:36 FUNC=main ▶ WARN 004 warning 15:25:21.133 PID=15179 MOD=example PKG=main main.go:37 FUNC=main ▶ ERRO 005 err 15:25:21.133 PID=15179 MOD=example PKG=main main.go:38 FUNC=main ▶ CRIT 006 critフォーマットについて
logging.Formatter という interface によって定義します。
サンプルコードで利用したもの以外にも、様々な情報をログに出力することができます。%{id} 一意となるログのID %{pid} プロセスID %{time} ログ出力のタイムスタンプ %{level} ログの種類(レベル) %{module} モジュール名(MustGetLogger で指定した文字列) %{program} os.Args[0] で取得できる文字列 %{message} ログのメッセージ %{longfile} ログが出力されたファイル名と行数を返す (/a/b/c/d.go:23) %{shortfile} ログが出力されたファイル名と行数を返す (d.go:23) %{longpkg} パッケージ名を出力 (github.com/go-logging) %{shortpkg} パッケージ名を出力 (go-logging) %{longfunc} 関数名を出力 (littleEndian.PutUint32) %{shortfunc} 関数名を出力 (PutUint32) %{color} ログの種類(レベル)毎に色を付与できるログレベルの制御について
SetLevel によって出力するログのレベルを制御できます。
サンプルコードでは logging.DEBUG を渡していましたが、 logging.ERROR を渡すと ERROR 以上のログレベルのみを出力するようになります。main.go(省略) func main() { backend := Backend(logFmt, logging.ERROR) logging.SetBackend(backend) log.Debug("debug") //出力されない log.Info("info") //出力されない log.Notice("notice") //出力されない log.Warning("warning") //出力されない log.Error("err") log.Critical("crit") }実行結果
$ go run main.go 15:48:02.674 PID=15555 MOD=example PKG=main main.go:39 FUNC=main ▶ ERRO 001 err 15:48:02.674 PID=15555 MOD=example PKG=main main.go:40 FUNC=main ▶ CRIT 002 critまとめ
go-logging は非常に使いやすいロギングライブラリとなっています。趣味や業務などで Go 言語を利用されている方は是非使ってみてください。
- 投稿日:2020-07-12T16:50:38+09:00
Go言語のメソッドの互換性の破壊検証
はじめに
この記事はGo言語のメソッドの仕様を検証したものです。誤っている記述があると思う場合は、ご指摘いただければ嬉しいです。
Go言語のリリースノートの Go 1 and the Future of Go Programs を調べていて、このような記述を見つけました。
Although we expect that the vast majority of programs will maintain this compatibility over time, it is impossible to guarantee that no future change will break any program. This document is an attempt to set expectations for the compatibility of Go 1 software in the future. There are a number of ways in which a program that compiles and runs today may fail to do so after a future point release. They are all unlikely but worth recording.
(翻訳)プログラムの大部分はこの互換性を長期にわたって維持すると予想されますが、将来の変更によってプログラムが機能しなくなることを保証することは不可能です。このドキュメントは、Go 1ソフトウェアの将来の互換性に対する期待を設定するための試みです。今日コンパイルして実行するプログラムが、将来のポイントリリース後に失敗する可能性のある方法がいくつかあります。全てがあり得そうにはありませんが、記録する価値はあります。
(中略)
Methods. As with struct fields, it may be necessary to add methods to types. Under some circumstances, such as when the type is embedded in a struct along with another type, the addition of the new method may break the struct by creating a conflict with an existing method of the other embedded type. We cannot protect against this rare case and do not guarantee compatibility should it arise.
(翻訳)メソッド。構造体フィールドと同様に、型にメソッドを追加する必要がある場合があります。型が別の型と共に構造体に埋め込まれている場合など、状況によっては、新しいメソッドを追加すると、他の埋め込み型の既存のメソッドとの競合が発生して構造体が壊れる場合があります。このまれなケースから保護することはできません。また、それが発生した場合の互換性も保証しません。
"the addition of the new method may break the struct by creating a conflict with an existing method of the other embedded type." とは、どういうことなのか...?よくわからなかったので、検証して確かめてみました。
埋め込み型のサンプル
まず埋め込み型のサンプルコードを書いてみます。
swan/swan.gopackage swan type Swan struct { }main.gopackage main import ( "fmt" "./swan" ) type Duck struct { } func (Duck) Quack() string { return "quack." } type Birds struct { Duck swan.Swan } func main() { birds := Birds{Duck: Duck{}, Swan: swan.Swan{}} fmt.Println(birds.Duck.Quack(), birds.Quack()) // quack. quack. }importした
Swan
とmain.go
内で定義されているDuck
を使用してBirds
を定義し、Duck
のメソッドQuack()
を使用して文字列を表示しています。この場合、特に問題なくコードは実行されます。同名のメソッドをパッケージに追加
swanパッケージにコードを追加してみます。
swan/swan.gopackage swan type Swan struct { } // 追加 func (Swan) Quack() string { return "quack!!" }
Duck
と同じメソッドであるQuack()
をSwan
に追加しました。この状態でコードを実行してみます。main.go...(省略) func main() { birds := Birds{Duck: Duck{}, Swan: swan.Swan{}} fmt.Println(birds.Duck.Quack(), birds.Quack()) // ./main.go:24:39: ambiguous selector birds.Quack }
ambiguous selector birds.Quack
というコンパイルエラーが発生しました。
birds.Quack()
のセレクターが曖昧になっている、とエラーメッセージが表示されています。Swan
にQuack()
メソッドが追加されたので、Duck.Quack()
とSwan.Quack()
のどちらなのか判別ができないため、プログラムが実行不可能と判断されてしまいました。
Swan
をGoの標準パッケージと置き換えて考えた場合、標準パッケージでメソッドが追加されると同じことが起きると考えられます。衝突しないようなメソッド名を使う、埋め込み型のメソッドは明示的に型を示すなどすると良さそうですね?
- 投稿日:2020-07-12T10:59:29+09:00
golang appendを使ってスライスから特定の要素を削除
リソースに対するDELETE
https://tutorialedge.net/golang/creating-restful-api-with-golang/#deleting-articlesにて
deleteArticle関数の中でmain.goArticles = append(Articles[:index], Articles[index+1:]... )これ、『スライスの要素を削除しているのだろう。』と推測するのは容易だがどう動いているのか気になったため調べてみた。
このとき、Articlesは以下のJSONを返す。
[ {"Id":"1","Title":"Hello","desc":"Article Description","content":"Article Content"}, {"Id":"2","Title":"Hello 2","desc":"Article Description","content":"Article Content"}, {"Id":"3","Title":"Hello 3","desc":"Article Description","content":"Article Content"}, ]append関数
そもそも、appendは第一引数に要素の追加元のスライス、第二引数以降に可変長引数で末尾に追加する要素をとる関数だった。
第一引数
第一引数を見てみると、Articles[:index]。indexが2のときを出力する
fmt.Printf("%+v", Article[:index]) >> [{Id:1 Title:Hello Desc:Article Description Content:Article Content} {Id:2 Title:Hello 2 Desc:Article Description Content:Article Content}]indexを含まない直前の要素までがすべて含まれている。
第二引数
Articles[index+1:]...は何のなのか。
同様に出力するが、indexを0とする。fmt.Printf("%+v", Articles[index+1:]) >> [{Id:2 Title:Hello 2 Desc:Article Description Content:Article Content} {Id:3 Title:Hello 3 Desc:Article Description Content:Article Content}]index+1を含む以降の要素を取得している。つづくは"..."はスライスを展開して可変長引数としてappendに渡している。
ということは!!!
Articles = append(Articles[:index], Articles[index+1:]... )この記述で、特定要素の前後をくっつけることで削除し、Articlesに再代入しているということがわかる。
一行のコードだがappend関数とスライスの要素の取得方法、スライスを展開して間変調引数として渡す方法と基礎文法の良い復習となった。
- 投稿日:2020-07-12T10:17:31+09:00
Go vs Rust
どうも。
首を刺して自■する寸前でとある企業に雇われ半年ほど経った者です。ところで、Golangはマルチスレッド処理が手軽にできる言語ですが、何故か分かりませんがこの言語とRustはしばしば比較されます。というわけで、どっちがパフォーマンスの良い言語なのか比較したいと思います。
比較を行った背景
何故か僕の周辺では何かに取り憑かれたように「RustいいよRust」と、無条件なRust賛美をよく目にするようになりました。でもぶっちゃけ、僕はどうして彼らがそこまでRustに心酔するのかよく分からないので、実際に検証コードなりを書いてRustを体験してみた・・・というのが背景です。
何を比較したか
- フィボナッチ数列を計算量$O(\log n)$で計算するコードをGoとRustで実装し、計算時間を測る。計算時間の短いほうをパフォーマンスの良い言語とする。
- 「Hello World」というテキストを返すウェブサーバーを実装し、abコマンド(Apache Benchmark)を使って秒あたりのスループットを計測した。スループットが高いほうの言語をパフォーマンスの良い言語とする。
- 1.及び2.のファイルサイズ
比較に使用した機器
自作マシン hyamamoto@hyamamoto-home (pts/3) ~/Documents/Workspace/go-vs-rust master ?1 archey3 ✔ 13:18:19 + OS: Arch Linux x86_64 # Hostname: hyamamoto-home ### Kernel Release: 5.7.7-arch1-1 ##### Uptime: 2:44 ###### WM: KWin ; #####; DE: KDE +##.##### Packages: 1493 +########## RAM: 5311 MB / 32085 MB #############; Processor Type: AMD Ryzen 7 3700X 8-Core Processor ###############+ $EDITOR: vim ####### ####### Root: 39G / 457G (8%) (ext4) .######; ;###;`". .#######; ;#####. #########. .########` ######' '###### ;#### ####; ##' '## #' `#比較するアプリの前提
- アプリはリリースされる事を前提とする。つまり、標準のコンパイラの機能を使って最適化処理が可能であればこれを行う。(i.e. rust版アプリには
--release
フラグがつくよ!!)検証コード
また、計算にはGoの場合は
math/big
、Rustの場合はrugという多倍長数値演算ライブラリを使用します。比較1. フィボナッチ数列
フィボナッチ数列を生成する問題は各社のコーディングテストや言語のベンチマーク等では定番中の定番です。単純実装の場合の時間計算量は$O(2^n)$、計算後の状態を次の計算に利用する実装(メモ化)では$O(n)$、2x2の行列を用いて計算する実装では$O(\log n)$となります。
今回の場合、最も計算量が少ない方法で実装するので、コンパイラの最適化能力が直に顕れる・・・と思います。
計測結果
以下が計測の結果です。「Go」のラベルがあるものはGoで書かれた実装のもの、「Rust」のラベルがあるものがRustで書かれた実装のものです。今回は、$2^{20} = 1,048,576$番目のフィボナッチ数列を計算します。
Go hyamamoto@hyamamoto-home (pts/3) ~/Doc/W/go-vs-rust/go master !1 ?4 time ./bin/fib 1048576 ✔ 13:39:25 (Super big number) ./bin/fib 1048576 198.51s user 24.31s system 180% cpu 2:03.65 totalRust hyamamoto@hyamamoto-home (pts/1) ~/Doc/W/go-vs-rust/rust master !1 ?4 time ./target/release/fib 1048576 ✔ 28s 13:47:38 (Super big number) ./target/release/fib 1048576 25.65s user 1.40s system 99% cpu 27.083 totalRustの圧勝 Goは並列化によってほぼ2個分のCPUを使っているように見えるにも関わらず、トータルでの計算時間は2分程度であることに対して、RustはCPUを1個しか使っていないにも関わらず、計算時間は30秒程度しか掛かっていません。つまり、フィボナッチ数列の計算だけ、言い換えれば単純なループでも結構な差が出ている事が分かりました。
・・・と、言いたい所ですが、後々、
math/big
とrug
のコードを調べた所、math/big
では多倍長整数の乗算にKaratsuba法を、rug
ではGNU MPを使っている、つまり、FFTを用いて時間計算量を最適化しています。FFTによる乗算はKaratsuba法より計算量が少なくなるので、そりゃぁRustが圧勝するわな?というわけで、Goで書かれたコードでもGMPを利用するハイレベルコードを使用して、再度計測を行った結果がこちらになります:
go_revised hyamamoto@hyamamoto-home (pts/1) ~/Doc/W/go-vs-rust master !4 ?1 time ./go/bin/fib_gmp 1048576 ✔ 09:17:28 ./go/bin/fib_gmp 1048576 72.74s user 13.31s system 117% cpu 1:13.52 total計算時間は1分10秒程度まで短縮できましたが、やはりRustの約30秒という速さには負けるようです。
比較2: Webサービス
次に、「Hello World」という文字列を表示するだけの非常に単純なWebサービスを実装し、そのスループットを比較しました。Goではデフォルトの
net/http
を、Rustではwarp
と呼ばれるライブラリを使用し、またベンチマークツールにはApache Benchmarkを使用しました。:Go hyamamoto@hyamamoto-home (pts/3) ~/Doc/W/go-vs-rust/go master !1 ab -n 16000000 -c $(nproc) 'http://localhost:5000/' 22 ✘ 14:19:11 This is ApacheBench, Version 2.3 <$Revision: 1874286 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 1600000 requests Completed 3200000 requests Completed 4800000 requests Completed 6400000 requests Completed 8000000 requests Completed 9600000 requests Completed 11200000 requests Completed 12800000 requests Completed 14400000 requests Completed 16000000 requests Finished 16000000 requests Server Software: Server Hostname: localhost Server Port: 5000 Document Path: / Document Length: 11 bytes Concurrency Level: 16 Time taken for tests: 551.510 seconds Complete requests: 16000000 Failed requests: 0 Total transferred: 2048000000 bytes HTML transferred: 176000000 bytes Requests per second: 29011.27 [#/sec] (mean) Time per request: 0.552 [ms] (mean) Time per request: 0.034 [ms] (mean, across all concurrent requests) Transfer rate: 3626.41 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 4 Processing: 0 0 0.1 0 11 Waiting: 0 0 0.1 0 10 Total: 0 1 0.1 1 11 Percentage of the requests served within a certain time (ms) 50% 1 66% 1 75% 1 80% 1 90% 1 95% 1 98% 1 99% 1 100% 11 (longest request)Rust hyamamoto@hyamamoto-home (pts/3) ~/Doc/W/go-vs-rust/go master !1 ab -n 16000000 -c $(nproc) 'http://localhost:5000/' ✔ 9m 22s 14:28:43 This is ApacheBench, Version 2.3 <$Revision: 1874286 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 1600000 requests Completed 3200000 requests Completed 4800000 requests Completed 6400000 requests Completed 8000000 requests Completed 9600000 requests Completed 11200000 requests Completed 12800000 requests Completed 14400000 requests Completed 16000000 requests Finished 16000000 requests Server Software: Server Hostname: localhost Server Port: 5000 Document Path: / Document Length: 11 bytes Concurrency Level: 16 Time taken for tests: 527.022 seconds Complete requests: 16000000 Failed requests: 0 Total transferred: 2048000000 bytes HTML transferred: 176000000 bytes Requests per second: 30359.25 [#/sec] (mean) Time per request: 0.527 [ms] (mean) Time per request: 0.033 [ms] (mean, across all concurrent requests) Transfer rate: 3794.91 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 2 Processing: 0 0 0.1 0 11 Waiting: 0 0 0.1 0 11 Total: 0 1 0.1 0 11 ERROR: The median and mean for the total time are more than twice the standard deviation apart. These results are NOT reliable. Percentage of the requests served within a certain time (ms) 50% 0 66% 1 75% 1 80% 1 90% 1 95% 1 98% 1 99% 1 100% 11 (longest request)Goで書かれたコードのスループットは約29.0k req/secである事に対して、Rustで書かれたコードのスループットは約30.4k req/secでした。Rustのが若干速いが、ほぼ差は無いように見えます。では何が原因なのかというと、恐らく実装が単純すぎたため、最終的にIOのレイテンシーの勝負になってしまったのではないかと思います。i.e. 数MB程度のバイナリデータを圧縮して出力するような計測方法を取れば有意な差は出たかも?
ファイルサイズ
Go hyamamoto@hyamamoto-home (pts/3) ~/Doc/W/go-vs-rust master !1 ls -lh go/bin ✔ 14:53:11 total 9.4M -rwxr-xr-x 1 hyamamoto hyamamoto 2.2M Jul 11 13:39 fib -rwxr-xr-x 1 hyamamoto hyamamoto 7.2M Jul 11 13:39 httpRust hyamamoto@hyamamoto-home (pts/3) ~/Doc/W/go-vs-rust master !1 ls -lh rust/target/release ✔ 14:53:17 total 7.8M -rwxr-xr-x 2 hyamamoto hyamamoto 3.1M Jul 11 13:46 fib -rwxr-xr-x 2 hyamamoto hyamamoto 4.7M Jul 11 13:46 httpフィボナッチ数列のコードのバイナリはGoのほうがRustよりもコンパクトであることに対して、ウェブサーバーのコードのバイナリはRustの方がコンパクトです。最適化によってデバッグシンボルと不要なコードが削除されているのが効いているように見えます。
結論
上記の結果から考察するに、Rustの利用が向いている分野として、恐らく計算に掛かるレイテンシーを低く抑えたい分野、例えばPied Piperのような圧縮アルゴリズムを作ったりだとか、あるいはDBMSの実装など、比較的に低レベルな領域の実装には向いている言語ではないかと思います。対して、Webの場合では大抵のレイテンシーの問題はIOであり、またWebの圧縮技術はCDNやnginx等が実装してくれているため、単純にDBからデータを取ってそれをJSONなどにして出力するといったものでは、バイナリサイズが問題とならない限りはRustで実装するメリットはあまり感じられないかと思います。
しかし!!僕は敢えて言おう!!
Rust良いよRust!
結局どちらを学ぶべきか
勿論どちらもです。ただし、Goの方が構造がシンプルなので、まずはGoから手を付けられる事をオススメします。
- 投稿日:2020-07-12T03:29:16+09:00
$GOPATH/pkg/mod配下のファイルが消せない
$GOPATH
を変更した話Goのインストール時に
$GOPATH
を~/workspace/go
な感じにしたら扱いにくかったのでデフォルト~
に戻して、元のディレクトリをrm
しようとしたときに消せなかったのでメモterminal$ echo $GOPATH #変更後 /Users/usr/go $ rm -rf ~/workspace/go rm: cannot remove '/Users/usr/workspace/go/pkg/mod/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2/internal/subtle/aliasing.go': Permission denied rm: cannot remove '/Users/usr/workspace/go/pkg/mod/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2/internal/subtle/aliasing_test.go': Permission denied rm: cannot remove '/Users/usr/workspace/go/pkg/mod/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2/internal/subtle/aliasing_appengine.go': Permission denied rm: cannot remove '/Users/usr/workspace/go/pkg/mod/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2/internal/chacha20/chacha_s390x.s': Permission denied [...]パーミッションがREAD_ONLY
ゴミプロセスが掴んでるのかと思ったら仕様らしい
解決策
go mod
は以下で消せとのこと。terminal$ go clean -modcacheめんどくさいので
w
パーミッションつけて消しました。terminal$ find ~/workspace/go/pkg/mod -exec chmod u+w {} \; $ rm -rf ~/workspace/goめでたし、めでたし。
Links