20200126のGoに関する記事は15件です。

AtCoder Beginner Contest 153 参戦記

AtCoder Beginner Contest 153 参戦記

ABC153A - Serval vs Monster

1分半で突破. 書くだけ.

H, A = map(int, input().split())

print((H + (A - 1)) // A)

ABC153B - Common Raccoon vs Monster

2分半で突破. 書くだけ. 必殺技の合計ダメージがモンスターの体力を上回っているか、それだけ.

H, N = map(int, input().split())
A = list(map(int, input().split()))

if sum(A) >= H:
    print('Yes')
else:
    print('No')

ABC153C - Fennec vs Monster

2分半で突破. 書くだけ. できるだけ体力が多いやつを必殺技で倒したいので、ソートして先頭K匹以降を攻撃で倒すものとして集計すればいいだけ.

N, K = map(int, input().split())
A = list(map(int, input().split()))

A.sort(reverse=True)
print(sum(A[K:]))

ABC153D - Caracal vs Monster

7分半で突破. log なので TLE にはならないので、再帰関数で定義通りカウントしていくだけで OK.

from sys import setrecursionlimit

setrecursionlimit(1000000)

H = int(input())


def f(n):
    if n == 1:
        return 1
    else:
        return 1 + f(n // 2) * 2


print(f(H))

ABC153E - Crested Ibis vs Monster

55分半で突破. DP で総当り.

package main

import (
    "bufio"
    "fmt"
    "math"
    "os"
    "strconv"
)

func main() {
    AMax := 10000

    H := readInt()
    N := readInt()
    AB := make([]struct{ A, B int }, N)
    for i := 0; i < N; i++ {
        AB[i].A = readInt()
        AB[i].B = readInt()
    }

    dpLen := H + AMax + 1
    dp := make([]int, dpLen)
    for i := 0; i < dpLen; i++ {
        dp[i] = math.MaxInt64
    }

    dp[0] = 0
    for i := 0; i < H; i++ {
        if dp[i] == math.MaxInt64 {
            continue
        }
        for j := 0; j < N; j++ {
            a := AB[j].A
            b := AB[j].B
            if dp[i]+b < dp[i+a] {
                dp[i+a] = dp[i] + b
            }
        }
    }

    result := math.MaxInt64
    for i := H; i < dpLen; i++ {
        if dp[i] < result {
            result = dp[i]
        }
    }
    fmt.Println(result)
}

const (
    ioBufferSize = 1 * 1024 * 1024 // 1 MB
)

var stdinScanner = func() *bufio.Scanner {
    result := bufio.NewScanner(os.Stdin)
    result.Buffer(make([]byte, ioBufferSize), ioBufferSize)
    result.Split(bufio.ScanWords)
    return result
}()

func readString() string {
    stdinScanner.Scan()
    return stdinScanner.Text()
}

func readInt() int {
    result, err := strconv.Atoi(readString())
    if err != nil {
        panic(err)
    }
    return result
}

ABC153F - Silver Fox vs Monster

敗退. AtCoder の Go のバージョンがもっと新しくて、スライスのソートができたら突破していたと思う. そうでなくてももう少し時間が残っていたら C# で書き直したのだが…….

追記: 勘違い. 全然理解できていなかった. 爆弾範囲内のモンスターへのダメージの処理を O(N) より低いオーダーで処理できるかが問題の肝だった. 解説動画通り、爆弾のダメージと有効範囲をキューに入れて、範囲外にでたらリタイアさせることで O(1) で実装して AC.

N, D, A = map(int, input().split())
XH = [list(map(int, input().split())) for _ in range(N)]

XH.sort()
q = []
t = 0
result = 0
for x, h in XH:
    while q:
        if x <= q[0][0]:
            break
        t -= q[0][1]
        q.pop(0)
    h -= t
    if h < 0:
        continue
    c = (h + A - 1) // A
    result += c
    t += c * A
    q.append((x + 2 * D, c * A))
print(result)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Golang]redigoでデータベース番号を指定する

方法

redigoでデータベース番号を指定するには、オプションを追加する。

db := 1
opt := redis.DialDatabase(db)
conn, err := redis.Dial("tcp", "localhost:6379", opt)

参考

redis - GoDoc
machinery/redis.go at 84f17e8ec73bbec34184beb067f22301ffa5263b · RichardKnop/machinery

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

redigoでデータベース番号を指定する

方法

redigoでデータベース番号を指定するには、オプションを追加する。

db := 1
opt := redis.DialDatabase(db)
conn, err := redis.Dial("tcp", "localhost:6379", opt)

参考

redis - GoDoc
machinery/redis.go at 84f17e8ec73bbec34184beb067f22301ffa5263b · RichardKnop/machinery

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

go+gin+nginx環境をec2にデプロイ

はじめに

web初心者が簡単なgoアプリケーションをec2にデプロイしてみました。
色々おかしなところがあると思いますので、何かありましたら、ご指摘いただけると幸いです。

githubを介してEC2インスタンスにプロジェクトファイルをあげるので、ローカルで開発したgoアプリケーションはgithubのリポジトリに上げておいてください。

EC2インスタンス作成

AWSコンソール画面から EC2 > サービス > インスタンス > インスタンスの作成の手順でインスタンスを作成していきます。
今回はAMIにubuntuを選択して、そのほかの設定はデフォルトです。

インスタンスの接続に必要なキーペアも新規に作成してダウンロードします。名前はわかりやすいものにしてください。
キーペアのダウンロードが終わったら、キーペアがあるディレクトリに移動し、パーミッションを設定します。

$ chmod 400 [キーペアの名前].pem

EC2インスタンスにssh接続

インスタンスが起動できたら、キーペアがあるディレクトリに移動して以下のコマンドでインスタンスにssh接続します。

$ ssh -i "[キーペアの名前].pem" ubuntu@[インスタンスのパブリックDNS]

必要なパッケージのインストール

go

https://golang.org/dl/ から、Linux用のtar.gzのURLをコピーし、以下のコマンドを実行。
今回はgo1.13.6をダウンロード。

$ wget https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz

ダウンロードできたら展開して配置し、パスを通します。

$ sudo tar -C /usr/local -xzf go1.13.6.linux-amd64.tar.gz
$ export PATH=$PATH:/usr/local/go/bin
$ exec $SHELL -l   #shellの再起動

パスが通っているか確認します

$ go version
go version go1.13.6 linux/amd64

nginx, sqlite3

次にnginxとsqlite3をダウンロードします。

$ sudo apt-get install nginx
$ sudo apt-get install sqlite3
$ which nginx   # 確認
$ which sqlite3   # 確認

プロジェクトファイルをクローン

あらかじめgithubのリポジトリに上げておいたgoアプリケーションをクローンします。

$ git clone 'gitリポジトリのパス'

gccのインストール

go-sqlite3でgccがいるっぽいんでインストール

$ sudo apt install gcc

必要なパッケージをインストール

$ go get "github.com/gin-gonic/gin"
$ go get "github.com/jinzhu/gorm"
$ go get "github.com/mattn/go-sqlite3"

build

buildする前にアプリケーションがきちんと動くか確認。
きちんと動くことが確認できたらビルド。

$ go build main.go

nginxの設定

/etc/nginx/conf.d/にgin.confを作成

gin.conf
server {
    listen       80;
    server_name  13.231.121.27;

    location ~ / {
        proxy_pass      http://127.0.0.1:8080;
    }
}

nignxを起動

$ sudo systemctl start nginx

EC2のセキュリティグループの設定

EC2のコンソールの左側メニューからセキュリティーグループを選択し、セキュリティグループを以下のように作成します。
スクリーンショット 2020-01-26 12.10.42.png
※ sshのソースは自分のグローバルIPアドレスです。マイIPを選択すると自動で入力されます。

次に、インスタンス一覧から実行中のインスタンスを右クリック > ネットワーキング > セキュリティグループの変更 で先ほど作成したものを選択してください。

ブラウザで確認

EC2インスタンスのパブリックIPをブラウザに入力してアクセスします。
確認できてたらokです。

参考

https://qiita.com/Dragon-taro/items/09a67b93093770348ce5
https://qiita.com/notchi/items/5f76b2f77cff39eca4d8
https://qiita.com/Utr/items/9469c1611abe8a0a3486

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

Golangのパッケージ周りを整理する

以下のキーワードをあんまり理解できていなかったので調べてまとめた。(随時調べて更新していく)

  • コンパイルとビルドの違い
  • Golangのパッケージとモジュールとライブラリの違い
  • $GOPATH/go/配下のsrc,pkg,binの違い()

コンパイルとビルドの違い

ここによると

  • コンパイル = ソースコードをオブジェクトコードに変換すること
  • リンク = オブジェクトコードをくっつけて一つのバイナリコードにする
  • ビルド = コンパイル + リンク

パッケージとモジュールとライブラリの違い

  • 同一パッケージのファイルは同一フォルダ$GOPATH/src/(package名)/などに含める(1つのディレクトリには1種類のパッケージ定義のみ許容される)。
  • 同じパッケージに属するファイルは互いに丸見え。これらはまとめてコンパイルされる。
  • main packageに属するファイルに関しては$GOPATH/src/配下に直置き。複数ある場合は go build *.goでビルド

  • ライブラリは(main packageを含まない)packageの集まり。ビルドして一つのバイナリになる。

パッケージのインポート

  • $GOPATH/src/配下のパッケージはimport (package名)で。
  • 同プロジェクト内のパッケージはimport (module名).(package名)で。
  • スクリプト内でパッケージ名を省略して関数や構造体を呼び出すには. import (package名)で。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

なぜGoはDuck typingを採用していると言えるのか、データ構造から詳しく解説してみた

Goのinterfaceは明示的に実装を宣言せず、実装側がinterfaceのシグネチャを満たしているかによって型が決まるDuck typingを採用していると言われています。私は当初このような説明を聞いたときにピンと来ず、むしろそれはTypeScript等に見られるStructural Typingなのでは?と思いました。

しかし実際にデータ構造を調べて見ると、確かにDuck typingであり、かつStructural Typingでもあるという考えに至りました。

これまでなかなかGoのデータ構造に触れる機会がありませんでしたが、調べてみるとこれまで気づかなかった観点がいくつも出てきたのでぜひ紹介したいと思います。

Duck typingについて

今回の主題となる部分ですので、Duck typing(ダックタイピング)についておさらいします。

Duck typingはアナロジーである「ダックテスト」に端を発しています。(厳密な起源は不明?なようですが、達人プログラマとして知られるDave ThomasによりRubyコミュニティで生まれた概念とされています。)

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
もしもアヒルのように見え、アヒルのように泳ぎ、アヒルのように鳴くのなら、それはアヒルなのだろう

これは例えばJavaであればオブジェクトの型がコンパイル時に検証されるのに対して、Rubyではランタイム時にインターフェースを持っているかが検証されるため、柔軟にポリモーフィズム(多態性)を実現できるというものです。

class Duck
  def cry
    puts 'duck'
  end
end

class Cat
  def cry
    puts 'cat'
  end
end

def cry(obj)
  obj.cry
end

cry(Duck.new) # -> cry
cry(Cat.new) # -> cat

上記のRubyのコードはDuckとCatとで共通のスーパークラスを持たないクラスのインスタンスですが、cryメソッド内ではどちらも同じように鳴かせることができました。

これがJavaであれば明示的にimplementsを宣言するか、共通のスーパークラスを持つ必要がありますが、Duck typingにおいては実装側に明示的な宣言がないのが特徴です。どのクラスに属しているかというよりも、オブジェクトが実際にどのような性質を持っているかに注目するという点で、まさに「アヒルのように見えればアヒルである」というアナロジーにマッチしています。

Nominal TypingStructural Typing

本題に入る前にもう少し型システムについて説明します。型システムにおいてNominal TypingStructural Typingという分類があります。

Nominal Typingの「Nominal」とは「名目上の」と言う意味で、型の内部構造よりもその型が何であるかによって区別します。対してStructual Typingはその名の通り、型の構造によって区別されるものです。

文字での説明よりも実際にコードを見たほうがわかりやすいので、まずはNominal TypingとしてJavaのコードを以下に示します。

// Javaのサンプルコード
class FirstName {
    String value;

    FirstName(String value) {
        this.value = value;
    }
}

class LastName {
    String value;

    LastName(String value) {
        this.value = value;
    }
}

class Main {
    static void callName(FirstName name) {
        System.out.println(name.value);
    }

    public static void main(String[] argv) {
        callName(new FirstName("takeshi")); // OK
        callName(new LastName("gouda")); // Error!
    }   
}

FirstNameLastNameはどちらのメンバ関数を持たず、valueというプロパティがあり、同一の内部構造を持ったクラスです。しかしFirstNameを引数に取る関数に対しては、FirstNameのインスタンスしか受け付けないという性質を持っています。もしもSecondClassのインスタンスを利用したい場合は、FirstClassのコンストラクタを通してからでないとコンパイルが通りません。

対してStructural Typingの性質を持つTypeScriptでは異なるクラスのオブジェクトでも、内部構造が同じであればエラーが検出されません。(※厳密にはJavaScriptのクラスは糖衣構文ですが、ここではその説明は割愛します)

// TypeScriptのサンプルコード
class FirstName {
  value: string;

  constructor(value: string) {
    this.value = value;
  }
}

class LastName {
  value: string;

  constructor(value: string) {
    this.value = value;
  }
}

const callName = (name: FirstName) => console.log(name.value);

callName(new FirstName("takeshi")); // OK
callName(new LastName("gouda")); // こっちもOK!

Nominal Typingに慣れている方からすれば不思議に感じるかもしれませんが、 TypeScriptでは型の構造に着目するため、異なるクラスのインスタンスであってもコンパイルが通るのです。

お気づきの方がいるかもしれませんが、Duck typingとStructural Typingはとても似ています。実際にTypeScriptを使ってDuck typingらしいコードを書いてみましょう。

type Duck = {cry: () => void, swimSpeed: number};
type Cat = {cry: () => void};

const cry = (obj: {cry: () => void}) => obj.cry();

const duck: Duck = {cry: () => console.log('Duck'), swimSpeed: 2.0};
const cat: Cat = {cry: () => console.log('Duck')};

cry(duck);
cry(cat);

Duck型とCat型の異なるタイプを持つオブジェクトですが、cryの型構造が同じであるために同一の関数に渡すことができました。

オブジェクトの振る舞いに注目しているという点で実質的にDuck typingに見えますが、Duck typingはランタイム時に動的にアクセスされるのに対して、Structural Typingは静的な型システムである点で異なります。ただしDuck typingとStructural Typingは対立する概念ではないので、明確に区別する必要があるかというと考えものですが。

GoにおけるStructural Typing

冒頭で触れた通り、Goは明示的な宣言を必要とせず、実装側がinterfaceのシグネチャを満たすことでポリモーフィズムを実現するので、Structural Typingの性質を持っています。

type Duck interface {
    cry()
}

type Cat struct {
}

func (c Cat) cry() {
    fmt.Println("Cat")
}

func cry(obj Duck)  {
    obj.cry()
}

func main() {
    cry(Cat{})
}

InterfaceのDuckはcry()というメンバ関数を持ち、本来Duckとして扱うべきものではなくても、CatはDuckのシグネチャを満たすことでDuckとして扱うことができます。

RubyやPythonといった動的型付け言語と異なりシグネチャのチェックはコンパイル時に行われるので、もしもCatにcryメソッドがなければコンパイルに失敗します。そのため静的な型付けという面でStructural Typingの性質を持つと言えます。

ではなぜGoはDuck typingと言われるのかと疑問に思うかもしれませんが、それは後ほど説明します。

Nominal TypingとStructural Typingの両立

Nominal TypingとStructural Typingを対立構造のように紹介したままでは、誤解を招くかもしれないと思い、少し補足的な説明を加えさせて頂きます。

NominalとStructuralは言語につきどちらか一方しか実現できないわけではなく、Goはいずれの性質を持っており、他にもScalaやTypeScrip等でも実現可能です。

// Goのサンプルコード
type Duck struct {
    cry func()
}

type Cat struct {
    cry func()
}

func cry(obj Duck) {
    obj.cry()
}

func main() {
    cry(Duck{}) // OK
    cry(Cat{}) // NG!
}

InterfaceではなくStructを利用する場合はNominalに型がチェックされるため、同じ構造でもコンパイル時にエラーが検出されます。むしろポリモーフィズムを意識しない場合、多くはNominalにチェックされるでしょう。

ちなみにTypeScriptはNominal Typingを言語機能としてサポートして欲しいという意見が長らく出ていますが、実装者が少し工夫をすることでNominalを実現することができます。例えばDDDを実践したいといった場合には有用になるでしょう。

なぜGoはDuck typingと言われるのか

ここまでDuck typingはランタイム時、Structural Typingは静的な型システムであると説明しました。そしてGoはコンパイル時にシグネチャがチェックされるためStructural Typingでありながら、なぜDuck typingとも言えるのでしょうか。

それはInterfaceによるポリモーフィズムを実現する際、Goはitable(Interface table)に動的にデータ構造を生成するという特徴を持っているためです。

例えばC++ではVirtual method table(vtable)と呼ばれるlookupのためのデータ構造をコンパイル時に作成するのに対して、Goはシグネチャのみを事前にチェックし、lookupは動的に行われるという点で異なります。これはアプリケーションレベルのコードを書いているときにはあまり意識されないことでピンと来ないかもしれませんので、詳しく追っていきましょう。

Virtual method table(vtable)について

Goのitableに入る前に、まずはvtableについて説明します。

vtableは通常、Subtypingによるポリモーフィズムのため、動的なディスパッチを実現するためのものです。具体的にはあるクラスがスーパークラスを継承している場合、そのクラスのメンバ関数がどちらに属するものなのかを通常コンパイル時に解決します。

// Javaのサンプルコード
class Animal {
    void cry() {
        System.out.println("cat");
    }

    void swim() {
        System.out.println("swim");
    }
}

class Cat extends Animal {
    @Override
    void cry() {
        System.out.println("cat");
    }
}

class Main {
    public static void main(String[] argv) {
        Animal cat = new Cat();
        cat.cry(); // -> Catクラスのcryが呼ばれる
        cat.swim(); // -> Animalクラスのswimが呼ばれる
    }   
}

上のサンプルコードでは、CatのインスタンスをAnimalクラスとして宣言しています。これはポリモーフィズムのうちSubtyping(部分型付け)と呼ばれ、派生型も継承元の型として扱うことができる性質によるものです。

Catクラスのインスタンスを通じてcryswimを呼んでいますが、cryはCatクラスの実装が、swimはAnimalクラスの実装が呼ばれます。これはコンパイル時にvtableと呼ばれるデータ構造を生成し、cryの場合はCat->cryのポインタを、swimの場合はAnimal->swimのポインタを保持することで実現されます。

Goではランタイム時にディスパッチが動的に行われる

Goの場合もvtableによるディスパッチの仕組みは同じです。しかし静的型付け言語では上記のようなディスパッチは通常コンパイル時に解決されるのに対して、Goではランタイム時に行われます。そしてこれがDuck typingと言われる理由だと解釈できます。実際にGoのDeveloperであるIan Lance Taylorの記事を見ても分かります。

This is not pure duck typing, because when possible the Go compiler will statically check whether the type implements the interface. However, Go does have a purely dynamic aspect, in that you can convert from one interface type to another. In the general case, that conversion is checked at runtime.

(訳: これは純粋なダックタイピングではありません。なぜならGoのコンパイラは可能なら型がインターフェースを実装しているかを静的に解釈しようとするからです。一方でGoは純粋に動的な側面もあり、あるインターフェースから別のものに変換する際、ランタイム時に動的に変換が行われます。)

それでは実際にサンプルコードを使ってこの動きを説明していきます。

type Animal interface {
    cry()
}

type Cat struct {
}

func (c Cat) cry() {
    fmt.Println("cat")
}

func (c Cat) swim() {
    fmt.Println("swim")
} 

func cry(obj Animal) {
    obj.cry()
}

func main() {
    cry(&Cat{})
}

CatはAnimal型のシグネチャを満たしており、Animal型を引数に取るcryメソッドに渡すことができ、もしもCatがシグネチャを満たしていなければコンパイルに失敗します。

Goは明示的にimplementsを宣言しないため、全てのStructがどのInterfaceを満たしているかを逐一チェックしているのではないかと思われるかもしれません。M個のInterfaceに対してN個のStructがある場合、M×N回数のチェックが必要になると多くの計算量が求めれますし、ほとんどが使われないので事前にデータ構造を作成するのは効率的ではありません。

Goはこれを動的に解決しているので必要になった時のみ、itableと呼ばれる専用のデータ構造を生成し、lookupを行います。また毎回データ構造を作成するのはコストになるため、一度生成されたものはハッシュテーブルに格納され、次回からは生成済みのものが再利用されるようになっています。

Goの実装について

これらの実装はiface.goに書かれているので、簡単に内容を紹介していきます。

var (
    itabLock      mutex                               // lock for accessing itab table
    itabTable     = &itabTableInit                    // pointer to current table
    itabTableInit = itabTableType{size: itabInitSize} // starter table
)

type itabTableType struct {
    size    uintptr             // length of entries array. Always a power of 2.
    count   uintptr             // current number of filled entries.
    entries [itabInitSize]*itab // really [size] large
}

(※コードはバージョン1.13.4時点のものを掲載しています)

itableはinterfaceとそれを実装する変数のペアから生成され、itabTableType型のitableTableに格納されていきます。その中のフィールドであるentriesがハッシュテーブルの役割を果たしており、デフォルトでは512のサイズを保持しています。

まずはinterfaceと変数のペアをもとに、entriesからitableを探索します。探索方法は少し複雑ですが以下のようなステップで行われます。

  1. interfaceと実装変数、およびitableTableのサイズを使ったビット演算で初期キーを生成する
  2. entriesのポインタと組み合わせてハッシュのキーを生成する
  3. そのキーに当たる値がなければitableを新たに生成するフローに入る
  4. 既に値があり、それが現在探しているinterfaceと変数に合致すれば、キャッシュ済みの値としてそれを返す
  5. 他の値とキーが衝突している場合は、ループしながらQuadratic probingの手法で空いている番地を探していく→3に戻る

itableの生成処理

では上記の「3」のフローにおいて、itableを生成する際にどのような処理が行われているのでしょうか。

func (m *itab) init() stringという関数がこれにあたり、実際にコードを掲載しながら紹介したいのですが、分量が多いのでこの項では割愛します。興味のある方はこちらのリンクからチェックしてみてださい。

大まかに説明すると、itableはinterfaceと変数の型と、実装メソッドのポインタ配列の先頭を持ちますが、キモとなるのは実装メソッドのポインタ配列を作る部分です。

interfaceと変数が持つメソッドを参照しながら、interfaceの各メソッドにあたる実装のポインタを配列に格納していきます。(メソッドが合致するかどうかはメソッドの名前やパッケージの状態によって判別されます)

ここで保持されるのはinterfaceのメソッド分のみで、例えばinterfaceのメソッドが2つ、変数のメソッドが5つあったとしても、2つのメソッド分のポインタが格納されます。interfaceのメソッドを呼び出すときはこれを使ってディスパッチが行われることになります。

ランタイム時にinterfaceの実装をチェックしながらitableを生成し、これを使うことでlookupが行われるのがDuck typingにおける重要なポイントです。これはGoの特徴的な挙動で、開発者の記事にも「Goが初めてかどうかは分からないが、確かにこれは良くある方式ではない」と書かれています。

意外と実装方法はシンプルで、interfaceのメソッド数と変数のメソッド数の分だけループを回し、それぞれが合致すれば配列に格納します。(ただしパフォーマンス向上のためにメソッドのリストは事前ソートされています)

なおitableは実際に以下のように定義されています。

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

この中のfun [1]uintptrは、interfaceの先頭メソッドの実装にあたるポインタ(を保持する値)を格納しており、これが0であればinterfaceが正しく実装されていないことを示します。

そのためitableの生成後はfun[0] != 0のチェックを行い、もしもゼロなら生成できていないとみなすかpanicを発生させます。

itableTableへ値を追加する

一度生成したitableは、次回以降に再利用するためにitableTableのentriesに格納されます。以下のコードがそれに当たりますが、ハッシュテーブルのパフォーマンス向上のために工夫がされています。

func itabAdd(m *itab) {
    // <中略>

    t := itabTable
    if t.count >= 3*(t.size/4) { // 75% load factor
// Grow hash table.
        // t2 = new(itabTableType) + some additional entries
        // We lie and tell malloc we want pointer-free memory because
        // all the pointed-to values are not in the heap.
        t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
        t2.size = t.size * 2

        // <中略>
        t = itabTable
        // Note: the old table can be GC'ed here.
    }
    t.add(m)
}

ハッシュテーブルでは事前にデータセットが分かっている場合は完全ハッシュ関数によって衝突を防げるのですが、値が動的に追加される場合、いかに工夫してもキーの衝突を防ぐことは至難です。(これは「誕生日のパラドックス」としても知られています)

そこでキーが衝突したときの対応としてGoはOpen addressingであるQuadratic probingを採用しています。これは衝突時に同じキーを持つ値を配列で管理せずに、別の番地を探すための手法です。

load factorと呼ばれるハッシュテーブルの占拠率が低いときには高速に処理できるものの、これが高まると飛躍的にパフォーマンスが低下する性質があるため、itabTableのload factorが75%を超えたときにはサイズを増やすことで、load factorを下げてパフォーマンスを改善しています。

(初期は512のサイズが与えられており、itableを追加する前に逐次load factorをチェックして、逼迫していればサイズを増やします)

以上のフローを経てitableがentriesに格納されます。次からはハッシュ関数とOpen addressingによって格納された値を発見できるので、再度の生成&格納をスキップしてitableを返すことになります。

繰り返しになりますが、これらはランタイム時に行われ、コンパイル時はシグネチャのチェックのみを行うので、GoはDuck typingであり、Structural Typingでもあるということになります。

最後に

当初は「なぜGoはDuck typingを採用していると言われるのか」と疑問に思ったことから調べてみましたが、データ構造を調べてみるとGoがユニークな方式を採用していることが分かりました。

私自身、データ構造やアルゴリズムは勉強中の身ですが、言語実装を読んでみると実際に理論がどのように使われているのかを知ることができて殊の外楽しく学習できました。

「Goを勉強するならGo自体のソースを読んだほうが良い」と以前言われたことがあり、興味本位で初めてみましたが凄く良い勉強になったので、もしGoを勉強されている方がいれば興味を持って頂ければ幸いです。

参考資料

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

なぜGoはDuck typingを採用していると言えるのか、データ構造から詳しく紹介してみる

Goのinterfaceは明示的に実装を宣言せず、実装側がinterfaceのシグネチャを満たしているかによって型が決まるDuck typingを採用していると言われています。私は当初このような説明を聞いたときにピンと来ず、むしろそれはTypeScript等に見られるStructural Typingなのでは?と思いました。

しかし実際にデータ構造を調べて見ると、確かにDuck typingであり、かつStructural Typingでもあるという考えに至りました。

これまでなかなかGoのデータ構造に触れる機会がありませんでしたが、調べてみるとこれまで気づかなかった観点がいくつも出てきたのでぜひ紹介したいと思います。

Duck typingについて

今回の主題となる部分ですので、Duck typing(ダックタイピング)についておさらいします。

Duck typingはアナロジーである「ダックテスト」に端を発しています。(厳密な起源は不明?なようですが、達人プログラマとして知られるDave ThomasによりRubyコミュニティで生まれた概念とされています。)

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
もしもアヒルのように見え、アヒルのように泳ぎ、アヒルのように鳴くのなら、それはアヒルなのだろう

これは例えばJavaであればオブジェクトの型がコンパイル時に検証されるのに対して、Rubyではランタイム時にインターフェースを持っているかが検証されるため、柔軟にポリモーフィズム(多態性)を実現できるというものです。

class Duck
  def cry
    puts 'duck'
  end
end

class Cat
  def cry
    puts 'cat'
  end
end

def cry(obj)
  obj.cry
end

cry(Duck.new) # -> cry
cry(Cat.new) # -> cat

上記のRubyのコードはDuckとCatとで共通のスーパークラスを持たないクラスのインスタンスですが、cryメソッド内ではどちらも同じように鳴かせることができました。

これがJavaであれば明示的にimplementsを宣言するか、共通のスーパークラスを持つ必要がありますが、Duck typingにおいては実装側に明示的な宣言がないのが特徴です。どのクラスに属しているかというよりも、オブジェクトが実際にどのような性質を持っているかに注目するという点で、まさに「アヒルのように見えればアヒルである」というアナロジーにマッチしています。

Nominal TypingStructural Typing

本題に入る前にもう少し型システムについて説明します。型システムにおいてNominal TypingStructural Typingという分類があります。

Nominal Typingの「Nominal」とは「名目上の」と言う意味で、型の内部構造よりもその型が何であるかによって区別します。対してStructual Typingはその名の通り、型の構造によって区別されるものです。

文字での説明よりも実際にコードを見たほうがわかりやすいので、まずはNominal TypingとしてJavaのコードを以下に示します。

// Javaのサンプルコード
class FirstName {
    String value;

    FirstName(String value) {
        this.value = value;
    }
}

class LastName {
    String value;

    LastName(String value) {
        this.value = value;
    }
}

void callName(FirstName name) {
    System.out.println(name.value);
}

callName(new FirstName("takeshi")); // OK
callName(new LastName("gouda")); // Error!

FirstNameLastNameはどちらのメンバ関数を持たず、valueというプロパティがあり、同一の内部構造を持ったクラスです。しかしFirstNameを引数に取る関数に対しては、FirstNameのインスタンスしか受け付けないという性質を持っています。もしもSecondClassのインスタンスを利用したい場合は、FirstClassのコンストラクタを通してからでないとコンパイルが通りません。

対してStructural Typingの性質を持つTypeScriptでは異なるクラスのオブジェクトでも、内部構造が同じであればエラーが検出されません。(※厳密にはJavaScriptのクラスは糖衣構文ですが、ここではその説明は割愛します)

// TypeScriptのサンプルコード
class FirstName {
  value: string;

  constructor(value: string) {
    this.value = value;
  }
}

class LastName {
  value: string;

  constructor(value: string) {
    this.value = value;
  }
}

const callName = (name: FirstName) => console.log(name.value);

callName(new FirstName("takeshi")); // OK
callName(new LastName("gouda")); // こっちもOK!

Nominal Typingに慣れている方からすれば不思議に感じるかもしれませんが、 TypeScriptでは型の構造に着目するため、異なるクラスのインスタンスであってもコンパイルが通るのです。

お気づきの方がいるかもしれませんが、Duck typingとStructural Typingはとても似ています。実際にTypeScriptを使ってDuck typingらしいコードを書いてみましょう。

type Duck = {cry: () => void, swimSpeed: number};
type Cat = {cry: () => void};

const cry = (obj: {cry: () => void}) => obj.cry();

const duck: Duck = {cry: () => console.log('Duck'), swimSpeed: 2.0};
const cat: Cat = {cry: () => console.log('Duck')};

cry(duck);
cry(cat);

Duck型とCat型の異なるタイプを持つオブジェクトですが、cryの型構造が同じであるために同一の関数に渡すことができました。

オブジェクトの振る舞いに注目しているという点で実質的にDuck typingに見えますが、Duck typingはランタイム時に動的にアクセスされるのに対して、Structural Typingは静的な型システムである点で異なります。ただしDuck typingとStructural Typingは対立する概念ではないので、明確に区別する必要があるかというと考えものですが。

GoにおけるStructural Typing

冒頭で触れた通り、Goは明示的な宣言を必要とせず、実装側がinterfaceのシグネチャを満たすことでポリモーフィズムを実現するので、Structural Typingの性質を持っています。

type Duck interface {
    cry()
}

type Cat struct {
}

func (c Cat) cry() {
    fmt.Println("Cat")
}

func cry(obj Duck)  {
    obj.cry()
}

func main() {
    cry(Cat{})
}

InterfaceのDuckはcry()というメンバ関数を持ち、本来Duckとして扱うべきものではなくても、CatはDuckのシグネチャを満たすことでDuckとして扱うことができます。

RubyやPythonといった動的型付け言語と異なりシグネチャのチェックはコンパイル時に行われるので、もしもCatにcryメソッドがなければコンパイルに失敗します。そのため静的な型付けという面でStructural Typingの性質を持つと言えます。

ではなぜGoはDuck typingと言われるのかと疑問に思うかもしれませんが、それは後ほど説明します。

Nominal TypingとStructural Typingの両立

Nominal TypingとStructural Typingを対立構造のように紹介したままでは、誤解を招くかもしれないと思い、少し補足的な説明を加えさせて頂きます。

NominalとStructuralは言語につきどちらか一方しか実現できないわけではなく、Goはいずれの性質を持っており、他にもScalaやTypeScrip等でも実現可能です。

// Goのサンプルコード
type Duck struct {
    cry func()
}

type Cat struct {
    cry func()
}

func cry(obj Duck) {
    obj.cry()
}

func main() {
    cry(Duck{}) // OK
    cry(Cat{}) // NG!
}

InterfaceではなくStructを利用する場合はNominalに型がチェックされるため、同じ構造でもコンパイル時にエラーが検出されます。むしろポリモーフィズムを意識しない場合、多くはNominalにチェックされるでしょう。

ちなみにTypeScriptはNominal Typingを言語機能としてサポートして欲しいという意見が長らく出ていますが、実装者が少し工夫をすることでNominalを実現することができます。例えばDDDを実践したいといった場合には有用になるでしょう。

なぜGoはDuck typingと言われるのか

ここまでDuck typingはランタイム時、Structural Typingは静的な型システムであると説明しました。そしてGoはコンパイル時にシグネチャがチェックされるためStructural Typingでありながら、なぜDuck typingとも言えるのでしょうか。

それはInterfaceによるポリモーフィズムを実現する際、Goはitable(Interface table)に動的にデータ構造を生成するという特徴を持っているためです。

例えばC++ではVirtual method table(vtable)と呼ばれるlookupのためのデータ構造をコンパイル時に作成するのに対して、Goはシグネチャのみを事前にチェックし、lookupは動的に行われるという点で異なります。これはアプリケーションレベルのコードを書いているときにはあまり意識されないことでピンと来ないかもしれませんので、詳しく追っていきましょう。

Virtual method table(vtable)について

Goのitableに入る前に、まずはvtableについて説明します。

vtableは通常、Subtypingによるポリモーフィズムのため、動的なディスパッチを実現するためのものです。具体的にはあるクラスがスーパークラスを継承している場合、そのクラスのメンバ関数がどちらに属するものなのかを通常コンパイル時に解決します。

// Javaのサンプルコード
class Animal {
    void cry() {
        System.out.println("cat");
    }

    void swim() {
        System.out.println("swim");
    }
}

class Cat extends Animal {
    @Override
    void cry() {
        System.out.println("cat");
    }
}

Animal cat = new Cat();
cat.cry(); // -> Catクラスのcryが呼ばれる
cat.swim(); // -> Animalクラスのswimが呼ばれる

上のサンプルコードでは、CatのインスタンスをAnimalクラスとして宣言しています。これはポリモーフィズムのうちSubtyping(部分型付け)と呼ばれ、派生型も継承元の型として扱うことができる性質によるものです。

Catクラスのインスタンスを通じてcryswimを呼んでいますが、cryはCatクラスの実装が、swimはAnimalクラスの実装が呼ばれます。これはコンパイル時にvtableと呼ばれるデータ構造を生成し、cryの場合はCat->cryのポインタを、swimの場合はAnimal->swimのポインタを保持することで実現されます。

Goではランタイム時にディスパッチが動的に行われる

Goの場合もvtableによるディスパッチの仕組みは同じです。しかし静的型付け言語では上記のようなディスパッチは通常コンパイル時に解決されるのに対して、Goではランタイム時に行われます。そしてこれがDuck typingと言われる理由だと解釈できます。実際にGoのDeveloperであるIan Lance Taylorの記事を見ても分かります。

This is not pure duck typing, because when possible the Go compiler will statically check whether the type implements the interface. However, Go does have a purely dynamic aspect, in that you can convert from one interface type to another. In the general case, that conversion is checked at runtime.

(訳: これは純粋なダックタイピングではありません。なぜならGoのコンパイラは可能なら型がインターフェースを実装しているかを静的に解釈しようとするからです。一方でGoは純粋に動的な側面もあり、あるインターフェースから別のものに変換する際、ランタイム時に動的に変換が行われます。)

それでは実際にサンプルコードを使ってこの動きを説明していきます。

type Animal interface {
    cry()
}

type Cat struct {
}

func (c Cat) cry() {
    fmt.Println("cat")
}

func (c Cat) swim() {
    fmt.Println("swim")
} 

func cry(obj Animal) {
    obj.cry()
}

func main() {
    cry(&Cat{})
}

CatはAnimal型のシグネチャを満たしており、Animal型を引数に取るcryメソッドに渡すことができ、もしもCatがシグネチャを満たしていなければコンパイルに失敗します。

Goは明示的にimplementsを宣言しないため、全てのStructがどのInterfaceを満たしているかを逐一チェックしているのではないかと思われるかもしれません。M個のInterfaceに対してN個のStructがある場合、M×N回数のチェックが必要になると多くの計算量が求めれますし、ほとんどが使われないので事前にデータ構造を作成するのは効率的ではありません。

Goはこれを動的に解決しているので必要になった時のみ、itableと呼ばれる専用のデータ構造を生成し、lookupを行います。また毎回データ構造を作成するのはコストになるため、一度生成されたものはハッシュテーブルに格納され、次回からは生成済みのものが再利用されるようになっています。

Goの実装を紹介

これらの実装はiface.goに書かれているので、簡単に内容を紹介していきます。

var (
    itabLock      mutex                               // lock for accessing itab table
    itabTable     = &itabTableInit                    // pointer to current table
    itabTableInit = itabTableType{size: itabInitSize} // starter table
)

type itabTableType struct {
    size    uintptr             // length of entries array. Always a power of 2.
    count   uintptr             // current number of filled entries.
    entries [itabInitSize]*itab // really [size] large
}

(※コードはバージョン1.13.4時点のものを掲載しています)

itableはinterfaceとそれを実装する変数のペアから生成され、itabTableType型のitableTableに格納されていきます。その中のフィールドであるentriesがハッシュテーブルの役割を果たしており、デフォルトでは512のサイズを保持しています。

まずはinterfaceと変数のペアをもとに、entriesからitableを探索します。探索方法は少し複雑ですが以下のようなステップで行われます。

  1. interfaceと実装変数、およびitableTableのサイズを使ったビット演算で初期キーを生成する
  2. entriesのポインタと組み合わせてハッシュのキーを生成する
  3. そのキーに当たる値がなければitableを新たに生成するフローに入る
  4. 既に値があり、それが現在探しているinterfaceと変数に合致すれば、キャッシュ済みの値としてそれを返す
  5. 他の値とキーが衝突している場合は、ループしながらQuadratic probingの手法で空いている番地を探していく→3に戻る

itableの生成処理

では上記の「3」のフローにおいて、itableを生成する際にどのような処理が行われているのでしょうか。

func (m *itab) init() stringという関数がこれにあたり、実際にコードを掲載しながら紹介したいのですが、分量が多いのでこの項では割愛します。興味のある方はこちらのリンクからチェックしてみてださい。

大まかに説明すると、itableはinterfaceと変数の型と、実装メソッドのポインタ配列の先頭を持ちますが、キモとなるのは実装メソッドのポインタ配列を作る部分です。

interfaceと変数が持つメソッドを参照しながら、interfaceの各メソッドにあたる実装のポインタを配列に格納していきます。(メソッドが合致するかどうかはメソッドの名前やパッケージの状態によって判別されます)

ここで保持されるのはinterfaceのメソッド分のみで、例えばinterfaceのメソッドが2つ、変数のメソッドが5つあったとしても、2つのメソッド分のポインタが格納されます。interfaceのメソッドを呼び出すときはこれを使ってディスパッチが行われることになります。

ランタイム時にinterfaceの実装をチェックしながらitableを生成し、これを使うことでlookupが行われるのがDuck typingにおける重要なポイントです。これはGoの特徴的な挙動で、開発者の記事にも「Goが初めてかどうかは分からないが、確かにこれは良くある方式ではない」と書かれています。

意外と実装方法はシンプルで、interfaceのメソッド数と変数のメソッド数の分だけループを回し、それぞれが合致すれば配列に格納します。(ただしパフォーマンス向上のためにメソッドのリストは事前ソートされています)

なおitableは実際に以下のように定義されています。

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

この中のfun [1]uintptrは、interfaceの先頭メソッドの実装にあたるポインタ(を保持する値)を格納しており、これが0であればinterfaceが正しく実装されていないことを示します。

そのためitableの生成後はfun[0] != 0のチェックを行い、もしもゼロなら生成できていないとみなすかpanicを発生させます。

itableTableへ値を追加する

一度生成したitableは、次回以降に再利用するためにitableTableのentriesに格納されます。以下のコードがそれに当たりますが、ハッシュテーブルのパフォーマンス向上のために工夫がされています。

func itabAdd(m *itab) {
    // <中略>

    t := itabTable
    if t.count >= 3*(t.size/4) { // 75% load factor
// Grow hash table.
        // t2 = new(itabTableType) + some additional entries
        // We lie and tell malloc we want pointer-free memory because
        // all the pointed-to values are not in the heap.
        t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
        t2.size = t.size * 2

        // <中略>
        t = itabTable
        // Note: the old table can be GC'ed here.
    }
    t.add(m)
}

ハッシュテーブルでは事前にデータセットが分かっている場合は完全ハッシュ関数によって衝突を防げるのですが、値が動的に追加される場合、いかに工夫してもキーの衝突を防ぐことは至難です。(これは「誕生日のパラドックス」としても知られています)

そこでキーが衝突したときの対応としてGoはOpen addressingであるQuadratic probingを採用しています。これは衝突時に同じキーを持つ値を配列で管理せずに、別の番地を探すための手法です。

load factorと呼ばれるハッシュテーブルの占拠率が低いときには高速に処理できるものの、これが高まると飛躍的にパフォーマンスが低下する性質があるため、itabTableのload factorが75%を超えたときにはサイズを増やすことで、load factorを下げてパフォーマンスを改善しています。

(初期は512のサイズが与えられており、itableを追加する前に逐次load factorをチェックして、逼迫していればサイズを増やします)

以上のフローを経てitableがentriesに格納されます。次からはハッシュ関数とOpen addressingによって格納された値を発見できるので、再度の生成&格納をスキップしてitableを返すことになります。

繰り返しになりますが、これらはランタイム時に行われ、コンパイル時はシグネチャのチェックのみを行うので、GoはDuck typingであり、Structural Typingでもあるということになります。

最後に

当初は「なぜGoはDuck typingを採用していると言われるのか」と疑問に思ったことから調べてみましたが、データ構造を調べてみるとGoがユニークな方式を採用していることが分かりました。

私自身、データ構造やアルゴリズムは勉強中の身ですが、言語実装を読んでみると実際に理論がどのように使われているのかを知ることができて殊の外楽しく学習できました。

「Goを勉強するならGo自体のソースを読んだほうが良い」と以前言われたことがあり、興味本位で初めてみましたが凄く良い勉強になったので、もしGoを勉強されている方がいれば興味を持って頂ければ幸いです。

参考資料

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

Golangでhttp通信をやってみる

はじめに

GolangにはまったときにCodewarsで遊んでました。
楽しかったのですが、そのあとSwiftC#http通信やったのでせっかくなのでGolangでもやってみようかと思います。

開発環境

  • Windows 10 Home 64bit
  • Visual Studio code 1.41.1
  • go version go1.13.6 windows/amd64

開発環境構築

開発環境の構築は以下の記事を参考にさせていただきました。
本当につまるところなく動いたのですごいなぁと思います。

VSCodeでGo言語の開発環境を構築する

Http通信してみよう

前回の記事Swiftでやってみたサイトにアクセスしてみます。

Packegeを調べる

公式のドキュメントが優秀ですね。
ちゃんと英語版で読みましょう(助けてGoogle翻訳先生!)

Package http

httpsを使い場合は、Transportを設定する必要あるようです。
また、Go 1.6以降はHTTP/2に対応しているようですね。

やってみよう

試したコードはこちらになります。

main.go
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    url := "http://md5.jsontest.com/?text="
    url += "examble"
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("handle error")
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}

