20200711のGoに関する記事は5件です。

【Goのお勉強中】ラップアラウンド(wrap-around)が要注意な件

背景

Goの独学を進める中で、ラップアラウンドなるものに出会いました。
こりゃ要注意だぞ...と思ったのでまとめます
※何かしら知見を得られたらその度に更新します

ラップアラウンドって何?

ざっくり

桁あふれ(オーバーフロー)の上位互換みたいなやつ。
最大値を超えたら、また最小値からスタートする、みたいな。
反対に最小値を下回ったら、最大値になっちゃう、みたいな。
(C言語でも同じことが発生するとかなんとか)

例えば

8ビットで数値を管理保持するint8の型を持つ変数aがあった場合
int8の範囲は-128~127じゃないですか。
すると、
現在 a = 127 だったとしたら、a++ をするとaの値が-128になります。
めっちゃ減っとるやんけ
逆にa = -128 の時に1を引くと127になります。

嘘だと思うならやってみてください

main.go
package main

import "fmt"

func main() {
    var a int8 = 127

    a++
    fmt.Printf("ふつうは127+1=128のはず。でもこの結果は... %d\n", a)
    a--
    fmt.Printf("じゃあ-128-1は... %d\n", a)
}

それでよ

Goの場合、こういった現象が起きても特にエラーは発生しないらしく、ちょっと慎重に実装する必要がありそうです。

どうやって対策するの?

どうやら演算を行う前に、演算結果がラップアラウンドを起こさないか確認する必要があるみたいです。
なんてこったい。

int64だとこんな感じで事前チェックを入れるのかな...全然足りないので気が向いたら充実させます。

wrapArround.go
func wrapArround(a, b int64) bool {

    if math.MaxInt64-a < b {    // 足し算をして問題がないか?
        return false
    } else if a < math.MinInt64+b || b < math.MinInt64+a { // 引き算をして問題がないか?
        return false
    }

    return true
}

気になっていること

もしや日付表現とかでも問題になったりする...?

参考図書

https://www.amazon.co.jp/dp/B01FH3KRTI/ref=cm_sw_r_tw_dp_x_msCcFb90E9FFA

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goでエラーだったときにlogrusでログ出力してみる

はじめに

GoでOpenWeatherMapAPI叩いてみたでやったものをちょっとだけ改良して、エラー時のログ処理を追加してみたものになります。

GitHubはこちら

環境

OS:maOS Catalina 10.15.5
Go:go version go1.14.4 darwin/amd64

手順

  • logrusをインストールする

※ログレベルを分けたい&速度を気にする必要がない&シンプルに使えそうだったのでlogrusを選択

$ go get github.com/sirupsen/logrus
  • init関数でログ設定を行う
 func init() {
    // Info以上のレベルを出力する設定
    log.SetLevel(log.InfoLevel)
 }
  • エラーだった場合にログを出力する

    • APIを叩いたとき
    res, resErr := http.Get(url)
    if resErr != nil {
        log.Fatalf("Failed to Get API: %v", resErr)
    }
    
    • レスポンスを取得したとき
    // レスポンスを取得
    defer res.Body.Close()
    body, redErr := ioutil.ReadAll(res.Body)
    
    // レスポンスを読み込めなかった場合はログを出力
    if redErr != nil {
        log.Warnf("Failed to read response: %v", redErr)
    }
    
    • JSONをパースしたとき
    // 返却されたJSONをパース
    if paErr := json.Unmarshal(body, &data); paErr != nil {
        log.Warnf("Failed to parse JSON: %v", paErr)
    }
    

課題

  • ちゃんとテスト書くとどうやってやるんだろう・・・?
  • 全部mainに書いちゃってるので、リファクタリングが必要
  • API叩いたときにエラーだったらリトライ処理あってもよい
  • Goの例外処理の思想がJavaとだいぶ異なるのでそのあたり整理しておさえたい

参考にさせていただいたもの

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

go修行19日目 HMAC

HAMC

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

// サーバサイドで正しいクライアントのものかを判定する
var DB = map[string]string{
    "User1Key": "User1Secret",
    "User2Key": "User2Secret",
}

func Server(apiKey, sign string, data []byte) {
    apiSecret := DB[apiKey]
    // sha256の同じアルゴリズムを使う
    h := hmac.New(sha256.New, []byte(apiSecret))
    h.Write(data)
    expectedHMAC := hex.EncodeToString(h.Sum(nil))
    // 同じアルゴリズムでエンコードしたhmacが一致するかを確認
    fmt.Println(sign == expectedHMAC)
}

