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

golangの時刻処理におけるint型の取り扱い方

はじめに

golangで簡単なタイマーをつくろうとしたときに出会いました。
timeパッケージはint型を使用するのにひと工夫が必要です。

time.Duration型

以下のようなソースを書いたところ

package main

import (
    "flag"
    "fmt"
    "strconv"
    "time"
)

func main() {
    fmt.Printf("timer start %v\n", time.Now())
    // 第一引数の値をintegerに変換する
    flag.Parse()
    second, err := strconv.Atoi(flag.Arg(0))
    if err != nil {
        fmt.Println(err)
    }

    // データは読み捨てる
    <-time.After(second * time.Second)
    fmt.Printf("timer stop %v\n", time.Now())
}

こんなエラーが出ました。

invalid operation: second * time.Second (mismatched types int and time.Duration)

time.After()ではtime.Duration型を受け付けますが, int型*Duration型は使えないみたいです。
そのため, 時刻処理にint型を使用する際にはtime.Duration()でラップしてやる必要があります。

<-time.After(time.Duration(second) * time.Second)

エラーも出なくなりましたので動かしてみましょうか。

go run timer.go 5
timer start 2019-03-10 23:26:00.38674904 +0900 JST m=+0.000227757
timer stop 2019-03-10 23:26:05.386941458 +0900 JST m=+5.000420240

ばっちり動いてますね!

さいごに

そもそもなぜタイマーをつくっていたのかというと渋川よしきさん著の「Goならわかるシステムプログラミング」という本を読んでいたためでした。
こちらの本、とてもわくわくしながら読み進められています!
golangを身につけたい方、システムプログラミングを知りたい方、是非手にとってみてはどうでしょうか?とてもおすすめです!

さて、こちらの記事が私の初めて書いた記事となります。
これからは自分が学習中に気づいたこと、そしてみなさんを共有したいことを書いていこうと思います!
それではまた近いうちにお会いしましょう!

参考

time-The Go Programming Language

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

Serverless Frameworkを使ってgoを定期実行させる

勉強がてら触っていた際のメモです。

slsのインストール

npm install serverless -g

goのテンプレート作成

sls create -u https://github.com/serverless/serverless-golang/ -p go-hatena

go modulesの初期化

go mod init go-hatena

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

go get github.com/aws/aws-lambda-go/lambda

serverless.ymlの編集

以下のような感じで.aws/credentialsを仕事用、プライベート用で分けている。

$ vim ~/.aws/credentials
[default]
aws_access_key_id = hoge
aws_secret_access_key = huga
cloudfront=true
[private]
aws_access_key_id = hogehoge
aws_secret_access_key = hugahuga

今回、lambdaのデプロイはprivate用の方で行いたい。
なのでserverless.ymlにその設定を書いていく。

service: go-hatena

provider:
  name: aws
  runtime: go1.x
  stage: ${opt:stage, self:custom.defaultStage}
  #profileオプション追加
  profile: ${opt:profile, self:custom.defaultProfile}
  #regionオプション追加
  region: ${opt:region, self:custom.defaultRegion}

custom:
  defaultStage: dev
  #何も指定がなければ private profile
  defaultProfile: private
  #何も指定がなければ 東京リージョン
  defaultRegion: ap-northeast-1