このサイトwebAPIなのかな?にアクセスしてテキストのMD5を出してくれるそうです。
結果はPrintlnで出力しています。stringに変換してあげないとRuneのリストが出力されるので全くわかりません。

最後に

久しぶりにGolang使いましたがやはり楽しいですね。
本当はM5stackwebAPI使ってTwitterにアクセスしたりしてみたかったのですが、どうやらHTTPSのライブラリで証明書を更新しないで読み込むので中間サーバー傍受ができるとかなんとか書いてたので今停滞中です・・・
とりあえずマイコンだろうがGolangだろうがライブラリの使い方次第なのでまずはGolangで使えるようにしようかなと思います。

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

Go言語とginで作ったHTTPサーバをHeroku上で動かしてみる

はじめに

Goとginで作ったHTTPサーバをHeroku上で動かすまでの試行錯誤をまとめてみました。
試行錯誤の結果、ゴールへたどり着いていますので、間違いがあると思います。
いまだに深くは理解ができていません。
参考程度にどうぞ。

最低限どういった手順で進めればよいかだけを見たい場合はまとめを見ていただければと思います。

環境

  • OS: windows 10 Home
  • Go: 1.13.6
  • gin: 1.5.0
  • Git: 2.25.0
  • Text Editor: Atom

ローカル環境でのgo run main.goはコマンドプロンプトより行います。
ginは、go get -u github.com/gin-gonic/ginでローカル環境へ導入済みとします。