func main() {
    const apiKey = "User1Key"
    const apiSecret = "User1Secret"

    data := []byte("data")
    h := hmac.New(sha256.New, []byte(apiSecret))
    h.Write(data)
    sign := hex.EncodeToString(h.Sum(nil))

    // ハッシュ値
    fmt.Println(sign)

    // サーバに投げる
    Server(apiKey, sign, data)
}

出力

076b55e7f7e12624b4569f162302f1e36c11fb3a9134889267748de14a84b996
true

Semaphore

  • goroutineが走る数を限定することができる
go get golang.org/x/sync/semaphore
package main

import (
    "context"
    "fmt"
    "time"

    "golang.org/x/sync/semaphore"
)

// 並行実行できるプロセス数を制限
var s *semaphore.Weighted = semaphore.NewWeighted(1)


func longProcess(ctx context.Context){
    // 処理が並行している場合ロックをかけることができる
    isAcquire := s.TryAcquire(1)
    if !isAcquire{
        fmt.Println("Could not get lock")
        return
    }

    defer s.Release(1)
    fmt.Println("Wait...")
    time.Sleep(1 * time.Second)
    fmt.Println("Done")
}

func main() {
    ctx := context.TODO()
    go longProcess(ctx)
    go longProcess(ctx)
    go longProcess(ctx)
    time.Sleep(5 * time.Second)
}
Could not get lock
Could not get lock
Wait...
Done

ini

  • configファイルを読み込む
  • サーバの設定値など
package main

import (
    "fmt"

    "github.com/go-ini/ini"
)

type ConfigList struct{
    Port int
    DbName string
    SQLDriver string
}

var Config ConfigList

// main関数が呼ばれる前に設定を呼び込む
func init(){
    cfg, _ := ini.Load("config.ini")
    Config = ConfigList{
        Port: cfg.Section("web").Key("port").MustInt(),
        DbName: cfg.Section("db").Key("name").MustString("example.sql"),
        SQLDriver: cfg.Section("db").Key("driver").String(),
    }
}

func main(){
    fmt.Printf("%T %v\n", Config.Port, Config.Port)
    fmt.Printf("%T %v\n", Config.DbName, Config.DbName)
    fmt.Printf("%T %v\n", Config.SQLDriver, Config.SQLDriver)

}
config.ini
[web]
port = 8080

[db]
name = stockdata.sql
driver = sqlite3

出力

int 8080
string stockdata.sql
string sqlite3

教材

https://www.udemy.com/course/go-fintech/learn/lecture/12088980

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

go/analysis.Analyzerをnolintできるようにする

go/analysis.Analyzer を使いこなして行こうと思った時に、
ふと思い立ったので作ってみました。

https://github.com/kyoh86/nolint

func main() {
    multichecker.Main(
        nolint.WrapAll(
            exportloopref.Analyzer,
            bodyclose.Analyzer,
            // ...
        ),
    )
}

と書くと、 // nolint のコメントでDiagnosticsの出力を抑制できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

言語別Stack処理速度比較

1. はじめに

Go言語にはStackが実装されていませんが、

Stackの処理自体は、どの言語でも、
配列や線形リストを使って自作で実装することができます。

Go言語向けに自作で作ったついでに、
Stackが実装されている他言語との処理速度(処理時間)を比較してみました。

実験で使用した言語は、

  • C#(.NET Core3.0)
  • go1.14.4
  • java 13.0.2
  • Python 3.8.3
  • C言語(MinGW.org GCC-8.2.0-3)(2020/07/12追記)

の5言語です。

各言語のソースは、githubでも公開しています。
https://github.com/NobuyukiInoue/StackSpeedTest

2. 言語別のメソッド比較

Stackといっても、言語によって、各種操作がメソッドで提供されていたり、
プロパティでチェックしなければならないなど、細かな処理の実装内容が異なります。

今回の実験で使用した各言語でのクラス/インタフェース/機能を一覧にすると、以下のようになります。

処理内容 C#(.NET Core3.x)
(Stackクラス)
Java 8
(クラスStack)
Java 8
(クラスArrayDeque)
Java 8
(クラスDeque)
Python3.8.3
(リスト)
Stackへの書き込み Pushメソッド pushメソッド pushメソッド addFirstメソッド append関数
Stackからの取り出し Popメソッド popメソッド popメソッド removeFirstメソッド pop関数
値の検索 Containsメソッド
(戻り値はbool型)
searchメソッド
(戻り値はindex番号)
Containsメソッド
(戻り値はbool型)
Containsメソッド
(戻り値はbool型)
index関数
Stackが空かどうか調べる Countプロパティを利用 emptyメソッド isEmptyメソッド isEmptyメソッド len関数を利用

