雑なメモ書き

気楽にいきます

camelcaseでgoのbenchをやってみた

結論

  • forの入れ子無くなると早いよね
  • allocationの減少がパフォーマンスに影響が高い
  • []bytes操作はやい

ベンチ結果

  • goのtest benchで実行
go test -bench . -benchmem -count=1
  • 1と2の間は実行速度に差があるレベル
  • 2と3はallocationが減ってる
BenchmarkCamelize1-4       500000          2216 ns/op        1392 B/op         45 allocs/op
BenchmarkCamelize2-4     2000000           953 ns/op         320 B/op         14 allocs/op
BenchmarkCamelize3-4     2000000           812 ns/op         224 B/op          8 allocs/op

本体

  • 1-3まである
package camelcase

import "strings"

func camelize1(src string) string {
    splitedSrcs := strings.Split(src, "_")
    cameliedStrs := make([]string, len(splitedSrcs))

    for splitedSrcIndex, splitedSrc := range splitedSrcs {
        splitedStrsParts := make([]string, len(splitedSrc))
        for i, v := range strings.Split(splitedSrc, "") {
            if i == 0 {
                splitedStrsParts[i] = strings.ToUpper(v)
            } else {
                splitedStrsParts[i] = strings.ToLower(v)
            }
        }
        cameliedStrs[splitedSrcIndex] = strings.Join(splitedStrsParts, "")
    }

    return strings.Join(cameliedStrs, "")
}

func camelize2(src string) string {
    splitedSrcs := strings.Split(src, "_")

    cameliedStrs := make([]string, len(splitedSrcs))

    for splitedSrcIndex, splitedSrc := range splitedSrcs {
        splitedSrcRune := []rune(splitedSrc)
        cameliedStrs[splitedSrcIndex] = strings.ToUpper(string(splitedSrcRune[0])) + strings.ToLower(string(splitedSrcRune[1:]))
    }

    return strings.Join(cameliedStrs, "")
}

func camelize3(src string) string {
    splitedSrcs := strings.Split(src, "_")
    cameliedStrs := make([]byte, 0, len(src))

    for _, splitedSrc := range splitedSrcs {
        splitedSrcRune := []rune(splitedSrc)
        cameliedStrs = append(cameliedStrs, []byte(strings.ToUpper(string(splitedSrcRune[0]))+strings.ToLower(string(splitedSrcRune[1:])))...)
    }

    return string(cameliedStrs)
}
package camelcase

import (
    "log"
    "testing"
)

func TestCamelize1(t *testing.T) {
    if re := camelize1("AAAAA_Bnnnn_AA_CCCCCCCCC_DDDDDD_XXXXXXX"); re != "AaaaaBnnnnAaCccccccccDdddddXxxxxxx" {
        log.Fatal("unexpected case: " + re)
    }
}

func TestCamelize2(t *testing.T) {
    if re := camelize2("AAAAA_Bnnnn_AA_CCCCCCCCC_DDDDDD_XXXXXXX"); re != "AaaaaBnnnnAaCccccccccDdddddXxxxxxx" {
        log.Fatal("unexpected case: " + re)
    }
}

func TestCamelize3(t *testing.T) {
    if re := camelize3("AAAAA_Bnnnn_AA_CCCCCCCCC_DDDDDD_XXXXXXX"); re != "AaaaaBnnnnAaCccccccccDdddddXxxxxxx" {
        log.Fatal("unexpected case: " + re)
    }
}

func BenchmarkCamelize1(b *testing.B) {
    for i := 0; i < b.N; i++ {
        camelize1("AAAAA_Bnnnn_AA_CCCCCCCCC_DDDDDD_XXXXXXX")
    }
}

func BenchmarkCamelize2(b *testing.B) {
    for i := 0; i < b.N; i++ {
        camelize2("AAAAA_Bnnnn_AA_CCCCCCCCC_DDDDDD_XXXXXXX")
    }
}