ローカル環境でのテスト

HTML をレンダリングする』を参考にmain.goとtemplates/index.tmplを用意します。
また、フォルダ構成は以下です。

soitech-go-gin-test/
 ├ templates/
 │ └ index.tmpl
 └ main.go

index.tmpl
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>{{ .title }}</title>
    </head>
    <body>
        <h1>{{ .h1 }}</h1>
    </body>
</html>
main.go
package main

import(
    "net/http"
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()
    router.LoadHTMLGlob("templates/*")
    router.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
            "title": "/",
            "h1": "/",
        })
    })
    router.GET("/hoge", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
            "title": "hoge",
            "h1": "/hoge",
        })
    })
    router.GET("/huga", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
            "title": "huga",
            "h1": "/huga",
        })
    })
    router.Run()
}

コマンドプロンプトよりgo run main.goを実行し、ブラウザからlocalhost:8080localhost:8080/hogeおよびlocalhost:8080/hugaにアクセスし、プログラムが動いていることを確認します。

Herokuへデプロイする

さあ!Herokuへデプロイします!
私はGitHubへファイルをコミットしたら自動的にHeroku上でデプロイされるようにしていますので、GitHubへファイルをコミットします。

-----> App not compatible with buildpack: https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/go.tgz
       More info: https://devcenter.heroku.com/articles/buildpacks#detection-failure
 !     Push failed