package:
 exclude:
   - ./**
 include:
   - ./bin/**

functions:
  hello:
    handler: bin/main

また、awsのアカウントを指定してデプロイを行う別の方法として、--aws-profileオプションを使う方法もあるぽい。参考

serverless deploy --aws-profile private

Makefileの作成

Makefileという名前でファイルを作成し、ビルドとデプロイのコマンドを記載

build:
    GOOS=linux go build -o bin/main

deploy:
    sls deploy

ビルド、デプロイ

make build
make deploy

これでawsのlambdaを見に行けば関数がデプロイされている。

スクリーンショット 2019-03-10 20.35.26.png

実行

Makefileに以下を追加し、

run:
    sls invoke -f hello

実行。レスポンスが返ってくる。

$ make run
sls invoke -f hello
{
    "message": "Go Serverless v1.0! Your function executed successfully!"
}
$ 

作成した関数を定期実行させる

serverless.ymlにscheduleを追記

functions:
  hello:
    handler: bin/main
    events:
      - schedule: cron(0/1 * * * ? *)

定期実行されていることはcloudWatchLogsから確認できる。わかりやすくするために「hello!!」とログ表示させるようにする。

main.go
func Handler() (Response, error) {
    fmt.Println("hello!!") // 追記
    return Response{
        Message: "Go Serverless v1.0! Your function executed successfully!",
    }, nil
}

ビルドし、デプロイ

make build
make deploy

定期実行されていることの確認

hello!!と定期的に表示されている。

スクリーンショット 2019-03-10 20.56.55.png

cloudWatchLogsの保存期間を設定

デフォルトでslsではCloudWatch Logsの保存期間は無期限らしい。
個人アカウントの場合、課金されても嫌なので、保存期間を設定する。

serverless.ymlを修正

provider:
  logRetentionInDays: 1 #追加

また、このlogRetentionInDaysに指定できる日数は決まってる

[1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]

上記以外の日数を指定するとデプロイで怒られる

Invalid retention value. Valid values are: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653] (Service: AWSLogs; Status Code: 400; Error Code: InvalidParameterException; Request ID: 8aca73dc-43a1-11e9-a81a-6bf91d5376af).

lambdaのバージョン管理をしない

デフォルトでslsはデプロイした全てのバージョンのlambdaを保存するらしい。
今回お遊びで触っているだけなので、バージョン管理をしないように設定。

provider:
  versionFunctions: false #追加

定期実行を止める

lambdaの管理コンソール上からCloudWatchEventsの有効/無効が切り替えれる。
無効にし、保存すれば定期実行は止まる

スクリーンショット 2019-03-10 21.10.32.png

参考

https://qiita.com/imura81gt/items/5cf490468f95ce3d100b
https://tikasan.hatenablog.com/entry/2018/06/27/083316
https://dev.classmethod.jp/etc/serverless-framework-lambda-cron-execute/
https://tech.ga-tech.co.jp/entry/2018/12/12/120000

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

Buildkit の Goのコードを読んで Dockerfile 抽象構文木から LLB を生成するフローを覗いてみよう!!

こんにちはpo3rinです。最近趣味でmoby project の buildkit の実装をぼーっと眺めてます。

前回の記事で Dockerfile の parser を使い、Dockerfie の抽象構文木の構造を確認しました。https://qiita.com/po3rin/items/a3934f47b5e390acfdfd

更に暇なので、Dockerfile の AST がどのように LLB に変換されているかを大雑把に探ったので記事にしました。執筆時 moby/buildkit のバージョンはv0.3.3です。

LLBとは

Docker 18.09から BuildKit が正式に統合され、Dockerfileのビルドをローレベルの中間ビルドフォーマット(LLB:Low-Level intermediate build format)を介して行われるようになりました。LLBはDAG構造(上の画像のような非循環な構造)を取ることにより、ステージごとの依存を解決し、並列実行可能な形で記述可能です。これにより、BuildKitを使ったdocker build は並列実行を可能にし、従来よりもビルドを高速化しています。

参考:https://www.publickey1.jp/blog/18/docker_engine_1809buildkit9.html

Perserがどこで使われているのか探す

まず、ASTの確認で使った moby/buildkit/frontend/dockerfile/parser/parser.goparser.Parse 関数を呼び出している部分を検索してみましょう。そうすると下記がヒットします。

buildkit/frontend/dockerfile/parser/dumper/main.go
buildkit/frontend/dockerfile/instructions/parse_test.go
buildkit/frontend/dockerfile/instructions/parse_test.go
buildkit/frontend/dockerfile/dockerfile2llb/convert.go

この中で明らかにbuildkit/frontend/dockerfile/dockerfile2llb/convert.goだけが匂います。コードを見ると下記の関数が呼び出していました。

func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error){
    // ...
    dockerfile, err := parser.Parse(bytes.NewReader(dt))
    if err != nil {
        return nil, nil, err
    }
    // ...
}

Dockerfile から LLB(Low-Level intermediate build format) を生成するドンピシャの関数発見です。引数の dt は parser.Parse の引数に使うところから推察して Dockerfile のバイト列を渡しているのでしょう。opt は ConvertOpt という名前から LLB に Convert するときの Option を渡せるそうです。

DockerfileのASTがLLBになるフローを読む

それでは前回確認した Dockerfile の AST が使われているコードをみてみましょう。dockerfile.AST をさらに Parse している箇所があります。

func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error){
    // ...
    stages, metaArgs, err := instructions.Parse(dockerfile.AST)
    if err != nil {
        return nil, nil, err
    }
    // ...
}

何やら AST から stages & metaArgs という物を手に入れています。これはなんでしょうか。ローカルで確認してみます。import で多分ハマるので moby/buildkit をクローンしてきて適当な場所にディレクトリを切るとすんなり行きます。

package main

import (
    "os"

    "github.com/kr/pretty"
    "github.com/moby/buildkit/frontend/dockerfile/instructions"
    "github.com/moby/buildkit/frontend/dockerfile/parser"
)

func main() {
    f, _ := os.Open("./Dockerfile")
    r, _ := parser.Parse(f)

    stages, metaArgs, _ := instructions.Parse(r.AST)
    pretty.Print(stages)
    pretty.Print(metaArgs)
}

対象は下記のようなマルチステージビルドするDockerfileにしましょう。

FROM golang:1.11.1 as builder

WORKDIR /api
COPY . .
ENV GO111MODULE=on
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .

FROM alpine:latest

RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /api .
CMD ["./server"]

実行するとstagesは下記の構造であることがわかります。

[]instructions.Stage{
    {
        Name:     "builder",
        Commands: {
            &instructions.WorkdirCommand{
                withNameAndCode: instructions.withNameAndCode{
                    code:"WORKDIR /api",
                    name:"workdir"
                },
                Path:            "/api",
            },
            &instructions.CopyCommand{
                withNameAndCode: instructions.withNameAndCode{
                    code:"COPY . .",
                    name:"copy"
                },
                SourcesAndDest:  {".", "."},
                From:            "",
                Chown:           "",
            },
            &instructions.EnvCommand{
                withNameAndCode: instructions.withNameAndCode{
                    code:"ENV GO111MODULE=on",
                    name:"env"
                },
                Env:             {
                    {Key:"GO111MODULE", Value:"on"},
                },
            },
            &instructions.RunCommand{
                withNameAndCode:       instructions.withNameAndCode{
                    code:"RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .",
                    name:"run"
                },
                withExternalData:      instructions.withExternalData{},
                ShellDependantCmdLine: instructions.ShellDependantCmdLine{
                    CmdLine:      {"CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo ."},
                    PrependShell: true,
                },
            },
        },
        BaseName:   "golang:1.11.1",
        SourceCode: "FROM golang:1.11.1 as builder",
        Platform:   "",
    },
    {
        Name:     "",
        Commands: {
            &instructions.RunCommand{
                withNameAndCode:       instructions.withNameAndCode{
                    code:"RUN apk --no-cache add ca-certificates",
                    name:"run"
                 },
                withExternalData:      instructions.withExternalData{},
                ShellDependantCmdLine: instructions.ShellDependantCmdLine{
                    CmdLine:      {"apk --no-cache add ca-certificates"},
                    PrependShell: true,
                },
            },
            &instructions.WorkdirCommand{
                withNameAndCode: instructions.withNameAndCode{
                    code:"WORKDIR /app",
                    name:"workdir"
                },
                Path:            "/app",
            },
            &instructions.CopyCommand{
                withNameAndCode: instructions.withNameAndCode{
                    code:"COPY --from=builder /api .",
                    name:"copy"
                },
                SourcesAndDest:  {"/api", "."},
                From:            "builder",
                Chown:           "",
            },
            &instructions.CmdCommand{
                withNameAndCode:       instructions.withNameAndCode{
                    code:"CMD [\"./server\"]",
                    name:"cmd"
                },
                ShellDependantCmdLine: instructions.ShellDependantCmdLine{
                    CmdLine:      {"./server"},
                    PrependShell: false,
                },
            },
        },
        BaseName:   "alpine:latest",
        SourceCode: "FROM alpine:latest",
        Platform:   "",
    },
}

何やらinstructions.Stage構造体が出てきた。中身を見るとASTよりもさらに進んで、Dockerfileをビルド可能なステージの集まりに解析している。今回はマルチステージビルドを行うDockerfileなのでlen(instructions.Stage)==2であることがわかる。そしてinstructions.Stageの中ではコマンド種類ごとに解析されている。

では更に深ぼるためにinstructions.Perserのコードを読んでみよう。

// Parse a Dockerfile into a collection of buildable stages.
// metaArgs is a collection of ARG instructions that occur before the first FROM.
func Parse(ast *parser.Node) (stages []Stage, metaArgs []ArgCommand, err error) {
    for _, n := range ast.Children {
        cmd, err := ParseInstruction(n)
        if err != nil {
            return nil, nil, &parseError{inner: err, node: n}
        }
        if len(stages) == 0 {
            // meta arg case
            if a, isArg := cmd.(*ArgCommand); isArg {
                metaArgs = append(metaArgs, *a)
                continue
            }
        }
        switch c := cmd.(type) {
        case *Stage:
            stages = append(stages, *c)
        case Command:
            stage, err := CurrentStage(stages)
            if err != nil {
                return nil, nil, err
            }
            stage.AddCommand(c)
        default:
            return nil, nil, errors.Errorf("%T is not a command type", cmd)
        }

    }
    return stages, metaArgs, nil
}

Children Nodeの数だけforで回している。そして、switch文でそのNodeがstageに関するものなのかcommandに関するものなのかを分けている。stageなら[]instructions.Stageに追加し、commandなら対象のinstructions.Stageに追加している。

commandはそれぞれ意味ごとにinstructions.CmdCommandinstructions.CopyCommandなどに大別されている。Goを書いたことがある人ならば何やらinterfaceの香りがしてきますね。buildkit/frontend/dockerfile/instructions/commands.goを見るとやはりありました。

// Command is implemented by every command present in a dockerfile
type Command interface {
    Name() string
}

これがCommandを表現するinterfaceですね。予想通りcommandの識別子を返します。識別子はbuildkit/frontend/dockerfile/command/command.goにあります。つまりこれがDockerfileで使えるコマンドの全てです。

// Define constants for the command strings
const (
    Add         = "add"
    Arg         = "arg"
    Cmd         = "cmd"
    Copy        = "copy"
    Entrypoint  = "entrypoint"
    Env         = "env"
    Expose      = "expose"
    From        = "from"
    Healthcheck = "healthcheck"
    Label       = "label"
    Maintainer  = "maintainer"
    Onbuild     = "onbuild"
    Run         = "run"
    Shell       = "shell"
    StopSignal  = "stopsignal"
    User        = "user"
    Volume      = "volume"
    Workdir     = "workdir"
)

switch文を見ると、defaultでエラーを返しています。つまり上のcommandに当てはまらないものに対してエラーを吐いています。ASTを作る時ではなくここで検知するんですね。

LLBの中身を見る

dockerfile2llb.Dockerfile2LLB のここから先は []instructions.Stage からLLBを構築していく部分になります。dockerfile2llb.Dockerfile2LLB関数で最終的に出てくるLLBの出力をサラッと確認しましょう。

package main

import (
    "io/ioutil"

    "github.com/kr/pretty"
    "github.com/moby/buildkit/client/llb/imagemetaresolver"
    "github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
    "github.com/moby/buildkit/util/appcontext"
)

func main() {
    df, _ := ioutil.ReadFile("./Dockerfile")

    st, img, _ := dockerfile2llb.Dockerfile2LLB(appcontext.Context(), df, dockerfile2llb.ConvertOpt{})
    pretty.Print(st)
    pretty.Print(img)
}

出力が長いので、各自確認してみてください。st(llb.State型)はまさに欲していた LLB の定義を表し、img (dockerfile2llb.Image型) コンテナイメージの基本情報が入ります。

まとめ

dockerfile2llb.Dockerfile2LLB 関数が行なっていることを図にすると下記のようなフローになります。(ちょっとBuildable Stages から LLB までの詰めが甘いですが。。)

スクリーンショット 2019-03-10 17.43.05.png

Goは読みやすくていいですね。実は README.md の moby/buildkit の buildctl コマンドを使った example でもLLBの構造を簡単に確認できます。暇ならそちらの紹介も記事にします。

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

GolangでQuoineのAPIを使ってみた

はじめに

最近UdemyでGolangを勉強しているのですが、応用編のビットコイン自動売買の講義でbitflyerのアカウントがなく実際に売買できないってなったので代わりにみつけたQuoine APIを使って講義を受けています。

実際に動くものを書きながら講義を受けたほうが理解が深まりやすいと思ったので、初めてですが記事を書いてみることにしました。
こちらのかたの講座です。
https://www.udemy.com/share/100BhMBEsZeF9QQn4=/

Liquid by Quoineについて

bitflyerの持っていない方はアカウントを作れない現状(2019年3月)これ以外特に選択肢がないように思えます。
現物取引が手数料無料なので、bitflyerをお持ちの方にもliquidがおすすめです。

アカウントの準備

https://www.liquid.com/

こちらでアカウントを作成してAPIのIDとキーを取得してください。
こちらはググれば出てくるので割愛します。

APIをGoで書いてみる

APIのリファレンスです。
https://developers.quoine.com/

LiquidAPIの認証

quoine.go
type APIClient struct {
    key        string
    secret     string
    httpClient *http.Client
}

// header リクエストに追加するヘッダー情報
func (api APIClient) header(method, endpoint string, body []byte) map[string]string {
    token := jwt.New(jwt.GetSigningMethod("HS256"))
    token.Claims = jwt.MapClaims{
        "path":     endpoint,
        "nonce":    strconv.FormatInt(time.Now().Unix(), 10),
        "token_id": "APIキーをここに",
    }
    signature, _ := token.SignedString([]byte("APIシークレットをここに"))
    return map[string]string{
        "X-Quoine-Auth":        signature,
        "X-Quoine-API-Version": "2",
        "Content-Type":         "application/json",
    }
}

すみません。JWTについてはよく分からなかったのでgithub.com/dgrijalva/jwt-goを使いました。

残高の取得

quoine.go
type Balance struct {
    Currency string `json:"currency"`
    Balance  string `json:"balance"`
}

// GetBalances 現在の総合資産を取得する
func (api *APIClient) GetBalances() []Balance {
    url := "/accounts/balance"
    var balances []Balance
    resp, err := api.doRequest("GET", url, nil, nil)
    if err != nil {
        log.Printf("action=GETBalances err=%s", err.Error())
        return balances
    }
    err = json.Unmarshal(resp, &balances)
    if err != nil {
        log.Printf("action=GETBalances(unmarshal) err=%s", err.Error())
        return balances
    }
    return balances
}

注文

quoine.go
// Order 注文
type Order struct {
    OrderType string `json:"order_type"`
    ProductID int    `json:"product_id"`
    Side      string `json:"side"`
    Quantity  string `json:"quantity"`
    Price     string `json:"price"`
}

// ResponseSendChildOrder 注文のレスポンス情報
type ResponseSendChildOrder struct {
    ID                   int         `json:"id"`
    OrderType            string      `json:"order_type"`
    Quantity             string      `json:"quantity"`
    DiscQuantity         string      `json:"disc_quantity"`
    IcebergTotalQuantity string      `json:"iceberg_total_quantity"`
    Side                 string      `json:"side"`
    FilledQuantity       string      `json:"filled_quantity"`
    Price                float64     `json:"price"`
    CreatedAt            int         `json:"created_at"`
    UpdatedAt            int         `json:"updated_at"`
    Status               string      `json:"status"`
    LeverageLevel        int         `json:"leverage_level"`
    SourceExchange       string      `json:"source_exchange"`
    ProductID            int         `json:"product_id"`
    ProductCode          string      `json:"product_code"`
    FundingCurrency      string      `json:"funding_currency"`
    CryptoAccountID      interface{} `json:"crypto_account_id"`
    CurrencyPairCode     string      `json:"currency_pair_code"`
    AveragePrice         float64     `json:"average_price"`
    Target               string      `json:"target"`
    OrderFee             float64     `json:"order_fee"`
    SourceAction         string      `json:"source_action"`
    UnwoundTradeID       interface{} `json:"unwound_trade_id"`
    TradeID              interface{} `json:"trade_id"`
}

// SendOrder 注文を送る
func (api *APIClient) SendOrder(order *SendOrder) (*ResponseSendChildOrder, error) {
    data, _ := json.Marshal(order)
    url := "/orders/"
    resp, err := api.doRequest("POST", url, map[string]string{}, data)
    if err != nil {
        log.Printf("Order Request fail, err=%s", err.Error())
        return nil, err
    }
    var response ResponseSendChildOrder
    err = json.Unmarshal(resp, &response)
    if err != nil {
        log.Printf("Order Request Unmarshal fail, err=%s", err.Error())
        return nil, err
    }
    return &response, nil
}

// GetOrder 注文IDの情報を取得する
func (api *APIClient) GetOrder(orderID int) (*GetOrder, error) {
    var getOrder *GetOrder
    spath := fmt.Sprintf("/orders/%d", orderID)
    resp, err := api.doRequest("GET", spath, nil, nil)
    if err != nil {
        log.Printf("Get Order Request Error, err = %s", err.Error())
        return nil, err
    }

    err = json.Unmarshal(resp, &getOrder)
    if err != nil {
        log.Printf("Get Order Request Unmarshal Error, err = %s", err.Error())
        return nil, err
    }
    return getOrder, nil
}


さいごに

まだ勉強中なのでこれを機にたくさんアウトプットをしてみようと思います。

ありがとうございました。

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

オフラインリアルタイムどう書く E31 の実装例( Go, C++ )

問題の名前 : ぐるぐる数
問題 : http://nabetani.sakura.ne.jp/hena/orde31rotnum/
実装リンク集 : https://qiita.com/Nabetani/items/d139d5ef70aa23c2d038

次回のイベントは 4/6
see https://yhpg.doorkeeper.jp/events/88379

で。

ナイーブな実装を Go と C++ で。

Go

go
package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "os"
    "strconv"
    "strings"
    "time"
)

func isGuru(b, i int32) bool {
    prev := int32(-1)
    for {
        num := i % b
        if 0 <= prev && prev != num && prev != (num+1)%b {
            return false
        }
        i = (i - num) / b
        if i == 0 {
            return true
        }
        prev = num
    }
}

func impl(b, x, y int32) int32 {
    c := int32(0)
    for i := x; i <= y; i++ {
        if isGuru(b, i) {
            c++
        }
    }
    return c
}

func solveSlow(src string) string {
    vals := strings.Split(src, ",")
    b, e0 := strconv.Atoi(vals[0])
    x, e1 := strconv.ParseInt(vals[1], b, 32)
    y, e2 := strconv.ParseInt(vals[2], b, 32)
    if e0 != nil || e1 != nil || e2 != nil {
        panic(fmt.Sprint(e0, e1, e2))
    }
    count := impl(int32(b), int32(x), int32(y))
    return fmt.Sprint(count)
}

func bytesFromFile(fn string) []byte {
    f, err := os.Open(fn)
    if err != nil {
        panic(err)
    }
    defer f.Close()
    b, err := ioutil.ReadAll(f)
    if err != nil {
        panic(err)
    }
    return b
}

type testDataType struct {
    Number   int    `json:"number"`
    Src      string `json:"src"`
    Expected string `json:"expected"`
}

type dataType struct {
    EventID  string         `json:"event_id"`
    EventURL string         `json:"event_url"`
    TestData []testDataType `json:"test_data"`
}

func runTest(list []testDataType) {
    for _, t := range list {
        start := time.Now()
        actual := solveSlow(t.Src)
        tick := time.Now().Sub(start).Seconds()
        result := func() string {
            if actual == t.Expected {
                return "ok"
            }
            return "***NG***"
        }()
        fmt.Printf("%2d:%s solve(%q)=%q, want %q ( %.3f sec )\n", t.Number, result, t.Src, actual, t.Expected, tick)
    }
}

func main() {
    data := dataType{}
    json.Unmarshal(bytesFromFile(os.Args[1]), &data)
    start := time.Now()
    runTest(data.TestData)
    tick := time.Now().Sub(start).Seconds()
    fmt.Printf("total : %.2f sec\n", tick)
}

実行は go run slow.go data.json みたいな感じで。

随所で int32 と書いているのは、int よりもたぶん速いから。手元の環境では int は 64bit なので、落ちるコードに差が出てくる。

この実装で、手元の環境で 75秒ぐらい。
goルーチンを活用すれば半分ぐらいにはなるはずだけど、何もしていない。

C++

こちらは、cielavenir さんの記事を参考にして、OpenMP を入れてみた。

c++17
// clang++ -O2 -std=c++17 -Wall -Xpreprocessor -fopenmp -lomp  3.cpp

#include <iomanip>
#include <iostream>
#include <numeric>
#include <regex>
#include <sstream>
#include <string>

int is_guru(int b, int i) {
  auto prev{-1};
  for (;;) {
    auto num = i % b;
    if (0 <= prev && prev != num && prev != (num + 1) % b) {
      return 0;
    }
    i = (i - num) / b;
    if (i == 0) {
      return 1;
    }
    prev = num;
  }
}

std::string solve(std::string const &src) {
  auto regex = std::regex(R"(^([^\,]+)\,([^\,]+)\,([^\,]+))");
  std::smatch m;
  std::regex_match(src, m, regex);
  auto b = std::stoi(m[1]);
  auto x = std::strtol(m[2].str().c_str(), nullptr, b);
  auto y = std::strtol(m[3].str().c_str(), nullptr, b);
  int count = 0;
#pragma omp parallel for reduction(+ : count)
  for (int i = x; i <= y; ++i) {
    count += is_guru(b, i);
  }
  return std::to_string(count);
}

void test(std::string const &src, std::string const &expected) {
  auto actual{solve(src)};
  auto okay{actual == expected};
  std::cout << (okay ? "ok" : "**NG**") << " " << src << " " << actual << " "
            << expected << std::endl;
}

int main() {
  /*0*/ test("4,1313,3012", "12");
  /*1*/ test("10,100,110", "0");
  /*2*/ test("36,zoo,zyz", "0");
  /*3*/ test("4,1300000,2222221", "0");
  // 中略
  /*68*/ test("2,101001011010110000110101,110101110001110110110101", "3240321");
}

Go の実装とロジックは同じ。
というか、Go の実装を移植しただけ。
入力のパースで正規表現を使ってみた。std::split とかあると嬉しいんだけどなぁ。
「中略」のところを略さないで実行して、手元のマシンで、10秒弱。OpenMP 様々。

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