func BenchmarkCamelize3(b *testing.B) {
    for i := 0; i < b.N; i++ {
        camelize3("AAAAA_Bnnnn_AA_CCCCCCCCC_DDDDDD_XXXXXXX")
    }
}

Golandのショートカットメモ (Mac)

定義元への移動

Cmd + B
  • インタフェースか実装へ移動したい場合
Cmd + Alt + B

プロジェクトウインドウの切り替え

Command + option + `

指定行へ移動

Command + L

プロジェクトのテキストの検索

Shift + Command + F

Rename

fn + Shift + F6

structureの表示・非表示

command + 7

if err != nilの補完

  • err
  • tab
  • enter

main関数の補完

  • main
  • tab
  • enter

Find Useage

  • 使用箇所一覧
fn + option + F7

コメントアウト

option + command + /

大文字小文字の切り替え

  • 選択範囲を大文字小文字に切り替える
command + shift + u

Makefileをいじってみる(2)

暗黙のルール

main.o : main.c defs.h
        cc -c main.c
  • この記述が暗黙のルールを採用すると
main.o : defs.h
  • ここまで短くなる
  • .cを推論している

ディレクトリのクリーンアップ

clean:
        rm edit $(objects)
  • この場合にエラーが起きる可能性を排除してくれる
.PHONY : clean
clean :
        -rm edit $(objects)

Makefileにふくまれているもの

以下の5つが含まれている

  • explicit rules
  • implicit rules
  • variable definitions
  • directives
  • comments.

includeと上書き

foo:
        frobnicate > foo

%: force
        @$(MAKE) -f Makefile $@
force: ;

Makefileをいじってみる(1)

基本的なルール

  • ターゲット: 前提条件
  • 処理内容
target … : prerequisites …
        recipe
        …
        …
  • 実例を見ると以下の様になる
  • このeditの場合は、main.oからutils.oまでを条件にしてccを実行してeditを作成している
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
        cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
  • 名前が被っているので面倒なのだが
  • 前提条件は以下になる
  • 要は依存している処理をtargetの横に書いている
  • それを元にしてrecipeが実行される
main.o : main.c defs.h
        cc -c main.c
kbd.o : kbd.c defs.h command.h
        cc -c kbd.c
command.o : command.c defs.h command.h
        cc -c command.c
display.o : display.c defs.h buffer.h
        cc -c display.c
insert.o : insert.c defs.h buffer.h
        cc -c insert.c
search.o : search.c defs.h buffer.h
        cc -c search.c
files.o : files.c defs.h buffer.h command.h
        cc -c files.c
utils.o : utils.c defs.h
        cc -c utils.c

変数定義

  • さっき出てきたeditの前提条件を変数にまとめると
  • こう定義して
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
  • 以下の様に参照出来る
edit : $(objects)
        cc -o edit $(objects)

zipのパスワードについてきになったこと

  • goのzipでパスワード付きのzipを解凍しようとしたんですが
  • https://golang.org/pkg/archive/zip/
  • 公式を見ている限りその機能がない
  • 結論からいうと
  • https://github.com/yeka/zip
  • こちらのライブラリを使用すると解凍することが出来る
  • 気になったのは、なんでこれは公式のライブラリに無いのかということ
zip.StandardEncryption
zip.AES128Encryption
zip.AES192Encryption
zip.AES256Encryption
  • yeka/zipのドキュメントを見るとこれが使えるという話になっている
  • AESは標準の仕様では無いのだろうか
  • https://support.pkware.com/display/PKZIP/APPNOTE
  • ここが標準らしい
  • 他にも拡張が存在するらしいのでそちらがデファクトになってしまっているのではないだろうか

goのmainの始まる前(1)

  • そう言えばgoのmainの始まる前をまじまじと折ったことはないなと
  • 思ったのでちょっとだけ追う
  • runtime.mainから呼ばれているようだ
// The main goroutine.
func main() {
  • getg()が実行されて
  • currentのgが返される
   g := getg()
type g struct {
    stack       stack   // offset known to runtime/cgo
    stackguard0 uintptr // offset known to liblink
    stackguard1 uintptr // offset known to liblink
    _panic         *_panic // innermost panic - offset known to liblink
    _defer         *_defer // innermost defer
    m              *m      // current m; offset known to arm liblink
    sched          gobuf
    syscallsp      uintptr        // if status==Gsyscall, syscallsp = sched.sp to use during gc
    syscallpc      uintptr        // if status==Gsyscall, syscallpc = sched.pc to use during gc
    stktopsp       uintptr        // expected sp at top of stack, to check in traceback
    param          unsafe.Pointer // passed parameter on wakeup
    atomicstatus   uint32
    stackLock      uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
    goid           int64
    schedlink      guintptr
    waitsince      int64      // approx time when the g become blocked
    waitreason     waitReason // if status==Gwaiting
    preempt        bool       // preemption signal, duplicates stackguard0 = stackpreempt
    paniconfault   bool       // panic (instead of crash) on unexpected fault address
    preemptscan    bool       // preempted g does scan for gc
    gcscandone     bool       // g has scanned stack; protected by _Gscan bit in status
    gcscanvalid    bool       // false at start of gc cycle, true if G has not run since last scan; TODO: remove?
    throwsplit     bool       // must not split stack
    raceignore     int8       // ignore race detection events
    sysblocktraced bool       // StartTrace has emitted EvGoInSyscall about this goroutine
    sysexitticks   int64      // cputicks when syscall has returned (for tracing)
    traceseq       uint64     // trace event sequencer
    tracelastp     puintptr   // last P emitted an event for this goroutine
    lockedm        muintptr
    sig            uint32
    writebuf       []byte
    sigcode0       uintptr
    sigcode1       uintptr
    sigpc          uintptr
    gopc           uintptr         // pc of go statement that created this goroutine
    ancestors      *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
    startpc        uintptr         // pc of goroutine function
    racectx        uintptr
    waiting        *sudog         // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
    cgoCtxt        []uintptr      // cgo traceback context
    labels         unsafe.Pointer // profiler labels
    timer          *timer         // cached timer for time.Sleep
    selectDone     uint32         // are we participating in a select and did someone win the race?
    gcAssistBytes int64
}
  • max stack sizeを設定
  • 64bitだと1Gで32bitだと250Mに設定
   if sys.PtrSize == 8 {
        maxstacksize = 1000000000
    } else {
        maxstacksize = 250000000
    }
  • メインゴルーチンをmain threadに固定する
   lockOSThread()
//go:nosplit
func lockOSThread() {
    getg().m.lockedInt++
    dolockOSThread()
}
  • 現在のgへm.lockedglockedmへセット
func dolockOSThread() {
    if GOARCH == "wasm" {
        return // no threads on wasm yet
    }
    _g_ := getg()
    _g_.m.lockedg.set(_g_)
    _g_.lockedm.set(_g_.m)
}
  • runtimeの初期化
runtime_init() // must be before defer
  • 中身どこだろうなこれ
//go:linkname runtime_init runtime.init
func runtime_init()
  • gcの初期化
  • bgsweepをgoroutineで実行
func gcenable() {
    c := make(chan int, 1)
    go bgsweep(c)
    <-c
    memstats.enablegc = true // now that runtime is initialized, GC is okay
}
  • これがsweepの実体
func bgsweep(c chan int) {
    sweep.g = getg()

    lock(&sweep.lock)
    sweep.parked = true
    c <- 1
    goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1)

    for {
        for sweepone() != ^uintptr(0) {
            sweep.nbgsweep++
            Gosched()
        }
        for freeSomeWbufs(true) {
            Gosched()
        }
        lock(&sweep.lock)
        if !isSweepDone() {
            // This can happen if a GC runs between
            // gosweepone returning ^0 above
            // and the lock being acquired.
            unlock(&sweep.lock)
            continue
        }
        sweep.parked = true
        goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1)
    }
}
  • で、やっとmainを呼んでいる箇所がきた
fn := main_init // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
fn()
close(main_init_done)

とりあえず、今日はこのぐらいにする。 気が向いたら続き