さあ、エラーが出ました!
インターネットの海で色々と調べてみると、Procfilevendor/vendor.jsonいうものが必要らしい...
用意してみます。

procfile
web: soitech-go-gin-test
vendor.json
{
    "rootPath": "github.com/soichiloYoda/soitech-go-gin-test"
}

コミットして、自動デプロイされると、またもやエラーです。

-----> Running: go install -v -tags heroku . 
main.go:5:2: cannot find package "github.com/gin-gonic/gin" in any of:
    /tmp/tmp.9lmb10Wear/.go/src/github.com/soichiloYoda/soitech-go-gin-test/vendor/github.com/gin-gonic/gin (vendor tree)
    /app/tmp/cache/go1.12.12/go/src/github.com/gin-gonic/gin (from $GOROOT)
    /tmp/tmp.9lmb10Wear/.go/src/github.com/gin-gonic/gin (from $GOPATH)
 !     Push rejected, failed to compile Go app.
 !     Push failed

どうやらginのimportがうまくいっていないようです。
またインターネットの海で色々調べてみると、packageの管理にはGo言語1.11より標準で使えるようになったModulesを使うようです。

こちらのページ→『Go言語の依存モジュール管理ツール Modules の使い方』を参考にさせていただきました。
プロジェクトフォルダへ移動し、vendor/vendor/vendor.jsonを削除してgo mod init soitech-go-gin-testをし、go.modを生成します。