3. 検証環境と実験結果(2020/07/11追記)

項目
OS MS-Windows 10
CPU Intel Core-i7 8559u
メモリ 16GB(LPDDR3/2133MHz)

メモリ容量を追記しました。(2020/07/11)

処理の内容は、

  • 1~1億までの数値のpush()
  • 最も深い位置にある値1の検索
  • 1億~1までのPop()

です。

1億個の数値をint64(8byte)で割り当てると、762MByteの容量が必要になります。
int32(4byte)なら、半分の381MByteですね。

生成するスタックの容量が小さいうちは、それほど処理時間に差はありませんが、
1億個のスタック生成となると、選択した方法によってはメモリ消費が4GBを超えます。

搭載されているメモリが8GB以下だと、言語によっては(デフォルト設定では)、
ヒープメモリが足りないといったエラーがでます。

各言語での処理時間は以下の通りでした。

消費したメモリについても、Windows10のリソースモニター(コミット値)による表示の目視ではありますが、参考程度に掲載しています。(2020/07/11追記)

C# NET Core 3.0.100

Stackクラスを使用
https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.stack-1?view=netcore-3.1

メモリ消費は0.39GBでした。

times Execution time
1st 977 [ms]
2nd 1003 [ms]
3rd 1004 [ms]

メモリ消費からみて、内部ではint32で処理が行われているようです。

他言語と比べてかなりメモリ消費が少なく、
処理データの少なさが処理時間の短さに影響しているようです。

今回の比較では最速の数値です。

go1.14.4 windows/amd64(配列版)

メモリ消費は2.36GBでした。

times Execution time
1st 2089 [ms]
2nd 1853 [ms]
3rd 1737 [ms]

配列の再割り当てを繰り返しても、それほど遅くはないようです。
Javaより速い結果となりました。

go1.14.4 windows/amd64(線形リスト版)

メモリ消費は1.49GBでした。

times Execution time
1st 5617 [ms]
2nd 5400 [ms]
3rd 5569 [ms]

処理時間は配列版よりも遅くなりました。

線形リストでは次ノードへのポインタが必要なので、
直線的なメモリ配置の配列より不要なデータが増えてしまいますが、
配列版より消費が少ない結果となりました。

Go言語では、配列の再割り当てを行っても、
プログラムの終了までメモリの解放が行われていないのかもしれません。
(あくまで推測で、だれか検証してほしい)

java 13.0.2(クラスStack版)

クラスStackを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Stack.html

メモリ消費は4.21GBでした。

times Execution time
1st 6743 [ms]
2nd 6690 [ms]
3rd 6733 [ms]

java 13.0.2(クラスArrayDeque版)

クラスArrayDequeを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/ArrayDeque.html

メモリ消費は3.96GBでした。

times Execution time
1st 4397 [ms]
2nd 4612 [ms]
3rd 4477 [ms]

クラスStackよりも処理時間が短くなりました。

java 13.0.2(インタフェースDeque版)

インタフェースDequeを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Deque.html

メモリ消費は4.31GBでした。

times Execution time
1st 21416 [ms]
2nd 20187 [ms]
2nd 20341 [ms]

クラスStackよりも処理時間が増えてしまいました。

Python 3.8.3

リストを使用

メモリ消費は4.42GBでした。

times Execution time
1st 16957 [ms]
2nd 16561 [ms]
3rd 17558 [ms]

他言語では、一度確保したメモリは消費したままでしたが、
Python3では、1億個のPush直後がメモリ消費の最大値で、
Pop処理が進むにしたがって消費メモリが減っていく様子が目視できます。

C言語(64K個単位のセグメント(配列)でStackを実装)(2020/07/12追記)

メモリ消費は0.38GBでした。

さすがにC言語は速いですね。
といっても、C#に負けてしまいました。

確保したブロックは破棄せずにそのまま保持するようにすれば、もっと速くなるかもしれません。

times Execution time
1st 1591 [ms]
2nd 1554 [ms]
3rd 1558 [ms]

C言語(線形リストでStackを実装)(2020/07/12追記)

メモリ消費は1.55GBでした。

Push処理、Pop処理、それぞれ5秒程度ずつといったところでしょうか。
メモリの確保・解放の処理回数が多いので、それだけ処理時間が遅くなっています。

times Execution time
1st 10981 [ms]
2nd 11066 [ms]
3rd 10799 [ms]

関連(2020/07/11追記)

LeetCodeのProblemの中にも、Stackを自分で実装する問題があります。
いろんな言語での回答例が投稿されているので、処理時間を比較するのも面白いと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む