go.mod
module soitech-go-gin-test

go 1.13

その後、go buildを実行するとgo.sumが生成され、go.mod内に依存モジュールの情報が記述されます。

go.mod
module soitech-go-gin-test

go 1.13

require github.com/gin-gonic/gin v1.5.0

GitHub上からもvendor/vendor/vendor.jsonを削除してからgo.modをコミットします。
Herokuで自動デプロイされ、ようやくBuild Succeededとなりました。
ブラウザからsoitech-go-gin-test.herokuapp.comへ接続すると、無事HTTPサーバが動作していることが確認できました。
image.png
また、soitech-go-gin-test.herokuapp.com/hogesoitech-go-gin-test.herokuapp.com/hugaへも無事接続することができました。
image.png
image.png

ちなみにですが、Procfileを以下のように記述してデプロイしましたが、Procfileはなくてもデプロイできるようです。

Procfile
web: soitech-go-gin-test // <- おそらく間違い

また、Heroku上のBuild Logを見るに、Procfileは以下のように記述するのが正解なようです。

Build_Log
Created a Procfile with the following entries:
            web: bin/soitech-go-gin-test
Procfile
web: bin/soitech-go-gin-test // <- おそらく正解

Procfileなしでデプロイすることができましたが、Heroku上のBuild Logに以下のように表示されるため、Procfileは自分で作成しておいたほうがよさそうです。

Build_Log
If these entries look incomplete or incorrect please create a Procfile with the required entries.
       See https://devcenter.heroku.com/articles/procfile for more details about Procfiles

まとめ

試行錯誤してみましたが最低限以下の手順を踏めばよさそうです、といったまとめです。

1.プロジェクトディレクトリを作成する

 mkdir PROJECT_NAME

2.プロジェクトディレクトリ内でmodファイルを作成する

 go mod init PROJECT_NAME

3.main.gotemplates/index.tmplを用意してから、一度ビルドしてmodファイルに依存モジュールの情報を記述させる。

 go build

4.以下のファイルをGitHubへコミットして、Heroku上にデプロイする。

  • templates/index.tmpl
  • go.mod
  • main.go

さいごに

とりあえず、Go言語で作成したHTTPサーバをHeroku上で動かすことができました、うれしいです。
これを元に、FormからPOSTできるものを作ってみようと思います。

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

Protocol Buffersの後方互換性を覗いてみる

Protocol Buffersではメッセージのスキーマを変更する際に、後方互換性を損なうことなく新しいフィールドを追加することができます。例えば以下のようなmessageがあったとします。

message User {
    required string name = 1;
    required int32 age = 2;
    required string address = 3;
}

これをv1とした時に、以下のようにparents情報を追加したv2を作成するとします

message User {
    required string name = 1;
    required int32 age = 2;
    required string address = 3;
    repeated User parents = 4;
}

v1でシリアライズされたUserのバイナリデータが、v2のデータ構造に対してデシリアライズされるときにはparentsフィールドに対応するデータは無視されます。同様にv2でシリアライズされたバイナリデータが、v1のデータ構造に対してデコードされる場合には、v2parentsに対応するバイト列は無視されるため、エラーを発生させずにv2のバイナリデータをv1のデータ構造にマッピングことができます。
(この時無視されたデータがどうなるかは後述します)

文章だけだとわかりにくいのでコードで解説していきます。

v1からv2への変換

protocを使って、上記のv1, v2のprotoファイルからGoのコードを生成します。
そして、v1Userにデータを詰め込んで、バイナリ化します。

u1 := &v1.User{
    Name:    "Tanake",
    Age:     30,
    Address: "Japan",
}

p, err := proto.Marshal(u1)
if err != nil {
    panic(err)
}

fmt.Println("---version1---")
fmt.Printf("binary: %d\n", b)
//---version1---
//binary: [10 6 84 97 110 97 107 101 16 30 26 5 74 97 112 97 110]

バイナリ化したデータを今度はv2のデータ構造にデコードします。

    u2 := &v2.User{
        Name:    "Tanake",
        Age:     5,
        Address: "Japan",
        Parents: []*v2.User{
            {
                Name:    "Tanaka",
                Age:     30,
                Address: "Japan",
            }},
    }

    b, err := proto.Marshal(u2)
    if err != nil {
        panic(err)
    }

    fmt.Println("---version2---")
    fmt.Printf("hex: %x\n", b)
    fmt.Printf("binary: %d\n", b)
    //---version2---
    //hex: 0a0654616e616b6510051a054a6170616e22110a0654616e616b65101e1a054a6170616e
    // binary: [10 6 84 97 110 97 107 101 16 5 26 5 74 97 112 97 110 34 17 10 6 84 97 110 97 107 101 16 30 26 5 74 97 112 97 110]

    p := new(v1.User)
    err = proto.Unmarshal(b, p)
    if err != nil {
        panic(err)
    }
    fmt.Println("---version1---")
    fmt.Printf("%+v\n", p)

    f, err := proto.Marshal(p)
    if err != nil {
        panic(err)
    }
    fmt.Printf("binary: %d\n", f)
    //---version1---
    // name:"Tanake" age:5 address:"Japan" 4:"\n\x06Tanake\x10\x1e\x1a\x05Japan"
    //binary: [10 6 84 97 110 97 107 101 16 5 26 5 74 97 112 97 110 34 17 10 6 84 97 110 97 107 101 16 30 26 5 74 97 112 97 110]

v2のバイナリデータからv1のデータ構造にデコードできることが確認できました。

v1とv2の差分の行方

v2にしかないparentsフィールドは、v1にデコードしたタイミングでなくなってしまいましたが、代わりに何やら元のデータっぽいものがv1のデータの中に見えています。(4:"\n\x06Tanake\x10\x1e\x1a\x05Japan"の部分。)

これは、protoのMessageからGoのStructを生成した時に自動で付与されるXXX_unrecognizedというフィールドの値です。
protocol buffersは上記のように後方互換性を保っていますが、そのなかでv2 -> v1のようにデータの欠損が起きてしまうようなケースもあるので、データ構造にマッピングさせられなかったタグのフールドなどは、XXX_unrecognizedに格納されるようになっているようです。
XXX_unrecognizedの中身はv2User.parentsに対応するバイト列なので、デコードすることももちろん可能です。

    other := new(v2.User)
    proto.Unmarshal(p.XXX_unrecognized, other)
    fmt.Printf("%+v\n", other)
    // parents:<name:"Tanake" age:30 address:"Japan" >

実際にmarhal, unmarhal時にどうなっているかを知りたい場合は以下の処理をみてみると良いかと思います

marshal

https://github.com/golang/protobuf/blob/master/proto/table_unmarshal.go#L134

unmarshal

https://github.com/golang/protobuf/blob/master/proto/table_unmarshal.go#L134

参考

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

golangでエラー出力

e.go
package main

import (
        "fmt"
        "os"
)

func main() {
        fmt.Println("stdout")
        fmt.Fprintln(os.Stderr, "stderr")
}
$ go run e.go
stdout
stderr
$ go run e.go 2>/dev/null
stdout
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoとRedisでAPIを実装する (Part.1)

はじめに

本記事では、Go 言語と Redis を使用し簡単なAPIの作成手順について説明する。docker-compose を使って実装していく為、環境に依存しない開発環境で実装する。効率的に開発が行える様、開発の中でいくつかの工夫を施しているので参考になれば幸いである。

また、今回は記述量が多くなるので以下の2部構成にした。

  • Part.1 : 環境構築 ~ 動作確認
  • Part.2 : API実装 ~ 実装検証

本記事では環境構築から動作確認までを説明する。

初めて実装していく方の為に、実行環境と予備知識としていくつか説明する。

1. 実行環境

  • macOS Catalina Ver.10.15.2
  • Docker version 19.03.5, build 633a0ea
  • docker-compose version 1.24.1, build 4667896b
  • golang 1.13.6
  • Redis 5.0.7

【補足①】

  • Go言語
    • 2009年、Googleによって開発されたオープンソース(OSS)のプログラミング言語。
    • 特徴として、「高速コンパイル」、「シンプルな記述」が挙げられる。

【補足②】

  • Redis
    • キーバリュー型(KVS)の NoSQL の一種。
    • キーと5種類の値型の対応関係を格納ができる。
    • インメモリデータベースであるため、高速なデータへのアクセスが可能である。

【補足③】

  • Docker
    • コンテナ型の仮想化ツール。
    • 従来のハイパーバイザ型(Hyper-V)、ホスト型(VMWareやVirtualBoxなど)に比べ軽量で、且つ利便性が高い。
    • docker-compose を使用する事で、複数のコンテナを組み合わせて1つのアプリケーションを構成できる。

2. 前提条件

今回、実装するに至って用意するべき事項は以下の2点である。

  • PC内に docker、docker-composeがインストールされている
  • Goの開発環境が整備されている

docker、docker-compose が動作する環境があれば、特必要となるものは無い。コードを書く際は、好みのエディター (Atom、VSCodeなど)を使用すると良い。Goの開発環境に関しては、開発を進める上でgo.modgo.sumを事前に作成しておく必要がある為、インストールしている。

Goの開発環境に関しては、Homebrewを使用した環境構築がオススメである。以下にインストール手順を示しておく。

$ brew install go

尚、今回使用するものが正常にインストールされているかどうかを確認したい場合は、以下のコマンドで確認が可能である。

# Goの確認
$ go version
go version go1.13.6 darwin/amd64

# dockerの確認
$ docker version
Client: Docker Engine - Community
 Version:           19.03.5
 API version:       1.40
...

# docker-composeの確認
$ docker-compose version
docker-compose version 1.24.1, build 4667896b
docker-py version: 3.7.3
...

3. 環境構築

3.1. ファイル構成

今回のファイル構成は以下の通りである。

sample_project
├── docker
│   ├── api
│   │   └── Dockerfile-api //API用
│   └── database
│       └── Dockerfile-redis //DB用
├── docker-compose.yml
└── src
    └── app
        ├── controller
        │   ├── sender.go
        │   └── receiver.go
        ├── infrastructure
        │   └── database.go
        ├── interface
        │   └── user.go
        └── main.go

今回は個々の処理を各ディレクトリに分けた。構成に関しては様々な設計思想があると思うので、それに基づいて開発を行う場合は、はじめにファイル構成などを考えてから構築して行くと良い。

3.2. Go Modulesの作成

GoではGo Modulesと呼ばれる外部パッケージの管理システムがある。今回はそれを使用する為、事前にmain.goが配置されているディレクトリで以下のコマンドを実行し、go.modgo.sumを作成しておく。

# appディレクトリまで移動
~/sample_project $ cd src/app
# go.modファイルを作成する
~/sample_project/src/app $ go mod init main.go

# go build コマンド、または go mod download コマンドのどちらかを実行する
~/sample_project/src/app # go build
~/sample_project/src/app # go mod download

次に、Dockerfileの作成する。

3.3. Dockerfileの作成

3.3.1. Dockerfile (API)

まず、API用のDockerfileを作成していく。API側のDockerfileではGoの環境を構築していく。開発環境ではソースコードが随時更新される事を考慮し、ホットリロード環境を導入するためにfreshと呼ばれるライブラリを使用した。

Dockerfile-api
###############################
# Builder Container
###############################
FROM golang:1.13.6 AS builder

# Go Moduleが使えるようにする
ENV GO111MODULE=on

WORKDIR /go/src/app/

# GOROOTが/goになるため
COPY src/app /go/src/app

# gormとginのドライバをインストールする
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y git && \
    go mod download && \
    go get github.com/pilu/fresh && \

# ホットリロードを実行する
CMD ["fresh"]

###############################
# Production Container
###############################
FROM golang:1.13.6-alpine AS production
COPY --from=builder /go/src/app/ /app

上記の様に、開発環境と本番環境とで環境を分ける為、マルチステージビルドしても良い。マルチステージビルドを使用する事で、可読性、保守性を保つ事ができる。また、逐一Dockerfileを最適化するために書き直す面倒くささも解消できる。

尚、環境を分割する必要のない場合、以下の記述例でも良い。

Dockerfile-api-slim
FROM golang:1.13.6-alpine

ENV GO111MODULE=on

WORKDIR /go/src/app/

COPY src/app /go/src/app

RUN apk add --no-cache --virtual alpine-sdk git && \
    go mod download && \
    go get github.com/pilu/fresh

CMD ["fresh"]

alpineを使用する事で、イメージが軽量化されるので、ビルド時間の短縮にもなる。しかし、RUNコマンドの記述が上記とは異なる為、注意してほしい。

alpine : Alpine Linuxと呼ばれる、BusyBoxとmuslをベースにしたLinuxディストリビューションを指す

今回は、簡単なAPIの実装なのでDockerfile-api-slim(alpineの方)を使用して開発を行う。

3.3.2. Dockerfile (Database)

DB用のDockerfileを作成していく。DB側のDockerfileではRedisの環境を構築していく。
こちら側では今回、特に設定をする事はないので下記のような記述で良い。

Dockerfile-database
FROM redis:5.0.7-alpine

Dockerfileの作成は以上である。

3.4. docker-compose.yml

次に、docker-compose.ymlの作成を行う。

docker-compose.yml
version: '3.7'
services:
  api:
    build:
      context: .
      # マルチステージビルドを使用しない場合、targetは不要
      target: builder
      dockerfile: ./docker/api/Dockerfile-api
    container_name: sample_project-api
    image: sample_project/api:0.1.0
    ports:
      - "8080:8080"
    # ホストOSとコンテナ内でソースコードを共有
    volumes:
      - ./src/app:/go/src/app
    depends_on:
      - redis
    tty: true

  redis:
    build: 
      context: .
      dockerfile: ./docker/database/Dockerfile-database
    container_name: sample_project-redis
    image: sample_project/redis:0.1.0
    ports:
      - "6379:6379"
    restart: always
    volumes:
      - ./docker/database:/data

今回の実装の場合、DB側のコンテナの起動が完了してからAPIのコンテナを起動させたいので、
depends_onでコンテナの作成順序を依存関係を決めている。

また、tty:trueを追記する事でコンテナを常時起動させる。ポート番号に関しては、デフォルトの80806379をそれぞれ設定している。

マルチステージビルドを採用している場合は、targetの箇所で、自分がビルドしたいステージを指定すると良い。

3.5. main.go

コンテナのイメージを作成後に正しく環境構築されているかどうかを確認するために、main.goの作成をしておく。ここでは、簡単に作成し、後ほどAPIの開発のために改修していく。

以下に作成したソースコードを示す。

main.go
package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()
    router.GET("/", func(context *gin.Context) {
        context.JSON(200, gin.H{
            "message": "Hello, World!!",
        })
    })
    router.Run(":8080")
}

今回は、GoのWebフレームワークの1種である、Gin を使用した。処理の内容としては、http://localhost:8080/へアクセスすることでHello, World!!とメッセージが返ってくるだけの簡単なものである。

3.6. dockerイメージの作成

Dockerfile, docker-compose.ymlを作成後、以下のコマンドを実行し dockerイメージを作成する。

~/sample_project $ docker-compose up -d

実行後、イメージができているかどうかを以下のコマンドを実行し、確認する。

$ docker images
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
sample_project/redis   0.1.0               b68707e68547        9 days ago          29.8MB
redis                  5.0.7-alpine        b68707e68547        9 days ago          29.8MB
sample_project/api     0.1.0               f8d6549bee6c        24 minutes ago      449MB
golang                 1.13.6-alpine       9954d1348cd8        10 days ago         359MB

3.7. コンテナの起動 / 動作確認

イメージの作成後、以下のコマンドを実行しコンテナを起動させる。

$ docker-compose up

# docker-composeコンテナ一覧の確認
$ dokcer-compose ps
       Name                      Command               State           Ports         
--------------------------------------------------------------------------------------
sample_project-api     fresh                            Up      0.0.0.0:8080->8080/tcp
sample_project-redis   docker-entrypoint.sh redis ...   Up      0.0.0.0:6379->6379/tcp

コンテナを起動後、http://localhost:8080/ へアクセスするか、またはcurlコマンドを実行する事で確認できる。

$ curl localhost:8080
{"message":"Hello, World!!"}

また、redisの接続確認は以下のコマンドの通りである。

# コンテナ内部に入る
$ docker exec -it [CONTAINER ID] sh
# redis-cliの起動
/data # redis-cli
# 以下のIPアドレスに入れればOK
127.0.0.1:6379>

以上まで確認できれば、一通りの環境構築、動作検証は終了である。

4. まとめ

今回は環境構築から動作確認までを行った。今回の構成を真似しなくとも、ホットリロード環境は便利なので今後参考にして頂ければと思う。

次回は、この続きでAPIの実装を行っていく。

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

IntelliJでGoアプリケーション開発の始め方

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

眺めて覚えるGo言語 その14 チーティング(ずる)特集

Go言語を書き始めて慣れないためにエラーを出してします。

たとえば必要のないimportは、

.\ex1.go:3:9: imported and not used: "fmt"
.\ex1.go:3:15: imported and not used: "reflect"
.\ex1.go:3:25: imported and not used: "os"
.\ex1.go:3:30: imported and not used: "log"

「使っていませんダメです。」的なお叱りを受けます。

よく使用するlibは、コピー&ペーストで下記のimportを行う。一行にまとめた

import ("fmt";"reflect";"os";"log";"strings";"strconv";"time")

また、これらのライブラリを使っている関数を次のように定義します。
使い方の例を下記に

// ReadFileは、ファイル読み込み
func ReadFile(fn string) string{
    buf:=make([]byte,32768)
    f:=m2s(os.Open(fn)).(*os.File) //cast
    defer f.Close()
    f.Read(buf)
    return string(buf)

}
// m2sは、ずるいことツール
// m2s mult value return to single value
func m2s(x,err interface{}) interface{}{
    // fmt.Printf("Type:%t\n",reflect.TypeOf(x))
    if err != nil {
        log.Fatal(err)
    }
    return x
}
// printは、デバック時にずるするための関数
func print(a ...interface{}){
    fmt.Println(a)
}
// typofは、デイバック時にずるするための関数
func typeof(a interface{}){
    print(reflect.TypeOf(a))
}
// Int string to int デイバック時にずるするための関数
func Int(a string) int{
    i:=m2s(strconv.Atoi(a)).(int)
    return i
}
var st time.Time
// tmnow デイバック時にずるするための関数
func tmnow() {
    if st.IsZero()==true {
        st=time.Now()
    } else {
        w:=time.Now()
        sec:=w.Sub(st).Seconds()
        print("実行時間",sec)
    }

}
// Split デイバック時にずるするための関数
func split(s string,sep string) []string {
    return strings.Split(s,sep)
}

m2sは、二つの値を返す関数を一つにするための関数です。

file, err := os.Open(`/path/to/file`) // os.Openは、fileとerrを返します。

いちいちエラー処理を書くのがめんどうなのでm2sを用意しています。m2sを利用するとcastさえすれば直接関数のパラメータにしたり、デバック時のerror logにもなります。

以下に例を示します。

ex1.go
package main

import ("fmt";"reflect";"os";"log";"strings";"strconv";"time";)

func main(){
    tmnow()

    defer tmnow()
    buf:=ReadFile("d:\\00Data\\テストutf_8.csv")
    line:=split(buf,"\n")
    for _,x:=range line{
        print(split(x,","))
    }

}
// ReadFileは、ファイル読み込み
func ReadFile(fn string) string{
    buf:=make([]byte,32768)
    f:=m2s(os.Open(fn)).(*os.File) //cast
    defer f.Close()
    f.Read(buf)
    return string(buf)

}
// m2sは、ずるいことツール
// m2s mult value return to single value
func m2s(x,err interface{}) interface{}{
    // fmt.Printf("Type:%t\n",reflect.TypeOf(x))
    if err != nil {
        log.Fatal(err)
    }
    return x
}
// printは、デバック時にずるするための関数
func print(a ...interface{}){
    fmt.Println(a)
}
// typofは、デイバック時にずるするための関数
func typeof(a interface{}){
    print(reflect.TypeOf(a))
}
// Int string to int デイバック時にずるするための関数
func Int(a string) int{
    i:=m2s(strconv.Atoi(a)).(int)
    return i
}
var st time.Time
// tmnow デイバック時にずるするための関数
func tmnow() {
    if st.IsZero()==true {
        st=time.Now()
    } else {
        w:=time.Now()
        sec:=w.Sub(st).Seconds()
        print("実行時間",sec)
    }

}
// Split デイバック時にずるするための関数
func split(s string,sep string) []string {
    return strings.Split(s,sep)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アラフォーのおっさんが一ヶ月でWebサービスを作った話

初めてWebサービスを作ってみました。
年末年始を使い、約1ヶ月程度で、どうにか公開までこぎつけられたので、振り返ってみたいと思います。

モチベーション

筆者は職業プログラマーではありませんが、将来に不安を感じ、プログラミングスキルを磨くことにしました。
「プログラマ35歳定年説」に言われる30代半ばを突き抜けた、まもなく40歳になるオッサンが、プログラミングスキルへ投資することに賭けたのです。

アラフォーになるまでの経験値

  • ユーザ系システムインテグレータに勤務
  • 数年前まではインフラ業務を担当(「VMware」とか)
  • 今は社内システム(バッケージソフトウェアで構築)の保守を担当
  • 数年前から深層学習(「TensorFlow」とか)の本を読み始めた

作り始める前(1ヶ月前)のレベル

  • 「jQuery」が少し分かる
  • 最近のWebシステムのトレンドって何?
  • フレームワークって何を使うといいの?「React」、「Vue」とか一杯あるけど
  • 「WebPack」?「Bazel」?何それ?
  • いつか「Docker」を使ってみたい
  • バックエンドはConcurrent(並行)処理に強い「Go」がいいらしい?

どんなWebサービスを作ったのか

手書き文字(カタカナ)の認識です。
深層学習ライブラリ「TensorFlow」を使った、「MNIST」等の手書き数字認識のカタカナ版になります。

[カタカナ道場]
https://katakanadojo.tokyo

si_1.pngsi_2.png

筆者には子供がいますが、この子の書くカタカナが怪しい。
(「シ」と「ツ」、「ソ」と「ン」が同じに見えるなど)

「コンピュータが読める字を書けた方がいいぞ!」とモチベーションを促し、字の上達につなげてもらう目論見で作りました。

※突貫工事で作ったので今後、手直ししていく予定です。メンテナンス中で落ちている場合があるのでご容赦ください。他人様に見せられる状態になったらソースコードもアップします。

まずは認識モデルを作る

画像認識の界隈では、「VGG-19」などの事前学習モデルを使った転移学習モデルが高精度の結果を出していますが、シンプルな畳み込み層だけで実装することにしました。

実際にご自身で試していただきたいと思いますが、雑に書いた字でも認識してくれるので、重厚なモデルは使わなくても十分かと考えます。
mo_1.png

得点は認識モデルが出力した確率を1万倍しています。
最初はパーセンテージで出力しようとしたのですが、小さい子にパーセンテージの概念は難しいようだったので整数にしました。

認識モデルが返すJSON出力はこんな感じです。

{
  predict: 'MO',
  top_3: [
    { label: 'MO', probability: 0.93235594 },
    { label: 'NE', probability: 0.05294613 },
    { label: 'SE', probability: 0.013120668 }
  ]
}

また、コンピュータにはどう見えているのかも興味深いです。
dora.png

トレーニングセットはETL文字データベースを使わせていただきました。
http://etlcdb.db.aist.go.jp/?lang=ja

Web化する

先述の認識モデルを作る際の言語は「Python」を使いました。
pythonでWeb化するには、ということで調べてみると、「Django」や「Flask」というWebフレームワークがあることがわかりました。
どちらも「WSGI」と呼ばれるインターフェースをベースにしているようなので、以下のパッケージを使って試しに書いてみました。

from wsgiref.simple_server import make_server

ここで疑問が

  • もっと速くできないか?
  • 認識モデルをメモリに常駐させておくとして、同時アクセスに耐えられるか?
  • フレームワークを使えばスレッドをうまく使ってくれるのか?

そうだ、「Go」にしよう

「TensorFlow」ライブラリは、「Python」以外の言語版APIも公開されているということで、「Go」版を調べてみました。
[TensorFlow Go API]
https://www.tensorflow.org/install/lang_go

Caution: The TensorFlow Go API is not covered by the TensorFlow API stability guarantees.

「安定性は保証しない」とあります。
これが企業の開発なら即刻、候補から外されるのでしょうが、個人の開発なので試してみることにしました。
「Go API」の詳細については、他の方が書いた記事をご覧ください。

[GoでTensorFlowのAPIを使ってみた話]
https://qiita.com/yasuno0327/items/8f8fa5629df3243347bd

結果は・・・速い。採用!!!

見た目をリッチにする

フロントエンドに着手します。
どのフレームワークを使うか、この辺の技術選定は本当に悩ましい。
筆者には遠回りしている時間がない。これから生き残るであろう技術を選びたい。
今回は「React」を使うことにしました。
理由はモバイル開発にもつなげられそうだと感じたからです。

バックボーンや目的によって違ってくるかと思います。他の方が書いた記事をご覧ください。

[ReactとVueのどちらを選ぶか]
https://qiita.com/yoichiwo7/items/236b6535695ea67b4fbe

「Material-UI」といった「UIフレームワーク」が公開されているので活用させていただきました。
クライアント側は、画像をBase64エンコードして、Ajax通信でサーバ(Goで実装)にPOSTします。

VPSを借りる

「create-react-app」コマンドで「React」アプリを作り、それをbuildすると、画面に「ZEIT Now」を紹介する出力が出てきます。

https://github.com/zeit/now

Webのフロント部分だけなら、「Zeit Now」でいいと思いました。
ドメイン、証明書まで付いていて無料。すごい。

しかし、今回は「TensorFlow」のモデルを動かすので、ある程度のリソース(CPU、メモリ)は確保したい。

NTTPC社が提供するVPSサービス「WebARENA」を使うことにしました。
https://web.arena.ne.jp/indigo/

メモリ1GBプラン、月額349円(税込)をチョイス。
「1vCPU」、「1GBメモリ」、OSは「CentOS」か「Ubuntu」を選択可能。

この辺はVPSサービスから探し始めて、わりと直感的に決めてしまった面があり、「AWS」や「Azure」といったクラウドサービスは検討していません。
時代はパブリッククラウドの流れにあるし、後になって検討すべきだったと悔いるかもしれません。でも、いいのだ。

コンテナ化する

「Ubuntu」上に「Docker」エンジンを入れます。
Dokerホストが1台だけなので「kubernetes」は無し。
(「kubernetes」がどんなものなのか興味はありましたが、今回はパス)
下記3つのコンテナを立てました。

  1. HTTPS化、証明書周り(「https-portal」)
  2. リクエストを振り分けるリバースプロキシ(「Node.js」)
  3. Base64エンコードされた画像を認識し判別結果をJOSNで返す(「Go」)

図にすると以下のようなイメージになります。
1vCPU、1GBメモリのゲストOS上で動いていることに驚嘆します。
katakanadojo_構成図.png

ドメインを取る

「ムームードメイン」で、1年契約の「katakanadojo.tokyo」を取りました。
消費税込みで「110円」でした。
「お名前.com」と「ムームードメイン」の2つを見て、安い方にしました。

かかった費用

VPS利用料が毎月349円、ドメイン契約料が毎年発生するして、ランニング費用は月額500円を切るといったところでしょうか。
個人で容易にサービスを公開できる時代になったと感じます。
このサービスをいつまで維持するかは未定です。

終わりに

ざっと振り返った流れは以上になります。
一ヶ月前のことなのに忘れかけている部分も多く、せっかく苦労して乗り越えた部分を残せていないと感じました。
もし興味を持っていただける方がいらっしゃいましたら、詳細を加筆していきたいと思います。

最後まで読んでいただき、ありがとうございました。

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